diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index a58e4510c0fd..bd91307b0c82 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -115,6 +115,7 @@ function SearchMultipleSelectionPicker({ sections={sections} ListItem={MultiSelectListItem} initiallyFocusedItemKey={initiallyFocusedKey} + shouldUpdateFocusedIndex shouldClearInputOnSelect={false} shouldShowTextInput={shouldShowTextInput} textInputOptions={textInputOptions} diff --git a/src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx b/src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx index 4f6bb2a1f020..f162b2d5e52a 100644 --- a/src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx +++ b/src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx @@ -170,6 +170,9 @@ function BaseSelectionListWithSections({ } } if (shouldUpdateFocusedIndex && typeof indexToFocus === 'number') { + if (indexToFocus !== focusedIndex) { + suppressNextFocusScrollRef.current = true; + } setFocusedIndex(indexToFocus); } onSelectRow(item); @@ -268,6 +271,10 @@ function BaseSelectionListWithSections({ setFocusedIndex, }); + const suppressNextFocusScroll = () => { + suppressNextFocusScrollRef.current = true; + }; + useSearchFocusSync({ searchValue: textInputOptions?.value, data: flattenedData, @@ -277,7 +284,9 @@ function BaseSelectionListWithSections({ shouldUpdateFocusedIndex, scrollToIndex, setFocusedIndex, + focusedIndex, firstFocusableIndex, + suppressNextFocusScroll, }); const textInputComponent = () => { diff --git a/src/components/SelectionList/hooks/useSearchFocusSync.ts b/src/components/SelectionList/hooks/useSearchFocusSync.ts index 9a7fe7aefe4a..3aaf46412b44 100644 --- a/src/components/SelectionList/hooks/useSearchFocusSync.ts +++ b/src/components/SelectionList/hooks/useSearchFocusSync.ts @@ -27,8 +27,14 @@ type UseSearchFocusSyncParams = { /** Function to set the focused index */ setFocusedIndex: (index: number) => void; + /** The current focused index — needed to avoid arming scroll suppression when the index won't actually change */ + focusedIndex?: number; + /** The first focusable index in the list (useful when index 0 is a header). Defaults to 0. */ firstFocusableIndex?: number; + + /** Optional callback to suppress the scroll that onFocusedIndexChange would otherwise trigger when setFocusedIndex is called */ + suppressNextFocusScroll?: () => void; }; /** @@ -47,7 +53,9 @@ function useSearchFocusSync({ shouldUpdateFocusedIndex, scrollToIndex, setFocusedIndex, + focusedIndex, firstFocusableIndex = 0, + suppressNextFocusScroll, }: UseSearchFocusSyncParams) { const prevSearchValue = usePrevious(searchValue); const prevSelectedOptionsCount = usePrevious(selectedOptionsCount); @@ -72,6 +80,9 @@ function useSearchFocusSync({ if (foundSelectedItemIndex !== -1 && !canSelectMultiple) { scrollToIndex(foundSelectedItemIndex, false); + if (foundSelectedItemIndex !== focusedIndex) { + suppressNextFocusScroll?.(); + } setFocusedIndex(foundSelectedItemIndex); return; } @@ -91,6 +102,9 @@ function useSearchFocusSync({ // Scroll to top of list and focus on first focusable item (not header) scrollToIndex(0, false); + if (firstFocusableIndex !== focusedIndex) { + suppressNextFocusScroll?.(); + } setFocusedIndex(firstFocusableIndex); }, [ canSelectMultiple, @@ -104,7 +118,9 @@ function useSearchFocusSync({ shouldUpdateFocusedIndex, searchValue, isItemSelected, + focusedIndex, firstFocusableIndex, + suppressNextFocusScroll, ]); }