Commit Graph

338 Commits

Author SHA1 Message Date
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
Jordan Pittman 724ee378fc Allow clicks inside dialog panel when target is inside shadow root (#2079)
* Allow clicks inside dialog panel when target is inside shadow root

* fixup

* Update changelog
2022-12-08 16:37:01 -05:00
Jordan Pittman 2e941f85dd Ignore mouse move/leave events when the cursor hasn’t moved (#2069)
* Ignore mouse move/leave events when the cursor hasn’t moved

A mouse enter / leave event where the cursor hasn’t moved happen only because of:
- Scrolling
- The container moved

* Fix linting errors

* Update changelog

* wip

* Fix tests

* fix linting error

* Tweak tests to bypass tracked pointer checks

* Fixup

* Add stuff

* Fix build script

* fix stuff

* wip
2022-12-07 13:50:57 -05:00
Robin Malfait a6dea8af4b Fix Dialog unmounting problem due to incorrect transitioncancel event in the Transition component on Android (#2071)
* remove `transitioncancel` logic

On Desktop Sarai, Chrome, and on mobile iOS Safari the
`transitioncancel` is never called on outside click of the Dialog.
However, on mobile Android Chrome it _is_ called, and the
`transitionend` is never triggered for _some_ reason.

According to the MDN docs:
> If the transitioncancel event is fired, the transitionend event will not fire.
>
> — https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event

When testing this, I never got into the `transitionend` when I got into
the `transitioncancel` first. But, once I removed the `transitioncancel`
logic, the `transitionend` code _was_ being called.

The code is now both simpler, and works again. The nice part is that we
never did anything with the `cancel` event. We marked it as done using
the `Reason.Cancelled` and that's about it.

* cleanup transition completion `Reason`

* update changelog
2022-12-06 17:22:27 +01:00
Robin Malfait 9ef269e936 Add null as a valid type for Listbox and Combobox in Vue (#2064)
* add `null` as a valid type for Listbox and Combobox in Vue

* update changelog
2022-12-05 15:40:19 +01:00
Robin Malfait 219901c84f Allow passing in your own id prop (#2060)
* accept `id` as a prop where it is currently hardcoded (React)

Continuation of #2020

Co-authored-by: Olivier Louvignes <olivier@mgcrea.io>

* accept `id` as a prop where it is currently hardcoded (Vue)

* update changelog

* apply React's hook rules

Co-authored-by: Olivier Louvignes <olivier@mgcrea.io>
2022-12-02 23:56:33 +01:00
Robin Malfait 7509124e56 Apply enter and enterFrom classes in SSR for Transition component (#2059)
* apply `enter` and `enterFrom` classes in SSR

This only happens on the server when `show=true` and `appear=true`. This
will guarantee that the `class` is already set to the correct value
before the transition happens.

It worked before if you moved your classes from `enterFrom` to
`className` because that prop was SSR'd.

* update changelog
2022-12-02 16:00:44 +01:00
Robin Malfait a0bcbae93a Fix crash when using multiple mode without value prop (uncontrolled) for Listbox and Combobox components (#2058)
* ensure `value` in `multiple` mode is always an array

In case nothing or `undefined` was passed.

* update changelog
2022-12-02 15:18:31 +01:00
Robin Malfait 4da0b3aba9 Improve syncing of the Combobox.Input value (#2042)
* make combobox playgrounds in React and Vue similar

* syncing of the input should happen when the value changes internally or externally

I also got rid of the manually dispatching of the change event if the
value changes from internally.

I think the correct mental model is:
- That the `Combobox.Input` value should change if the selected value
  changes from the outside or from the inside.
  - Note: It should _not_ do that if you are currently typing (once you
    choose a new value it will re-sync, once you reset (escape / outside
    click) it will also sync again).
- The `onChange`/`onInput` of the `Combobox.Input` itself should only be
  called if you as the user type something. Not when the value is
  "synced" based on the selected value. We were currently manually
  dispatching events which works (to a certain extend) but smelled a bit
  fishy to me.

The manual dispatching of events tried to solve an issue
(https://github.com/tailwindlabs/headlessui/issues/1875), but I think
this can be solved in 2 other ways that make a bit more sense:

1. (Today) Use the `onBlur` on the input to reset the `query` value to filter
   options.
2. (In the future)  Use an exposed `onClose` (or similar) event to reset
   your `query` value.

* update changelog

* ignore flakey test
2022-11-23 16:16:10 +01:00
Robin Malfait 8b4363d7a4 Ensure shift+home and shift+end works as expected in the Combobox.Input component (#2024)
* ensure shift+home and shift+end works as expected

While testing with a normal input, the Home and End keys don't do
anything on their own, so therefore using them to go to the first or
last item respectively is still a good solution.

However, shift+home and shift+end will do _something_, it will select
the text in the input.

* update changelog
2022-11-17 15:00:26 +01:00
Robin Malfait a4c7cab8a7 Ensure Popover doesn't crash when focus is going to window (#2019)
* ensure Popover doesn't crash when `focus` is going to `window`

* update changelog
2022-11-16 15:11:01 +01:00
Robin Malfait f0dd25fbab Add warning when using <Popover.Button /> multiple times (#2007)
* add warning when using `<Popover.Button />` multiple times

* update changelog
2022-11-10 15:21:24 +01:00
Robin Malfait c0f0d43383 Reset form-like components when the parent <form> resets (#2004)
* add reset button to form example

* refactor React Listbox

This splitsup the raw `[state, dispatch]` to separate `useActions` and `useData` hooks.

This allows us to make the actions themselves simpler and include logic
that doesn't really belong in the reducer itself.

This also allows us to expose data via the `useData` hook that doesn't
belong in the state exposed from the `useReducer` hook.

E.g.: we used to store a `propsRef` from the root `Listbox`, and update
the ref with the new props in a `useEffect`. Now, we will just expose
that information directly via the `useData` hook. This simplifies the
code, removes useEffect's and so on.

* refactor Tabs, ensure function reference stays the same

If the `isControlled` value changes, then the references to all the
functions changed. Now they won't because of the `useEvent` hooks.

* type the actions abg similar to how we type the data bag

* refactor RadioGroup to use useData/useActions hooks

* reset Listbox to defaultValue on form reset

* reset Combobox to defaultValue on form reset

* reset RadioGroup to defaultValue on form reset

* reset Switch to defaultChecked on form reset

* port combinations/form playground example to Vue

* update changelog
2022-11-09 23:39:23 +01:00
Robin Malfait 74e7b43781 1.7.4 2022-11-03 16:21:15 +01:00
Robin Malfait f424aa20db Add client-only to mark everything as client components (#1981)
* add `client-only` to mark everything as client components

This should improve the error messages when using Headless UI in a
Next.js 13+ repo instead of getting a cryptic error message that
`createContext` doesn't exist.

* update changelog
2022-11-03 16:17:22 +01:00
Ernest dce2a1af07 fix: the order of compositionend events varies by browser (#1890)
* fix: the order of compositionend events varies by browser

* apply our style conventions

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2022-11-03 16:10:28 +01:00
Kristóf Poduszló f66f4926c4 fix(RadioGroup): defaultValue type definition (#1920) 2022-10-14 15:11:52 +02:00
Jordan Pittman ab78fbd91e Fire user’s onChange handler when we update the combobox input value internally (#1916)
* Fire user’s onChange handler when we update the input value internally

* Update changelog

* Fix CS
2022-10-10 15:17:52 -04:00
Robin Malfait 0e147a0c75 Fix useOutsideClick, add improvements for ShadowDOM (#1914)
* Fix `useOutsideClick` not closing when clicking in ShadowDOM

https://github.com/tailwindlabs/headlessui/pull/1876#issuecomment-1264742366

* use `getRootNode` in `useOutsideClick` for Vue

* update changelog

* run prettier

Co-authored-by: Theodore Messinezis <7229472+theomessin@users.noreply.github.com>
2022-10-10 17:02:51 +02:00
Jordan Pittman 1127a55a76 Warn when changing Combobox between controlled and uncontrolled (#1878) 2022-10-10 10:41:15 -04:00
Robin Malfait 83a5f456c3 Expose close function for Menu and Menu.Item components (#1897)
* expose `close` function for `Menu` and `Menu.Item` components

The `Menu` will already automatically close if you invoke the
`Menu.Item` (which is typically an `a` or a `button`). However you have
control over this, so if you add an explicit `onClick={e =>
e.preventDefault()}` then we respect that and don't execute the default
behavior, ergo closing the menu.

The problem occurs when you are using another component like the Inertia
`Link` component, that does have this `e.preventDefault()` built-in to
guarantee SPA-like page transitions without refreshing the browser.
Because of this, the menu will never close (unless you go to a totally
different page where the menu is not present of course).

This is where the explicit `close` function comes in, now you can use
that function to "force" close a menu, if your 3rd party tool already
bypassed the default behaviour.

This API is also how we do it in the `Popover` component for scenario's
where you can't rely on the default behaviour.

* update changelog
2022-10-04 15:49:20 +02:00
Robin Malfait af68a347fe Fix <Popover.Button as={Fragment} /> crash (#1889)
* prevent infinite loop

When you use `as={Fragment}` an unmount and remount can happen. This
means that the `ref` gets called with `null` for the unmount and
`HTMLButtonElement` for the mount.

This keeps toggling which results in an infinite loop and eventually a
Maximum callback size exceeded issue.

This ensures that we only set the button if we have a button.

* update changelog
2022-09-30 21:48:35 +02:00
Jordan Pittman fdccf0767a 1.7.3 2022-09-30 11:54:08 -04:00
Robin Malfait 9e9c08c7cc Fix Tab incorrectly activating on focus event (#1887)
* rework Tabs so that they don't change on focus

The "change on focus" was an incorrect implementation detail that made
it a bit easier but this causes a problem as seen in #1858.

If you want to conditionally check if you want to change the tab or note
(e.g. by using `window.confirm`) then the focus is lost while the popup
is shown. Regardless of your choice, the browser will re-focus the Tab
therefore asking you *again* what you want to do.

This fixes that by only activating the tab if needed while using arrow
keys or when you click the tab (not when it is focused).

* update changelog
2022-09-30 13:51:14 +02:00
Robin Malfait e7cfb05036 Fix useOutsideClick swallowing events inside ShadowDOM (#1886)
* check inside shadow dom for use-outside-click

* update changelog

Co-authored-by: Raphael Melloni <raphael.melloni@nortal.com>
2022-09-29 12:14:01 +02:00
Robin Malfait 8536838bb3 update changelog 2022-09-28 14:53:04 +02:00
Robin Malfait b032679ed0 Improve Portal detection for Popover components (#1842)
* improve Popover heuristics for detecting being inside a Portal

* improve performance of checks

* make it work in Vue

* verify behaviour in tests
2022-09-28 14:16:59 +02:00
Jordan Pittman 060f37b667 Fix use of undefined and displayValue in Combobox (#1865)
* Work around Vue multi-source + undefined bug

* Update changelog
2022-09-19 09:56:57 -04:00
Robin Malfait dd2feefb9e 1.7.2 2022-09-15 18:31:27 +02:00
Robin Malfait b346736659 Ensure we handle the static prop in Tab.Panel components correctly (#1856)
* ensure we handle `static` panels

* update changelog
2022-09-15 18:26:13 +02:00
Ernest e1f3ef862a Prevent option selection in ComboboxInput while composing (#1850)
* Fix should do nothing when event is fired within a composing session

* update changelog

* link to PR instead of issue

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2022-09-14 15:01:34 +02:00
Robin Malfait e926d693c5 1.7.1 2022-09-12 21:09:38 +02:00
Robin Malfait 1a145fdf43 Remove forceRerender from Tab component (#1846)
* remove `forceRerender` code

This was necessary to ensure the `Panel` and the `Tab` were properly
connected with eachother because it could happen that the `Tab` renders
but the corresponding `Panel` is not the active one which means that it
didn't have a DOM node and no `id` attached.

Whenever new `Tab` became active, it rerendered but the `Panel` wasn't
available yet, after that the `Panel` rendered and an `id` was available
but the actual `Tab` was already rendered so there was no link between
them.

We then forced a re-render because now the `Panel` does have a DOM node
ref attached and the `aria-labelledby` could be filled in.

However, in #1837 we fixed an issue where the order of `Tab` elements
with their corresponding `Panel` elements weren't always correct. To fix
this we ensured that the `Panel` always rendered a `<Hidden />`
component which means that a DOM node is always available.

This now means that we can get rid of the `forceRerender`.

* update changelog
2022-09-12 13:56:14 +02:00
Robin Malfait 46a7ab67ab Ensure Combobox.Label is properly linked when rendered after Combobox.Button and Combobox.Input components (#1838)
* ensure `Combbox.Label` is properly linked when rendered after other components

Even when rendered after the Combobox.Input / Combobox.Button

* update changelog
2022-09-08 23:18:33 +02:00
Robin Malfait b296b73783 Ensure Tab order stays consistent, and the currently active Tab stays active (#1837)
* ensure tabs order stays consistent

This ensures that whenever you insert or delete tabs before the current
tab, that the current tab stays active with the proper panel.

To do this we had to start rendering the non-visible panels as well, but
we used the `Hidden` component already which is position fixed and
completely hidden so this should not break layouts where using flexbox
or grid.

* update changelog

* fix TypeScript issue
2022-09-08 19:14:42 +02:00
Jordan Pittman 10f932afef Add <fieldset disabled> check to radio group options in React (#1835)
* wip

* Update changelog
2022-09-08 10:55:17 -04:00
Robin Malfait 397ba5c8c2 Improve iOS scroll locking (#1830)
* use a simpler `position: fixed` approach to prevent scroll locking

This isn't super ideal, but just preventing the default behavior on the
entire document while `touchmove`-ing isn't ideal either because then
you can't scroll inside the dialog or on the backdrop if your dialog
panel is larger than the viewport.

Again, this is not 100% correct, but it is better because you will be
able to scroll the dialog, and not the body.

* update changelog
2022-09-07 12:32:46 +02:00
Robin Malfait b93b7463bc 1.7.0 2022-09-06 16:22:42 +02:00
Jonathan Reinink dd8cfb427f Tweak changelogs 2022-09-06 10:15:13 -04:00
Robin Malfait 5667e84f35 Fix "blank" screen on initial load of Transition component (#1823)
* use a "cancellable" microTask

* update changelog
2022-09-06 13:37:55 +02:00
Robin Malfait 0954ec5243 Fix maximum call stack size exceeded error on Tab component when using as={Fragment} (#1826) 2022-09-06 12:35:24 +02:00
Robin Malfait df5b6a288c fix incorrect links in CHANGELOGs 2022-09-06 11:53:30 +02:00