Commit Graph

379 Commits

Author SHA1 Message Date
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
Norman Dilthey 399ab4e22c change test to test whether the underlying dom tag is changed to span when using as prop (#2283)
Test in line 105 already covers using the as tag to change the underlying dom tag to an anchor tag. Therefore we can test whether a span tag is correctly rendered for example.
2023-02-15 12:17:28 +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
Thet Naing Tun db58664992 refactor: use proper type for switch forward ref (#2277) 2023-02-13 13:06:44 +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
Jordan Pittman 0ff2326171 Don’t fire afterLeave event more than once for a given transition (#2267)
* Don’t fire afterLeave event more than once for a given component

* Port test to React

* Fix CS

* Remove focus on test

* Update changelog
2023-02-10 09:36:09 -05: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
Jordan Pittman 0d28d588c1 Preserve default index when starting out with no tabs (#2250)
* Preserve default index when starting out with no tabs

* Add test to Vue

* Add test for Vue

it already works here so… yeah
2023-02-03 10:58:18 -05: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
Adventune f2d9ea08b6 fix(@headless-react / @headless-vue): update WAI-ARIA reference links (#2230)
* fix(@headlessui/react): WAI-ARIA reference links

* fix(@headlessui-vue): Fix WAI-ARIA reference links

---------

Co-authored-by: lumilno <nooa.lumilaakso@gmail.com>
2023-01-31 11:42:02 -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
Thet Naing Tun 676de1668b cleanup unused import (#2184) 2023-01-16 12:54:17 +01:00
Jordan Pittman aac78d52b7 Don’t overwrite classes during SSR when rendering fragments (#2173)
* Refactor SSR test helpers

* Add SSR tests for transition

* Don’t overwrite classes during SSR when rendering fragments

* Update changelog
2023-01-12 11:17:51 -05:00
Robin Malfait 08c083768f Fix failed to removeChild on Node bug (#2164)
* fix `failed to removeChild on Node` bug

Let's introduce a bit more defensive code to make sure that the code
doesn't crash when we don't pass a `Node` to `removeChild`

* update changelog
2023-01-09 12:11:45 +01:00
Robin Malfait c687c2e165 Fix false positive warning when using <Popover.Button /> in React 17 (#2163)
* fix false positive warning when using <Popover.Button /> in React 17

* update changelog
2023-01-09 11:43:12 +01:00
Robin Malfait 6fa6074cd5 Fix Tab key with non focusable elements in Popover.Panel (#2147)
* fix `Tab` key with non focusable elements in `Popover.Panel`

Fixes: #2112

* ensure all Dialog tests are running

* update changelog
2023-01-04 19:08:47 +01:00
Robin Malfait 69b953ae9d Fix false positive warning about using multiple <Popover.Button> components (#2146)
* fix false positive warning about using multiple `<Popover.Button>` components

Fixes: #2143

* update changelog
2023-01-04 16:26:35 +01:00
Robin Malfait e8b7b7fe60 Fix arrow key handling in Tab (after DOM order changes) (#2145)
* detect change in `Tab` order

This will guarantee that when you are using your arrow keys that the
previous / next values are the correct ones instead of the "old" values
before the order change happened.

Fixes: #2131

* update changelog
2023-01-04 14:59:30 +01:00
Jordan Pittman 865bd57357 Fix SSR tab rendering on React 17 (#2102)
* Allow clicks inside dialog panel when target is inside shadow root

* Introduce resettable “server” state

This will aid in testing

* Add SSR and hydration tests for react

* Fix server rendering of Tabs on React 17

* Fix CS

* Skip hydration tests

* Tweak SSR implementation in Vue

* Update changelog
2022-12-16 12:55:51 -05:00
Robin Malfait b7c9e57343 1.7.7 - @headlessui/react 2022-12-16 17:38:32 +01:00
Robin Malfait 5e480e1d08 update changelog 2022-12-16 17:31:55 +01:00
Robin Malfait 599428373e improve scrolling to in-page location 2022-12-16 17:31:55 +01:00
Robin Malfait 92e9302020 1.7.6 - @headlessui/react 2022-12-15 17:35:18 +01:00
Robin Malfait 98b081794e update CHANGELOG 2022-12-15 16:46:47 +01:00
Robin Malfait 45fde141d2 improve scroll offset 2022-12-15 16:46:13 +01:00
Robin Malfait 3cb80795ae update aria-haspopup to use the correct role (#2101)
`aria-haspopup` should now contain the corresponding role instead of
just true or false. The `aria-haspopup="true"` is considered a `menu`
now.

Context: https://w3c.github.io/aria/#aria-haspopup

Fixes: #2099
2022-12-15 16:32:14 +01:00
Robin Malfait 962528c216 Improve scroll locking on iOS (#2100)
* improve types for addEventListener inside disposables

* improve scroll locking

Instead of using the "simple" hack with the `position: fixed;` we now
went back to the `touchmove` implementation.

The `position: fixed;` causes some annoying issues. For starters, on iOS
you will now get a strange gap (due to safe areas). Some applications
also saw "blank" screens based on how the page was implemented.

We also saw some issues internally, where clicking changing the scroll
position on the main page from within the Dialog.

Think about something along the lines of:
```html
<a href="#interesting-link-on-the-current-page">Interesting link on the page</a>
```

This doesn't work becauase the page is now fixed, and there is nothing
to scroll...

Instead, we now use the `touchmove` again. The problem with this last
time was that this disabled _all_ touch move events. This is obviously
not good.

Luckily, we already have a concept of "safe containers". This is what we
use for the `outside click` behaviour as well. Basically in a Dialog,
your `Dialog.Panel` is the safe container. But also third party DOM
elements that are rendered inside that Panel (or as a sibling of the
Dialog, but not your main app).

We can re-use this knowledge of "safe containers", and only cancel the
`touchmove` behaviour if this didn't happen in any of the safe
containers.

* update changelog
2022-12-15 16:09:33 +01:00
Robin Malfait d31bb5c08e Fix FocusTrap escape due to strange tabindex values (#2093)
* sort DOM nodes using tabIndex first

It will still keep the same DOM order if tabIndex matches, thanks to
stable sorts!

* refactor `focusIn` API

All the arguments resulted in usage like `focusIn(container,
Focus.First, true, null)`, and to make things worse, we need to add
something else to this list in the future.

Instead, let's keep the `container` and the type of `Focus` as known
params, all the other things can sit in an options object.

* fix FocusTrap escape due to strange tabindex values

This code will now ensure that we can't escape the FocusTrap if you use
`<tab>` and you happen to tab to an element outside of the FocusTrap
because the next item in line happens to be outside of the FocusTrap and
we never hit any of the focus guard elements.

How it works is as follows:

1. The `onBlur` is implemented on the `FocusTrap` itself, this will give
   us some information in the event itself.
   - `e.target` is the element that is being blurred (think of it as `from`)
   - `e.currentTarget` is the element with the event listener (the dialog)
   - `e.relatedTarget` is the element we are going to (think of it as `to`)
2. If the blur happened due to a `<tab>` or `<shift>+<tab>`, then we
   will move focus back inside the FocusTrap, and go from the `e.target`
   to the next or previous value.
3. If the blur happened programmatically (so no tab keys are involved,
   aka no direction is known), then the focus is restored to the
   `e.target` value.

Fixes: #1656

* update changelog
2022-12-14 16:26:38 +01:00
Robin Malfait 1f2de63b40 Fix displayValue syncing when Combobox.Input is unmounted and re-mounted in different trees (#2090)
* simplify `currentDisplayValue` calculation

Always calculate the currentDisplayValue, and only apply it if the user
is not typing. In all other cases it can be applied (e.g.: when the
value changes from the outside, inside or on reset)

* update changelog
2022-12-12 19:16:46 +01:00
Robin Malfait 46754e637c Fix regression where displayValue crashes (#2087)
* fix regression where `displayValue` crashes

It regressed in the sense that it now uses `displayValue` for the
`defaultValue` as well, but if nothing is passed it would crash.

Right now, it makes sure to only run the displayValue value on the
actual value and the actual default value if they are not undefined.

Note: if your displayValue is implemented like `(value) => value.name`,
and your `value` is passed as `null`, it will still crash (as expected)
because then you are in charge of rendering something else than null. If
we would "fix" this, then no value can be rendered instead of `null`.

Fixes: #2084

* update changelog
2022-12-12 16:43:57 +01:00
Robin Malfait 326a43f73f 1.7.5 - @headlessui/react 2022-12-08 23:17:01 +01:00