diff --git a/src/CONST.ts b/src/CONST.ts index 72a84925f594..f26176d668cf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -47,6 +47,7 @@ const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow ?? 'keyInputUpArr const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow ?? 'keyInputDownArrow'; const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow ?? 'keyInputLeftArrow'; const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInputRightArrow'; +const keyInputSpace = ' '; // describes if a shortcut key can cause navigation const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; @@ -928,6 +929,14 @@ const CONST = { shortcutKey: 'Backspace', modifiers: [], }, + SPACE: { + descriptionKey: null, + shortcutKey: 'Space', + modifiers: [], + trigger: { + DEFAULT: {input: keyInputSpace}, + }, + }, }, KEYBOARD_SHORTCUTS_TYPES: { NAVIGATION_SHORTCUT: KEYBOARD_SHORTCUT_NAVIGATION_TYPE, diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index b8dc71aef515..4703458083a7 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,9 +1,9 @@ /* eslint-disable react/jsx-props-no-spreading */ import lodashIsEqual from 'lodash/isEqual'; import type {ReactNode, RefObject} from 'react'; -import React, {useLayoutEffect, useState} from 'react'; +import React, {useCallback, useLayoutEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; -import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; @@ -12,8 +12,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as Browser from '@libs/Browser'; -import * as Modal from '@userActions/Modal'; +import {isSafari} from '@libs/Browser'; +import getPlatform from '@libs/getPlatform'; +import {close as closeModal} from '@userActions/Modal'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; @@ -189,7 +190,8 @@ function PopoverMenu({ const currentMenuItemsFocusedIndex = getSelectedItemIndex(currentMenuItems); const [enteredSubMenuIndexes, setEnteredSubMenuIndexes] = useState(CONST.EMPTY_ARRAY); const {windowHeight} = useWindowDimensions(); - + const platform = getPlatform(); + const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: currentMenuItemsFocusedIndex, maxIndex: currentMenuItems.length - 1, isActive: isVisible}); const selectItem = (index: number) => { @@ -202,9 +204,9 @@ function PopoverMenu({ setEnteredSubMenuIndexes([...enteredSubMenuIndexes, index]); const selectedSubMenuItemIndex = selectedItem?.subMenuItems.findIndex((option) => option.isSelected); setFocusedIndex(selectedSubMenuItemIndex); - } else if (selectedItem.shouldCallAfterModalHide && !Browser.isSafari()) { + } else if (selectedItem.shouldCallAfterModalHide && !isSafari()) { onItemSelected?.(selectedItem, index); - Modal.close( + closeModal( () => { selectedItem.onSelected?.(); }, @@ -312,6 +314,21 @@ function PopoverMenu({ {isActive: isVisible}, ); + const keyboardShortcutSpaceCallback = useCallback( + (e?: GestureResponderEvent | KeyboardEvent) => { + if (shouldUseScrollView) { + return; + } + + e?.preventDefault(); + }, + [shouldUseScrollView], + ); + + // On web and desktop, pressing the space bar after interacting with the parent view + // can cause the parent view to scroll when the space bar is pressed. + useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.SPACE, keyboardShortcutSpaceCallback, {isActive: isWebOrDesktop && isVisible, shouldPreventDefault: false}); + const onModalHide = () => { setFocusedIndex(currentMenuItemsFocusedIndex); }; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 2edf248e12d4..511a1a899c28 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -20,6 +20,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import useScrollEnabled from '@hooks/useScrollEnabled'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -137,6 +138,7 @@ function BaseSelectionList( const shouldShowSelectAll = !!onSelectAll; const activeElementRole = useActiveElementRole(); const isFocused = useIsFocused(); + const scrollEnabled = useScrollEnabled(); const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true); const {isKeyboardShown} = useKeyboardState(); @@ -876,6 +878,7 @@ function BaseSelectionList( listHeaderContent ) } + scrollEnabled={scrollEnabled} ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} diff --git a/src/hooks/useScrollEnabled/index.native.ts b/src/hooks/useScrollEnabled/index.native.ts new file mode 100644 index 000000000000..ad9e5b9e54fa --- /dev/null +++ b/src/hooks/useScrollEnabled/index.native.ts @@ -0,0 +1,5 @@ +import type UseScrollEnabled from './types'; + +const useScrollEnabled: UseScrollEnabled = () => undefined; + +export default useScrollEnabled; diff --git a/src/hooks/useScrollEnabled/index.ts b/src/hooks/useScrollEnabled/index.ts new file mode 100644 index 000000000000..e47b8a1cc2dd --- /dev/null +++ b/src/hooks/useScrollEnabled/index.ts @@ -0,0 +1,8 @@ +import {useIsFocused} from '@react-navigation/native'; +import type UseScrollEnabled from './types'; + +const useScrollEnabled: UseScrollEnabled = () => { + const isFocused = useIsFocused(); + return isFocused; +}; +export default useScrollEnabled; diff --git a/src/hooks/useScrollEnabled/types.ts b/src/hooks/useScrollEnabled/types.ts new file mode 100644 index 000000000000..5bb84e5ef6a8 --- /dev/null +++ b/src/hooks/useScrollEnabled/types.ts @@ -0,0 +1,3 @@ +type UseScrollEnabled = () => boolean | undefined; + +export default UseScrollEnabled; diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts index 4f89d44da959..a6f62967e9ee 100644 --- a/src/libs/KeyboardShortcut/index.ts +++ b/src/libs/KeyboardShortcut/index.ts @@ -42,6 +42,7 @@ const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow?.toString() ?? 'k const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow?.toString() ?? 'keyInputDownArrow'; const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow?.toString() ?? 'keyInputLeftArrow'; const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow?.toString() ?? 'keyInputRightArrow'; +const keyInputSpace = ' '; /** * Generates the normalized display name for keyboard shortcuts. @@ -67,6 +68,9 @@ function getDisplayName(key: string, modifiers: string | string[]): string { if (key.toLowerCase() === keyInputRightArrow.toLowerCase()) { return ['ARROWRIGHT']; } + if (key === keyInputSpace) { + return ['SPACE']; + } return [key.toUpperCase()]; })(); diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 271a1c1df5c4..06093b88e306 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -17,6 +17,7 @@ import Section from '@components/Section'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useScrollEnabled from '@hooks/useScrollEnabled'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -39,7 +40,7 @@ function ProfilePage() { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {safeAreaPaddingBottomStyle} = useStyledSafeAreaInsets(); - + const scrollEnabled = useScrollEnabled(); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); @@ -156,6 +157,7 @@ function ProfilePage() {