-
Notifications
You must be signed in to change notification settings - Fork 377
feat(Select/Dropdown/MenuContainer): arrow key handling to focus items #11132
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 4 commits
0fe487a
039e095
fd712a7
fdbc8b3
513c6f5
cdd6f0f
de44721
901e606
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 |
|---|---|---|
|
|
@@ -62,6 +62,10 @@ export interface SelectProps extends MenuProps, OUIAProps { | |
| onOpenChange?: (isOpen: boolean) => void; | ||
| /** Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ | ||
| onOpenChangeKeys?: string[]; | ||
| /** Callback to override the default behavior when pressing up/down arrow keys when the toggle has focus and the menu is open. By default non-disabled menu items will receive focus - the first item on arrow down or the last item on arrow up. */ | ||
| onToggleArrowKeydown?: (event: KeyboardEvent) => void; | ||
| /** Indicates that the Select is used as a typeahead (combobox). Focus won't shift to menu items when pressing up/down arrows. */ | ||
| isTypeahead?: boolean; | ||
|
||
| /** Indicates if the select should be without the outer box-shadow */ | ||
| isPlain?: boolean; | ||
| /** @hide Forwarded ref */ | ||
|
|
@@ -95,6 +99,8 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({ | |
| shouldFocusFirstItemOnOpen = false, | ||
| onOpenChange, | ||
| onOpenChangeKeys = ['Escape', 'Tab'], | ||
| onToggleArrowKeydown, | ||
| isTypeahead, | ||
| isPlain, | ||
| innerRef, | ||
| zIndex = 9999, | ||
|
|
@@ -131,6 +137,21 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({ | |
| }, [isOpen]); | ||
|
|
||
| React.useEffect(() => { | ||
| const onToggleArrowKeydownDefault = (event: KeyboardEvent) => { | ||
| event.preventDefault(); | ||
|
|
||
| let listItem: HTMLLIElement; | ||
| if (event.key === 'ArrowDown') { | ||
| listItem = menuRef.current?.querySelector('li'); | ||
| } else { | ||
| const allItems = menuRef.current?.querySelectorAll('li'); | ||
| listItem = allItems ? allItems[allItems.length - 1] : null; | ||
| } | ||
|
|
||
| const focusableElement = listItem?.querySelector('button:not(:disabled),input:not(:disabled)'); | ||
kmcfaul marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| focusableElement && (focusableElement as HTMLElement).focus(); | ||
| }; | ||
|
|
||
| const handleMenuKeys = (event: KeyboardEvent) => { | ||
| // Close the menu on tab or escape if onOpenChange is provided | ||
| if ( | ||
|
|
@@ -144,6 +165,18 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({ | |
| toggleRef.current?.focus(); | ||
| } | ||
| } | ||
|
|
||
| if ( | ||
| isOpen && | ||
| toggleRef.current?.contains(event.target as Node) && | ||
| (event.key === 'ArrowDown' || event.key === 'ArrowUp') | ||
| ) { | ||
| if (onToggleArrowKeydown) { | ||
| onToggleArrowKeydown(event); | ||
| } else if (!isTypeahead) { | ||
| onToggleArrowKeydownDefault(event); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleClick = (event: MouseEvent) => { | ||
|
|
@@ -168,6 +201,7 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({ | |
| toggleRef, | ||
| onOpenChange, | ||
| onOpenChangeKeys, | ||
| onToggleArrowKeydown, | ||
| shouldPreventScrollOnItemFocus, | ||
| shouldFocusFirstItemOnOpen, | ||
| focusTimeoutDelay | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
I think this prop can be generalized to
onToggleKeydownsince it could be used to add any key behavior.The description could also be updated to
Callback to override the toggle keydown behavior. By default, when the toggle has focus and the menu is open, pressing the up/down arrow keys will focus a valid non-disabled menu item - the first item for the down arrow key and last item for the up arrow key.The doublewhenis slightly confusing to me.Same goes for
MenuContainerandSelect.