diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index da7998a265e9..2e9a02912c2b 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -1,12 +1,13 @@ import {useNavigationState} from '@react-navigation/native'; import debounce from 'lodash/debounce'; -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal'; import Modal from '@components/Modal'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {SearchQueryJSON} from '@components/Search/types'; +import type {SelectionListHandle} from '@components/SelectionList/types'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -30,6 +31,7 @@ function SearchRouter() { const {isSmallScreenWidth} = useResponsiveLayout(); const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext(); + const listRef = useRef(null); const [textInputValue, setTextInputValue] = useState(''); const [userSearchQuery, setUserSearchQuery] = useState(undefined); @@ -66,7 +68,7 @@ function SearchRouter() { clearUserQuery(); return; } - + listRef.current?.updateAndScrollToFocusedIndex(0); const queryJSON = SearchUtils.buildSearchQueryJSON(userQuery); if (queryJSON) { @@ -127,8 +129,8 @@ function SearchRouter() { onSubmit={() => { onSearchSubmit(userSearchQuery); }} + routerListRef={listRef} /> - diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index ee5924002a06..6ad5d4df0ba7 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import type {RefObject} from 'react'; +import type {SelectionListHandle} from '@components/SelectionList/types'; import BaseTextInput from '@components/TextInput/BaseTextInput'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -8,9 +10,10 @@ type SearchRouterInputProps = { setValue: (searchTerm: string) => void; updateSearch: (searchTerm: string) => void; onSubmit: () => void; + routerListRef: RefObject; }; -function SearchRouterInput({value, setValue, updateSearch, onSubmit}: SearchRouterInputProps) { +function SearchRouterInput({value, setValue, updateSearch, onSubmit, routerListRef}: SearchRouterInputProps) { const styles = useThemeStyles(); const onChangeText = (text: string) => { @@ -28,6 +31,12 @@ function SearchRouterInput({value, setValue, updateSearch, onSubmit}: SearchRout inputStyle={[styles.searchInputStyle, styles.searchRouterInputStyle, styles.ph2]} role={CONST.ROLE.PRESENTATION} autoCapitalize="none" + onFocus={() => { + routerListRef?.current?.updateExternalTextInputFocus(true); + }} + onBlur={() => { + routerListRef?.current?.updateExternalTextInputFocus(false); + }} /> ); } diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index b8a01a92d349..4550829d3476 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -1,10 +1,11 @@ -import React, {useCallback} from 'react'; +import React, {forwardRef, useCallback} from 'react'; +import type {ForwardedRef} from 'react'; import * as Expensicons from '@components/Icon/Expensicons'; import type {SearchQueryJSON, SearchRouterListItem} from '@components/Search/types'; import SelectionList from '@components/SelectionList'; import SingleIconListItem from '@components/SelectionList/Search/SingleIconListItem'; import type {ListItemWithSingleIcon, SingleIconListItemProps} from '@components/SelectionList/Search/SingleIconListItem'; -import type {SectionListDataType, UserListItemProps} from '@components/SelectionList/types'; +import type {SectionListDataType, SelectionListHandle, UserListItemProps} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -44,7 +45,10 @@ function SearchRouterItem(props: UserListItemProps | Singl return )} />; } -function SearchRouterList({currentQuery, reportForContextualSearch, recentSearches, recentReports, onSearchSubmit, updateUserSearchQuery, closeAndClearRouter}: SearchRouterListProps) { +function SearchRouterList( + {currentQuery, reportForContextualSearch, recentSearches, recentReports, onSearchSubmit, updateUserSearchQuery, closeAndClearRouter}: SearchRouterListProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const {translate} = useLocalize(); const sections: Array> = []; @@ -96,7 +100,7 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch const onSelectRow = useCallback( (item: SearchRouterListItem) => { - // eslint-disable-next-line default-case + // eslint-disable-next-line default-case, @typescript-eslint/switch-exhaustiveness-check switch (item.itemType) { case CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.SEARCH: // Handle selection of "Recent search" @@ -131,10 +135,11 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch onSelectRow={onSelectRow} ListItem={SearchRouterItem} containerStyle={styles.mh100} + ref={ref} /> ); } -export default SearchRouterList; +export default forwardRef(SearchRouterList); export {SearchRouterItem}; export type {ItemWithQuery}; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 197c64b99a26..fb594d2f1d6b 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -617,7 +617,21 @@ function BaseSelectionList( [flattenedSections.allOptions, setFocusedIndex, updateAndScrollToFocusedIndex], ); - useImperativeHandle(ref, () => ({scrollAndHighlightItem, clearInputAfterSelect}), [scrollAndHighlightItem, clearInputAfterSelect]); + /** + * Changes isTextInputFocusedRef value when using external TextInput, to handle shouldSync focus properly + * + * @param isTextInputFocused - Is external TextInput focused. + */ + const updateExternalTextInputFocus = useCallback((isTextInputFocused: boolean) => { + isTextInputFocusedRef.current = isTextInputFocused; + }, []); + + useImperativeHandle(ref, () => ({scrollAndHighlightItem, clearInputAfterSelect, updateAndScrollToFocusedIndex, updateExternalTextInputFocus}), [ + scrollAndHighlightItem, + clearInputAfterSelect, + updateAndScrollToFocusedIndex, + updateExternalTextInputFocus, + ]); /** Selects row when pressing Enter */ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { diff --git a/src/components/SelectionList/Search/SingleIconListItem.tsx b/src/components/SelectionList/Search/SingleIconListItem.tsx index 9aa3274d3686..73f6e0ebd4c6 100644 --- a/src/components/SelectionList/Search/SingleIconListItem.tsx +++ b/src/components/SelectionList/Search/SingleIconListItem.tsx @@ -16,9 +16,10 @@ type SingleIconListItemProps = { showTooltip?: boolean; onSelectRow: (item: TItem) => void; onFocus?: () => void; + shouldSyncFocus?: boolean; }; -function SingleIconListItem({item, isFocused, showTooltip, onSelectRow, onFocus}: SingleIconListItemProps) { +function SingleIconListItem({item, isFocused, showTooltip, onSelectRow, onFocus, shouldSyncFocus}: SingleIconListItemProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -32,6 +33,7 @@ function SingleIconListItem({item, isFocus keyForList={item.keyForList} onFocus={onFocus} hoverStyle={item.isSelected && styles.activeComponentBG} + shouldSyncFocus={shouldSyncFocus} > <> {item.singleIcon && ( diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 8c8c33eb2cc9..395e1f27397f 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -554,6 +554,8 @@ type BaseSelectionListProps = Partial & { type SelectionListHandle = { scrollAndHighlightItem?: (items: string[], timeout: number) => void; clearInputAfterSelect?: () => void; + updateAndScrollToFocusedIndex: (newFocusedIndex: number) => void; + updateExternalTextInputFocus: (isTextInputFocused: boolean) => void; }; type ItemLayout = {