-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add a quick trigger action to the Menu
, Listbox
and Combobox
components
#3700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c5999be
3fadba0
efa81c3
981f4cd
bb52374
6ef6fd2
9271511
b223a83
22abb9f
e17a568
276b394
7716f07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ import React, { | |
type FocusEvent as ReactFocusEvent, | ||
type KeyboardEvent as ReactKeyboardEvent, | ||
type MouseEvent as ReactMouseEvent, | ||
type PointerEvent as ReactPointerEvent, | ||
type Ref, | ||
} from 'react' | ||
import { flushSync } from 'react-dom' | ||
|
@@ -34,6 +35,7 @@ import { useLatestValue } from '../../hooks/use-latest-value' | |
import { useOnDisappear } from '../../hooks/use-on-disappear' | ||
import { useOutsideClick } from '../../hooks/use-outside-click' | ||
import { useOwnerDocument } from '../../hooks/use-owner' | ||
import { Action as QuickReleaseAction, useQuickRelease } from '../../hooks/use-quick-release' | ||
import { useRefocusableInput } from '../../hooks/use-refocusable-input' | ||
import { useResolveButtonType } from '../../hooks/use-resolve-button-type' | ||
import { useScrollLock } from '../../hooks/use-scroll-lock' | ||
|
@@ -989,9 +991,43 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>( | |
...theirProps | ||
} = props | ||
|
||
let inputElement = useSlice(machine, (state) => state.inputElement) | ||
let [comboboxState, inputElement, optionsElement] = useSlice(machine, (state) => [ | ||
state.comboboxState, | ||
state.inputElement, | ||
state.optionsElement, | ||
]) | ||
let refocusInput = useRefocusableInput(inputElement) | ||
|
||
let enableQuickRelease = comboboxState === ComboboxState.Open | ||
useQuickRelease(enableQuickRelease, { | ||
trigger: localButtonElement, | ||
action: useCallback( | ||
(e) => { | ||
if (localButtonElement?.contains(e.target)) { | ||
return QuickReleaseAction.Ignore | ||
} | ||
|
||
if (inputElement?.contains(e.target)) { | ||
return QuickReleaseAction.Ignore | ||
} | ||
|
||
let option = e.target.closest('[role="option"]:not([data-disabled])') | ||
if (option !== null) { | ||
return QuickReleaseAction.Select(option as HTMLElement) | ||
} | ||
|
||
if (optionsElement?.contains(e.target)) { | ||
return QuickReleaseAction.Ignore | ||
} | ||
|
||
return QuickReleaseAction.Close | ||
}, | ||
[localButtonElement, inputElement, optionsElement] | ||
), | ||
close: machine.actions.closeCombobox, | ||
select: machine.actions.selectActiveOption, | ||
}) | ||
Comment on lines
+1001
to
+1029
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open to suggestions for the API here. But essentially what we want to know is:
The reason I didn't just call If we check for the time differences for any action, then it could result in the menu not closing if you were quick enough. |
||
|
||
let handleKeyDown = useEvent((event: ReactKeyboardEvent<HTMLElement>) => { | ||
switch (event.key) { | ||
// Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12 | ||
|
@@ -1044,9 +1080,9 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>( | |
} | ||
}) | ||
|
||
let handleMouseDown = useEvent((event: ReactMouseEvent<HTMLButtonElement>) => { | ||
// 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 | ||
let handlePointerDown = useEvent((event: ReactPointerEvent<HTMLButtonElement>) => { | ||
// We use the `poitnerdown` 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() | ||
|
@@ -1074,11 +1110,6 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>( | |
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled }) | ||
let { pressed: active, pressProps } = useActivePress({ disabled }) | ||
|
||
let [comboboxState, optionsElement] = useSlice(machine, (state) => [ | ||
state.comboboxState, | ||
state.optionsElement, | ||
]) | ||
|
||
let slot = useMemo(() => { | ||
return { | ||
open: comboboxState === ComboboxState.Open, | ||
|
@@ -1102,7 +1133,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>( | |
'aria-labelledby': labelledBy, | ||
disabled: disabled || undefined, | ||
autoFocus, | ||
onMouseDown: handleMouseDown, | ||
onPointerDown: handlePointerDown, | ||
onKeyDown: handleKeyDown, | ||
}, | ||
focusProps, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to add this to make the tests pass