Skip to content

Commit 0fe487a

Browse files
committed
feat(Select): default arrow key handling to focus items
1 parent f19a7a5 commit 0fe487a

File tree

8 files changed

+37
-7
lines changed

8 files changed

+37
-7
lines changed

packages/react-core/src/components/Select/Select.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export interface SelectProps extends MenuProps, OUIAProps {
6262
onOpenChange?: (isOpen: boolean) => void;
6363
/** 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. */
6464
onOpenChangeKeys?: string[];
65+
/** Custom callback to override the default behaviour when pressing up/down arrows. Default is focusing the menu items (first item on arrow down, last item on arrow up). */
66+
onArrowUpDownKeyDown?: (event: KeyboardEvent) => void;
67+
/** Indicates that the Select is used as a typeahead (combobox). Focus won't shift to menu items when pressing up/down arrows. */
68+
isTypeahead?: boolean;
6569
/** Indicates if the select should be without the outer box-shadow */
6670
isPlain?: boolean;
6771
/** @hide Forwarded ref */
@@ -95,6 +99,8 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
9599
shouldFocusFirstItemOnOpen = false,
96100
onOpenChange,
97101
onOpenChangeKeys = ['Escape', 'Tab'],
102+
onArrowUpDownKeyDown,
103+
isTypeahead,
98104
isPlain,
99105
innerRef,
100106
zIndex = 9999,
@@ -131,6 +137,21 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
131137
}, [isOpen]);
132138

133139
React.useEffect(() => {
140+
const onArrowUpDownKeyDownDefault = (event: KeyboardEvent) => {
141+
event.preventDefault();
142+
143+
let listItem: HTMLLIElement;
144+
if (event.key === 'ArrowDown') {
145+
listItem = menuRef.current?.querySelector('li');
146+
} else {
147+
const allItems = menuRef.current?.querySelectorAll('li');
148+
listItem = allItems ? allItems[allItems.length - 1] : null;
149+
}
150+
151+
const focusableElement = listItem?.querySelector('button:not(:disabled),input:not(:disabled)');
152+
focusableElement && (focusableElement as HTMLElement).focus();
153+
};
154+
134155
const handleMenuKeys = (event: KeyboardEvent) => {
135156
// Close the menu on tab or escape if onOpenChange is provided
136157
if (
@@ -144,6 +165,14 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
144165
toggleRef.current?.focus();
145166
}
146167
}
168+
169+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
170+
if (onArrowUpDownKeyDown) {
171+
onArrowUpDownKeyDown(event);
172+
} else if (!isTypeahead) {
173+
onArrowUpDownKeyDownDefault(event);
174+
}
175+
}
147176
};
148177

149178
const handleClick = (event: MouseEvent) => {
@@ -168,6 +197,7 @@ const SelectBase: React.FunctionComponent<SelectProps & OUIAProps> = ({
168197
toggleRef,
169198
onOpenChange,
170199
onOpenChangeKeys,
200+
onArrowUpDownKeyDown,
171201
shouldPreventScrollOnItemFocus,
172202
shouldFocusFirstItemOnOpen,
173203
focusTimeoutDelay

packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => {
247247
!isOpen && closeMenu();
248248
}}
249249
toggle={toggle}
250-
shouldFocusFirstItemOnOpen={false}
250+
isTypeahead
251251
>
252252
<SelectList isAriaMultiselectable id="select-multi-typeahead-listbox">
253253
{selectOptions.map((option, index) => (

packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => {
241241
!isOpen && closeMenu();
242242
}}
243243
toggle={toggle}
244-
shouldFocusFirstItemOnOpen={false}
244+
isTypeahead
245245
>
246246
<SelectList isAriaMultiselectable id="select-multi-typeahead-checkbox-listbox">
247247
{selectOptions.map((option, index) => (

packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
260260
!isOpen && closeMenu();
261261
}}
262262
toggle={toggle}
263-
shouldFocusFirstItemOnOpen={false}
263+
isTypeahead
264264
>
265265
<SelectList isAriaMultiselectable id="select-multi-create-typeahead-listbox">
266266
{selectOptions.map((option, index) => (

packages/react-core/src/components/Select/examples/SelectTypeahead.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export const SelectTypeahead: React.FunctionComponent = () => {
244244
!isOpen && closeMenu();
245245
}}
246246
toggle={toggle}
247-
shouldFocusFirstItemOnOpen={false}
247+
isTypeahead
248248
>
249249
<SelectList id="select-typeahead-listbox">
250250
{selectOptions.map((option, index) => (

packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
251251
!isOpen && closeMenu();
252252
}}
253253
toggle={toggle}
254-
shouldFocusFirstItemOnOpen={false}
254+
isTypeahead
255255
>
256256
<SelectList id="select-create-typeahead-listbox">
257257
{selectOptions.map((option, index) => (

packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ export const MultiTypeaheadSelectBase: React.FunctionComponent<MultiTypeaheadSel
320320
!isOpen && closeMenu();
321321
}}
322322
toggle={toggle}
323-
shouldFocusFirstItemOnOpen={false}
323+
isTypeahead
324324
ref={innerRef}
325325
{...props}
326326
>

packages/react-templates/src/components/Select/TypeaheadSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
361361
!isOpen && closeMenu();
362362
}}
363363
toggle={toggle}
364-
shouldFocusFirstItemOnOpen={false}
364+
isTypeahead
365365
ref={innerRef}
366366
{...props}
367367
>

0 commit comments

Comments
 (0)