e662f12398
* bump React & React DOM dependencies
* fix typo `TOmitableProps` → `TOmittableProps`
* bump prettier
* run prettier after prettier version bump
* bump TypeScript
* run prettier after TypeScript version bump
* enable `verbatimModuleSyntax`
This ensures all imported types are using the `type` keyword.
* add `type` to type related imports
* add common testing scenarios
Will be used in the new and existing components.
* add script to make Next.js happy
Right now Next.js does barrel file optimization and re-writing imports
to a real path in the `dist` folder. Most of those rewrites don't
actually exist because they have an assumption:
```js
import { FooBar } from '@headlessui/react'
```
is rewritten as:
```js
import { FooBar } from '@headlessui/react/dist/components/foo-bar/foo-bar'
```
This script will make sure these paths exist...
* improve `by` prop, introduce `useByComparator`
This hook has a default implementation when comparing objects. If the
object contains an `id`, then we will compare the objects by their
`id`'s without the user of the library needing to specify `by="id"`.
If the objects don't have an `id` prop, then the default is still to
compare by reference (unless specicified otherwise).
* sync yarn.lock
* rename `Features` to `HiddenFeatures` for `Hidden` component
* rename `Features` to `FocusTrapFeatures` in `FocusTrap` component
* rename `Features` to `RenderFeatures` in `render` util
* add `floating-ui` as a dependency + introduce internal floating related components
* bump Vue dependencies
* ensure scroll bar calculations can't go negative
* improve types in `@headlessui/vue`
* use snapshot tests for `Transition` tests in `@headlessui/vue`
* use snapshot tests for `portal` tests in `@headlessui/vue`
* rename `src/components/transitions/` to `src/components/transition/` (singular)
This is so that we can be consistent with the other components.
* drop custom `toMatchFormattedCss`, prefer snapshot tests instead
* use snapshot tests for `Label` tests in `@headlessui/vue`
* use snapshot tests for `Description` tests in `@headlessui/vue`
* sort exported components in tests for `@headlessui/vue`
* use snapshot tests in `@headlessui/tailwindcss`
* rename `mergeProps` to `mergePropsAdvanced`
This is a more complex version of a soon to be exported `mergeProps`
that we will be using in our components.
* do not expose `aria-labelledby` if it is only referencing itself
* expose boolean state as `kebab-case` instead of `camelCase`
These are the ones being exposed inside `data-headlessui-state="..."`
* expose boolean data attributes
A slot with `{active,focus,hover}` will be exposed as:
```html
<span data-headlessui-state="active focus hover"></span>
```
But also as boolean attributes:
```html
<span data-active data-focus data-hover></span>
```
* improve internal types for `className` in `render` util
* ensure we keep exposed data attributes into account when trying to forward them to the component inside the `Fragment`
* add small typescript type fix
This is internal code, and the public API is not influenced by this
`:any`. It does make TypeScript happy.
* introduce `mergeProps` util to be used in our components
This will help us to merge props, when event handlers are available they
will be merged by wrapping them in a function such that both (or more)
event handlers are called for the same `event`.
* add new internal `Modal` component
* fix: when using `Focus.Previous` with `activeIndex = -1` should start at the end
* prefer `window.scrollY` instead of `window.pageYOffset`
Because `window.pageYOffset` is deprecated.
* add `'use client'` directives on client only components
These components use hooks that won't work in server components and you
will receive an error otherwise.
* drop `import 'client-only'` in favor of the `'use client'` directive
* add React Aria dependencies
* pin beta dependencies
* prettier bump formatting
* improve TypeScript types in tests
* use new Jest matchers instead of deprecated ones
* improve typescript types in Vue
* prefer `useLabelledBy` and `useDescribedBy`
* add internal `DisabledProvider`
* add internal `IdProvider`
* add internal `useDidElementMove` hook
* add internal `useElementSize` hook
* add internal `useIsTouchDevice` hook
* add internal `useActivePress` hook
* use snapshot tests for `Description` tests
* use snapshot tests for `Label` tests
* use snapshot tests for `Portal` tests
* use snapshot tests for `render` tests
* add (private) `Tooltip` component
Currently this one is not ready yet, so its not publicly exposed yet.
* add internal `FormFields` component
This one adds a component to render (hidden) inputs for native form
support. It also ensures that form fields can be hoisted to the end of
the nearest `Field`. If the components are not inside a `Field` they
will be rendered in place.
* add new `Button` component
* add new `Checkbox` component
* add new `DataInteractive` component
* add new `Field` component
* add new `Fieldset` component
* add new `Legend` component
* add new `Input` component
* add new `Select` component
* add new `Textarea` component
* export new components
* WIP
* remove `within: true`
This only makes sense if anything inside the current element receives
focus, which is not the case for `input`, `select`, `textarea` or
`Radio/RadioOption`.
* group focus/hover/active hooks together
* conditionally link anchor panel
* immediately focus the container
* prevent premature disabling of `Listbox`'s floating integration
+ Track whether the button moved or not when disabling such that we can
disable the transitions earlier.
* improve scroll locking on iOS
* skip hydration tests for now
* skip certain focus trap tests for now
* update CHANGELOG.md
* add missing requires
* drop unused `@ts-expect-error`
* ignore type issues in playgrounds
These playgrounds are mainly test playgrounds. Lower priority for now,
we will get back to them.
* add yarn resolutions to solve swc bug
99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import { useRef, useState } from 'react'
|
|
import { getOwnerDocument } from '../utils/owner'
|
|
import { useDisposables } from './use-disposables'
|
|
import { useEvent } from './use-event'
|
|
|
|
// Only the necessary props from a DOMRect
|
|
type Rect = { left: number; right: number; top: number; bottom: number }
|
|
|
|
function pointerRectFromPointerEvent(event: PointerEvent): Rect {
|
|
// Center of the pointer geometry
|
|
let offsetX = event.width / 2
|
|
let offsetY = event.height / 2
|
|
|
|
return {
|
|
top: event.clientY - offsetY,
|
|
right: event.clientX + offsetX,
|
|
bottom: event.clientY + offsetY,
|
|
left: event.clientX - offsetX,
|
|
}
|
|
}
|
|
|
|
function areRectsOverlapping(a: Rect | null, b: Rect | null) {
|
|
if (!a || !b) {
|
|
return false
|
|
}
|
|
|
|
if (a.right < b.left || a.left > b.right) {
|
|
return false
|
|
}
|
|
|
|
if (a.bottom < b.top || a.top > b.bottom) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
export function useActivePress({ disabled = false }: Partial<{ disabled: boolean }> = {}) {
|
|
let target = useRef<HTMLElement | null>(null)
|
|
let [pressed, setPressed] = useState(false)
|
|
|
|
let d = useDisposables()
|
|
|
|
let reset = useEvent(() => {
|
|
target.current = null
|
|
setPressed(false)
|
|
d.dispose()
|
|
})
|
|
|
|
let handlePointerDown = useEvent((event: PointerEvent) => {
|
|
d.dispose() // Cancel any scheduled tasks
|
|
|
|
if (target.current !== null) return
|
|
|
|
// Keep track of the current element
|
|
target.current = event.currentTarget as HTMLElement
|
|
|
|
// We are definitely pressing the element now
|
|
setPressed(true)
|
|
|
|
// Setup global handlers to catch events on elements that are not the current element
|
|
{
|
|
let owner = getOwnerDocument(event.currentTarget as Element)!
|
|
|
|
// `pointerup` on any element means that we are no longer pressing the current element
|
|
d.addEventListener(owner, 'pointerup', reset, false)
|
|
|
|
// `pointerleave` isn't called consistently (if at all) on iOS Safari, so we use `pointermove` instead
|
|
// to determine if we are still "pressing". We also compare the pointer position to the target element
|
|
// so that we can tell if the pointer is still over the element or not.
|
|
d.addEventListener(
|
|
owner,
|
|
'pointermove',
|
|
(event: PointerEvent) => {
|
|
if (target.current) {
|
|
let pointerRect = pointerRectFromPointerEvent(event)
|
|
setPressed(areRectsOverlapping(pointerRect, target.current.getBoundingClientRect()))
|
|
}
|
|
},
|
|
false
|
|
)
|
|
|
|
// Whenever the browser decides to fire a `pointercancel` event, we should abort
|
|
d.addEventListener(owner, 'pointercancel', reset, false)
|
|
}
|
|
})
|
|
|
|
return {
|
|
pressed,
|
|
pressProps: disabled
|
|
? {}
|
|
: {
|
|
onPointerDown: handlePointerDown,
|
|
onPointerUp: reset,
|
|
onClick: reset,
|
|
},
|
|
}
|
|
}
|