Commit Graph

793 Commits

Author SHA1 Message Date
Janeene Beeforth 3905be67a9 Fix esm compatibility for @headlessui/tailwindcss plugin package (#3051)
* Set @headlessui/tailwindcss module type

The distributed tailwindcss plugin package uses `.js` for esm files, and `.cjs` for commonjs files. This corresponds to `"type": "module"` in the package.json file.

One build script `fix-types.js` was a commonjs script which was breaking this module file naming pattern. Renamed the script to `fix-types.cjs` so that we could then standardise the module type for @headlessui/tailwindcss package as a whole.

* Fix esm import of tailwindcss/plugin

ESM imports need to specify the filename extension when importing individual files.

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-05-29 16:17:06 +02:00
Sebastian Silbermann 3070ad9d99 Use useId instead of React internals (#3254)
* Use `useId` instead of React internals

These may break in any React release which may result in blocking the ecosystem from seemlessly upgrading to new React releases.

We can use `useId` instead which is available since React 18.

* inline `useId` hook

No need to use `useStableCollectionKey` anymore

* reformat comment

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-05-29 15:46:11 +02:00
Dan Thompson 025e115277 Fix visual jitter in Combobox component when using native scrollbar (#3190)
* Avoid `scrollToIndex` when using native scrollbar in `ComboBox`

* format comment

* set `ActivationTrigger` in mousedown

If you use the scrollbar, a `mousedown` and `pointerdown` event is fired
before you start interacting with the scrollbar. If you use the `scroll`
event, then the callback is fired while scrolling.

To improve performance a bit more, we will set the activation trigger in
the `mousedown` event because we only need to set it once.

If you are done scrolling, we don't reset it (we didn't do that before
either), but instead we rely on other events to override the trigger
(e.g.: you start using the keyboard).

The big benefit is that this now only calls the `setActivationTrigger`
once, instead of `n` times with the same value.

* optimize `onWheel` to only trigger once

This is a similar performance trick as before. We only need to set the
activationTrigger once, so there is no need to keep calling this
function for no reason.

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-05-29 14:21:44 +02:00
Robin Malfait b3a508b6ca Prevent focus on <Checkbox /> when it is disabled (#3251)
* prevent focus on `<Checkbox />` when disabled

* update changelog
2024-05-28 15:57:32 +02:00
Robin Malfait 94bc4e15fd Merge incoming style prop on ComboboxOptions, ListboxOptions, MenuItems, and PopoverPanel components (#3250)
* merge incoming `style` prop

We were overriding the `style` prop entirely on the `<ComboboxOptions>`,
`<ListboxOptions>`, `<MenuItems>`, and `<PopoverPanel>` for anchoring
purposes, as well as provided some CSS variables.

This now ensures that the incoming `style` prop gets merged in.

* update changelog
2024-05-28 15:37:33 +02:00
Robin Malfait 7267cc4c1c Keep <Combobox /> open when clicking in scrollbar on <ComboboxOptions> (#3249)
* keep `<Combobox />` open when clicking in scrollbar on `<ComboboxOptions>`

* update changelog

* Update packages/@headlessui-react/CHANGELOG.md

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>

* Update packages/@headlessui-react/src/components/combobox/combobox.tsx

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>

* format comment

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-05-28 15:33:14 +02:00
Robin Malfait e8c766190d bump dependencies (#3247) 2024-05-28 12:33:09 +02:00
Robin Malfait 6ac6930116 Implement sibling <Dialog /> components (#3242)
* add `DefaultMap` implementation

* add `useHierarchy` hook

* start `FocusTrapFeatures.None` with `0` instead of `1`

* simplify `Dialog`'s implementation

By making use of the new `useHierarchy` hook.

* delete `StackContext` and `StackProvider` components

They are now replaced by the new `useHierarchy` hook.

* use `useHierarchy` in `useOutsideClick` hook

This way we can scope the hierarchy inside of the `useOutsideClick`
hook. This now ensures that we only enable the `useOutsideClick` on the
top-most element (the one that last enabled it).

* use `useHierarchy` in `useInertOthers` hook

* add new `useEscape` hook

* use new `useEscape` hook

* use `useHierarchy` in `useEscape` hook

* use `useHierarchy` in `useScrollLock` hook

* pass features instead of `enabled` boolean

* simplify demo mode feature flags

No need to setup focus feature flags and then disable it all again if
demo mode is enabled.

* use similar signature for hooks with `enabled` parameter

Whenever a hook requires an `enabled` state, the `enabled` parameter is
moved to the front. Initially this was the last argument and enabled by
default but everywhere we use these hooks we have to pass a dedicated
boolean anyway.

This makes sure these hooks follow a similar pattern. Bonus points
because Prettier can now improve formatting the usage of these hooks.
The reason why is because there is no additional argument after the
potential last callback.

Before:
```ts
let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
useInertOthers(
  {
    allowed: useEvent(() => [
      data.inputRef.current,
      data.buttonRef.current,
      data.optionsRef.current,
    ]),
  },
  enabled
)
```

After:
```ts
let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
useInertOthers(enabled, {
  allowed: useEvent(() => [
    data.inputRef.current,
    data.buttonRef.current,
    data.optionsRef.current,
  ]),
})
```

* move `focusTrapFeatures` parameter to the front

* drop `FocusTrapFeatures.All`, list them explicitly

The `All` feature didn't include all feature flags (didn't include
`FocusTrapFeatures.AutoFocus` for example).

* always enable `FocusTrapFeatures.RestoreFocus` when enabled

This way we can get rid of the `Position.HasChild` check, which will
allow us to do more cleanup soon in the `useHierarchy` hook.

* use `useHierarchy` in `<FocusTrap>` component

* drop `useHierarchy` from `<Dialog>` component

* simplify focusTrapFeatures setup

* simplify `useHierarchy`

The `useHierarchy` hook allowed us to determine whether we are in the
root, in the middle, a leaf, have a parent, have a child, ... but we
only ever checked whether we are a leaf node or not.

In other words, you can think of it like "are we the top layer"

This simplifies the implementation and usage of this new hook.

* move `enabled`-like argument to front

Just to be consistent with the other hooks.

* polyfill `toSpliced` for older Node versions

* add sibling dialogs playground

* rename `useHierarchy` to `useIsTopLayer`

* inline variable

* remove `unstable_batchedUpdates`

This is not necessary.

* add tiny bit of information to dialog

Because it might not be super obvious that we are going to close the
`<Dialog />`.

* update changelog

* re-add internal `PortalGroup`

This is necessary to make sure that a component like a `<MenuItems
anchor />` is rendered inside of the `<Dialog />` and not as a sibling.

While this all works from a functional perspective, if you rely on a
CSS variable that was defined on the `<Dialog />` and you use it in the
`<MenuItems />` then without this change it wouldn't work.
2024-05-27 23:35:38 +02:00
Robin Malfait 1ee4cfd1b7 [internal] Move enabled parameter in hooks to first argument (#3245)
* move `enabled` parameter in hooks to front

Whenever a hook requires an `enabled` state, the `enabled` parameter is
moved to the front. Initially this was the last argument and enabled by
default but everywhere that we use these hooks we have to pass a
dedicated boolean anyway.

This makes sure these hooks follow a similar pattern. Bonus points
because Prettier can now improve formatting the usage of these hooks.
The reason why is because there is no additional argument after the
potential last callback.

Before:
```ts
let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
useInertOthers(
  {
    allowed: useEvent(() => [
      data.inputRef.current,
      data.buttonRef.current,
      data.optionsRef.current,
    ]),
  },
  enabled
)
```

After:
```ts
let enabled = data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
useInertOthers(enabled, {
  allowed: useEvent(() => [
    data.inputRef.current,
    data.buttonRef.current,
    data.optionsRef.current,
  ]),
})
```

Much better!

* inline variables
2024-05-27 17:45:21 +02:00
Robin Malfait 7be23e5c7e 2.0.4 - @headlessui/react 2024-05-25 00:30:50 +02:00
Robin Malfait f740050c2a Use native fieldset instead of div by default for <Fieldset /> component (#3237)
* improve TypeScript types for `Fieldset` component

* use `fieldset` instead of `div` by default

* only apply `role="group"` when not using a native `fieldset`

* apply `disabled` attribute

This is necessary if we want to make use of the default fieldset tag
(which also disables native form elements)

* adjust tests reflecting new changes

* conditionally apply props based on rendered element

* add `useResolvedTag` hook

This allows us to compute the `tag` name of a component. We can use a
shortcut based on the `props.as` and/or the `DEFAULT_XXX_TAG` of a
component. If this is not known/passed, then we compute it based on the
`ref` instead which requires an actual re-render.

* use `useResolvedTag` hook

* reflect change in `Field` related test

* update changelog

* inline variable
2024-05-25 00:27:24 +02:00
Robin Malfait 8c3499cc8d Only handle form reset when defaultValue is used (#3240)
* add `useDefaultValue` hook

This allows us to have a guaranteed `default value` that never changes
unless the component re-mounts.

Since the hook returns a stable value, we can safely include it in
dependency arrays of certain hooks.

Before this change, including this is in the dependency arrays it would
cause a trigger or change of the hook when the `defaultValue` changes
but we never want that.

* do not handle `reset` when no `defaultValue` or `defaultChecked` was provided

If a `defaultValue` is provided, then the reset will be handled and the
`onChange` will be called with this value.

If no `defaultValue` was provided, we won't handle the `reset`,
otherwise we would call the `onChange` with `undefined` which is
incorrect.

* update changelog
2024-05-25 00:26:51 +02:00
Robin Malfait c2754bcb16 Ensure tabbing to a portalled <PopoverPanel> component moves focus inside (without using <PortalGroup>) (#3239)
* ensure we allow focus in the focus sentinel button

We already checked this button when inside of a `PopoverGroup`, but we
didn't when you weren't using a `PopoverGroup` component.

* add test

* update changelog
2024-05-24 23:48:09 +02:00
Robin Malfait b822c8a400 Fix crash when toggling between virtual and non-virtual mode in Combobox component (#3236)
* ensure we correctly merge the `virtual` configuration

* use more generic `UpdateVirtualOptions`

This way we can passthrough the `disabled` function as well.

* properly handle re-use of `disabled` function

* use same order in objects

* cleanup `!` and `?` if we already know we are in `virtual` mode

* directly enable virtual mode in state if previously we weren't using virtual mode

* update changelog
2024-05-24 18:15:14 +02:00
Robin Malfait b478189fad Mark SwitchGroup as deprecated, prefer Field instead (#3232)
* mark `SwitchGroup` as deprecated

Also updated the `Switch.Group` message to also prefer the `<Field>`
component.

* update changelog
2024-05-23 16:24:26 +02:00
Robin Malfait 7fcb410be4 do not apply aria-modal in demo mode (#3227)
When you have a `role="dialog"` and an `aria-modal="true"` at the same
time, then Safari will focus the first focusable element inside the
dialog.

This is not ideal, because in demo mode this means that the focus is
moved around to various DOM elements.

This commit ensures that the `aria-modal` is not applied when demo mode
is enabled to prevent that behavior in Safari.
2024-05-22 14:57:57 +02:00
Robin Malfait 045f2bc761 Ensure page doesn't scroll down when pressing Escape to close the Dialog component (#3218)
* ensure we blur the `document.activeElement` when pressing `Escape`

* update changelog
2024-05-21 13:07:04 +02:00
Jonathan Reinink 300e9eb957 Update changelog 2024-05-13 08:22:33 -04:00
Jonathan Reinink 3407625c51 Use same tense 2024-05-13 08:18:05 -04:00
Jonathan Reinink ea65164e66 Add active prop deprecation to changelog 2024-05-13 08:16:25 -04:00
Jordan Pittman 4eff138ada Don’t set a focus fallback for Dialog’s in demo mode (#3194)
* Don’t set a focus fallback for Dialog’s in demo mode

* Update changelog
2024-05-10 14:55:17 -04:00
Robin Malfait 031b39d522 update @headlessui/vue version in package-lock.json 2024-05-08 13:35:04 +02:00
Robin Malfait 35e7cbb375 sync with 1.x branch 2024-05-08 12:56:49 +02:00
Jordan Pittman f513614ffe 2.0.3 - @headlessui/react 2024-05-07 14:14:50 -04:00
Jordan Pittman a303819018 Make sure disabling demo mode on <Combobox> works (#3182)
* Make sure disabling demo mode on `<Combobox>` works

* Update changelog
2024-05-07 14:10:38 -04:00
Robin Malfait 48cf712d80 2.0.2 - @headlessui/react 2024-05-07 19:19:19 +02:00
Robin Malfait e0688c4865 Improve performance of internal useInertOthers hook (#3181)
* do not use default function for `allowed` and `disallowed`

Otherwise the fallback function will be used which will be a new
reference on each render. On pages with lots of elements this causes
performance issues.

* update changelog
2024-05-07 19:18:57 +02:00
Robin Malfait 886fdf7e6c Ensure clicking a ComboboxOption after filtering the options, correctly triggers a change (#3180)
* add mouse buttons

* add `useDisposables` hook

* add `useFrameDebounce` hook

Schedule a task in the next frame

* ensure we reset the `isTyping` flag correctly

* use same `mousedown` API as we did in React

This allows us to never leave the `input`, even when clicking on an
option.

* update changelog

* format comments

* inline `cb`
2024-05-07 16:49:07 +02:00
Jordan Pittman 2d5d35a533 2.0.1 - @headlessui/react 2024-05-06 15:07:53 -04:00
Jordan Pittman cfbcf5b840 Remove accidental deprecation comments on <DialogPanel> and <DialogTitle> (#3176)
* Remove accidental deprecations

These got left in when they shouldn’t’ve been

* Update changelog
2024-05-06 15:05:55 -04:00
Robin Malfait fb131905b4 2.0.0 - @headlessui/react 2024-05-06 17:41:56 +02:00
Jordan Pittman d416c1ca59 Don’t cancel touchmove on input elements inside a dialog (#3166)
* Don’t cancel touchmove on `input` elements inside a dialog

* Update changelog

* Update
2024-05-03 10:35:50 -04:00
Robin Malfait a45cb6ff6a Remove deprecated DialogBackdrop and DialogOverlay components (#3171)
* remove `DialogBackdrop` and `DialogOverlay`

We deprecated those components in v1.6, since they are no longer
documented and this is a major release, we can safely get rid of it.

* update changelog

* migrate playground example to use `Dialog.Panel`
2024-05-03 16:22:39 +02:00
Jordan Pittman 1ae1af72ab Prep changelog for v2 (#3156)
* Prep changelog for v2

* Update packages/@headlessui-react/CHANGELOG.md

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>

* Tweak changelog

* Fix grammar

* wip

* Update

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2024-05-03 10:07:53 -04:00
Jordan Pittman 8d20cfb0c6 Deprecate dot notation (#3170)
* Expose new components under dot notation

Most of them already where so only a couple were missing.

* Deprecate dot notation

* Add deprecation to `RadioGroupOption`

* Update deprecations

* Update changelog

* Update changelog
2024-05-03 09:55:02 -04:00
Robin Malfait 0bd8c47c28 use HTMLElement for generic DOM node types (#3169)
We keep specific types for elements with special meaning, such as
`HTMLButtonElement`, `HTMLLabelElement` or `HTMLInputElement` because
they receive certain attributes that generic DOM nodes (such as
HTMLDivElement) don't

For the components where we use simple `div` elements (and where people
use `as={...}`) that renders a different element, it doesn't make sense
to use `HTMLDivElement`. Using a more generic `HTMLElement` is simpler
and more correct (we still had `HTMLUListElement` and `HTMLLIElement`
for "div" DOM nodes which is incorrect).

This shouldn't be a breaking change because an `HTMLDivElement` is still
a valid `HTMLElement`. The other way around wouldn't be the case.
2024-05-03 15:32:55 +02:00
Robin Malfait f0e3e5b4a6 Bump dependencies (#3158)
* use `act` from `react` instead of `@testing-library/react`

* bump dependencies

* bump `@testing-library/react`

* bump `@react-aria/interactions`

* bump "@tanstack/react-virtual"

* add `ResizeObserver` polyfill, and enable it by default for tests

* mock `getBoundingClientRect`

Otherwise the virtualization tests don't work as expected because they
rely on the client rect which is not supported (or not correctly
measured) in JSDOM.
2024-05-02 14:41:58 +02:00
Jordan Pittman 1a440e1ee7 Fix issues with scrolling in a virtual combobox (#3163)
* Scrolling to active option in combobox after opening when last option was selected with the mouse

* Allow virtual combobox to scroll freely with mouse wheel after changing options with keyboard

* Update changelog
2024-05-01 15:57:28 -04:00
Jordan Pittman db702a7cec Only render virtual options wrapper when there are items to show (#3161)
* Only render virtual options wrapper when there are items to show

* Update changelog
2024-05-01 09:56:48 -04:00
Jordan Pittman 5952268bf7 Update changelog 2024-05-01 09:15:45 -04:00
Jordan Pittman 2e6cb126ef Render virtual items during an exiting transition (#3160)
Otherwise the render function will get called with different options by `render()` resulting in undefined
2024-05-01 09:14:49 -04:00
Jordan Pittman cb1abe42e6 Fix anchored elements not flipping when there is padding (#3157)
* Fix anchored elements not flipping when there is padding

* Update changelog
2024-04-30 16:08:02 -04:00
Robin Malfait f35214db4c calculate the size of an element as soon as possible (#3153)
Instead of waiting for a `useEffect` to trigger, we can use `useMemo` to
always compute the size the moment a DOM element is available (or
changes).

We also make sure to force a re-render when resizes are detected on the
stable DOM node, which in turn causes the `useMemo` to recompute.
2024-04-30 19:31:40 +02:00
Jordan Pittman 319bcbad3b Only check for elements in useInertOthers (#3154)
* Only check for elements in useInertOthers

* Update changelog

* Update packages/@headlessui-react/CHANGELOG.md

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2024-04-30 10:40:58 -04:00
Robin Malfait 4acf9e27f1 Ensure the static and portal props work nicely together (#3152)
* ensure we keep the `static` prop into account when using the `<Portal/>`
around anchored elements.

* update changelog
2024-04-29 20:47:00 +02:00
Robin Malfait 872808c8fc ensure we always set portal to true when the anchor props is truthy
We already had this for most components, but I missed it for this
component. This fixes that.
2024-04-29 20:29:28 +02:00
Robin Malfait beaae6a3f3 Use var(--anchor-max-height) when using the anchor prop (#3148)
* use `var(--anchor-max-height)` if available

When using the `anchor` prop, we try to position the anchored element
within the viewport. We use the size middleware of Floating UI to ensure
we are working in a constrained `max-width` and `max-height`.

However, if you want to limit the height of let's say a
`ComboboxOptions` then you instinctively add `max-h-60` for example. The
problem is that the `max-height` set by Floating UI will win because
it's inline styles.

You could use `!max-h-60` which makes it `!important`, but then you can
run into an issue where the max height set by the user is larger than
the available space which results in combobox options that are
unavailable.

To solve this, we want best of both worlds by ensuring we prefer the
size from the user, but constrain it with the value we know.

We now read from a `var(--anchor-max-height)` variable where you can set
your own preferred max height.

E.g.:

```ts
<Combobox>
  <ComboboxInput />
  <ComboboxOptions anchor="bottom start" className="[--anchor-gap:var(--spacing-4)] [--anchor-max-height:var(--spacing-60)]">
    …
  </ComboboxOptions>
</Combobox>
```

* update changelog
2024-04-29 17:16:04 +02:00
Robin Malfait afc9cb648b Ensure TransitionRoot component without props transitions correctly (#3147)
* ensure `TransitionRoot` component without props transitions correctly

A bit of a weird one, but you can use the `TransitionRoot` component
without any props for transitions themselves (so no real transition can
happen). Even crazier, it can happen that it doesn't even render a DOM
node, but just its children.

At this point, the `TransitionRoot` component is purely there for
orchestration purposes of child components.

Since there is no DOM node in certain situations, the transitions (and
its `onStart` and `onStop` callbacks) won't even happen at all. This
causes a bug (obvious in react strict mode) where children don't
properly mount or the transition component doesn't properly unmount.

* update changelog
2024-04-26 23:28:43 +02:00
Robin Malfait 539c124c69 Improve internal demo mode (#3143)
* improve demo mode for the `Dialog` component

This still disabled `inert` and focus stealing code. But it does allow
outside click.

* improve demo mode for the `Combobox` component

Before this, once you start interacting with the `Combobox`, the options
weren't properly scrolled into view.

* improve demo mode for the `Menu` component

* improve demo mode for the `Popover` component

* add demo mode to the `Listbox` component
2024-04-26 16:35:12 +02:00
Robin Malfait 26e164447f Add overflow: auto when using the anchor prop (#3138) 2024-04-25 18:14:41 +02:00