Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix selection list focus #34196

Merged
merged 21 commits into from
Feb 15, 2024
Merged
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d733aa2
fix selection list focus
abzokhattab Jan 9, 2024
2a50e43
Using memo on the sorted sections
abzokhattab Jan 12, 2024
3920a4f
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Jan 12, 2024
0e2f487
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Jan 14, 2024
10f06b7
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Jan 17, 2024
f702796
Convert changes to typescript
abzokhattab Jan 17, 2024
d53c12f
Minor
abzokhattab Jan 17, 2024
8ab6576
migrating to ts
abzokhattab Jan 17, 2024
216793f
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Jan 20, 2024
54cdcc3
Merge branch 'main' into fix-base-selection-list-focus
abzokhattab Jan 25, 2024
6d9a69f
fix arrow selection
abzokhattab Jan 26, 2024
a123043
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Jan 26, 2024
17c2f7e
Update src/components/SelectionList/BaseSelectionList.tsx
abzokhattab Jan 27, 2024
a015eb5
comments changes
abzokhattab Jan 27, 2024
b8f58a2
minor
abzokhattab Jan 27, 2024
b43f5c4
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Feb 5, 2024
322d554
Removing the sortedsections logic
abzokhattab Feb 5, 2024
ec03b9a
Update src/components/SelectionList/BaseSelectionList.tsx
abzokhattab Feb 6, 2024
86d242d
Adjusting useEffect dependencies
abzokhattab Feb 6, 2024
3dab35f
Merge remote-tracking branch 'origin/main' into fix-base-selection-li…
abzokhattab Feb 12, 2024
f4393b1
minor edit
abzokhattab Feb 14, 2024
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
78 changes: 66 additions & 12 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useFocusEffect, useIsFocused} from '@react-navigation/native';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListRenderItemInfo} from 'react-native';
import {View} from 'react-native';
import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager';
import Button from '@components/Button';
import Checkbox from '@components/Checkbox';
Expand All @@ -16,6 +16,7 @@ import TextInput from '@components/TextInput';
import useActiveElementRole from '@hooks/useActiveElementRole';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import Log from '@libs/Log';
import variables from '@styles/variables';
Expand Down Expand Up @@ -73,6 +74,41 @@ function BaseSelectionList<TItem extends User | RadioItem>(
const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true);

/**
* Sorts sections in the list based on the selection status and whether items are disabled.
* Disabled items are moved to the top, followed by selected items, and then unselected items.
*/
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved
const sortedSections = useMemo(() => {
// If multiple selection is not allowed, return the original list
if (!canSelectMultiple) {
return sections;
}

return sections.map((section) => {
const disabledItems: TItem[] = [];
const selectedItems: TItem[] = [];
const unselectedItems: TItem[] = [];

section.data.forEach((item) => {
if (item.isDisabled) {
disabledItems.push(item);
} else if (item.isSelected) {
selectedItems.push(item);
} else {
unselectedItems.push(item);
}
});

// Combine items in the order: disabled, selected, unselected
const sortedData = [...disabledItems, ...selectedItems, ...unselectedItems];
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved

return {
...section,
data: sortedData,
};
});
}, [canSelectMultiple, sections]);

/**
* Iterates through the sections and items inside each section, and builds 3 arrays along the way:
* - `allOptions`: Contains all the items in the list, flattened, regardless of section
Expand All @@ -91,7 +127,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(

const selectedOptions: TItem[] = [];

sections.forEach((section, sectionIndex) => {
sortedSections.forEach((section, sectionIndex) => {
const sectionHeaderHeight = variables.optionsListSectionHeaderHeight;
itemLayouts.push({length: sectionHeaderHeight, offset});
offset += sectionHeaderHeight;
Expand Down Expand Up @@ -142,7 +178,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(
itemLayouts,
allSelected: selectedOptions.length > 0 && selectedOptions.length === allOptions.length - disabledOptionsIndexes.length,
};
}, [canSelectMultiple, sections]);
}, [canSelectMultiple, sortedSections]);

// If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member
const [focusedIndex, setFocusedIndex] = useState(() => flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey));
Expand Down Expand Up @@ -172,7 +208,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(
// Otherwise, it will cause an index-out-of-bounds error and crash the app.
let adjustedSectionIndex = sectionIndex;
for (let i = 0; i < sectionIndex; i++) {
if (sections[i].data) {
if (sortedSections[i].data) {
adjustedSectionIndex--;
}
}
Expand Down Expand Up @@ -357,18 +393,36 @@ function BaseSelectionList<TItem extends User | RadioItem>(
}, [shouldShowTextInput]),
);

const prevTextInputValue = usePrevious(textInputValue);
useEffect(() => {
// do not change focus on the first render, as it should focus on the selected item
if (isInitialSectionListRender) {
// do not change focus when the textInputValue is the same
if (prevTextInputValue === textInputValue) {
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// set the focus on the first item when the sections list is changed
if (sections.length > 0) {
updateAndScrollToFocusedIndex(0);
if (flattenedSections.allOptions.length > 0) {
let newSelectedIndex;

if (textInputValue === '') {
// if the textInputValue is empty then focus is removed
newSelectedIndex = -1;
} else {
// if multiple selection then focus on the first non-selected item
// else focus on the first item
newSelectedIndex = canSelectMultiple ? flattenedSections.selectedOptions.length + flattenedSections.disabledOptionsIndexes.length : 0;
}

updateAndScrollToFocusedIndex(newSelectedIndex);
}
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line react-hooks/exhaustive-deps
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved
}, [sections]);
}, [
abzokhattab marked this conversation as resolved.
Show resolved Hide resolved
canSelectMultiple,
flattenedSections.allOptions.length,
flattenedSections.disabledOptionsIndexes.length,
flattenedSections.selectedOptions.length,
textInputValue,
updateAndScrollToFocusedIndex,
]);

/** Selects row when pressing Enter */
useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, {
Expand Down Expand Up @@ -459,7 +513,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(
)}
<SectionList
ref={listRef}
sections={sections}
sections={sortedSections}
stickySectionHeadersEnabled={false}
renderSectionHeader={renderSectionHeader}
renderItem={renderItem}
Expand Down
Loading