Open Menu and Listbox on mousedown (#3689)

This is a small behavioral change, but this PR will change when the
`Menu` and `Listbox` components open.

This PR will now open the `Menu` and `Listbox` components on `mousedown`
instead of `click`. This will make it feel more responsive and faster to
the user.

This is also how macOS for example opens menu-like components on the OS
level. This is also how the native `<select>` (at least on macOS) works.
This commit is contained in:
Robin Malfait
2025-04-11 00:00:39 +02:00
committed by GitHub
parent 9685af7148
commit 51acc1bff5
5 changed files with 9 additions and 14 deletions
+1
View File
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve `Menu` component performance ([#3685](https://github.com/tailwindlabs/headlessui/pull/3685))
- Improve `Listbox` component performance ([#3688](https://github.com/tailwindlabs/headlessui/pull/3688))
- Open `Menu` and `Listbox` on `mousedown` ([#3689](https://github.com/tailwindlabs/headlessui/pull/3689))
## [2.2.1] - 2025-04-04
@@ -3963,11 +3963,10 @@ describe('Mouse interactions', () => {
it(
'should be possible to click outside of the listbox, on an element which is within a focusable element, which closes the listbox',
suppressConsoleLogs(async () => {
let focusFn = jest.fn()
render(
<div>
<Listbox value={undefined} onChange={(x) => console.log(x)}>
<Listbox.Button onFocus={focusFn}>Trigger</Listbox.Button>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="alice">alice</Listbox.Option>
<Listbox.Option value="bob">bob</Listbox.Option>
@@ -3995,9 +3994,6 @@ describe('Mouse interactions', () => {
// Ensure the outside button is focused
assertActiveElement(document.getElementById('btn'))
// Ensure that the focus button only got focus once (first click)
expect(focusFn).toHaveBeenCalledTimes(1)
})
)
@@ -393,7 +393,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
}
})
let handleClick = useEvent((event: ReactMouseEvent) => {
let handleMouseDown = useEvent((event: ReactMouseEvent) => {
if (event.button !== 0) return // Only handle left clicks
if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
if (machine.state.listboxState === ListboxStates.Open) {
flushSync(() => machine.actions.closeListbox())
@@ -450,7 +451,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
onKeyPress: handleKeyPress,
onClick: handleClick,
onMouseDown: handleMouseDown,
},
focusProps,
hoverProps,
@@ -3077,11 +3077,10 @@ describe('Mouse interactions', () => {
it(
'should be possible to click outside of the menu, on an element which is within a focusable element, which closes the menu',
suppressConsoleLogs(async () => {
let focusFn = jest.fn()
render(
<div>
<Menu>
<Menu.Button onFocus={focusFn}>Trigger</Menu.Button>
<Menu.Button>Trigger</Menu.Button>
<Menu.Items>
<Menu.Item as="a">alice</Menu.Item>
<Menu.Item as="a">bob</Menu.Item>
@@ -3109,9 +3108,6 @@ describe('Mouse interactions', () => {
// Ensure the outside button is focused
assertActiveElement(document.getElementById('btn'))
// Ensure that the focus button only got focus once (first click)
expect(focusFn).toHaveBeenCalledTimes(1)
})
)
@@ -229,7 +229,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
state.itemsElement,
])
let handleClick = useEvent((event: ReactMouseEvent) => {
let handleMouseDown = useEvent((event: ReactMouseEvent) => {
if (event.button !== 0) return // Only handle left clicks
if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
if (disabled) return
if (menuState === MenuState.Open) {
@@ -273,7 +274,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
autoFocus,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
onClick: handleClick,
onMouseDown: handleMouseDown,
},
focusProps,
hoverProps,