From d0afb7d97c0486cd983dc5cc79f48bf427ab6e36 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 16 Apr 2024 10:12:05 +0200 Subject: [PATCH 01/12] refactor(Select): rename shouldFocusFirstMenuItemOnOpen --- packages/react-core/src/components/Select/Select.tsx | 8 ++++---- .../src/components/Select/examples/SelectTypeahead.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index f9c25d170d7..82bcda7bf99 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -53,8 +53,8 @@ export interface SelectProps extends MenuProps, OUIAProps { toggle: SelectToggleProps | ((toggleRef: React.RefObject) => React.ReactNode); /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ shouldFocusToggleOnSelect?: boolean; - /** Flag indicating the first menu item should be focused after opening the menu. */ - shouldFocusFirstMenuItemOnOpen?: boolean; + /** @beta Flag indicating the first menu item should be focused after opening the menu. */ + shouldFocusFirstItemOnOpen?: boolean; /** Function callback when user selects an option. */ onSelect?: (event?: React.MouseEvent, value?: string | number) => void; /** Callback to allow the select component to change the open state of the menu. @@ -88,7 +88,7 @@ const SelectBase: React.FunctionComponent = ({ selected, toggle, shouldFocusToggleOnSelect = false, - shouldFocusFirstMenuItemOnOpen = true, + shouldFocusFirstItemOnOpen = true, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], isPlain, @@ -128,7 +128,7 @@ const SelectBase: React.FunctionComponent = ({ const handleClick = (event: MouseEvent) => { // toggle was opened, focus on first menu item - if (isOpen && shouldFocusFirstMenuItemOnOpen && toggleRef.current?.contains(event.target as Node)) { + if (isOpen && shouldFocusFirstItemOnOpen && toggleRef.current?.contains(event.target as Node)) { setTimeout(() => { const firstElement = menuRef?.current?.querySelector('li button:not(:disabled),li input:not(:disabled)'); firstElement && (firstElement as HTMLElement).focus(); diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 2ea2e032ac8..ebadc2941f0 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -221,7 +221,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstMenuItemOnOpen={false} + shouldFocusFirstItemOnOpen={false} > {selectOptions.map((option, index) => ( From 674ee20e958c09f1d55fc55ecc201f3f3c81dfd8 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 23 Apr 2024 14:38:48 +0200 Subject: [PATCH 02/12] feat(SelectTypeahead example): better arrow up/down keys handling - does not apply visual focus on the first menu option - handles disabled options - opens menu on pressing up/down arrow keys --- .../Select/examples/SelectTypeahead.tsx | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index ebadc2941f0..cb34b8432eb 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -60,15 +60,9 @@ export const SelectTypeahead: React.FunctionComponent = () => { setSelectOptions(newSelectOptions); }, [filterValue]); - React.useEffect(() => { - if (isOpen && selectOptions.length && selectOptions[0].value !== NO_RESULTS) { - setActiveAndFocusedItem(0); - } - }, [isOpen, filterValue]); - const setActiveAndFocusedItem = (itemIndex: number) => { setFocusedItemIndex(itemIndex); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[itemIndex]; + const focusedItem = selectOptions[itemIndex]; setActiveItem(`select-typeahead-${focusedItem.value.replace(' ', '-')}`); }; @@ -106,53 +100,77 @@ export const SelectTypeahead: React.FunctionComponent = () => { setInputValue(value); setFilterValue(value); + resetActiveAndFocusedItem(); + if (value !== selected) { setSelected(''); } }; const handleMenuArrowKeys = (key: string) => { - let indexToFocus; + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions.every((option) => option.isDisabled)) { + return; + } + + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; } } + } + + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; } } - - setActiveAndFocusedItem(indexToFocus); } + + setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { - // Select the first available option case 'Enter': - if (isOpen && focusedItem.value !== NO_RESULTS) { + // Select an option + if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) { setInputValue(String(focusedItem.children)); setFilterValue(''); setSelected(String(focusedItem.children)); + closeMenu(); } - setIsOpen((prevIsOpen) => !prevIsOpen); - resetActiveAndFocusedItem(); + if (!isOpen) { + setIsOpen(true); + } break; case 'ArrowUp': From 82bb47badf5483c9776fb452c1bee7517ec0509f Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 23 Apr 2024 14:43:01 +0200 Subject: [PATCH 03/12] feat(SelectTypeahead example): don't close menu on clicking clear button when open --- .../Select/examples/SelectTypeahead.tsx | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index cb34b8432eb..805d0e9a6e3 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -209,21 +209,19 @@ export const SelectTypeahead: React.FunctionComponent = () => { aria-controls="select-typeahead-listbox" /> - - {!!inputValue && ( - - )} + + From 85c6c357bbfc02cad680e3e8ab000b56b7a30d61 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 23 Apr 2024 16:45:14 +0200 Subject: [PATCH 04/12] refactor(SelectTypeahead example) --- .../Select/examples/SelectTypeahead.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 805d0e9a6e3..1d04bbe1f26 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -29,7 +29,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { const [filterValue, setFilterValue] = React.useState(''); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); + const [activeItemId, setActiveItemId] = React.useState(null); const textInputRef = React.useRef(); const NO_RESULTS = 'no results'; @@ -48,7 +48,6 @@ export const SelectTypeahead: React.FunctionComponent = () => { newSelectOptions = [ { isAriaDisabled: true, children: `No results found for "${filterValue}"`, value: NO_RESULTS } ]; - resetActiveAndFocusedItem(); } // Open the menu when the input value changes and the new value is not empty @@ -60,15 +59,17 @@ export const SelectTypeahead: React.FunctionComponent = () => { setSelectOptions(newSelectOptions); }, [filterValue]); + const createItemId = (value: any) => `select-typeahead-${value.replace(' ', '-')}`; + const setActiveAndFocusedItem = (itemIndex: number) => { setFocusedItemIndex(itemIndex); const focusedItem = selectOptions[itemIndex]; - setActiveItem(`select-typeahead-${focusedItem.value.replace(' ', '-')}`); + setActiveItemId(createItemId(focusedItem.value)); }; const resetActiveAndFocusedItem = () => { setFocusedItemIndex(null); - setActiveItem(null); + setActiveItemId(null); }; const closeMenu = () => { @@ -84,16 +85,22 @@ export const SelectTypeahead: React.FunctionComponent = () => { } }; - const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { + const selectOption = (value: string | number, content: string | number) => { // eslint-disable-next-line no-console - console.log('selected', value); + console.log('selected', content); + + setInputValue(String(content)); + setFilterValue(''); + setSelected(String(value)); + + closeMenu(); + }; + const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { if (value && value !== NO_RESULTS) { - setInputValue(value as string); - setFilterValue(''); - setSelected(value as string); + const optionText = selectOptions.find((option) => option.value === value)?.children; + selectOption(value, optionText as string); } - closeMenu(); }; const onTextInputChange = (_event: React.FormEvent, value: string) => { @@ -160,12 +167,8 @@ export const SelectTypeahead: React.FunctionComponent = () => { switch (event.key) { case 'Enter': - // Select an option if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) { - setInputValue(String(focusedItem.children)); - setFilterValue(''); - setSelected(String(focusedItem.children)); - closeMenu(); + selectOption(focusedItem.value, focusedItem.children as string); } if (!isOpen) { @@ -203,7 +206,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { autoComplete="off" innerRef={textInputRef} placeholder="Select a state" - {...(activeItem && { 'aria-activedescendant': activeItem })} + {...(activeItemId && { 'aria-activedescendant': activeItemId })} role="combobox" isExpanded={isOpen} aria-controls="select-typeahead-listbox" @@ -216,6 +219,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { setSelected(''); setInputValue(''); setFilterValue(''); + resetActiveAndFocusedItem(); textInputRef?.current?.focus(); }} aria-label="Clear input value" @@ -245,9 +249,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { key={option.value || option.children} isFocused={focusedItemIndex === index} className={option.className} - onMouseEnter={() => setActiveAndFocusedItem(index)} - onClick={() => setSelected(option.value)} - id={`select-typeahead-${option.value.replace(' ', '-')}`} + id={createItemId(option.value)} {...option} ref={null} /> From 9ba9f22a86da6f6e22d621f63a74922b7b24b819 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Wed, 24 Apr 2024 18:49:48 +0200 Subject: [PATCH 05/12] refactor(SelectTypeahead example) --- .../Select/examples/SelectTypeahead.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 1d04bbe1f26..2c67674115e 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -184,15 +184,25 @@ export const SelectTypeahead: React.FunctionComponent = () => { } }; + const onToggleClick = () => { + setIsOpen(!isOpen); + textInputRef?.current?.focus(); + }; + + const onClearButtonClick = () => { + setSelected(''); + setInputValue(''); + setFilterValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); + }; + const toggle = (toggleRef: React.Ref) => ( { - setIsOpen(!isOpen); - textInputRef?.current?.focus(); - }} + onClick={onToggleClick} isExpanded={isOpen} isFullWidth > @@ -213,17 +223,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { /> - From 87fbdece7e11ed8329f6d085c131783e24362665 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Wed, 24 Apr 2024 18:50:42 +0200 Subject: [PATCH 06/12] fix(SelectTypeaheadCreatable example): changes based on SelectTypeahead --- .../examples/SelectTypeaheadCreatable.tsx | 190 +++++++++++------- 1 file changed, 114 insertions(+), 76 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index b079b1fb507..db5902c5141 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -29,10 +29,11 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { const [filterValue, setFilterValue] = React.useState(''); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); - const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created + const [activeItemId, setActiveItemId] = React.useState(null); const textInputRef = React.useRef(); + const CREATE_NEW = 'create'; + React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; @@ -42,10 +43,8 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase()) ); - // When no options are found after filtering, display creation option - if (!newSelectOptions.length) { - newSelectOptions = [{ isDisabled: false, children: `Create new option "${filterValue}"`, value: 'create' }]; - } + // Display creation option + newSelectOptions = [...newSelectOptions, { children: `Create new option "${filterValue}"`, value: CREATE_NEW }]; // Open the menu when the input value changes and the new value is not empty if (!isOpen) { @@ -54,96 +53,133 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { } setSelectOptions(newSelectOptions); - setActiveItem(null); + }, [filterValue]); + + const createItemId = (value: any) => `select-typeahead-${value.replace(' ', '-')}`; + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions[itemIndex]; + setActiveItemId(createItemId(focusedItem.value)); + }; + + const resetActiveAndFocusedItem = () => { setFocusedItemIndex(null); - }, [filterValue, onCreation]); + setActiveItemId(null); + }; - const onToggleClick = () => { - setIsOpen(!isOpen); + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); }; - const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!inputValue) { + closeMenu(); + } + }; + + const selectOption = (value: string | number, content: string | number) => { // eslint-disable-next-line no-console + console.log('selected', content); + + setInputValue(String(content)); + setFilterValue(''); + setSelected(String(value)); + closeMenu(); + }; + + const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { if (value) { - if (value === 'create') { - if (!initialSelectOptions.some((item) => item.value === filterValue)) { + if (value === CREATE_NEW) { + if (!initialSelectOptions.some((item) => item.children === filterValue)) { initialSelectOptions = [...initialSelectOptions, { value: filterValue, children: filterValue }]; } setSelected(filterValue); - setOnCreation(!onCreation); setFilterValue(''); + resetActiveAndFocusedItem(); } else { - // eslint-disable-next-line no-console - console.log('selected', value); - setInputValue(value as string); - setFilterValue(''); - setSelected(value as string); + const optionText = selectOptions.find((option) => option.value === value)?.children; + selectOption(value, optionText as string); } } - - setIsOpen(false); - setFocusedItemIndex(null); - setActiveItem(null); }; const onTextInputChange = (_event: React.FormEvent, value: string) => { setInputValue(value); setFilterValue(value); + + resetActiveAndFocusedItem(); + + if (value !== selected) { + setSelected(''); + } }; const handleMenuArrowKeys = (key: string) => { - let indexToFocus; + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions.every((option) => option.isDisabled)) { + return; + } - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; } } + } - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; } } - - setFocusedItemIndex(indexToFocus); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; - setActiveItem(`select-create-typeahead-${focusedItem.value.replace(' ', '-')}`); } + + setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { - // Select the first available option case 'Enter': - if (isOpen) { + if (isOpen && focusedItem && !focusedItem.isAriaDisabled) { onSelect(undefined, focusedItem.value as string); - setIsOpen((prevIsOpen) => !prevIsOpen); - setFocusedItemIndex(null); - setActiveItem(null); } - setIsOpen((prevIsOpen) => !prevIsOpen); - setFocusedItemIndex(null); - setActiveItem(null); + if (!isOpen) { + setIsOpen(true); + } - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); break; case 'ArrowUp': case 'ArrowDown': @@ -153,6 +189,19 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { } }; + const onToggleClick = () => { + setIsOpen(!isOpen); + textInputRef?.current?.focus(); + }; + + const onClearButtonClick = () => { + setSelected(''); + setInputValue(''); + setFilterValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); + }; + const toggle = (toggleRef: React.Ref) => ( { - - {!!inputValue && ( - - )} + + @@ -204,10 +242,11 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { isOpen={isOpen} selected={selected} onSelect={onSelect} - onOpenChange={() => { - setIsOpen(false); + onOpenChange={(isOpen) => { + !isOpen && closeMenu(); }} toggle={toggle} + shouldFocusFirstItemOnOpen={false} > {selectOptions.map((option, index) => ( @@ -215,8 +254,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { key={option.value || option.children} isFocused={focusedItemIndex === index} className={option.className} - onClick={() => setSelected(option.value)} - id={`select-typeahead-${option.value.replace(' ', '-')}`} + id={createItemId(option.value)} {...option} ref={null} /> From 6e8ee52c7582359c47cb5a7f59e7dad0f78ab850 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Thu, 25 Apr 2024 11:53:03 +0200 Subject: [PATCH 07/12] fix(SelectMultiTypeahead example): changes based on SelectTypeahead --- .../Select/examples/SelectMultiTypeahead.tsx | 177 +++++++++++------- 1 file changed, 111 insertions(+), 66 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index 9df0c8978e7..57f2b4d27f6 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -30,9 +30,11 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { const [selected, setSelected] = React.useState([]); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); + const [activeItemId, setActiveItemId] = React.useState(null); const textInputRef = React.useRef(); + const NO_RESULTS = 'no results'; + React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; @@ -45,7 +47,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { // When no options are found after filtering, display 'No results found' if (!newSelectOptions.length) { newSelectOptions = [ - { isDisabled: false, children: `No results found for "${inputValue}"`, value: 'no results' } + { isAriaDisabled: true, children: `No results found for "${inputValue}"`, value: NO_RESULTS } ]; } @@ -56,56 +58,113 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { } setSelectOptions(newSelectOptions); - setFocusedItemIndex(null); - setActiveItem(null); }, [inputValue]); + const createItemId = (value: any) => `select-multi-typeahead-${value.replace(' ', '-')}`; + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions[itemIndex]; + setActiveItemId(createItemId(focusedItem.value)); + }; + + const resetActiveAndFocusedItem = () => { + setFocusedItemIndex(null); + setActiveItemId(null); + }; + + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); + }; + + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!inputValue) { + closeMenu(); + } + }; + + const onSelect = (value: string) => { + if (value && value !== NO_RESULTS) { + // eslint-disable-next-line no-console + console.log('selected', value); + + setSelected( + selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] + ); + } + + textInputRef.current?.focus(); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setInputValue(value); + resetActiveAndFocusedItem(); + }; + const handleMenuArrowKeys = (key: string) => { - let indexToFocus; + let indexToFocus = 0; - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions.every((option) => option.isDisabled)) { + return; + } + + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; } } + } - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; } } - - setFocusedItemIndex(indexToFocus); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; - setActiveItem(`select-multi-typeahead-${focusedItem.value.replace(' ', '-')}`); } + + setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { - // Select the first available option case 'Enter': + if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) { + onSelect(focusedItem.value); + } + if (!isOpen) { - setIsOpen((prevIsOpen) => !prevIsOpen); - } else if (isOpen && focusedItem.value !== 'no results') { - onSelect(focusedItem.value as string); + setIsOpen(true); } - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); + break; case 'ArrowUp': case 'ArrowDown': @@ -117,24 +176,17 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { const onToggleClick = () => { setIsOpen(!isOpen); + textInputRef?.current?.focus(); }; - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); + const onClearButtonClick = () => { + setSelected([]); + setInputValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); }; - const onSelect = (value: string) => { - // eslint-disable-next-line no-console - console.log('selected', value); - - if (value && value !== 'no results') { - setSelected( - selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] - ); - } - - textInputRef.current?.focus(); - }; + const getChildren = (value: string) => initialSelectOptions.find((option) => option.value === value)?.children; const toggle = (toggleRef: React.Ref) => ( { { onSelect(selection); }} > - {selection} + {getChildren(selection)} ))} - - {selected.length > 0 && ( - - )} + + @@ -198,9 +240,12 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { id="multi-typeahead-select" isOpen={isOpen} selected={selected} - onSelect={(ev, selection) => onSelect(selection as string)} - onOpenChange={() => setIsOpen(false)} + onSelect={(_event, selection) => onSelect(selection as string)} + onOpenChange={(isOpen) => { + !isOpen && closeMenu(); + }} toggle={toggle} + shouldFocusFirstItemOnOpen={false} > {selectOptions.map((option, index) => ( @@ -208,7 +253,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { key={option.value || option.children} isFocused={focusedItemIndex === index} className={option.className} - id={`select-multi-typeahead-${option.value.replace(' ', '-')}`} + id={createItemId(option.value)} {...option} ref={null} /> From da0179a4306ae1f4a2f70c2a2439d657fe1a2484 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Thu, 25 Apr 2024 12:42:35 +0200 Subject: [PATCH 08/12] fix(SelectTypeaheadCreatable example): don't show create option if that exact option exists --- .../components/Select/examples/SelectTypeaheadCreatable.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index db5902c5141..f735605963b 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -43,8 +43,10 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase()) ); - // Display creation option - newSelectOptions = [...newSelectOptions, { children: `Create new option "${filterValue}"`, value: CREATE_NEW }]; + // If no option matches the filter exactly, display creation option + if (!initialSelectOptions.some((option) => option.value === filterValue)) { + newSelectOptions = [...newSelectOptions, { children: `Create new option "${filterValue}"`, value: CREATE_NEW }]; + } // Open the menu when the input value changes and the new value is not empty if (!isOpen) { From f34d61cbdc9c93f98ec009132e23572454be1677 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Thu, 25 Apr 2024 12:55:37 +0200 Subject: [PATCH 09/12] fix(SelectMultiTypeaheadCreatable): changes based on SelectTypeahead --- .../SelectMultiTypeaheadCreatable.tsx | 204 +++++++++++------- 1 file changed, 125 insertions(+), 79 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx index 9be7a6d6152..16f3afb2c63 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -30,10 +30,12 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { const [selected, setSelected] = React.useState([]); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); + const [activeItemId, setActiveItemId] = React.useState(null); const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created const textInputRef = React.useRef(); + const CREATE_NEW = 'create'; + React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; @@ -43,9 +45,9 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) ); - // When no options are found after filtering, display creation option - if (!newSelectOptions.length) { - newSelectOptions = [{ isDisabled: false, children: `Create new option "${inputValue}"`, value: 'create' }]; + // If no option matches the filter exactly, display creation option + if (!initialSelectOptions.some((option) => option.value === inputValue)) { + newSelectOptions = [...newSelectOptions, { children: `Create new option "${inputValue}"`, value: CREATE_NEW }]; } // Open the menu when the input value changes and the new value is not empty @@ -55,56 +57,125 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { } setSelectOptions(newSelectOptions); - setFocusedItemIndex(null); - setActiveItem(null); }, [inputValue, onCreation]); + const createItemId = (value: any) => `select-multi-create-typeahead-${value.replace(' ', '-')}`; + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions[itemIndex]; + setActiveItemId(createItemId(focusedItem.value)); + }; + + const resetActiveAndFocusedItem = () => { + setFocusedItemIndex(null); + setActiveItemId(null); + }; + + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); + }; + + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!inputValue) { + closeMenu(); + } + }; + + const onSelect = (value: string) => { + if (value) { + if (value === CREATE_NEW) { + if (!initialSelectOptions.some((item) => item.value === inputValue)) { + initialSelectOptions = [...initialSelectOptions, { value: inputValue, children: inputValue }]; + } + setSelected( + selected.includes(inputValue) + ? selected.filter((selection) => selection !== inputValue) + : [...selected, inputValue] + ); + setOnCreation(!onCreation); + resetActiveAndFocusedItem(); + } else { + // eslint-disable-next-line no-console + console.log('selected', value); + setSelected( + selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] + ); + } + } + + textInputRef.current?.focus(); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setInputValue(value); + resetActiveAndFocusedItem(); + }; + const handleMenuArrowKeys = (key: string) => { - let indexToFocus; + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { + if (selectOptions.every((option) => option.isDisabled)) { + return; + } + + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; } } + } - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; } } - - setFocusedItemIndex(indexToFocus); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; - setActiveItem(`select-multi-create-typeahead-${focusedItem.value.replace(' ', '-')}`); } + + setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { - // Select the first available option case 'Enter': - if (!isOpen) { - setIsOpen((prevIsOpen) => !prevIsOpen); - } else if (isOpen && focusedItem.value !== 'no results') { + if (isOpen && focusedItem && !focusedItem.isAriaDisabled) { onSelect(focusedItem.value as string); } - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); + + if (!isOpen) { + setIsOpen(true); + } + break; case 'ArrowUp': case 'ArrowDown': @@ -116,35 +187,17 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { const onToggleClick = () => { setIsOpen(!isOpen); + textInputRef?.current?.focus(); }; - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); + const onClearButtonClick = () => { + setSelected([]); + setInputValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); }; - const onSelect = (value: string) => { - if (value) { - if (value === 'create') { - if (!initialSelectOptions.some((item) => item.value === inputValue)) { - initialSelectOptions = [...initialSelectOptions, { value: inputValue, children: inputValue }]; - } - setSelected( - selected.includes(inputValue) - ? selected.filter((selection) => selection !== inputValue) - : [...selected, inputValue] - ); - setOnCreation(!onCreation); - } else { - // eslint-disable-next-line no-console - console.log('selected', value); - setSelected( - selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] - ); - } - } - - textInputRef.current?.focus(); - }; + const getChildren = (value: string) => initialSelectOptions.find((option) => option.value === value)?.children; const toggle = (toggleRef: React.Ref) => ( { { onSelect(selection); }} > - {selection} + {getChildren(selection)} ))} - - {selected.length > 0 && ( - - )} + + @@ -208,9 +251,12 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { id="multi-create-typeahead-select" isOpen={isOpen} selected={selected} - onSelect={(ev, selection) => onSelect(selection as string)} - onOpenChange={() => setIsOpen(false)} + onSelect={(_event, selection) => onSelect(selection as string)} + onOpenChange={(isOpen) => { + !isOpen && closeMenu(); + }} toggle={toggle} + shouldFocusFirstItemOnOpen={false} > {selectOptions.map((option, index) => ( @@ -218,7 +264,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { key={option.value || option.children} isFocused={focusedItemIndex === index} className={option.className} - id={`select-multi-create-typeahead-${option.value.replace(' ', '-')}`} + id={createItemId(option.value)} {...option} ref={null} /> From 2848912fb441b8ca7f4f87501ede3f5a7a2c03df Mon Sep 17 00:00:00 2001 From: adamviktora Date: Fri, 26 Apr 2024 14:01:56 +0200 Subject: [PATCH 10/12] fix(SelectMultiTypeaheadCheckbox): changes based on SelectTypeahead --- .../examples/SelectMultiTypeaheadCheckbox.tsx | 163 +++++++++++------- 1 file changed, 103 insertions(+), 60 deletions(-) diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index 7a88b6f0a1a..da38e0930e1 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -28,10 +28,12 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { const [selected, setSelected] = React.useState([]); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); + const [activeItemId, setActiveItemId] = React.useState(null); const [placeholder, setPlaceholder] = React.useState('0 items selected'); const textInputRef = React.useRef(); + const NO_RESULTS = 'no results'; + React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; @@ -45,9 +47,9 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { if (!newSelectOptions.length) { newSelectOptions = [ { - isDisabled: false, + isAriaDisabled: true, children: `No results found for "${inputValue}"`, - value: 'no results', + value: NO_RESULTS, hasCheckbox: false } ]; @@ -60,56 +62,99 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { } setSelectOptions(newSelectOptions); - setFocusedItemIndex(null); - setActiveItem(null); }, [inputValue]); + React.useEffect(() => { + setPlaceholder(`${selected.length} item${selected.length !== 1 ? 's' : ''} selected`); + }, [selected]); + + const createItemId = (value: any) => `select-multi-typeahead-${value.replace(' ', '-')}`; + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions[itemIndex]; + setActiveItemId(createItemId(focusedItem.value)); + }; + + const resetActiveAndFocusedItem = () => { + setFocusedItemIndex(null); + setActiveItemId(null); + }; + + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); + }; + + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!inputValue) { + closeMenu(); + } + }; + const handleMenuArrowKeys = (key: string) => { - let indexToFocus; + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions.every((option) => option.isDisabled)) { + return; + } + + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } - if (isOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; } } + } + + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + // Skip disabled options + while (selectOptions[indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions.length) { indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; } } - - setFocusedItemIndex(indexToFocus); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; - setActiveItem(`select-multi-typeahead-checkbox-${focusedItem.value.replace(' ', '-')}`); } + + setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { - // Select the first available option case 'Enter': + if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) { + onSelect(focusedItem.value); + } + if (!isOpen) { - setIsOpen((prevIsOpen) => !prevIsOpen); - } else if (isOpen && focusedItem.value !== 'no results') { - onSelect(focusedItem.value as string); + setIsOpen(true); } - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); + break; case 'ArrowUp': case 'ArrowDown': @@ -121,17 +166,19 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { const onToggleClick = () => { setIsOpen(!isOpen); + textInputRef?.current?.focus(); }; const onTextInputChange = (_event: React.FormEvent, value: string) => { setInputValue(value); + resetActiveAndFocusedItem(); }; const onSelect = (value: string) => { - // eslint-disable-next-line no-console - console.log('selected', value); + if (value && value !== NO_RESULTS) { + // eslint-disable-next-line no-console + console.log('selected', value); - if (value && value !== 'no results') { setSelected( selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] ); @@ -140,9 +187,12 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { textInputRef.current?.focus(); }; - React.useEffect(() => { - setPlaceholder(`${selected.length} items selected`); - }, [selected]); + const onClearButtonClick = () => { + setSelected([]); + setInputValue(''); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); + }; const toggle = (toggleRef: React.Ref) => ( { - - {selected.length > 0 && ( - - )} + + @@ -193,19 +233,22 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { id="multi-typeahead-checkbox-select" isOpen={isOpen} selected={selected} - onSelect={(ev, selection) => onSelect(selection as string)} - onOpenChange={() => setIsOpen(false)} + onSelect={(_event, selection) => onSelect(selection as string)} + onOpenChange={(isOpen) => { + !isOpen && closeMenu(); + }} toggle={toggle} + shouldFocusFirstItemOnOpen={false} > - + {selectOptions.map((option, index) => ( From 87121d72ec22e8d0574444d567280e16f8de384c Mon Sep 17 00:00:00 2001 From: adamviktora Date: Tue, 30 Apr 2024 10:49:12 +0200 Subject: [PATCH 11/12] fix(SelectTypeaheadCreatable): close menu after creating option --- .../src/components/Select/examples/SelectTypeaheadCreatable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index f735605963b..7b0d9df194d 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -102,7 +102,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { } setSelected(filterValue); setFilterValue(''); - resetActiveAndFocusedItem(); + closeMenu(); } else { const optionText = selectOptions.find((option) => option.value === value)?.children; selectOption(value, optionText as string); From fa9a1af5a31e5bd7a238ca200d224532e40b40a6 Mon Sep 17 00:00:00 2001 From: adamviktora Date: Mon, 6 May 2024 10:06:56 +0200 Subject: [PATCH 12/12] fix(SelectTypeahead template): rename prop back to shouldFocusFirstItemOnOpen --- .../react-templates/src/components/Select/SelectTypeahead.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-templates/src/components/Select/SelectTypeahead.tsx b/packages/react-templates/src/components/Select/SelectTypeahead.tsx index c67134718e1..4d2657ad2bf 100644 --- a/packages/react-templates/src/components/Select/SelectTypeahead.tsx +++ b/packages/react-templates/src/components/Select/SelectTypeahead.tsx @@ -288,7 +288,7 @@ export const SelectTypeaheadBase: React.FunctionComponent !isOpen && closeMenu(); }} toggle={toggle} - shouldFocusFirstMenuItemOnOpen={false} + shouldFocusFirstItemOnOpen={false} ref={innerRef} {...props} >