diff --git a/src/components/ArrowKeyFocusManager.js b/src/components/ArrowKeyFocusManager.js index 19dc3a7ac614..2532e52156df 100644 --- a/src/components/ArrowKeyFocusManager.js +++ b/src/components/ArrowKeyFocusManager.js @@ -1,5 +1,6 @@ +import {useIsFocused} from '@react-navigation/native'; import PropTypes from 'prop-types'; -import {Component} from 'react'; +import React, {Component} from 'react'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import CONST from '@src/CONST'; @@ -16,6 +17,9 @@ const propTypes = { /** The maximum index – provided so that the focus can be sent back to the beginning of the list when the end is reached. */ maxIndex: PropTypes.number.isRequired, + /** Whether navigation is focused */ + isFocused: PropTypes.bool.isRequired, + /** A callback executed when the focused input changes. */ onFocusedIndexChanged: PropTypes.func.isRequired, @@ -32,7 +36,7 @@ const defaultProps = { shouldResetIndexOnEndReached: true, }; -class ArrowKeyFocusManager extends Component { +class BaseArrowKeyFocusManager extends Component { componentDidMount() { const arrowUpConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_UP; const arrowDownConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_DOWN; @@ -77,7 +81,7 @@ class ArrowKeyFocusManager extends Component { } onArrowUpKey() { - if (this.props.maxIndex < 0) { + if (this.props.maxIndex < 0 || !this.props.isFocused) { return; } @@ -96,7 +100,7 @@ class ArrowKeyFocusManager extends Component { } onArrowDownKey() { - if (this.props.maxIndex < 0) { + if (this.props.maxIndex < 0 || !this.props.isFocused) { return; } @@ -119,7 +123,20 @@ class ArrowKeyFocusManager extends Component { } } -ArrowKeyFocusManager.propTypes = propTypes; -ArrowKeyFocusManager.defaultProps = defaultProps; +function ArrowKeyFocusManager(props) { + const isFocused = useIsFocused(); + + return ( + + ); +} + +BaseArrowKeyFocusManager.propTypes = propTypes; +BaseArrowKeyFocusManager.defaultProps = defaultProps; +ArrowKeyFocusManager.displayName = 'ArrowKeyFocusManager'; export default ArrowKeyFocusManager; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 5b246caa6e07..553a712f45c4 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -24,6 +24,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@libs/actions/Policy'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -318,6 +319,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat sections={[{data: categoryList, isDisabled: false}]} onCheckboxPress={toggleCategory} onSelectRow={navigateToCategorySettings} + shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} onSelectAll={toggleAllCategories} showScrollIndicator ListItem={TableListItem} diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index faa766945452..04b11b31d3da 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -22,6 +22,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -310,6 +311,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) onDismissError={dismissError} showScrollIndicator ListItem={TableListItem} + shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} customListHeader={getCustomListHeader()} listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} /> diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 56cf00582782..2e2d3a483873 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -24,6 +24,7 @@ import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -317,6 +318,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { showScrollIndicator ListItem={TableListItem} customListHeader={getCustomListHeader()} + shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} onDismissError={(item) => Policy.clearPolicyTagErrors(route.params.policyID, item.value)} /> diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 8ce33ec783c0..5540e09693eb 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -22,6 +22,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {openPolicyTaxesPage} from '@libs/actions/Policy'; import {clearTaxRateError, deletePolicyTaxes, setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -278,6 +279,7 @@ function WorkspaceTaxesPage({ showScrollIndicator ListItem={TableListItem} customListHeader={getCustomListHeader()} + shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} onDismissError={(item) => (item.keyForList ? clearTaxRateError(policyID, item.keyForList, item.pendingAction) : undefined)} /> diff --git a/tests/perf-test/OptionsSelector.perf-test.tsx b/tests/perf-test/OptionsSelector.perf-test.tsx index 44dc4ac6c317..fe234dda1e19 100644 --- a/tests/perf-test/OptionsSelector.perf-test.tsx +++ b/tests/perf-test/OptionsSelector.perf-test.tsx @@ -5,6 +5,7 @@ import type {ComponentType} from 'react'; import {measurePerformance} from 'reassure'; import type {WithLocalizeProps} from '@components/withLocalize'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; +import type Navigation from '@libs/Navigation/Navigation'; import OptionsSelector from '@src/components/OptionsSelector'; import variables from '@src/styles/variables'; @@ -38,6 +39,18 @@ jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType return WithNavigationFocus; }); +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useNavigation: () => ({ + navigate: jest.fn(), + addListener: () => jest.fn(), + }), + useIsFocused: () => true, + } as typeof Navigation; +}); + type GenerateSectionsProps = Array<{numberOfItems: number; shouldShow?: boolean}>; const generateSections = (sections: GenerateSectionsProps) => diff --git a/tests/perf-test/ReportActionCompose.perf-test.tsx b/tests/perf-test/ReportActionCompose.perf-test.tsx index 8d6cb1ac7e57..91e4f51f1c66 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.tsx +++ b/tests/perf-test/ReportActionCompose.perf-test.tsx @@ -38,9 +38,7 @@ jest.mock('@react-navigation/native', () => { navigate: jest.fn(), addListener: () => jest.fn(), }), - useIsFocused: () => ({ - navigate: jest.fn(), - }), + useIsFocused: () => true, } as typeof Navigation; }); diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index ff3d1473c662..b55f4b9ccb93 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -81,15 +81,12 @@ jest.mock('@src/hooks/usePermissions.ts'); jest.mock('@src/libs/Navigation/Navigation'); -const mockedNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useFocusEffect: jest.fn(), - useIsFocused: () => ({ - navigate: mockedNavigate, - }), + useIsFocused: () => true, useRoute: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), diff --git a/tests/perf-test/SearchPage.perf-test.tsx b/tests/perf-test/SearchPage.perf-test.tsx index 95f5630e3fe9..b7cdee0d7417 100644 --- a/tests/perf-test/SearchPage.perf-test.tsx +++ b/tests/perf-test/SearchPage.perf-test.tsx @@ -45,15 +45,12 @@ jest.mock('@src/libs/API', () => ({ jest.mock('@src/libs/Navigation/Navigation'); -const mockedNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useFocusEffect: jest.fn(), - useIsFocused: () => ({ - navigate: mockedNavigate, - }), + useIsFocused: () => true, useRoute: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), diff --git a/tests/perf-test/SignInPage.perf-test.tsx b/tests/perf-test/SignInPage.perf-test.tsx index e3e2c20ae72a..dc93b0d81059 100644 --- a/tests/perf-test/SignInPage.perf-test.tsx +++ b/tests/perf-test/SignInPage.perf-test.tsx @@ -26,15 +26,12 @@ jest.mock('../../src/libs/API', () => ({ read: jest.fn(), })); -const mockedNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useFocusEffect: jest.fn(), - useIsFocused: () => ({ - navigate: mockedNavigate, - }), + useIsFocused: () => true, useRoute: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index f6eee590313b..58c765b5c98e 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type {NavigationProp} from '@react-navigation/core/src/types'; import type * as Navigation from '@react-navigation/native'; -import type {ParamListBase} from '@react-navigation/routers'; import {render} from '@testing-library/react-native'; import type {ReactElement} from 'react'; import React from 'react'; @@ -33,17 +31,13 @@ type MockedSidebarLinksProps = { currentReportID?: string; }; -// we have to mock `useIsFocused` because it's used in the SidebarLinks component -const mockedNavigate: jest.MockedFn['navigate']> = jest.fn(); jest.mock('@react-navigation/native', (): typeof Navigation => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, useRoute: jest.fn(), useFocusEffect: jest.fn(), - useIsFocused: () => ({ - navigate: mockedNavigate, - }), + useIsFocused: () => true, useNavigation: () => ({ navigate: jest.fn(), addListener: jest.fn(),