From 7e49708b96444d8ed083696b734801c79447550e Mon Sep 17 00:00:00 2001 From: adamviktora Date: Wed, 23 Oct 2024 18:18:41 +0200 Subject: [PATCH 1/9] feat(Select): default arrow key handling to focus items --- .../src/components/Select/Select.tsx | 30 +++++++++++++++++++ .../Select/examples/SelectMultiTypeahead.tsx | 2 +- .../examples/SelectMultiTypeaheadCheckbox.tsx | 2 +- .../SelectMultiTypeaheadCreatable.tsx | 2 +- .../Select/examples/SelectTypeahead.tsx | 2 +- .../examples/SelectTypeaheadCreatable.tsx | 2 +- .../Select/MultiTypeaheadSelect.tsx | 2 +- .../src/components/Select/TypeaheadSelect.tsx | 2 +- 8 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 4d42285f295..72ce31791dc 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -62,6 +62,10 @@ export interface SelectProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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[]; + /** 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). */ + onArrowUpDownKeyDown?: (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 = ({ shouldFocusFirstItemOnOpen = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], + onArrowUpDownKeyDown, + isTypeahead, isPlain, innerRef, zIndex = 9999, @@ -117,6 +123,21 @@ const SelectBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { + const onArrowUpDownKeyDownDefault = (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)'); + focusableElement && (focusableElement as HTMLElement).focus(); + }; + const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -130,6 +151,14 @@ const SelectBase: React.FunctionComponent = ({ toggleRef.current?.focus(); } } + + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + if (onArrowUpDownKeyDown) { + onArrowUpDownKeyDown(event); + } else if (!isTypeahead) { + onArrowUpDownKeyDownDefault(event); + } + } }; const handleClick = (event: MouseEvent) => { @@ -162,6 +191,7 @@ const SelectBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, + onArrowUpDownKeyDown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index 57f2b4d27f6..8f153b97096 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -245,7 +245,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index da38e0930e1..a1375446a81 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -238,7 +238,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx index 16f3afb2c63..058c6ce39b6 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -256,7 +256,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 2c67674115e..fa7237d5c9d 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -241,7 +241,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index 7b0d9df194d..4fcc517f910 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -248,7 +248,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead > {selectOptions.map((option, index) => ( diff --git a/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx b/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx index ff8a7973224..bf39463094f 100644 --- a/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx +++ b/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx @@ -320,7 +320,7 @@ export const MultiTypeaheadSelectBase: React.FunctionComponent diff --git a/packages/react-templates/src/components/Select/TypeaheadSelect.tsx b/packages/react-templates/src/components/Select/TypeaheadSelect.tsx index e344ebff1c5..99630a84dd9 100644 --- a/packages/react-templates/src/components/Select/TypeaheadSelect.tsx +++ b/packages/react-templates/src/components/Select/TypeaheadSelect.tsx @@ -366,7 +366,7 @@ export const TypeaheadSelectBase: React.FunctionComponent onSelect={_onSelect} onOpenChange={(isOpen) => !isOpen && closeMenu()} toggle={toggle} - shouldFocusFirstItemOnOpen={false} + isTypeahead ref={innerRef} {...props} > From 41421b51781a1526d159d5c660a2a0d042b4d803 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Fri, 25 Oct 2024 17:57:01 +0200 Subject: [PATCH 2/9] feat(Dropdown): default arrow key handling to focus items --- .../src/components/Dropdown/Dropdown.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index b1bac70c701..f1bbcf474f1 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -51,6 +51,8 @@ export interface DropdownProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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[]; + /** 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). */ + onArrowUpDownKeyDown?: (event: KeyboardEvent) => void; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; /** Indicates if the menu should be scrollable. */ @@ -85,6 +87,7 @@ const DropdownBase: React.FunctionComponent = ({ toggle, shouldFocusToggleOnSelect = false, onOpenChange, + onArrowUpDownKeyDown, isPlain, isScrollable, innerRef, @@ -111,6 +114,23 @@ const DropdownBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { + const onArrowUpDownKeyDownDefault = (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),a:not([aria-disabled="true"])' + ); + focusableElement && (focusableElement as HTMLElement).focus(); + }; + const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -123,6 +143,14 @@ const DropdownBase: React.FunctionComponent = ({ toggleRef.current?.focus(); } } + + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + if (onArrowUpDownKeyDown) { + onArrowUpDownKeyDown(event); + } else { + onArrowUpDownKeyDownDefault(event); + } + } }; const handleClick = (event: MouseEvent) => { @@ -157,6 +185,7 @@ const DropdownBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, + onArrowUpDownKeyDown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay From 72fcfad04a6f849d0ee20b8518f08e0bcd6aad5e Mon Sep 17 00:00:00 2001 From: adamviktora Date: Fri, 25 Oct 2024 17:57:27 +0200 Subject: [PATCH 3/9] feat(MenuContainer): default arrow key handling to focus items --- .../src/components/Menu/MenuContainer.tsx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index e6d2c8ad7e2..b21e7069bd3 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -17,6 +17,7 @@ export interface MenuPopperProps { /** Flag to prevent the popper from overflowing its container and becoming partially obscured. */ preventOverflow?: boolean; } + export interface MenuContainerProps { /** Menu to be rendered */ menu: React.ReactElement>; @@ -33,6 +34,8 @@ export interface MenuContainerProps { 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[]; + /** 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). */ + onArrowUpDownKeyDown?: (event: KeyboardEvent) => void; /** z-index of the dropdown menu */ zIndex?: number; /** Additional properties to pass to the Popper */ @@ -54,6 +57,7 @@ export const MenuContainer: React.FunctionComponent = ({ toggle, toggleRef, onOpenChange, + onArrowUpDownKeyDown, zIndex = 9999, popperProps, onOpenChangeKeys = ['Escape', 'Tab'], @@ -61,6 +65,23 @@ export const MenuContainer: React.FunctionComponent = ({ focusTimeoutDelay = 0 }: MenuContainerProps) => { React.useEffect(() => { + const onArrowUpDownKeyDownDefault = (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),a:not([aria-disabled="true"])' + ); + focusableElement && (focusableElement as HTMLElement).focus(); + }; + const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -72,6 +93,14 @@ export const MenuContainer: React.FunctionComponent = ({ toggleRef.current?.focus(); } } + + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + if (onArrowUpDownKeyDown) { + onArrowUpDownKeyDown(event); + } else { + onArrowUpDownKeyDownDefault(event); + } + } }; const handleClick = (event: MouseEvent) => { @@ -100,7 +129,16 @@ export const MenuContainer: React.FunctionComponent = ({ window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClick); }; - }, [focusTimeoutDelay, isOpen, menuRef, onOpenChange, onOpenChangeKeys, shouldPreventScrollOnItemFocus, toggleRef]); + }, [ + focusTimeoutDelay, + isOpen, + menuRef, + onOpenChange, + onOpenChangeKeys, + onArrowUpDownKeyDown, + shouldPreventScrollOnItemFocus, + toggleRef + ]); return ( Date: Tue, 29 Oct 2024 16:56:48 +0100 Subject: [PATCH 4/9] fix: invoke callback only when toggle is focused and menu opened --- .../src/components/Dropdown/Dropdown.tsx | 22 +++++++++++-------- .../src/components/Menu/MenuContainer.tsx | 22 +++++++++++-------- .../src/components/Select/Select.tsx | 22 +++++++++++-------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index f1bbcf474f1..d822dfd0117 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -51,8 +51,8 @@ export interface DropdownProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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[]; - /** 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). */ - onArrowUpDownKeyDown?: (event: KeyboardEvent) => void; + /** 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 if the menu should be without the outer box-shadow. */ isPlain?: boolean; /** Indicates if the menu should be scrollable. */ @@ -87,7 +87,7 @@ const DropdownBase: React.FunctionComponent = ({ toggle, shouldFocusToggleOnSelect = false, onOpenChange, - onArrowUpDownKeyDown, + onToggleArrowKeydown, isPlain, isScrollable, innerRef, @@ -114,7 +114,7 @@ const DropdownBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { - const onArrowUpDownKeyDownDefault = (event: KeyboardEvent) => { + const onToggleArrowKeydownDefault = (event: KeyboardEvent) => { event.preventDefault(); let listItem: HTMLLIElement; @@ -144,11 +144,15 @@ const DropdownBase: React.FunctionComponent = ({ } } - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - if (onArrowUpDownKeyDown) { - onArrowUpDownKeyDown(event); + if ( + isOpen && + toggleRef.current?.contains(event.target as Node) && + (event.key === 'ArrowDown' || event.key === 'ArrowUp') + ) { + if (onToggleArrowKeydown) { + onToggleArrowKeydown(event); } else { - onArrowUpDownKeyDownDefault(event); + onToggleArrowKeydownDefault(event); } } }; @@ -185,7 +189,7 @@ const DropdownBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, - onArrowUpDownKeyDown, + onToggleArrowKeydown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index b21e7069bd3..9e5af86e35d 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -34,8 +34,8 @@ export interface MenuContainerProps { 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[]; - /** 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). */ - onArrowUpDownKeyDown?: (event: KeyboardEvent) => void; + /** 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; /** z-index of the dropdown menu */ zIndex?: number; /** Additional properties to pass to the Popper */ @@ -57,7 +57,7 @@ export const MenuContainer: React.FunctionComponent = ({ toggle, toggleRef, onOpenChange, - onArrowUpDownKeyDown, + onToggleArrowKeydown, zIndex = 9999, popperProps, onOpenChangeKeys = ['Escape', 'Tab'], @@ -65,7 +65,7 @@ export const MenuContainer: React.FunctionComponent = ({ focusTimeoutDelay = 0 }: MenuContainerProps) => { React.useEffect(() => { - const onArrowUpDownKeyDownDefault = (event: KeyboardEvent) => { + const onToggleArrowKeydownDefault = (event: KeyboardEvent) => { event.preventDefault(); let listItem: HTMLLIElement; @@ -94,11 +94,15 @@ export const MenuContainer: React.FunctionComponent = ({ } } - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - if (onArrowUpDownKeyDown) { - onArrowUpDownKeyDown(event); + if ( + isOpen && + toggleRef.current?.contains(event.target as Node) && + (event.key === 'ArrowDown' || event.key === 'ArrowUp') + ) { + if (onToggleArrowKeydown) { + onToggleArrowKeydown(event); } else { - onArrowUpDownKeyDownDefault(event); + onToggleArrowKeydownDefault(event); } } }; @@ -135,7 +139,7 @@ export const MenuContainer: React.FunctionComponent = ({ menuRef, onOpenChange, onOpenChangeKeys, - onArrowUpDownKeyDown, + onToggleArrowKeydown, shouldPreventScrollOnItemFocus, toggleRef ]); diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 72ce31791dc..1192d9051e4 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -62,8 +62,8 @@ export interface SelectProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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[]; - /** 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). */ - onArrowUpDownKeyDown?: (event: KeyboardEvent) => void; + /** 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 */ @@ -99,7 +99,7 @@ const SelectBase: React.FunctionComponent = ({ shouldFocusFirstItemOnOpen = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], - onArrowUpDownKeyDown, + onToggleArrowKeydown, isTypeahead, isPlain, innerRef, @@ -123,7 +123,7 @@ const SelectBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { - const onArrowUpDownKeyDownDefault = (event: KeyboardEvent) => { + const onToggleArrowKeydownDefault = (event: KeyboardEvent) => { event.preventDefault(); let listItem: HTMLLIElement; @@ -152,11 +152,15 @@ const SelectBase: React.FunctionComponent = ({ } } - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - if (onArrowUpDownKeyDown) { - onArrowUpDownKeyDown(event); + if ( + isOpen && + toggleRef.current?.contains(event.target as Node) && + (event.key === 'ArrowDown' || event.key === 'ArrowUp') + ) { + if (onToggleArrowKeydown) { + onToggleArrowKeydown(event); } else if (!isTypeahead) { - onArrowUpDownKeyDownDefault(event); + onToggleArrowKeydownDefault(event); } } }; @@ -191,7 +195,7 @@ const SelectBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, - onArrowUpDownKeyDown, + onToggleArrowKeydown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay From 4aa4766f82015e81bc21c7b94ad405236a2cacd6 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 12 Nov 2024 14:17:52 +0100 Subject: [PATCH 5/9] fix: query selector, refactor common functionality --- .../src/components/Dropdown/Dropdown.tsx | 27 +++---------------- .../src/components/Menu/MenuContainer.tsx | 27 +++---------------- .../src/components/Select/Select.tsx | 25 +++-------------- .../src/helpers/KeyboardHandler.tsx | 27 +++++++++++++++++++ 4 files changed, 36 insertions(+), 70 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index d822dfd0117..3ed56cc8c67 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { Menu, MenuContent, MenuProps } from '../Menu'; import { Popper } from '../../helpers/Popper/Popper'; -import { useOUIAProps, OUIAProps } from '../../helpers'; +import { useOUIAProps, OUIAProps, onToggleArrowKeydownDefault } from '../../helpers'; export interface DropdownPopperProps { /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */ @@ -114,23 +114,6 @@ const DropdownBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); 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),a:not([aria-disabled="true"])' - ); - focusableElement && (focusableElement as HTMLElement).focus(); - }; - const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -144,15 +127,11 @@ const DropdownBase: React.FunctionComponent = ({ } } - if ( - isOpen && - toggleRef.current?.contains(event.target as Node) && - (event.key === 'ArrowDown' || event.key === 'ArrowUp') - ) { + if (isOpen && toggleRef.current?.contains(event.target as Node)) { if (onToggleArrowKeydown) { onToggleArrowKeydown(event); } else { - onToggleArrowKeydownDefault(event); + onToggleArrowKeydownDefault(event, menuRef); } } }; diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 9e5af86e35d..2770fa373bf 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Popper } from '../../helpers/Popper/Popper'; +import { onToggleArrowKeydownDefault, Popper } from '../../helpers'; export interface MenuPopperProps { /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */ @@ -65,23 +65,6 @@ export const MenuContainer: React.FunctionComponent = ({ focusTimeoutDelay = 0 }: MenuContainerProps) => { 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),a:not([aria-disabled="true"])' - ); - focusableElement && (focusableElement as HTMLElement).focus(); - }; - const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -94,15 +77,11 @@ export const MenuContainer: React.FunctionComponent = ({ } } - if ( - isOpen && - toggleRef.current?.contains(event.target as Node) && - (event.key === 'ArrowDown' || event.key === 'ArrowUp') - ) { + if (isOpen && toggleRef.current?.contains(event.target as Node)) { if (onToggleArrowKeydown) { onToggleArrowKeydown(event); } else { - onToggleArrowKeydownDefault(event); + onToggleArrowKeydownDefault(event, menuRef); } } }; diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 1192d9051e4..a2da669a142 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { Menu, MenuContent, MenuProps } from '../Menu'; import { Popper } from '../../helpers/Popper/Popper'; -import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers'; +import { getOUIAProps, OUIAProps, getDefaultOUIAId, onToggleArrowKeydownDefault } from '../../helpers'; export interface SelectPopperProps { /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */ @@ -123,21 +123,6 @@ const SelectBase: React.FunctionComponent = ({ : (toggle?.toggleRef as React.RefObject); 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)'); - focusableElement && (focusableElement as HTMLElement).focus(); - }; - const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( @@ -152,15 +137,11 @@ const SelectBase: React.FunctionComponent = ({ } } - if ( - isOpen && - toggleRef.current?.contains(event.target as Node) && - (event.key === 'ArrowDown' || event.key === 'ArrowUp') - ) { + if (isOpen && toggleRef.current?.contains(event.target as Node)) { if (onToggleArrowKeydown) { onToggleArrowKeydown(event); } else if (!isTypeahead) { - onToggleArrowKeydownDefault(event); + onToggleArrowKeydownDefault(event, menuRef); } } }; diff --git a/packages/react-core/src/helpers/KeyboardHandler.tsx b/packages/react-core/src/helpers/KeyboardHandler.tsx index 1d499b992fa..53617fdbded 100644 --- a/packages/react-core/src/helpers/KeyboardHandler.tsx +++ b/packages/react-core/src/helpers/KeyboardHandler.tsx @@ -170,6 +170,33 @@ export const setTabIndex = (options: HTMLElement[]) => { } }; +/** + * This function is used in Dropdown, Select and MenuContainer as a default toggle keydown behavior. 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. + * + * @param event Event triggered by the keyboard + * @param menuRef Menu reference + */ +export const onToggleArrowKeydownDefault = (event: KeyboardEvent, menuRef: React.RefObject) => { + if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') { + return; + } + + event.preventDefault(); + + const interactiveElementSelector = 'button:not(:disabled),input:not(:disabled),a:not([aria-disabled="true"])'; + const listItemSelector = `li:has(${interactiveElementSelector})`; + let listItem: Element; + if (event.key === 'ArrowDown') { + listItem = menuRef.current?.querySelector(listItemSelector); + } else { + const allItems = menuRef.current?.querySelectorAll(listItemSelector); + listItem = allItems ? allItems[allItems.length - 1] : null; + } + + const focusableElement = listItem?.querySelector(interactiveElementSelector); + focusableElement && (focusableElement as HTMLElement).focus(); +}; + class KeyboardHandler extends React.Component { static displayName = 'KeyboardHandler'; static defaultProps: KeyboardHandlerProps = { From 1ccd7827ed830db296067a0f19bf9fefcbf85733 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 12 Nov 2024 14:27:04 +0100 Subject: [PATCH 6/9] feat: use general onToggleKeydown instead of onToggleArrowKeydown --- .../src/components/Dropdown/Dropdown.tsx | 16 ++++++++-------- .../src/components/Menu/MenuContainer.tsx | 16 ++++++++-------- .../react-core/src/components/Select/Select.tsx | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 3ed56cc8c67..3bc93f54a84 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -51,8 +51,8 @@ export interface DropdownProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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; + /** 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. */ + onToggleKeydown?: (event: KeyboardEvent) => void; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; /** Indicates if the menu should be scrollable. */ @@ -87,7 +87,7 @@ const DropdownBase: React.FunctionComponent = ({ toggle, shouldFocusToggleOnSelect = false, onOpenChange, - onToggleArrowKeydown, + onToggleKeydown, isPlain, isScrollable, innerRef, @@ -127,10 +127,10 @@ const DropdownBase: React.FunctionComponent = ({ } } - if (isOpen && toggleRef.current?.contains(event.target as Node)) { - if (onToggleArrowKeydown) { - onToggleArrowKeydown(event); - } else { + if (toggleRef.current?.contains(event.target as Node)) { + if (onToggleKeydown) { + onToggleKeydown(event); + } else if (isOpen) { onToggleArrowKeydownDefault(event, menuRef); } } @@ -168,7 +168,7 @@ const DropdownBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, - onToggleArrowKeydown, + onToggleKeydown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 2770fa373bf..8092b9043f4 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -34,8 +34,8 @@ export interface MenuContainerProps { 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; + /** 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. */ + onToggleKeydown?: (event: KeyboardEvent) => void; /** z-index of the dropdown menu */ zIndex?: number; /** Additional properties to pass to the Popper */ @@ -57,7 +57,7 @@ export const MenuContainer: React.FunctionComponent = ({ toggle, toggleRef, onOpenChange, - onToggleArrowKeydown, + onToggleKeydown, zIndex = 9999, popperProps, onOpenChangeKeys = ['Escape', 'Tab'], @@ -77,10 +77,10 @@ export const MenuContainer: React.FunctionComponent = ({ } } - if (isOpen && toggleRef.current?.contains(event.target as Node)) { - if (onToggleArrowKeydown) { - onToggleArrowKeydown(event); - } else { + if (toggleRef.current?.contains(event.target as Node)) { + if (onToggleKeydown) { + onToggleKeydown(event); + } else if (isOpen) { onToggleArrowKeydownDefault(event, menuRef); } } @@ -118,7 +118,7 @@ export const MenuContainer: React.FunctionComponent = ({ menuRef, onOpenChange, onOpenChangeKeys, - onToggleArrowKeydown, + onToggleKeydown, shouldPreventScrollOnItemFocus, toggleRef ]); diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index a2da669a142..4e52931663a 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -62,8 +62,8 @@ export interface SelectProps extends MenuProps, OUIAProps { onOpenChange?: (isOpen: boolean) => void; /** @beta 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; + /** 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. */ + onToggleKeydown?: (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 */ @@ -99,7 +99,7 @@ const SelectBase: React.FunctionComponent = ({ shouldFocusFirstItemOnOpen = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], - onToggleArrowKeydown, + onToggleKeydown, isTypeahead, isPlain, innerRef, @@ -137,10 +137,10 @@ const SelectBase: React.FunctionComponent = ({ } } - if (isOpen && toggleRef.current?.contains(event.target as Node)) { - if (onToggleArrowKeydown) { - onToggleArrowKeydown(event); - } else if (!isTypeahead) { + if (toggleRef.current?.contains(event.target as Node)) { + if (onToggleKeydown) { + onToggleKeydown(event); + } else if (isOpen && !isTypeahead) { onToggleArrowKeydownDefault(event, menuRef); } } @@ -176,7 +176,7 @@ const SelectBase: React.FunctionComponent = ({ toggleRef, onOpenChange, onOpenChangeKeys, - onToggleArrowKeydown, + onToggleKeydown, shouldPreventScrollOnItemFocus, shouldFocusFirstItemOnOpen, focusTimeoutDelay From f2ab64abf0ce61776ce0a3f0b84c1655f167bff7 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 12 Nov 2024 15:06:25 +0100 Subject: [PATCH 7/9] refactor(onToggleArrowKeydownDefault): don't use :has() selector --- .../react-core/src/helpers/KeyboardHandler.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-core/src/helpers/KeyboardHandler.tsx b/packages/react-core/src/helpers/KeyboardHandler.tsx index 53617fdbded..c777e3f2e8d 100644 --- a/packages/react-core/src/helpers/KeyboardHandler.tsx +++ b/packages/react-core/src/helpers/KeyboardHandler.tsx @@ -183,17 +183,17 @@ export const onToggleArrowKeydownDefault = (event: KeyboardEvent, menuRef: React event.preventDefault(); - const interactiveElementSelector = 'button:not(:disabled),input:not(:disabled),a:not([aria-disabled="true"])'; - const listItemSelector = `li:has(${interactiveElementSelector})`; - let listItem: Element; + const listItems = Array.from(menuRef.current?.querySelectorAll('li')); + const focusableElements = listItems + .map((li) => li.querySelector('button:not(:disabled),input:not(:disabled),a:not([aria-disabled="true"])')) + .filter((el) => el !== null); + + let focusableElement: Element; if (event.key === 'ArrowDown') { - listItem = menuRef.current?.querySelector(listItemSelector); + focusableElement = focusableElements[0]; } else { - const allItems = menuRef.current?.querySelectorAll(listItemSelector); - listItem = allItems ? allItems[allItems.length - 1] : null; + focusableElement = focusableElements[focusableElements.length - 1]; } - - const focusableElement = listItem?.querySelector(interactiveElementSelector); focusableElement && (focusableElement as HTMLElement).focus(); }; From eb5f5a6fe94bef5cdba6298b4f4f6a3f68f6a28a Mon Sep 17 00:00:00 2001 From: adamviktora Date: Wed, 20 Nov 2024 11:51:39 +0100 Subject: [PATCH 8/9] fix: remove isTypeahead prop in favor of variant --- packages/react-core/src/components/Select/Select.tsx | 8 ++++---- .../components/Select/examples/SelectMultiTypeahead.tsx | 2 +- .../Select/examples/SelectMultiTypeaheadCheckbox.tsx | 2 +- .../Select/examples/SelectMultiTypeaheadCreatable.tsx | 2 +- .../src/components/Select/examples/SelectTypeahead.tsx | 2 +- .../Select/examples/SelectTypeaheadCreatable.tsx | 2 +- .../src/components/Select/MultiTypeaheadSelect.tsx | 2 +- .../src/components/Select/TypeaheadSelect.tsx | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 4e52931663a..7ff247a9d38 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -64,8 +64,8 @@ export interface SelectProps extends MenuProps, OUIAProps { onOpenChangeKeys?: string[]; /** 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. */ onToggleKeydown?: (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; + /** Select variant. For typeahead variant focus won't shift to menu items when pressing up/down arrows. */ + variant?: 'default' | 'typeahead'; /** Indicates if the select should be without the outer box-shadow */ isPlain?: boolean; /** @hide Forwarded ref */ @@ -100,7 +100,7 @@ const SelectBase: React.FunctionComponent = ({ onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], onToggleKeydown, - isTypeahead, + variant, isPlain, innerRef, zIndex = 9999, @@ -140,7 +140,7 @@ const SelectBase: React.FunctionComponent = ({ if (toggleRef.current?.contains(event.target as Node)) { if (onToggleKeydown) { onToggleKeydown(event); - } else if (isOpen && !isTypeahead) { + } else if (isOpen && variant !== 'typeahead') { onToggleArrowKeydownDefault(event, menuRef); } } diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index 8f153b97096..8db59ef0baf 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -245,7 +245,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - isTypeahead + variant="typeahead" > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index a1375446a81..493575dda67 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -238,7 +238,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - isTypeahead + variant="typeahead" > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx index 058c6ce39b6..df36f8cc5c6 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -256,7 +256,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - isTypeahead + variant="typeahead" > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index fa7237d5c9d..d516f1851a3 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -241,7 +241,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - isTypeahead + variant="typeahead" > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index 4fcc517f910..a9a5f2a87fe 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -248,7 +248,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - isTypeahead + variant="typeahead" > {selectOptions.map((option, index) => ( diff --git a/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx b/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx index bf39463094f..41d2f9afb34 100644 --- a/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx +++ b/packages/react-templates/src/components/Select/MultiTypeaheadSelect.tsx @@ -320,7 +320,7 @@ export const MultiTypeaheadSelectBase: React.FunctionComponent diff --git a/packages/react-templates/src/components/Select/TypeaheadSelect.tsx b/packages/react-templates/src/components/Select/TypeaheadSelect.tsx index 99630a84dd9..5e129c22721 100644 --- a/packages/react-templates/src/components/Select/TypeaheadSelect.tsx +++ b/packages/react-templates/src/components/Select/TypeaheadSelect.tsx @@ -366,7 +366,7 @@ export const TypeaheadSelectBase: React.FunctionComponent onSelect={_onSelect} onOpenChange={(isOpen) => !isOpen && closeMenu()} toggle={toggle} - isTypeahead + variant="typeahead" ref={innerRef} {...props} > From b6da628ffca3db12ee93923f3622ab2880f3f383 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Wed, 20 Nov 2024 13:19:28 +0100 Subject: [PATCH 9/9] feat(MenuContainer): update to include V6 changes --- .../src/components/Menu/MenuContainer.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 8092b9043f4..d59c36cb6fa 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -40,6 +40,8 @@ export interface MenuContainerProps { zIndex?: number; /** Additional properties to pass to the Popper */ popperProps?: MenuPopperProps; + /** @beta Flag indicating the first menu item should be focused after opening the dropdown. */ + shouldFocusFirstItemOnOpen?: boolean; /** Flag indicating if scroll on focus of the first menu item should occur. */ shouldPreventScrollOnItemFocus?: boolean; /** Time in ms to wait before firing the toggles' focus event. Defaults to 0 */ @@ -61,9 +63,26 @@ export const MenuContainer: React.FunctionComponent = ({ zIndex = 9999, popperProps, onOpenChangeKeys = ['Escape', 'Tab'], + shouldFocusFirstItemOnOpen = false, shouldPreventScrollOnItemFocus = true, focusTimeoutDelay = 0 }: MenuContainerProps) => { + const prevIsOpen = React.useRef(isOpen); + React.useEffect(() => { + // menu was opened, focus on first menu item + if (prevIsOpen.current === false && isOpen === true && shouldFocusFirstItemOnOpen) { + setTimeout(() => { + const firstElement = menuRef?.current?.querySelector( + 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])' + ); + firstElement && (firstElement as HTMLElement).focus({ preventScroll: shouldPreventScrollOnItemFocus }); + }, focusTimeoutDelay); + } + + prevIsOpen.current = isOpen; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isOpen]); + React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided @@ -87,16 +106,6 @@ export const MenuContainer: React.FunctionComponent = ({ }; const handleClick = (event: MouseEvent) => { - // toggle was opened, focus on first menu item - if (isOpen && toggleRef.current?.contains(event.target as Node)) { - setTimeout(() => { - const firstElement = menuRef?.current?.querySelector( - 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])' - ); - firstElement && (firstElement as HTMLElement).focus({ preventScroll: shouldPreventScrollOnItemFocus }); - }, focusTimeoutDelay); - } - // If the event is not on the toggle and onOpenChange callback is provided, close the menu if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) { if (isOpen && !menuRef.current?.contains(event.target as Node)) {