Skip to content
Closed
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
66 changes: 21 additions & 45 deletions src/components/Search/SearchList/BaseSearchList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {useIsFocused} from '@react-navigation/native';
import {FlashList} from '@shopify/flash-list';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import React from 'react';
import type {NativeSyntheticEvent} from 'react-native';
import Animated from 'react-native-reanimated';
import type {ExtendedTargetedEvent, SearchListItem} from '@components/SelectionListWithSections/types';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import {isMobileChrome} from '@libs/Browser';
import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener';
import CONST from '@src/CONST';
import type BaseSearchListProps from './types';

Expand All @@ -33,62 +32,48 @@ function BaseSearchList({
selectedTransactions,
customCardNames,
}: BaseSearchListProps) {
const hasKeyBeenPressed = useRef(false);
const isFocused = useIsFocused();

const setHasKeyBeenPressed = useCallback(() => {
if (hasKeyBeenPressed.current) {
return;
}
// We need to track whether a key has been pressed to enable focus syncing only if a key has been pressed.
// This is to avoid the default behavior of web showing blue border on click of items after a page refresh.
hasKeyBeenPressed.current = true;
}, []);

const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({
initialFocusedIndex: -1,
maxIndex: flattenedItemsLength - 1,
isActive: isFocused,
onFocusedIndexChange: (index: number) => {
scrollToIndex?.(index);
},
...(!hasKeyBeenPressed.current && {setHasKeyBeenPressed}),
isFocused,
captureOnInputs: false,
});

const renderItemWithKeyboardFocus = useCallback(
({item, index}: {item: SearchListItem; index: number}) => {
const isItemFocused = focusedIndex === index;
const renderItemWithKeyboardFocus = ({item, index}: {item: SearchListItem; index: number}) => {
const isItemFocused = focusedIndex === index;

const onFocus = (event: NativeSyntheticEvent<ExtendedTargetedEvent>) => {
// Prevent unexpected scrolling on mobile Chrome after the context menu closes by ignoring programmatic focus not triggered by direct user interaction.
if (isMobileChrome() && event.nativeEvent) {
if (!event.nativeEvent.sourceCapabilities) {
return;
}
// Ignore the focus if it's caused by a touch event on mobile chrome.
// For example, a long press will trigger a focus event on mobile chrome
if (event.nativeEvent.sourceCapabilities.firesTouchEvents) {
return;
}
const onFocus = (event: NativeSyntheticEvent<ExtendedTargetedEvent>) => {
// Prevent unexpected scrolling on mobile Chrome after the context menu closes by ignoring programmatic focus not triggered by direct user interaction.
if (isMobileChrome() && event.nativeEvent) {
if (!event.nativeEvent.sourceCapabilities) {
return;
}
setFocusedIndex(index);
};
return renderItem(item, index, isItemFocused, onFocus);
},
[focusedIndex, renderItem, setFocusedIndex],
);
// Ignore the focus if it's caused by a touch event on mobile chrome.
// For example, a long press will trigger a focus event on mobile chrome
if (event.nativeEvent.sourceCapabilities.firesTouchEvents) {
return;
}
}
setFocusedIndex(index);
};
return renderItem(item, index, isItemFocused, onFocus);
};

const selectFocusedOption = useCallback(() => {
const selectFocusedOption = () => {
const focusedItem = data.at(focusedIndex);

if (!focusedItem) {
return;
}

onSelectRow(focusedItem);
}, [data, focusedIndex, onSelectRow]);
};

useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, {
captureOnInputs: true,
Expand All @@ -98,16 +83,7 @@ function BaseSearchList({
shouldStopPropagation: true,
});

useEffect(() => {
addKeyDownPressListener(setHasKeyBeenPressed);

return () => removeKeyDownPressListener(setHasKeyBeenPressed);
}, [setHasKeyBeenPressed]);

const extraData = useMemo(
() => [focusedIndex, columns, newTransactions, selectedTransactions, customCardNames],
[focusedIndex, columns, newTransactions, selectedTransactions, customCardNames],
);
const extraData = [columns, newTransactions, selectedTransactions, customCardNames];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep focusedIndex in FlashList extraData

renderItemWithKeyboardFocus derives isItemFocused from focusedIndex, but extraData no longer tracks focusedIndex, so keyboard navigation can change the internal focused row without reliably re-rendering visible cells that draw the focus state. In practice this can make ArrowUp/ArrowDown selection appear stuck (no blue border update) while Enter still acts on a different item, which is a keyboard accessibility regression in Search list navigation.

Useful? React with 👍 / 👎.


return (
<AnimatedFlashListComponent
Expand Down
Loading