Commit Graph

142 Commits

Author SHA1 Message Date
Robin Malfait 67f3c4d824 Improve control over Menu and Listbox options while searching (#2471)
* add `get-text-value` helper

* use `getTextValue` in `Listbox` component

* use `getTextValue` in `Menu` component

* update changelog

* ensure we handle multiple values for `aria-labelledby`

* hoist regex

* drop child nodes instead of replacing its innerText

This makes it a bit slower but also more correct. We can use a cache on
another level to ensure that we are not creating useless work.

* add `useTextValue` to improve performance of `getTextValue`

This will add a cache and only if the `innerText` changes, only then
will we calculate the new text value.

* use better `useTextValue` hook
2023-05-04 14:41:44 +02:00
Mateusz Burzyński 0505e92b83 Move types condition to the front (#2469)
* move `types` condition to the front

* Update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-05-01 12:21:28 -04:00
Jordan Pittman 7c0cbbbca7 Stop <Transition appear> from overwriting classes on re-render (#2457)
* Add raw layout support to Vue playground

We can’t use ?raw here because Vite uses that itself for stuff. So here we opt for ?layout=raw instead

* Fix Transition for `appear` overwriting classes on re-render

* Set initial state just before animating

* Remove unused import

* Refactor

* Capture snapshot of element just after first render

With the new `setInitial` call before the animation starts — we don’t see the actual initial render result in this test because the queue has been emptied by the time it ends

* Update changelog
2023-04-28 13:06:39 -04:00
Muhammad Ilham Mubarak 5cfbb4b5e5 Ensure FocusTrap is only active when the given enabled value is true (#2456)
* fix(tabs): wrong tab focus when Tab contains a Dialog

* refactor(focus-trap): rename variable and move logic

* test(tabs): improve test by asserting the active element

* ensure `FocusTrap` is not active when `enabled = false`

* fix: move the enabled check to unmounting

* refactor to `useOnUnmount` hook

This will allow us to make the code relatively similar between React and
Vue.

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-04-26 14:51:01 +02:00
Robin Malfait 08127dd2ad [internal] add demo mode to Menu and Popover components (#2448)
* add demo mode to `Menu` and `Popover`

* update changelog
2023-04-21 13:51:26 +02:00
Jordan Pittman b98e642a67 Correctly handle IME composition in <Combobox.Input> (#2426)
* Don’t try to open combobox when composing characters

* wip

* Delay IME composition end until after keydown events

* Use `d.nextFrame` to handle `compositionend` event

* Update changelog
2023-04-17 10:17:28 -04:00
Robin Malfait c5d2e3b053 1.7.14 - @headlessui/react 2023-04-12 19:05:04 +02:00
Jordan Pittman 2063132f1a Merge className correctly when it’s a function (#2412)
* Add className tests for `render`

Fix snapshots

* Merge `className` correctly when it’s a function

* Update changelog
2023-04-03 14:43:09 -04:00
Robin Malfait c92a84782f Improve Combobox types to improve false positives (#2411)
* swap `Combobox` type order

If you use the default `Combobox` component then it defaults to
`Fragment`. This also means that if you provide an additional prop that
it would be forwarded to the `Fragment` but that won't work.

You do get a runtime error, but the types aren't 100% clear on what's
going on. In fact, they make it very confusing because it will use the
last "fallback" in all the `Combobox` definitions which marks the value
as "multiple".

Concretely, this means:
```ts
let [value, setValue] = useState('Tom Cook')

<Combobox
  value={value}
  onChange={setValue}
  placeholder="Hello!"
/>
```

Starts complaining about the `value` and `onChange` not handling the
`multiple` case correctly. But it should complain about the `placeholder`.

Switching the order _does_ solve this, but it is not the cleanest
solution.

Maybe we should be explicit about the `Fragment` case somehow.

However, there is a use case where I don't think TypeScript will be able
to help and it's a bit unfortunate.

```ts
let [value, setValue] = useState('Tom Cook')

<Combobox value={value} onChange={setValue} placeholder="Hello!">
   <div>
      {/* ... */}
   </div>
</Combobox>
```

This is valid because at runtime we will forward all the props to the
`div`. So not 100% sure what we should do here instead.

* update changelog
2023-04-03 17:46:20 +02:00
Jordan Pittman f4e9710bca Fix className hydration for <Transition appear> (#2390)
* Fix `className` hydration for `<Transition appear>`

* Update changelog
2023-03-22 11:41:09 -04:00
Kairui Liu d0888b03b5 Add FocusTrap event listeners once document has loaded (#2389)
* feat: addEventListener on document loaded

* Refactor

* Fix import

* Update changelog

* use function instead of arrow function

* make callback in `onDocumentReady` mandatory

---------

Co-authored-by: lkr <lkr@bytedance.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-03-22 13:37:07 +01:00
Robin Malfait fc9a625414 Fix "Can't perform a React state update on an unmounted component." when using the Transition component (#2374)
* only change flags when mounted

* update changelog
2023-03-16 16:30:58 +01:00
Arber Sylejmani fb612f7580 Add form prop to form-like components such as RadioGroup, Switch, Listbox, and Combobox (#2356)
* Adds form prop to Switch component

* add `form` prop to `Switch` component in Vue

+ tests for both React and Vue

* add `form` prop to `Combobox` component

* add `form` prop to `Listbox` comopnent

* add `form` prop to `RadioGroup` component

* update changelog

* add Oxford comma

* cleanup `screen` import

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-03-14 13:36:49 +01:00
Robin Malfait 0c0601f87a Fix focus styles showing up when using the mouse (#2347)
* update playground examples to use a shared `Button`

* expose a `ui-focus-visible` variant

* keep track of a `data-headlessui-focus-visible` attribute

* do not set the `tabindex`

The focus was always set, but the ring wasn't showing up. This was also
focusing a ring when the browser decided not the add one.

Let's make the browser decide when to show this or not.

* update changelog
2023-03-10 22:00:35 +01:00
Robin Malfait df61edcf3e 1.7.13 - @headlessui/react 2023-03-03 21:09:06 +01:00
Robin Malfait 7e150e408c Fix restore focus to buttons in Safari, when Dialog component closes (#2326)
* update dialog playground example

Includes a generic `Button` component that has explicit focus styles.

* keep track of "focus" history

Safari doesn't "focus" buttons when you mousedown on them. This means
that we don't capture the correct element to restore focus to when
closing a `Dialog` for example.

Now, we will make sure to keep track of a list of last "focused" items.
We do this by also capturing elements when you "click", "mousedown" or
"focus".

* let's use a button instead of a div in tests

* make `target` for Vue consistent with React

* update changelog
2023-03-03 18:24:57 +01:00
Robin Malfait c759016fa3 Fix invalid warning when using multiple Popover.Button components inside a Popover.Panel (#2333)
* add a bunch of tests to ensure we won't regress on this again

* fix incorrect warning when using multiple `Popover.Button` inside `Popover.Panel`

* update changelog
2023-03-03 14:59:10 +01:00
Robin Malfait 989cd6b040 Fix XYZPropsWeControl and cleanup internal TypeScript types (#2329)
* cleanup `XYZPropsWeControl`

The idea behind the `PropsWeControl` is that we can omit all the fields
that we are controlling entirely. In this case, passing a prop like
`role`, but if we already set the role ourselves then the prop won't do
anything at all. This is why we want to alert the end user that it is an
"error".

It can also happen that we "control" the value by default, but keep
incoming props into account. For example we generate a unique ID for
most components, but you can provide your own to override it. In this
case we _don't_ want to include the ID in the `XYZPropsWeControl`.

Additionally, we introduced some functionality months ago where we call
event callbacks (`onClick`, ...) from the incoming props before our own
callbacks. This means that by definition all `onXYZ` callbacks can be
provided.

* improve defining types

Whenever we explicitly provide custom types for certain props, then we
make sure to omit those keys first from the original props (of let's say
an `input`). This is important so that TypeScript doesn't try to "merge"
those types together.

* cleanup: move `useEffect`

* add `defaultValue` explicitly

* ensure tests are not using `any` because of `onChange={console.log}`

The `console.log` is typed as `(...args: any[]) => void` which means
that it will incorrectly mark its incoming data as `any` as well.
Converting it to `x => console.log(x)` makes TypeScript happy. Or in
this case, angry since it found a bug.

This is required because it _can_ be that your value (e.g.: the value of
a Combobox) is an object (e.g.: a `User`), but it is also nullable.

Therefore we can provide the value `null`. This would mean that
eventually this resolves to `keyof null` which is `never`, but we just
want a string in this case.

```diff
-export type ByComparator<T> = (keyof T & string) | ((a: T, b: T) => boolean)
+export type ByComparator<T> =
+  | (T extends null ? string : keyof T & string)
+  | ((a: T, b: T) => boolean)
```

* improve the internal types of the `Combobox` component

* improve the internal types of the `Disclosure` component

* improve the internal types of the `Listbox` component

* improve the internal types of the `Menu` component

* improve the internal types of the `Popover` component

* improve the internal types of the `Tabs` component

* improve the internal types of the `Transition` component

* use `Override` in `Hidden` as well

* cleanup unused code

* don't check the `useSyncExternalStoreShimClient`

* don't check the `useSyncExternalStoreShimServer`

* improve types in the render tests

* fix `Ref<TTag>` to be `Ref<HTMLElement>`

* improve internal types of the `Transition` component (Vue)

+ add `attrs.class` as well

* use different type for `AnyComponent`

* update changelog
2023-03-02 22:50:41 +01:00
Robin Malfait 948ae73608 Allow root containers from the Dialog component in the FocusTrap component (#2322)
* add (failing) test to verify moving focus to 3rd party containers work

* pass `resolveRootContainers` to `FocusTrap`

* handle lazy containers in `FocusTrap`

* update changelog
2023-03-01 18:13:07 +01:00
Dilshod de7ddf9e45 Enable native label behavior for Switch component (#2265)
* add native label behavior for switch

* Add reference tests for React and Vue

These don’t work in JSDOM so they’re skipped but we can use these to reference expected behavior once we have playwright-based tests

* Fix Vue playground switch example

* Only prevent default when the element is a label

* Port change to Vue

* Update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-02-28 12:22:37 -05:00
Robin Malfait 213dd529bf Ensure Transition component completes if nothing is transitioning (#2318)
* make `disposables` consistent

Also added a `group` function, this allows us to spawn a _sub_
disposables group that can be disposed on its own, but will also be
disposed the moment the "parent" is disposed.

* ensure Transition component works when nothing is transitioning

* update changelog
2023-02-28 12:38:11 +01:00
Robin Malfait 885f446e4e 1.7.12 - @headlessui/react 2023-02-24 16:29:14 +01:00
Robin Malfait 9ecd8dd926 Fix Dialog cleanup when the Dialog becomes hidden (#2303)
* use the Dialog's parent as the root for the Intersection observer

We have some code that allows us to auto-close the dialog the moment it
gets hidden. This is useful if you use a dialog for a mobile menu and
you resizet he browser. If you wrap the dialog in a `md:hidden` then it
auto closes. If we don't do this, then the dialog is still locking the
scrolling, keeping the focus in the dialog, ... but it is not visible.

To solve this we use an `IntersectionObserver` to verify that the
`boundingClientRect` is "gone" (x = 0, y = 0, width = 0 and height = 0).

However, the intersection observer is not always triggered. This happens
if the main content is scrollable.

Setting the `root` of the `IntersectionObserver` to the parent of the
`Dialog` does seem to solve it.

Not 100% sure what causes this behaviour exactly.

* use a `ResizeObserver` instead of `IntersectionObserver`

* implement a `ResizeObserver` for the tests

* update changelog
2023-02-24 13:22:29 +01:00
Jordan Pittman b8c214eebb Make React types more compatible with other libraries (#2282)
* Export explicit props types

* wip

* wip

* wip

* wip dialog types

* wip

* Fix build

* Upgrade esbuild

* Add aliased types for ComponentLabel and ComponentDescription

* Update lockfile

* Update changelog

* Update exported prop type names

* Make onChange optional

* Update tests

* Use `never` in CleanProps

Using a branded type doesn’t work properly with unions

* Fix types

* wip

* work on types

* wip

* wip

* Tweak types in render helpers

* Fix CS

* Fix changelog

* Tweak render prop types for combobox

* Update hidden props type name

* remove unused type

* Tweak types

* Update TypeScript version
2023-02-20 12:26:17 -05:00
Robin Malfait c7f6bc60ed Fix nested Popover components not opening (#2293)
* fix nested `Popover`s not working

* update changelog
2023-02-17 23:05:14 +01:00
Robin Malfait 10efaa921d Ensure the main tree and parent Dialog components are marked as inert (#2290)
* drop `@ts-expect-error`, because `inert` is available now

* fix logical error

We want to apply `inert` when we _don't_ have nested dialogs, because if
we _do_ have nested dialogs, then the inert should be applied from the
nested dialog (or visually the top most dialog).

* update changelog

* replace `useInertOthers` with `useInert`

* add `assertInert` and `assertNotInert` accessibility assertion helpers

* ensure the `main tree` root is marked as inert

As well as the parent dialogs in case of nested dialogs.
2023-02-17 16:49:41 +01:00
Robin Malfait 619d103114 update CHANGELOG 2023-02-15 15:33:02 +01:00
Robin Malfait 2c2a22bd4f 1.7.11 - @headlessui/react 2023-02-15 15:10:39 +01:00
Robin Malfait adfe121678 Start cleanup phase of the Dialog component when going into the Closing state (#2264)
* introduce `opening` and `closing` states

Also represent them as bits so that we can easily combine them while we
are transitioning from one state to the other.

* update `open/closed` state checks

Instead of checking whether it is in one state or an other, we can check
if the current state contains some potential sub-state.

This allows us to still check if we are in the `Open` state, while also
`Closing` because the state will be `S.Open | S.Closing`.

* expose `flags` from the `useFlags` hook

* add the `Closing` and `Opening` states to the Open/Closed state

* create dedicated `abcEnabled` variables

* keep the `State.Closing` into account for `scroll locking` and `inert others`

* add a test for the `Closing` state impacting the `Dialog` component

* cleanup unused imports

* add `unmount` util to the Vue Test renderer

* update changelog
2023-02-15 12:14:03 +01:00
Robin Malfait 22e388eb8d update changelog 2023-02-13 13:07:39 +01:00
Robin Malfait fcfd554514 Ensure we reset the activeOptionIndex if the active option is unmounted (#2274)
* ensure we reset the `activeOptionIndex` if the active option is unmounted

Unmounting of the active option can happen when you are in a
multi-select Combobox, and you filter out all the selected values. This
means that the moment you press "Enter" on an active item, it becomes
the selected item and therefore will be filtered out.

* update changelog
2023-02-10 19:54:58 +01:00
Robin Malfait b9af614919 Re-focus Combobox.Input when a Combobox.Option is selected (#2272)
* re-focus `Combobox.Input` when a `Combobox.Option` is selected

Except on mobile devices (ideally devices using a virtual keyboard), so
that the virtual keyboard won't be triggered every single time we
re-focus that input field.

* update changelog
2023-02-10 16:30:53 +01:00
Robin Malfait 7ecf8323dc Move aria-multiselectable to [role=listbox] in the Combobox component (#2271)
* move `aria-multiselectable` to `[role=listbox]` in the `Combobox` component

* update changelog
2023-02-10 15:41:30 +01:00
Robin Malfait 5051fff04f Ensure we handle null dataRef values correctly (#2258)
* ensure we handle `null` dataRef values correctly

Initially when the `dataRef` is created, then the `current` value is
going to be `null`. We didn't properly encode this in the types. Now
that we do, it exposed some places where this was used incorrectly
(because we assumed it was always defined).

* update changelog
2023-02-06 18:11:47 +01:00
Robin Malfait 3a54a9159d cleanup CHANGELOG 2023-02-06 12:35:03 +01:00
Robin Malfait 597256bd9e 1.7.10 - @headlessui/react 2023-02-06 12:33:23 +01:00
Robin Malfait 0cecd721ed update CHANGELOG 2023-02-06 12:33:10 +01:00
Robin Malfait d146b78a97 Revert "Use the import * as React from 'react' pattern (#2242)"
This reverts commit 0276231c31.
2023-02-06 12:30:30 +01:00
Robin Malfait 885fa6aedd 1.7.9 - @headlessui/react 2023-02-03 17:40:08 +01:00
Robin Malfait 0276231c31 Use the import * as React from 'react' pattern (#2242)
* use the `import * as React from 'react'` pattern

We use named imports, but we have to import `React` itself as well for
JSX because it compiles to `React.createElement`. We could get rid of
our own JSX and use it directly, or we can use this `import * as React
from 'react'` syntax.

This fixes an issue for people using `allowSyntheticDefaultImports: false` in TypeScript.

Fixes: #2117

* update changelog
2023-02-02 15:34:16 +01:00
Robin Malfait 551261ab5e Fix "This Suspense boundary received an update before it finished hydrating" error in the Disclosure component (#2238)
* use `startTransition` to work around `Suspense` boundary crash

* update changelog
2023-02-02 00:25:17 +01:00
Jordan Pittman 2f99644ed7 Don't break overflow when multiple dialogs are open at the same time (#2215)
* Fix overflow when swapping dialogs that use transition

* Refactor

* refactor

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Inline shim for ESM support

Until the official package adds an ESM version with a wildcard import we can’t use it. This version was copied from Remix Router

* Add dialog shadow root examples

* Fix SSR error

* Add repro for iOS scrolling issue

* Try to fix vercel build

idk what’s wrong here

* Update repro

A transition is required to delay closing enough to demonstrate the bug

* Port global dialog state to Vue

* Add dialog test to Vue

* wip

* wip

* Workaround bug

This shouldn’t happen at all and we need to find the source of the bug but this should “fix” things for the time being

* wip

* Rebuild overflow locking with simpler API

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update deps

* wip

* simplify

* Port to Vue

* wip

* wip

* Tweak tests

* Update changelog

* Ensure meta callbacks are cleaned up

* cleanup

* wip
2023-02-01 16:08:34 -05:00
Jordan Pittman e95f664a36 Fix SSR tab hydration when using Strict Mode in development (#2231)
* Work on SSR tests for react

* Use React internals to count tabs and panels

React’s double rendering in strict mode in development makes SSR + hydration matching impossible without reaching into internals. This is unfortunate but the way react works. Production builds of React are unaffected by this but still require a consistent mechanism that works so in that case we use Symbols just like we do in SSR.

* Update changelog
2023-01-31 15:40:13 -05:00
Robin Malfait b3a0ccb2f8 1.7.8 - @headlessui/react 2023-01-27 17:24:04 +01:00
Robin Malfait d8b263cb42 Fix shadow-root bug closing Dialog containers (#2217)
* ensure we consider `html > *` as valid containers as well

* update changelog
2023-01-27 14:49:43 +01:00
Robin Malfait 2f13496acb Allow setting tabIndex on the Tab.Panel (#2214)
* allow setting `tabIndex` on the `Tab.Panel`

* update changelog
2023-01-26 17:12:11 +01:00
Robin Malfait e2294f5280 Improve Tabs wrapping around when controlling the component and overflowing the selectedIndex (#2213)
* ensure chaning the `selectedIndex` tabs properly wraps around

We never want to use and index that doesn't map to a proper tab.

This commit also makes the implementation similar for both React and
Vue.

* add tests to prove the underflow and overflow wrapping

* drop updating the index manually

This is already adjusted when tabs change internally. You can still
manually change it of course, but for these tests that doesn't matter
and cause different results.

* update changelog
2023-01-26 15:55:55 +01:00
Robin Malfait dbcfb23bc3 Fix FocusTrap in Dialog when there is only 1 focusable element (#2172)
* add tests to guarantee `FocusTrap` with a single element works as expected

* it should keep the focus in the Dialog

Even if there is only 1 element. We were skipping the current active
element so the container didn't have any elements anymore and just
continued to the next focusable element in line. This will prevent that
and ensure that we can only skip elements if there are multiple ones.

* update changelog
2023-01-25 13:18:51 +01:00
Robin Malfait 6f205f07d2 Fix crash when reading headlessuiFocusGuard of relatedTarget in the FocusTrap component (#2203)
* ensure `relatedTarget` is an `HTMLElement`

Or in other words, Robin trust the type system...

I was assuming that this was always an `HTMLElement` or `null` but
that's not the case. Just using `e.relatedTarget` shows that `dataset`
is not always available.

* update changelog
2023-01-24 20:08:38 +01:00
Robin Malfait 1d94d15c79 Improve Combobox accessibility (#2153)
* add the `aria-autocomplete` attribute

* drop the `aria-activedescendant` attribute on the `Combobox.Options` component

It is only required on the `Combobox.Input` component.

* improve triggering VoiceOver when opening the `Combobox`

We do this by mutating the `input` value for a split second to trigger a
change that VoiceOver will pick up. We will also ensure to restore the
value and the selection / cursor position so that the end user won't
notice a difference at all.

* update changelog

Fixes: #2129
Co-authored-by: Andrea Fercia <a.fercia@gmail.com>
2023-01-24 18:10:14 +01:00