Skip to content

Commit 0a14649

Browse files
authored
Improve quick release by requiring some cursor movement (#3762)
This PR improves the quick release feature we have for Menu, Listbox and Combobox components. This now ensures that your mouse was moved at least `5px` before triggering the quick release action. This odd behavior is most prevalent in Listbox components where the `anchor="selection"` feature is used such that the options are rendered on top of the Listbox button. Because of this, if you hold your cursor a bit too long when clicking the Listbox it would immediately select the option and close again. With this improvement in place, you have to at least move your cursor `5px`. This is an arbitrary value I picked so we can tweak it if we wanted to, but `1px` to `3px` felt too small and `10px` felt too large. ### Test plan When initially opening the Listbox, I'm holding down my cursor a bit (but not moving!) and then releasing the "click". #### Before: Notice how the initial click results in the Listbox closing again once I release the cursor. https://github.com/user-attachments/assets/df8dc749-ce20-46c9-8815-ac817b789b7e #### After: Notice how the initial click results in the Listbox staying open even after releasing the cursor. This is because we didn't move enough so we don't register it as a quick release. https://github.com/user-attachments/assets/f8d76a7b-1edd-4d9a-a8d3-120aadf49c57
1 parent 2ff307c commit 0a14649

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Fix immediately closing Listbox by requiring some cursor movement ([#3762](https://github.com/tailwindlabs/headlessui/pull/3762))
1113

1214
## [2.2.5] - 2025-07-23
1315

packages/@headlessui-react/src/hooks/use-quick-release.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export const Action = {
2828
// Pointerdown -> drag over an item -> pointer up -> "click" on the item
2929
const POINTER_HOLD_THRESHOLD = 200
3030

31+
// We should at least move this amount of pixels before we consider it a quick
32+
// release. Otherwise it's just a normal click.
33+
const POINTER_MOVEMENT_THRESHOLD = 5
34+
3135
type PointerEventWithTarget = Exclude<PointerEvent, 'target'> & {
3236
target: HTMLElement
3337
}
@@ -54,10 +58,15 @@ export function useQuickRelease(
5458
// Capture the timestamp of when the `pointerdown` event happened on the
5559
// trigger.
5660
let triggeredAtRef = useRef<Date | null>(null)
61+
let startXRef = useRef<number | null>(null)
62+
let startYRef = useRef<number | null>(null)
5763
useDocumentEvent(enabled && trigger !== null, 'pointerdown', (e) => {
5864
if (!DOM.isNode(e?.target)) return
5965
if (!trigger?.contains(e.target)) return
6066

67+
startXRef.current = e.x
68+
startYRef.current = e.y
69+
6170
triggeredAtRef.current = new Date()
6271
})
6372

@@ -68,6 +77,15 @@ export function useQuickRelease(
6877
if (triggeredAtRef.current === null) return
6978
if (!DOM.isHTMLorSVGElement(e.target)) return
7079

80+
// Ensure we moved the pointer a bit before considering it a quick
81+
// release.
82+
if (
83+
Math.abs(e.x - (startXRef.current ?? e.x)) < POINTER_MOVEMENT_THRESHOLD &&
84+
Math.abs(e.y - (startYRef.current ?? e.y)) < POINTER_MOVEMENT_THRESHOLD
85+
) {
86+
return
87+
}
88+
7189
let result = action(e as PointerEventWithTarget)
7290

7391
let diffInMs = new Date().getTime() - triggeredAtRef.current.getTime()

0 commit comments

Comments
 (0)