diff --git a/src/components/Pressable/GenericPressable/types.ts b/src/components/Pressable/GenericPressable/types.ts index 35616cb600a..cfe35641bff 100644 --- a/src/components/Pressable/GenericPressable/types.ts +++ b/src/components/Pressable/GenericPressable/types.ts @@ -1,17 +1,11 @@ import {ElementRef, RefObject} from 'react'; import {GestureResponderEvent, HostComponent, PressableStateCallbackType, PressableProps as RNPressableProps, StyleProp, ViewStyle} from 'react-native'; import {ValueOf} from 'type-fest'; +import {Shortcut} from '@libs/KeyboardShortcut'; import CONST from '@src/CONST'; type StylePropWithFunction = StyleProp | ((state: PressableStateCallbackType) => StyleProp); -type Shortcut = { - displayName: string; - shortcutKey: string; - descriptionKey: string; - modifiers: string[]; -}; - type RequiredAccessibilityLabel = | { /** diff --git a/src/hooks/useKeyboardShortcut.js b/src/hooks/useKeyboardShortcut.js deleted file mode 100644 index 5427fc6a654..00000000000 --- a/src/hooks/useKeyboardShortcut.js +++ /dev/null @@ -1,56 +0,0 @@ -import {useEffect} from 'react'; -import KeyboardShortcut from '@libs/KeyboardShortcut'; -import CONST from '@src/CONST'; - -/** - * Register a keyboard shortcut handler. - * Recommendation: To ensure stability, wrap the `callback` function with the useCallback hook before using it with this hook. - * - * @param {Object} shortcut - * @param {Function} callback - * @param {Object} [config] - */ -export default function useKeyboardShortcut(shortcut, callback, config = {}) { - const { - captureOnInputs = true, - shouldBubble = false, - priority = 0, - shouldPreventDefault = true, - - // The "excludedNodes" array needs to be stable to prevent the "useEffect" hook from being recreated unnecessarily. - // Hence the use of CONST.EMPTY_ARRAY. - excludedNodes = CONST.EMPTY_ARRAY, - isActive = true, - shouldStopPropagation = false, - } = config; - - useEffect(() => { - if (isActive) { - return KeyboardShortcut.subscribe( - shortcut.shortcutKey, - callback, - shortcut.descriptionKey, - shortcut.modifiers, - captureOnInputs, - shouldBubble, - priority, - shouldPreventDefault, - excludedNodes, - shouldStopPropagation, - ); - } - return () => {}; - }, [ - isActive, - callback, - captureOnInputs, - excludedNodes, - priority, - shortcut.descriptionKey, - shortcut.modifiers, - shortcut.shortcutKey, - shouldBubble, - shouldPreventDefault, - shouldStopPropagation, - ]); -} diff --git a/src/hooks/useKeyboardShortcut.ts b/src/hooks/useKeyboardShortcut.ts new file mode 100644 index 00000000000..e4a7a16f4cf --- /dev/null +++ b/src/hooks/useKeyboardShortcut.ts @@ -0,0 +1,61 @@ +import {useEffect} from 'react'; +import {ValueOf} from 'type-fest'; +import KeyboardShortcut from '@libs/KeyboardShortcut'; +import CONST from '@src/CONST'; + +type Shortcut = ValueOf; +type KeyboardShortcutConfig = { + /* Should we capture the event on inputs too? */ + captureOnInputs?: boolean; + /* Should we bubble the event? */ + shouldBubble?: boolean; + /* The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. */ + priority?: number; + /* Should call event.preventDefault after callback? */ + shouldPreventDefault?: boolean; + /* Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) */ + excludedNodes?: string[]; + /* Is keyboard shortcut is already active */ + isActive?: boolean; +}; + +/** + * Register a keyboard shortcut handler. + * Recommendation: To ensure stability, wrap the `callback` function with the useCallback hook before using it with this hook. + */ +export default function useKeyboardShortcut(shortcut: Shortcut, callback: () => void, config: KeyboardShortcutConfig | Record = {}) { + const { + captureOnInputs = true, + shouldBubble = false, + priority = 0, + shouldPreventDefault = true, + + // The "excludedNodes" array needs to be stable to prevent the "useEffect" hook from being recreated unnecessarily. + // Hence the use of CONST.EMPTY_ARRAY. + excludedNodes = CONST.EMPTY_ARRAY, + isActive = true, + } = config; + + useEffect(() => { + if (!isActive) { + return () => {}; + } + + const unsubscribe = KeyboardShortcut.subscribe( + shortcut.shortcutKey, + callback, + shortcut.descriptionKey ?? '', + shortcut.modifiers, + captureOnInputs, + shouldBubble, + priority, + shouldPreventDefault, + excludedNodes as string[], + ); + + return () => { + unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isActive, callback, captureOnInputs, excludedNodes, priority, shortcut.descriptionKey, shortcut.modifiers.join(), shortcut.shortcutKey, shouldBubble, shouldPreventDefault]); +} diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts index 1b684a7ab19..44ba54953c4 100644 --- a/src/libs/KeyboardShortcut/index.ts +++ b/src/libs/KeyboardShortcut/index.ts @@ -19,11 +19,13 @@ type EventHandler = { // Handlers for the various keyboard listeners we set up const eventHandlers: Record = {}; +type ShortcutModifiers = readonly ['CTRL'] | readonly ['CTRL', 'SHIFT'] | readonly []; + type Shortcut = { displayName: string; shortcutKey: string; descriptionKey: string; - modifiers: string[]; + modifiers: ShortcutModifiers; }; // Documentation information for keyboard shortcuts that are displayed in the keyboard shortcuts informational modal @@ -102,13 +104,13 @@ function unsubscribe(displayName: string, callbackID: string) { /** * Return platform specific modifiers for keys like Control (CMD on macOS) */ -function getPlatformEquivalentForKeys(keys: string[]): string[] { +function getPlatformEquivalentForKeys(keys: ShortcutModifiers): string[] { return keys.map((key) => { if (!(key in CONST.PLATFORM_SPECIFIC_KEYS)) { return key; } - const platformModifiers = CONST.PLATFORM_SPECIFIC_KEYS[key as keyof typeof CONST.PLATFORM_SPECIFIC_KEYS]; + const platformModifiers = CONST.PLATFORM_SPECIFIC_KEYS[key]; return platformModifiers?.[operatingSystem as keyof typeof platformModifiers] ?? platformModifiers.DEFAULT ?? key; }); } @@ -130,12 +132,12 @@ function subscribe( key: string, callback: (event?: KeyboardEvent) => void, descriptionKey: string, - modifiers: string[] = ['shift'], + modifiers: ShortcutModifiers = ['CTRL'], captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, - excludedNodes = [], + excludedNodes: string[] = [], shouldStopPropagation = false, ) { const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); @@ -190,4 +192,4 @@ const KeyboardShortcut = { }; export default KeyboardShortcut; -export type {EventHandler}; +export type {EventHandler, Shortcut};