445 Commits

Author SHA1 Message Date
clonemycode 0933dd5e5f chore: remove redundant word in comment (#3742)
CI / install (push) Has been cancelled
Release Insiders / build (20) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / check-types (push) Has been cancelled
remove redundant word in comment
2025-06-09 16:55:03 +00:00
Robin Malfait 730ab68345 run prettier 2025-04-17 15:21:29 +02:00
Robin Malfait 97cb20806a Improve Combobox component performance (#3697)
This PR improves the performance of the `Combobox` component. This is a
similar implementation as:

- https://github.com/tailwindlabs/headlessui/pull/3685
- https://github.com/tailwindlabs/headlessui/pull/3688

Before this PR, the `Combobox` component is built in a way where all the
state lives in the `Combobox` itself. If state changes, everything
re-renders and re-computes the necessary derived state.

However, if you have a 1000 items, then every time the active item
changes, all 1000 items have to re-render.

To solve this, we can move the state outside of the `Combobox`
component, and "subscribe" to state changes using the `useSlice` hook
introduced in https://github.com/tailwindlabs/headlessui/pull/3684.

This will allow us to subscribe to a slice of the state, and only
re-render if the computed slice actually changes.

If the active item changes, only 3 things will happen:

1. The `ComboboxOptions` will re-render and have an updated
`aria-activedescendant`
2. The `ComboboxOption` that _was_ active, will re-render and the
`data-focus` attribute wil be removed.
3. The `ComboboxOption` that is now active, will re-render and the
`data-focus` attribute wil be added.

The `Combobox` component already has a `virtual` option if you want to
render many many more items. This is a bit of a different model where
all the options are passed in via an array instead of rendering all
`ComboboxOption` components immediately.

Because of this, I didn't want to batch the registration of the options
as part of this PR (similar to what we do in the `Menu` and `Listbox`)
because it behaves differently compared to what mode you are using
(virtual or not). Since not all components will be rendered, batching
the registration until everything is registered doesn't really make
sense in the general case. However, it does make sense in non-virtual
mode. But because of this difference, I didn't want to implement this as
part of this PR and increase the complexity of the PR even more.

Instead I will follow up with more PRs with more improvements. But the
key improvement of looking at the slice of the data is what makes the
biggest impact. This also means that we can do another release once this
is merged.

Last but not least, recently we fixed a bug where the `Combobox` in
`virtual` mode could crash if you search for an item that doesn't exist.
To solve it, we implemented a workaround in:

- https://github.com/tailwindlabs/headlessui/pull/3678

Which used a double `requestAnimationFrame` to delay the scrolling to
the item. While this solved this issue, this also caused visual flicker
when holding down your arrow keys.

I also fixed it in this PR by introducing `patch-package` and work
around the issue in the `@tanstack/virtual-core` package itself.

More info: 96f4da70b16d5cf259643

Before:


https://github.com/user-attachments/assets/132520d3-b4d6-42f9-9152-57427de20639

After:


https://github.com/user-attachments/assets/41f198fe-9326-42d1-a09f-077b60a9f65d

## Test plan

1. All tests still pass
2. Tested this in the browser with a 1000 items. In the videos below the
only thing I'm doing is holding down the `ArrowDown` key.

Before:


https://github.com/user-attachments/assets/945692a3-96e6-4ac7-bee0-36a1fd89172b

After:


https://github.com/user-attachments/assets/98a151d0-16cc-4823-811c-fcee0019937a
2025-04-17 15:16:30 +02:00
Robin Malfait 03fe3c573d Use correct ownerDocument when using internal <Portal/> (#3594)
This PR improves the internal `<Portal>` component by allowing to pass
in a custom `ownerDocument`.

This fixes an issue if you do something like this:

```ts
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { useState } from 'react'
import { createPortal } from 'react-dom'

export default function App() {
  let [target, setTarget] = useState(null)

  return (
    <div className="grid min-h-full place-content-center">
      <iframe
        ref={(iframe) => {
          if (!iframe) return
          if (target) return

          let el = iframe.contentDocument.createElement('div')
          iframe.contentDocument.body.appendChild(el)
          setTarget(el)
        }}
        className="h-[50px] w-[75px] border-black bg-white"
      >
        {target && createPortal(<MenuExample />, target)}
      </iframe>
    </div>
  )
}

function MenuExample() {
  return (
    <Menu>
      <MenuButton>Open</MenuButton>
      <MenuItems
        anchor="bottom"
        className="flex min-w-[var(--button-width)] flex-col bg-white shadow"
      >
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/settings">
            Settings
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/support">
            Support
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/license">
            License
          </a>
        </MenuItem>
      </MenuItems>
    </Menu>
  )
}
```

---

Here is a little reproduction video. The `<Menu/>` you see is rendered
in an `<iframe>`, the goal is that `<MenuItems/>` _also_ render inside
of the `<iframe>`.

In the video below we start with the fix where you can see that the
items are inside the iframe (and unstyled because I didn't load any
styles). The second part of the video is the before, where you can see
that the `<MenuItems/>` escape the `<iframe>` and are styled. That's not
what we want.


https://github.com/user-attachments/assets/2da7627e-7846-4c4d-bb14-278f80a03cd8
2024-12-12 15:45:02 +00:00
Robin Malfait 02b43d042d Cleanup process in Combobox component when using virtualization (#3495)
This PR is a different approach compared to #3487. 

Instead of checking whether we are in a test environment (specifically
in a Jest environment), I think we can just get rid of the check
entirely and use the virtualizer in all environments.

This will remove an unnecessary check for `process` being available and
gets rid of `process` entirely. It also fixes an issue that #3487 tries
to solve where `process` is available, but `process.env` is not.

Closes: #3487
2024-09-27 11:45:45 +02:00
Todd Austin f3353728bd updating various typos (#3387) 2024-07-16 12:48:21 -04:00
Steven Nunez 557c103e49 Update link to Vue docs (#3376) 2024-07-10 14:36:55 +02:00
Robin Malfait 91e959714b Fix restoring focus to correct element when closing Dialog component (#3365)
* resolve focusable element when recording elements

Right now, we have to record when a click / mousedown / focus event happens on an element. But when you click on a non-focusable element inside of a focusable element then we record the inner element instead of the outer one.

This happens in this scenario:
```html
<button>
  <span>click me</span>
</button>
```

This solves it by resolving the closest focusable element (and we fallback to the e.target as a last resort)

* update changelog
2024-07-05 16:14:50 +02:00
Jonathan Reinink 7a40af6b55 Clean up changelogs 2024-06-20 16:01:23 -04:00
Robin Malfait 03c22b42b6 Cancel outside click behavior on touch devices when scrolling (#3266)
* make `handleOutsideClick` stable

* cancel "outside click" when "scrolling" on touch device

When on a touch device, then the `touchend` event will fire, even if you
scrolled a bit and scrolling was your intention.

This now tracks that touches were at least 30px apart in either the X or
Y direction. If that's the case, then we do not consider it an outside
click.

* add `enabled` parameter to `useDocumentEvent` and `useWindowEvent`

* update `useDocumentEvent` and `useWindowEvent` usages

This now takes the new `enabled` value into account.

* update changelog

* bump vue and vite in playground
2024-06-03 16:18:14 +02:00
Robin Malfait 479853d5ed Ensure ComboboxInput does not sync while you are still typing (#3259)
* track `isTyping` in state

While you are typing, we should not sync the value with the `<input>`
because otherwise it would override your changes.

The moment you close the Combobox (by selecting an option, clicking
outside, pressing escape or tabbing away) we can mark the component as
not typing anymore. Once you are not typing anymore, then we can re-sync
the input with the given value.

* remove unused `useFrameDebounce` hook

* require `isTyping` boolean

* update changelog
2024-05-31 22:44:29 +02:00
Robin Malfait 35e7cbb375 sync with 1.x branch 2024-05-08 12:56:49 +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 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 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
Robin Malfait 9f44656d0f Prevent closing the Combobox component when clicking inside the scrollbar area (#3104)
* prevent closing `Combobox` when clicking inside the scrollbar area

* update changelog
2024-04-16 17:35:46 +02:00
Robin Malfait cb9cda7bd0 update changelog with latest v1.7 release 2024-04-15 17:38:05 +02:00
Robin Malfait 92a69ef687 Ensure import actions use the correct paths (#3093)
* drop unnecessary rootDir / paths in tsconfig

This was causing issues with automatic imports that pointed to
`utils/foo` instead of `../utils/foo` and caused the build to break.

* drop unnecessary tsconfig configuration from `@headlessui/vue`
2024-04-11 17:28:57 +02:00
Robin Malfait ae8c253c21 Fix typos (#3086)
* fix a bunch of typos

* fix typos in `@headlessui/vue`
2024-04-08 23:31:50 +02:00
Robin Malfait 000e0c0192 Prevent unnecessary execution of the displayValue callback in the ComboboxInput component (#3048)
* memoize the `currentDisplayValue`

This used to be re-executed every single render. This should typically
not be an issue, but if you use non-deterministic code (E.g.:
`Math.random`, `Date.now`, …) then it could result in incorrect values.

Using `useMemo` allows us to only re-run it if the `data.value` or thte
`displayValue` actually changes.

* add test to verify `currentDisplayValue` is stable

* update changelog
2024-03-21 13:46:17 +01:00
Robin Malfait 834dbf423e Respect selectedIndex for controlled <Tab/> components (#3037)
* ensure controlled `<Tab>` components respect the `selectedIndex`

* update changelog

* use older syntax in tests

* run prettier to fix lint step
2024-03-15 14:37:16 +01:00
Robin Malfait 626a253dcf copy License from the root (#3030) 2024-03-12 16:25:13 +01:00
Robin Malfait 043edb8319 Replace deprecated Jest methods with the new methods (#3005)
* replace deprecated `lastCalledWith` with `toHaveBeenLastCalledWith`

* replace deprecated `toThrowError` with `toThrow`
2024-02-21 14:28:19 +01:00
Robin Malfait a50be9255a Forward disabled state to hidden inputs in form-like components (#3004)
* make hidden inputs disabled if the wrapping component is disabled

* add tests to verify disabled hidden form elements

* update changelog
2024-02-21 14:16:31 +01:00
Jordan Pittman 08baf094d2 Update changelog 2024-02-07 17:12:43 -05:00
Jonathan Reinink 4156c45f78 Fix Combobox activeOption render prop (#2973)
* Fix Combobox `activeOption` render prop

* Update changelog
2024-02-07 17:09:42 -05:00
Robin Malfait 0e0277a684 Allow setting custom tabIndex on the <Switch /> component (#2966)
* allow setting a custom `tabIndex` on the `<Switch />` component

* update changelog
2024-02-03 17:31:02 +01:00
Robin Malfait edbcb81ead Add hidden attribute to internal <Hidden /> component when the Features.Hidden feature is used (#2955)
* add `hidden` attribute for `<Hidden features={Features.Hidden}>`

* update changelog
2024-01-31 13:29:50 +01:00
Jordan Pittman 3b2a102e8d Don’t override explicit disabled prop for components inside <MenuItem> (#2929)
* Don’t override explicit disabled prop inside `<MenuItem>`

* Update changelog
2024-01-16 15:34:41 -05:00
Robin Malfait aff438eb06 Prevent default behaviour when clicking outside of a Dialog.Panel (#2919)
* use `event.preventDefault()` in the `useOutsideClick` on Dialog's

When using a `Dialog`, we should prevent the default behaviour of the
event that triggered the "close" in the `useOutsideClick` call.

We recently made improvements to improve outside click behaviour on
touch devices (https://github.com/tailwindlabs/headlessui/pull/2572) but
due to the `touchend` event, the touch is still forwarded and therefore
a potential button _behind_ the "backdrop" will also be clicked. This is
not what we want.

Added the `event.preventDefault()` for the Dialog specifically because
there are other places where we use `useOutsideClick` and where we _do_
want the behaviour where the click just continues. A concrete example of
this is 2 `Menu`'s next to eachother where you open the first one, and
then click on the second one. This should close first one (outside
click) and open the second one (by not preventing the event)

* update changelog
2024-01-09 20:06:55 +01:00
Robin Malfait 2b7a57e337 Expose disabled state on <Tab /> component (#2918)
* expose `disabled` on `<Tab/>` component

This will expose it such that you can use it with `ui-disabled`. In the
Alpha version of React, you can also use `data-[disabled]` because it
will be exposed as `data-disabled` over there as well.

Fixes: #2864

* update changelog
2024-01-09 15:45:30 +01:00
Robin Malfait b83b5b6ffc sync CHANGELOG 2024-01-08 16:14:28 +01:00
Robin Malfait f6a99cad26 update changelog 2024-01-06 02:05:13 +01:00
Robin Malfait 799e98a56e improve iOS scroll locking
The scroll locking on iOS was flickering in some scenario's due to the
`window.scrollTo(0, 0)` related code. Instead of that, we now cancel
touch moves instead but still allow it in scrollable containers inside
the Dialog itself.

This was already applied in the React version, but this adds the same
improvement to the Vue version as well.
2024-01-06 01:58:22 +01:00
Robin Malfait bc4a744947 fix incorrect activeIndex when handling Focus.Previous
This is already available in the React version, now let's sync it in the
Vue version as well.
2024-01-06 01:51:07 +01:00
Robin Malfait a73007388f Ensure playgrounds work + switch to npm workspaces (#2907)
* bump Next in playground

* convert legacy Link after Next.js bump

* update yarn.lock

* switch to npm workspaces

* move `packages/playground-*` to `playgrounds/*`

* use `npm` instead of `yarn`

* sync package-lock.json

* use node 20 for insiders releases
2024-01-03 14:26:12 +01:00
Robin Malfait e662f12398 2.0.0 Alpha prep (#2887)
* 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
2023-12-20 19:57:57 +01:00
Jordan Pittman 01a34cb0c4 Update changelog 2023-12-12 15:14:49 -05:00
Евгений c24ba868bd Fix error when transition classes contain new lines (#2871)
* fix DOMException when remove class with '\n' character in react 'transition' component

* Split classes on all whitespace

* Revert "fix DOMException when remove class with '\n' character in react 'transition' component"

This reverts commit 76e835441b9cb84a1d867e1a37496c55cae86179.

* fix typo

* Add test

* Fix CS

* Update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-12-12 15:09:55 -05:00
Jordan Pittman c25e2e6036 Fix CJS types (#2880)
* Fix Vue type error

* Add separate CTS types

* Add “Are The Types Wrong” CLI

* wip

* Bump node versions in workflows

* wip

* wip

* wip

* yolo

* yolo (again?)

* wip

* wip
2023-12-12 11:43:34 -05:00
Jordan Pittman c2096b0ddd Update changelog 2023-12-05 11:25:18 -05:00
Jordan Pittman 684623131b Fix outside click detection when component is mounted in the Shadow DOM in Vue 2023-12-05 11:22:33 -05:00
Robin Malfait bfacb648dc Fix VoiceOver bug for Listbox component in Chrome (#2824)
* fix VoiceOver bug for Listbox in Chrome

Chrome currently has a bug if you use a `Listbox` with a `Label` and use
the `aria-multiselectable` attribute. This combination will cause
VoiceOver to _not_ announce the `role="option"` elements when
interacting with them.

If we drop the `aria-multiselectable` OR the `aria-labelledby` it starts
working. Alternatively replacing `aria-labelledby` with `aria-label`
won't work either.

I filed a Chrome bug report about this here: https://bugs.chromium.org/p/chromium/issues/detail?id=1498261

---

Luckily there is a workaround in our `Listbox` implementation. Right now
we always require the `Listbox.Button` to be there. The
`Listbox.Options` component doesn't work on its own in our
implementation.

This means that whenever we open the `Listbox` that we have to go via
the `Listbox.Button`. This `Listbox.Button` is already labelled by the
`Listbox.Label` if there is one.

This also means that we can safely drop the `id` of the label inside the
`aria-labelledby` from the `Listbox.Options`.

This wouldn't have worked if our `Listbox.Options` could be used in a
standalone way without the `Listbox.Button`.

At the end of the day the hierarchy looks like this:

- Options is labelled by the Button
   - Button is labelled by the Label
      - Label

Fixes: #2817

* update changelog
2023-11-02 17:52:43 +01:00
Jordan Pittman 1469b85c36 Update changelog 2023-10-04 13:45:58 -04:00
Jordan Pittman 20a224aaa4 Fix state data attribute in Vue (#2787)
* Add tests

* Fix state data attribute in Vue
2023-10-04 13:39:26 -04:00
Robin Malfait 99cdf91631 Implement new virtual API for the Combobox component (#2779)
* add `(Vue)` or `(React)` to playground header

* show amount of items in virtualized example

* improve calculating the active index

* disable strict mode

* update virtualized playground examples with preferred API

* optimize `calculateActiveIndex`

* implement new `virtual` API

* update changelog
2023-10-02 11:55:20 +02:00
Robin Malfait f016dc51db Add virtual prop to Combobox component (#2740)
* type timezones in playground data

* add `@tanstack/react-virtual` and `@tanstack/vue-virtual`

* use latest stable Tailwind CSS version

* add Combobox with virtual prop example

* add `virtual` prop to `Combobox`

Co-authored-by: Jordan Pittman <jordan@cryptica.me>

* add tests for `virtual` prop

- Also wrap `click` helpers in `act` for React (use `rawClick` without
  `act` in tests related to `Transition`)

* update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-09-15 14:29:18 +02:00
Robin Malfait f2179f36c0 further improve import sorting 2023-09-11 19:09:53 +02:00
Robin Malfait 76dd10ea55 Sort imports automatically (#2741)
* add `prettier-plugin-organize-imports` and `prettier-plugin-tailwindcss`

* format

* bump Tailwind CSS

* format playgrounds using updated Tailwind CSS and Prettier plugins

* use import syntax
2023-09-11 18:36:30 +02:00
Timur Zurbaev fa952626c7 Add immediate prop to <Combobox /> for immediately opening the Combobox when the input receives focus (#2686)
* Allow to open combobox on input focus

* Close focused combobox with openOnFocus prop when clicking the button

* ensure tabbing through a few fields, doesn't result in an incorrectly selected item

When you have a fwe inputs such as:

```html
<form>
   <input />
   <input />
   <input />
   <Combobox>
      <Combobox.Input />
   </Combobox>
   <input />
   <input />
   <input />
</form>
```

Tabbing through this list will open the combobox once you are on the
input field. When you continue tabbing, the first item would be
selected. However, if the combobox is not marked as nullable, it means
that just going through the form means that we set a value we can't
unset anymore.

We still want to open the combobox, we just don't want to select
anything in this case.

* only `openOnFocus` if the `<Combobox.Input />` is focused from the
outside

If the focus is coming from the `<Combobox.Button />` or as a side
effect of selecting an `<Combobox.Option />` then we don't want to
re-open the `<Combobox />`

* update tests to ensure that the `Combobox.Input` is the active element

* order `handleBlur` and `handleFocus` the same way in Vue & React

* only select the active option when the Combobox wasn't opened by focusing the input field

* convert to `immediate` prop on the `Combobox` itself

* update changelog

* ensure we see the "relatedTarget" in Safari

Safari doesn't fire a `focus` event when clicking a button, therefore it
does not become the `document.activeElement`, and events like `blur` or
`focus` doesn't set the button as the `event.relatedTarget`.

Keeping track of a history like this solves that problem. We already had
the code for the `FocusTrap` component.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-08-31 15:23:56 +02:00