From a8a9b459a41996b7281f1f8b9bd0e0e17b4482d0 Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Thu, 27 Nov 2025 12:57:07 +0100 Subject: [PATCH 1/2] Add useEffect to manage scrolling --- .../SelectionList/BaseSelectionList.tsx | 48 ++++++++++++++++++- .../SelectionList/components/TextInput.tsx | 7 +-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 48e732d4a182..de22d2e07ed0 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -12,6 +12,7 @@ import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useDebounce from '@hooks/useDebounce'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useKeyboardState from '@hooks/useKeyboardState'; +import usePrevious from '@hooks/usePrevious'; import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings'; import useScrollEnabled from '@hooks/useScrollEnabled'; import useSingleExecution from '@hooks/useSingleExecution'; @@ -288,7 +289,6 @@ function BaseSelectionList({ onFocusChange={(v: boolean) => (isTextInputFocusedRef.current = v)} showLoadingPlaceholder={showLoadingPlaceholder} isLoadingNewOptions={isLoadingNewOptions} - setFocusedIndex={setFocusedIndex} /> ); }; @@ -392,6 +392,52 @@ function BaseSelectionList({ [data.length, scrollToIndex, setFocusedIndex], ); + const prevSearchValue = usePrevious(textInputOptions?.value); + const prevSelectedOptionsLength = usePrevious(dataDetails.selectedOptions.length); + const prevAllOptionsLength = usePrevious(data.length); + + useEffect(() => { + const currentSearchValue = textInputOptions?.value; + const searchChanged = prevSearchValue !== currentSearchValue; + const selectedOptionsChanged = dataDetails.selectedOptions.length !== prevSelectedOptionsLength; + // Focus shouldn't be changed if: input value is the same or data length is 0 + // shouldUpdateFocusedIndex is true => other function handles the focus + if ((!searchChanged && !selectedOptionsChanged) || data.length === 0 || shouldUpdateFocusedIndex) { + return; + } + + // Clearing search + if (prevSearchValue && !currentSearchValue) { + const foundSelectedItemIndex = data.findIndex(isItemSelected); + + if (foundSelectedItemIndex !== -1 && !canSelectMultiple) { + scrollToIndex(foundSelectedItemIndex); + setFocusedIndex(foundSelectedItemIndex); + return; + } + } + + // Remove focus (-1) if the search is idle or if the user is just toggling options without changing the list content + // Otherwise (e.g. when filtering/typing), focus on the first item (0) + const isSearchIdle = !prevSearchValue && !currentSearchValue; + const newSelectedIndex = isSearchIdle || (selectedOptionsChanged && prevAllOptionsLength === data.length) ? -1 : 0; + + scrollToIndex(newSelectedIndex); + setFocusedIndex(newSelectedIndex); + }, [ + canSelectMultiple, + data, + dataDetails.selectedOptions.length, + isItemSelected, + prevAllOptionsLength, + prevSelectedOptionsLength, + prevSearchValue, + scrollToIndex, + setFocusedIndex, + shouldUpdateFocusedIndex, + textInputOptions?.value, + ]); + useEffect(() => { if (!itemFocusTimeoutRef.current) { return; diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index 80874e924830..c2ebd6c66ce2 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -45,9 +45,6 @@ type TextInputProps = { /** Whether to show the loading indicator for new options */ isLoadingNewOptions?: boolean; - /** Function to update the focused index in the list */ - setFocusedIndex: (index: number) => void; - /** Function to focus text input component */ focusTextInput: () => void; }; @@ -64,7 +61,6 @@ function TextInput({ showLoadingPlaceholder, isLoadingNewOptions, shouldShowTextInput, - setFocusedIndex, focusTextInput, }: TextInputProps) { const styles = useThemeStyles(); @@ -80,9 +76,8 @@ function TextInput({ const handleTextInputChange = useCallback( (text: string) => { onChangeText?.(text); - setFocusedIndex(0); }, - [onChangeText, setFocusedIndex], + [onChangeText], ); useFocusEffect( From e9e7f1d8f4a5ca7ee7dd8f1ac2f4256b94373d1c Mon Sep 17 00:00:00 2001 From: Zuzanna Furtak Date: Thu, 27 Nov 2025 14:22:47 +0100 Subject: [PATCH 2/2] Adjust comments to be clear --- src/components/SelectionList/BaseSelectionList.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index de22d2e07ed0..3c8296caa988 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -400,14 +400,16 @@ function BaseSelectionList({ const currentSearchValue = textInputOptions?.value; const searchChanged = prevSearchValue !== currentSearchValue; const selectedOptionsChanged = dataDetails.selectedOptions.length !== prevSelectedOptionsLength; - // Focus shouldn't be changed if: input value is the same or data length is 0 - // shouldUpdateFocusedIndex is true => other function handles the focus + // Do not change focus if: + // 1. Input value is the same or + // 2. Data length is 0 or + // 3. shouldUpdateFocusedIndex is true => other function handles the focus if ((!searchChanged && !selectedOptionsChanged) || data.length === 0 || shouldUpdateFocusedIndex) { return; } - // Clearing search - if (prevSearchValue && !currentSearchValue) { + const hasSearchBeenCleared = prevSearchValue && !currentSearchValue; + if (hasSearchBeenCleared) { const foundSelectedItemIndex = data.findIndex(isItemSelected); if (foundSelectedItemIndex !== -1 && !canSelectMultiple) { @@ -417,7 +419,9 @@ function BaseSelectionList({ } } - // Remove focus (-1) if the search is idle or if the user is just toggling options without changing the list content + // Remove focus (set focused index to -1) if: + // 1. If the search is idle or + // 2. If the user is just toggling options without changing the list content // Otherwise (e.g. when filtering/typing), focus on the first item (0) const isSearchIdle = !prevSearchValue && !currentSearchValue; const newSelectedIndex = isSearchIdle || (selectedOptionsChanged && prevAllOptionsLength === data.length) ? -1 : 0;