Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8052,6 +8052,11 @@ const CONST = {
/** Onyx prefix for domain security groups */
DOMAIN_SECURITY_GROUP_PREFIX: 'domain_securityGroup_',
},

SECTION_LIST_ITEM_TYPE: {
HEADER: 'header',
ROW: 'row',
},
} as const;

const CONTINUATION_DETECTION_SEARCH_FILTER_KEYS = [
Expand Down
77 changes: 19 additions & 58 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ 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';
import {focusedItemRef} from '@hooks/useSyncFocus/useSyncFocusImplementation';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import getEmptyArray from '@src/types/utils/getEmptyArray';
import Footer from './components/Footer';
import ListHeader from './components/ListHeader';
import TextInput from './components/TextInput';
import useSearchFocusSync from './hooks/useSearchFocusSync';
import useSelectedItemFocusSync from './hooks/useSelectedItemFocusSync';
import ListItemRenderer from './ListItem/ListItemRenderer';
import type {ButtonOrCheckBoxRoles, DataDetailsType, ListItem, SelectionListProps} from './types';

Expand Down Expand Up @@ -57,7 +59,7 @@ function BaseSelectionList<TItem extends ListItem>({
listFooterContent,
rightHandSideComponent,
alternateNumberOfSupportedLines,
selectedItems = CONST.EMPTY_ARRAY as unknown as string[],
selectedItems = getEmptyArray<string>(),
style,
isSelected,
isDisabled = false,
Expand Down Expand Up @@ -190,7 +192,7 @@ function BaseSelectionList<TItem extends ListItem>({

(shouldDebounceScrolling ? debouncedScrollToIndex : scrollToIndex)(index);
},
...(!hasKeyBeenPressed.current && {setHasKeyBeenPressed}),
setHasKeyBeenPressed,
isFocused,
onArrowUpDownCallback,
});
Expand Down Expand Up @@ -446,66 +448,25 @@ function BaseSelectionList<TItem extends ListItem>({
[data.length, scrollToIndex, setFocusedIndex],
);

const selectedItemIndex = useMemo(() => (initiallyFocusedItemKey ? data.findIndex(isItemSelected) : -1), [data, initiallyFocusedItemKey, isItemSelected]);

useEffect(() => {
if (selectedItemIndex === -1 || selectedItemIndex === focusedIndex || textInputOptions?.value) {
return;
}
setFocusedIndex(selectedItemIndex);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedItemIndex]);

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;
const selectionChangedByClicking = !searchChanged && selectedOptionsChanged && shouldUpdateFocusedIndex;
// Do not change focus if:
// 1. Input value is the same or
// 2. Data length is 0 or
// 3. Selection changed via user interaction (not filtering), so focus is handled externally
if ((!searchChanged && !selectedOptionsChanged) || data.length === 0 || selectionChangedByClicking) {
return;
}

const hasSearchBeenCleared = prevSearchValue && !currentSearchValue;
if (hasSearchBeenCleared) {
const foundSelectedItemIndex = data.findIndex(isItemSelected);

if (foundSelectedItemIndex !== -1 && !canSelectMultiple) {
scrollToIndex(foundSelectedItemIndex);
setFocusedIndex(foundSelectedItemIndex);
return;
}
}

// 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;
useSelectedItemFocusSync({
data,
initiallyFocusedItemKey,
isItemSelected,
focusedIndex,
searchValue: textInputOptions?.value,
setFocusedIndex,
});

scrollToIndex(newSelectedIndex);
setFocusedIndex(newSelectedIndex);
}, [
canSelectMultiple,
useSearchFocusSync({
searchValue: textInputOptions?.value,
data,
dataDetails.selectedOptions.length,
selectedOptionsCount: dataDetails.selectedOptions.length,
isItemSelected,
prevAllOptionsLength,
prevSelectedOptionsLength,
prevSearchValue,
canSelectMultiple,
shouldUpdateFocusedIndex,
scrollToIndex,
setFocusedIndex,
shouldUpdateFocusedIndex,
textInputOptions?.value,
]);
});

useEffect(() => {
if (!itemFocusTimeoutRef.current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {BaseListItemProps, ExtendedTargetedEvent, ListItem} from './types';
type ListItemRendererProps<TItem extends ListItem> = Omit<BaseListItemProps<TItem>, 'onSelectRow' | 'keyForList'> &
Pick<SelectionListProps<TItem>, 'ListItem' | 'shouldIgnoreFocus' | 'shouldSingleExecuteRowSelect'> & {
index: number;
normalizedIndex?: number;
selectRow: (item: TItem, indexToFocus?: number) => void;
setFocusedIndex: ReturnType<typeof useArrowKeyFocusManager>[1];
singleExecution: ReturnType<typeof useSingleExecution>['singleExecution'];
Expand All @@ -22,6 +23,7 @@ function ListItemRenderer<TItem extends ListItem>({
ListItem,
item,
index,
normalizedIndex,
isFocused,
isDisabled,
showTooltip,
Expand Down Expand Up @@ -91,7 +93,7 @@ function ListItemRenderer<TItem extends ListItem>({
if (isMobileChrome() && event.nativeEvent && !event.nativeEvent.sourceCapabilities) {
return;
}
setFocusedIndex(index);
setFocusedIndex(normalizedIndex ?? index);
}}
shouldSyncFocus={shouldSyncFocus}
wrapperStyle={wrapperStyle}
Expand Down
Loading
Loading