From 0777e189d9103871702f214c817f5d90f34acc69 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:35:15 +0200 Subject: [PATCH 01/16] bail the refocus if focus is already on the correct element --- packages/@headlessui-react/src/hooks/use-refocusable-input.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@headlessui-react/src/hooks/use-refocusable-input.ts b/packages/@headlessui-react/src/hooks/use-refocusable-input.ts index a13b2438ca..956aa9d4f1 100644 --- a/packages/@headlessui-react/src/hooks/use-refocusable-input.ts +++ b/packages/@headlessui-react/src/hooks/use-refocusable-input.ts @@ -29,6 +29,9 @@ export function useRefocusableInput(ref: MutableRefObject { let input = ref.current + + // If the input is already focused, we don't need to do anything + if (document.activeElement === input) return if (!(input instanceof HTMLInputElement)) return if (!input.isConnected) return From cd851d965f15002e0eaf9bd04d0b20caa20d9476 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:35:47 +0200 Subject: [PATCH 02/16] use `mousedown` instead of `click` event The `mousedown` event happens before the `focus` event. When we `e.preventDefault()` in this listener, the `focus` event will not fire. This also means that the focus is not lost on the actual `input` component which in turn means that we can maintain the selection / cursor position inside the `input`. We still use the `refocusInput()` as a fallback in case something else goes wrong. --- .../src/components/combobox/combobox.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 061cdbe58a..fa1a68441e 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1424,12 +1424,13 @@ function ButtonFn( } }) - let handleClick = useEvent((event: ReactMouseEvent) => { - if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault() + let handleMouseDown = useEvent((event: ReactMouseEvent) => { + event.preventDefault() + + if (isDisabledReactIssue7711(event.currentTarget)) return if (data.comboboxState === ComboboxState.Open) { actions.closeCombobox() } else { - event.preventDefault() actions.openCombobox() } @@ -1464,7 +1465,7 @@ function ButtonFn( 'aria-labelledby': labelledBy, disabled: disabled || undefined, autoFocus, - onClick: handleClick, + onMouseDown: handleMouseDown, onKeyDown: handleKeyDown, }, focusProps, @@ -1689,8 +1690,10 @@ function OptionFn< /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ data.activeOptionIndex, ]) - let handleClick = useEvent((event: { preventDefault: Function }) => { - if (disabled || data.virtual?.disabled(value)) return event.preventDefault() + let handleMouseDown = useEvent((event: { preventDefault: Function }) => { + event.preventDefault() + + if (disabled || data.virtual?.disabled(value)) return select() // We want to make sure that we don't accidentally trigger the virtual keyboard. @@ -1758,7 +1761,7 @@ function OptionFn< // both single and multi-select. 'aria-selected': selected, disabled: undefined, // Never forward the `disabled` prop - onClick: handleClick, + onMouseDown: handleMouseDown, onFocus: handleFocus, onPointerEnter: handleEnter, onMouseEnter: handleEnter, From a973f435152b22212a3a5eb5697e32cb1c3995e8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:36:04 +0200 Subject: [PATCH 03/16] add comments to describe _why_ we use `mousedown` --- .../src/components/combobox/combobox.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index fa1a68441e..3de35634d8 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1425,6 +1425,12 @@ function ButtonFn( }) let handleMouseDown = useEvent((event: ReactMouseEvent) => { + // We use the `mousedown` event because this will fire before the `focus` + // event. When we use `event.preventDefault()` here, the + // `document.activeElement` will stay on the current element. + // + // Typically that means that the `ComboboxInput` will stay focused, and thus + // the selection / cursor position will be preserved. event.preventDefault() if (isDisabledReactIssue7711(event.currentTarget)) return @@ -1691,6 +1697,12 @@ function OptionFn< ]) let handleMouseDown = useEvent((event: { preventDefault: Function }) => { + // We use the `mousedown` event because this will fire before the `focus` + // event. When we use `event.preventDefault()` here, the + // `document.activeElement` will stay on the current element. + // + // Typically that means that the `ComboboxInput` will stay focused, and thus + // the selection / cursor position will be preserved. event.preventDefault() if (disabled || data.virtual?.disabled(value)) return From 135b0b849abd1b7fc3bd1b1e3a22a4aacf4b2af6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:36:17 +0200 Subject: [PATCH 04/16] ensure we handle mouse buttons correctly --- .../src/components/combobox/combobox.tsx | 29 +++++++++++++++---- .../@headlessui-react/src/components/mouse.ts | 4 +++ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 packages/@headlessui-react/src/components/mouse.ts diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 3de35634d8..a5c9afed50 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -69,6 +69,7 @@ import { import { useDescribedBy } from '../description/description' import { Keys } from '../keyboard' import { Label, useLabelledBy, useLabels, type _internal_ComponentLabel } from '../label/label' +import { MouseButton } from '../mouse' enum ComboboxState { Open, @@ -1434,13 +1435,21 @@ function ButtonFn( event.preventDefault() if (isDisabledReactIssue7711(event.currentTarget)) return - if (data.comboboxState === ComboboxState.Open) { - actions.closeCombobox() - } else { - actions.openCombobox() + + // When we used the `click` event we didn't have to worry about this because + // that only fires if you use the `left` mouse button. However, now that + // we use the `mousedown` event, we do have to worry about which button is + // pressed. + if (event.button === MouseButton.Left) { + if (data.comboboxState === ComboboxState.Open) { + actions.closeCombobox() + } else { + actions.openCombobox() + } } - d.nextFrame(() => refocusInput()) + // Ensure we focus the input + refocusInput() }) let labelledBy = useLabelledBy([id]) @@ -1696,7 +1705,7 @@ function OptionFn< /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ data.activeOptionIndex, ]) - let handleMouseDown = useEvent((event: { preventDefault: Function }) => { + let handleMouseDown = useEvent((event: ReactMouseEvent) => { // We use the `mousedown` event because this will fire before the `focus` // event. When we use `event.preventDefault()` here, the // `document.activeElement` will stay on the current element. @@ -1705,6 +1714,14 @@ function OptionFn< // the selection / cursor position will be preserved. event.preventDefault() + // When we used the `click` event we didn't have to worry about this because + // that only fires if you use the `left` mouse button. However, now that we + // use the `mousedown` event, we do have to worry about which button is + // pressed. + if (event.button !== MouseButton.Left) { + return + } + if (disabled || data.virtual?.disabled(value)) return select() diff --git a/packages/@headlessui-react/src/components/mouse.ts b/packages/@headlessui-react/src/components/mouse.ts new file mode 100644 index 0000000000..8f56ab7ba5 --- /dev/null +++ b/packages/@headlessui-react/src/components/mouse.ts @@ -0,0 +1,4 @@ +export enum MouseButton { + Left = 0, + Right = 2, +} From f30343e68cb2e76fb972c9f3151b9d018b1cf96a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:37:27 +0200 Subject: [PATCH 05/16] ensure we handle `Enter` and `Space` explicitly Now that we use the `mousedown` event instead of the `click` event, we have to make sure that we handle the `enter` and `space` keys explicitly. This used to be covered by the `click` event, but not for the `mousedown` event. --- .../src/components/combobox/combobox.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index a5c9afed50..c4dfeb6242 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1389,6 +1389,16 @@ function ButtonFn( switch (event.key) { // Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12 + case Keys.Space: + case Keys.Enter: + event.preventDefault() + event.stopPropagation() + if (data.comboboxState === ComboboxState.Closed) { + actions.openCombobox() + } + + return d.nextFrame(() => refocusInput()) + case Keys.ArrowDown: event.preventDefault() event.stopPropagation() From 35ed5dc4518d072a0857c5fc510cddee5ee50f95 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:37:56 +0200 Subject: [PATCH 06/16] ensure we focus the first element when using `ArrowDown` on the `ComboboxButton` We go to the last one on `ArrownUp`, but we forgot to do this on `ArrowDown`. --- .../@headlessui-react/src/components/combobox/combobox.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index c4dfeb6242..8cce9198a5 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1404,6 +1404,11 @@ function ButtonFn( event.stopPropagation() if (data.comboboxState === ComboboxState.Closed) { actions.openCombobox() + d.nextFrame(() => { + if (!data.value) { + actions.goToOption(Focus.First) + } + }) } return d.nextFrame(() => refocusInput()) From 16e57b606e2d0471ec321251c458008b4f310419 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:38:11 +0200 Subject: [PATCH 07/16] fix tiny typo Not related to this PR, but noticed it and fixed it anyway. --- .../@headlessui-react/src/components/combobox/combobox.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 0e824d8562..15a64472af 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -5194,7 +5194,7 @@ describe.each([{ virtual: true }, { virtual: false }])('Mouse interactions %s', options={[ { value: 'alice', children: 'Alice', disabled: false }, { value: 'bob', children: 'Bob', disabled: true }, - { value: 'charile', children: 'Charlie', disabled: false }, + { value: 'charlie', children: 'Charlie', disabled: false }, ]} /> ) From da5ef5611f091943fe87971fc0f384a091464efd Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 11:42:22 +0200 Subject: [PATCH 08/16] update changelog --- packages/@headlessui-react/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index bd9dbba6e0..b592bc329b 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Prevent unnecessary execution of the `displayValue` callback in the `ComboboxInput` component ([#3048](https://github.com/tailwindlabs/headlessui/pull/3048)) - Expose missing `data-disabled` and `data-focus` attributes on the `TabsPanel`, `MenuButton`, `PopoverButton` and `DisclosureButton` components ([#3061](https://github.com/tailwindlabs/headlessui/pull/3061)) - Fix cursor position when re-focusing the `ComboboxInput` component ([#3065](https://github.com/tailwindlabs/headlessui/pull/3065)) +- Keep focus inside of the `` component ([#3073](https://github.com/tailwindlabs/headlessui/pull/3073)) ### Changed From 64832a70f9b2dda1b80e9dc0bb80dea86a6e7914 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 18:07:31 +0200 Subject: [PATCH 09/16] ensure we reset the `isTyping` flag While we are typing, the flag can remain true. But once we stop typing, the `nextFrame` handler will kick in and set it to `false` again. It currently behaves as a debounce-like function such that the `nextFrame` callbacks are cancelled once a new event is fired. --- .../src/components/combobox/combobox.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 8cce9198a5..e57c44d700 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1078,8 +1078,21 @@ function InputFn< }) }) + let dd = useDisposables() let handleKeyDown = useEvent((event: ReactKeyboardEvent) => { isTyping.current = true + + // Re-set the typing flag, this behaves as a debounce-like implementation, + // so as long as we are typing we will not reset the `isTyping` flag, but + // once we stop typing we will reset it. + { + d.add(dd.dispose) // Ensure we cleanup when `d` is disposed + dd.dispose() // Cleanup previous disposables + dd.nextFrame(() => { + isTyping.current = false + }) + } + switch (event.key) { // Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12 From 258baba65e294777e6e959e1d370ce303eadfc7c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 2 Apr 2024 18:08:33 +0200 Subject: [PATCH 10/16] ensure unique callbacks in the `_disposables` array This allows us to keep re-adding dispose functions and only register the callbacks once. Ideally we can use a `Set`, but we also want to remove a single callback if the callback is disposed on its own instead of disposing the whole group. For this we do require an `idx` which is not available in a `Set` unless you are looping over all disposable functions. --- packages/@headlessui-react/src/utils/disposables.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/@headlessui-react/src/utils/disposables.ts b/packages/@headlessui-react/src/utils/disposables.ts index 5e14bb9a20..55ca27b6b5 100644 --- a/packages/@headlessui-react/src/utils/disposables.ts +++ b/packages/@headlessui-react/src/utils/disposables.ts @@ -59,6 +59,10 @@ export function disposables() { }, add(cb: () => void) { + if (_disposables.includes(cb)) { + return + } + _disposables.push(cb) return () => { let idx = _disposables.indexOf(cb) From 59bea451806addf245e86e7f51f181c7c5edbdfb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:09:17 +0200 Subject: [PATCH 11/16] Update packages/@headlessui-react/src/components/combobox/combobox.tsx Co-authored-by: Jonathan Reinink --- .../src/components/combobox/combobox.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index e57c44d700..c36c8439b4 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1454,12 +1454,10 @@ function ButtonFn( }) let handleMouseDown = useEvent((event: ReactMouseEvent) => { - // We use the `mousedown` event because this will fire before the `focus` - // event. When we use `event.preventDefault()` here, the - // `document.activeElement` will stay on the current element. - // - // Typically that means that the `ComboboxInput` will stay focused, and thus - // the selection / cursor position will be preserved. + // We use the `mousedown` event here since it fires before the focus + // event, allowing us to cancel the event before focus is moved from + // the `ComboboxInput` to the `ComboboxButton`. This keeps the input + // focused, preserving the cursor position and any text selection. event.preventDefault() if (isDisabledReactIssue7711(event.currentTarget)) return From ac30fc3a041a518520e4f5b8fee4d689978cafde Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:09:39 +0200 Subject: [PATCH 12/16] Update packages/@headlessui-react/src/components/combobox/combobox.tsx Co-authored-by: Jonathan Reinink --- .../@headlessui-react/src/components/combobox/combobox.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index c36c8439b4..d220490729 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1462,10 +1462,9 @@ function ButtonFn( if (isDisabledReactIssue7711(event.currentTarget)) return - // When we used the `click` event we didn't have to worry about this because - // that only fires if you use the `left` mouse button. However, now that - // we use the `mousedown` event, we do have to worry about which button is - // pressed. + // Since we're using the `mousedown` event instead of a `click` event + // here to preserve the focus of the `ComboboxInput`, we need to also + // check that the `left` mouse button was clicked. if (event.button === MouseButton.Left) { if (data.comboboxState === ComboboxState.Open) { actions.closeCombobox() From 3e42d7df9a23df39ab0087ee871c43ecacd217c8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:18:48 +0200 Subject: [PATCH 13/16] update comments --- .../src/components/combobox/combobox.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index d220490729..262c55bfd8 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1454,17 +1454,17 @@ function ButtonFn( }) let handleMouseDown = useEvent((event: ReactMouseEvent) => { - // We use the `mousedown` event here since it fires before the focus - // event, allowing us to cancel the event before focus is moved from - // the `ComboboxInput` to the `ComboboxButton`. This keeps the input - // focused, preserving the cursor position and any text selection. + // We use the `mousedown` event here since it fires before the focus event, + // allowing us to cancel the event before focus is moved from the + // `ComboboxInput` to the `ComboboxButton`. This keeps the input focused, + // preserving the cursor position and any text selection. event.preventDefault() if (isDisabledReactIssue7711(event.currentTarget)) return - // Since we're using the `mousedown` event instead of a `click` event - // here to preserve the focus of the `ComboboxInput`, we need to also - // check that the `left` mouse button was clicked. + // Since we're using the `mousedown` event instead of a `click` event here + // to preserve the focus of the `ComboboxInput`, we need to also check + // that the `left` mouse button was clicked. if (event.button === MouseButton.Left) { if (data.comboboxState === ComboboxState.Open) { actions.closeCombobox() @@ -1731,18 +1731,15 @@ function OptionFn< ]) let handleMouseDown = useEvent((event: ReactMouseEvent) => { - // We use the `mousedown` event because this will fire before the `focus` - // event. When we use `event.preventDefault()` here, the - // `document.activeElement` will stay on the current element. - // - // Typically that means that the `ComboboxInput` will stay focused, and thus - // the selection / cursor position will be preserved. + // We use the `mousedown` event here since it fires before the focus event, + // allowing us to cancel the event before focus is moved from the + // `ComboboxInput` to the `ComboboxOption`. This keeps the input focused, + // preserving the cursor position and any text selection. event.preventDefault() - // When we used the `click` event we didn't have to worry about this because - // that only fires if you use the `left` mouse button. However, now that we - // use the `mousedown` event, we do have to worry about which button is - // pressed. + // Since we're using the `mousedown` event instead of a `click` event here + // to preserve the focus of the `ComboboxInput`, we need to also check + // that the `left` mouse button was clicked. if (event.button !== MouseButton.Left) { return } From 4b68e6386908e1e5779b33dc71cbe7ec9e25d6b1 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:28:35 +0200 Subject: [PATCH 14/16] abstract confusing logic to a `useFrameDebounce()` hook --- .../src/components/combobox/combobox.tsx | 17 +++++------------ .../src/hooks/use-frame-debounce.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 packages/@headlessui-react/src/hooks/use-frame-debounce.ts diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 262c55bfd8..17a40edc20 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -3,6 +3,7 @@ import { useFocusRing } from '@react-aria/focus' import { useHover } from '@react-aria/interactions' import { Virtualizer, useVirtualizer } from '@tanstack/react-virtual' +import { useFrameDebounce } from 'hooks/use-frame-debounce' import React, { Fragment, createContext, @@ -1078,20 +1079,12 @@ function InputFn< }) }) - let dd = useDisposables() + let debounce = useFrameDebounce() let handleKeyDown = useEvent((event: ReactKeyboardEvent) => { isTyping.current = true - - // Re-set the typing flag, this behaves as a debounce-like implementation, - // so as long as we are typing we will not reset the `isTyping` flag, but - // once we stop typing we will reset it. - { - d.add(dd.dispose) // Ensure we cleanup when `d` is disposed - dd.dispose() // Cleanup previous disposables - dd.nextFrame(() => { - isTyping.current = false - }) - } + debounce(() => { + isTyping.current = false + }) switch (event.key) { // Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12 diff --git a/packages/@headlessui-react/src/hooks/use-frame-debounce.ts b/packages/@headlessui-react/src/hooks/use-frame-debounce.ts new file mode 100644 index 0000000000..fa79640fec --- /dev/null +++ b/packages/@headlessui-react/src/hooks/use-frame-debounce.ts @@ -0,0 +1,18 @@ +import { useDisposables } from './use-disposables' +import { useEvent } from './use-event' + +/** + * Schedule some task in the next frame. + * + * - If you call the returned function multiple times, only the last task will + * be executed. + * - If the component is unmounted, the task will be cancelled. + */ +export function useFrameDebounce() { + let d = useDisposables() + + return useEvent((cb: () => void) => { + d.dispose() + d.nextFrame(() => cb()) + }) +} From 4fed3385d70358061d6aad38c984f58fc5c95de0 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:32:15 +0200 Subject: [PATCH 15/16] use correct path import --- packages/@headlessui-react/src/components/combobox/combobox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 17a40edc20..d31d66ed16 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -3,7 +3,6 @@ import { useFocusRing } from '@react-aria/focus' import { useHover } from '@react-aria/interactions' import { Virtualizer, useVirtualizer } from '@tanstack/react-virtual' -import { useFrameDebounce } from 'hooks/use-frame-debounce' import React, { Fragment, createContext, @@ -28,6 +27,7 @@ import { useControllable } from '../../hooks/use-controllable' import { useDisposables } from '../../hooks/use-disposables' import { useElementSize } from '../../hooks/use-element-size' import { useEvent } from '../../hooks/use-event' +import { useFrameDebounce } from '../../hooks/use-frame-debounce' import { useId } from '../../hooks/use-id' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' import { useLatestValue } from '../../hooks/use-latest-value' From 7dcd4acdb41541288cedaa7b224ca4091a627a8b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 3 Apr 2024 14:45:04 +0200 Subject: [PATCH 16/16] add some breathing room --- packages/@headlessui-react/src/hooks/use-refocusable-input.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@headlessui-react/src/hooks/use-refocusable-input.ts b/packages/@headlessui-react/src/hooks/use-refocusable-input.ts index 956aa9d4f1..2ed48dfbbc 100644 --- a/packages/@headlessui-react/src/hooks/use-refocusable-input.ts +++ b/packages/@headlessui-react/src/hooks/use-refocusable-input.ts @@ -32,6 +32,7 @@ export function useRefocusableInput(ref: MutableRefObject