From acac6ddf557e201384f2b76c6726a944ab9b2161 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 7 Oct 2025 17:05:19 -0700 Subject: [PATCH 01/20] fix: keyDownEvents stops propogation, specify default events for Pressable --- .../Components/Pressable/Pressable.js | 11 ++- .../Components/TextInput/TextInput.js | 70 ++++++++++++++++++- .../Libraries/Components/View/View.js | 39 +++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 4f2d7f7e36b083..985ed3d535074d 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -353,12 +353,11 @@ function Pressable( const accessibilityLabel = ariaLabel ?? props.accessibilityLabel; + const keyDownEvents = keyDownEvents ?? [{key: 'Space'}, {key: 'Enter'}]; + const restPropsWithDefaults: React.ElementConfig = { ...restProps, ...android_rippleConfig?.viewProps, - acceptsFirstMouse: acceptsFirstMouse !== false && !disabled, // [macOS] - mouseDownCanMoveWindow: false, // [macOS] - enableFocusRing: enableFocusRing !== false && !disabled, accessible: accessible !== false, accessibilityViewIsModal: restProps['aria-modal'] ?? restProps.accessibilityViewIsModal, @@ -368,6 +367,12 @@ function Pressable( focusable: focusable !== false && !disabled, // macOS] accessibilityValue, hitSlop, + // [macOS + acceptsFirstMouse: acceptsFirstMouse !== false && !disabled, + enableFocusRing: enableFocusRing !== false && !disabled, + keyDownEvents, + mouseDownCanMoveWindow: false, + // macOS] }; const config = useMemo( diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 71081918eebc32..41c6887ca182ce 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -12,6 +12,8 @@ import type {HostInstance} from '../../../src/private/types/HostInstance'; import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes'; import type { GestureResponderEvent, + KeyEvent, + HandledKeyEvent, NativeSyntheticEvent, ScrollEvent, } from '../../Types/CoreEventTypes'; @@ -1134,6 +1136,34 @@ export type TextInputProps = $ReadOnly<{ * unwanted edits without flicker. */ value?: ?Stringish, + + // [macOS + /** + * An array of key events that should be handled by the TextInput. + * When a key event matches one of these specifications, event propagation will be stopped. + * @platform macos + */ + keyDownEvents?: ?$ReadOnlyArray, + + /** + * An array of key events that should be handled by the TextInput. + * When a key event matches one of these specifications, event propagation will be stopped. + * @platform macos + */ + keyUpEvents?: ?$ReadOnlyArray, + + /** + * Callback that is called when a key is pressed down. + * @platform macos + */ + onKeyDown?: ?(e: KeyEvent) => mixed, + + /** + * Callback that is called when a key is released. + * @platform macos + */ + onKeyUp?: ?(e: KeyEvent) => mixed, + // macOS] }>; type ViewCommands = $NonMaybeType< @@ -1640,6 +1670,42 @@ function InternalTextInput(props: TextInputProps): React.Node { props.onScroll && props.onScroll(event); }; + const _keyDown = (event: KeyEvent) => { + if (props.keyDownEvents && event.isPropagationStopped() !== true) { + const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && + (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && + (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && + (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + ); + }); + if (isHandled) { + event.stopPropagation(); + } + } + props.onKeyDown && props.onKeyDown(event); + }; + + const _keyUp = (event: KeyEvent) => { + if (props.keyUpEvents && event.isPropagationStopped() !== true) { + const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && + (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && + (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && + (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + ); + }); + if (isHandled) { + event.stopPropagation(); + } + } + props.onKeyUp && props.onKeyUp(event); + }; + let textInput = null; const multiline = props.multiline ?? false; @@ -1795,8 +1861,8 @@ function InternalTextInput(props: TextInputProps): React.Node { onChange={_onChange} onContentSizeChange={props.onContentSizeChange} onFocus={_onFocus} - onKeyDown={props.onKeyDown} // [macOS] - onKeyUp={props.onKeyUp} // [macOS] + onKeyDown={_keyDown} // [macOS] + onKeyUp={_keyUp} // [macOS] onScroll={_onScroll} onSelectionChange={_onSelectionChange} onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue} diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 4f13e1ec49db7d..48ab76c170be44 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -13,6 +13,7 @@ import type {ViewProps} from './ViewPropTypes'; import TextAncestor from '../../Text/TextAncestor'; import ViewNativeComponent from './ViewNativeComponent'; import * as React from 'react'; +import type { KeyEvent, HandledKeyEvent } from '../../Types/CoreEventTypes'; export type Props = ViewProps; @@ -94,6 +95,42 @@ const View: component( }; } + const _keyDown = (event: KeyEvent) => { + if (otherProps.keyDownEvents && event.isPropagationStopped() !== true) { + const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && + (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && + (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && + (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + ); + }); + if (isHandled) { + event.stopPropagation(); + } + } + otherProps.onKeyDown && otherProps.onKeyDown(event); + }; + + const _keyUp = (event: KeyEvent) => { + if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) { + const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && + (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && + (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && + (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + ); + }); + if (isHandled) { + event.stopPropagation(); + } + } + otherProps.onKeyUp && otherProps.onKeyUp(event); + }; + const actualView = ( ); From 16b501f3bc6a4f333a3534ee0ca1ac4e8eebbe65 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 7 Oct 2025 17:10:13 -0700 Subject: [PATCH 02/20] tags --- .../Libraries/Components/TextInput/TextInput.js | 14 ++++++++------ .../react-native/Libraries/Components/View/View.js | 12 +++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 41c6887ca182ce..92155c865f74c0 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -12,8 +12,8 @@ import type {HostInstance} from '../../../src/private/types/HostInstance'; import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes'; import type { GestureResponderEvent, - KeyEvent, - HandledKeyEvent, + KeyEvent, // [macOS] + HandledKeyEvent, // [macOS] NativeSyntheticEvent, ScrollEvent, } from '../../Types/CoreEventTypes'; @@ -1670,7 +1670,8 @@ function InternalTextInput(props: TextInputProps): React.Node { props.onScroll && props.onScroll(event); }; - const _keyDown = (event: KeyEvent) => { + // [macOS + const _onKeyDown = (event: KeyEvent) => { if (props.keyDownEvents && event.isPropagationStopped() !== true) { const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( @@ -1688,7 +1689,7 @@ function InternalTextInput(props: TextInputProps): React.Node { props.onKeyDown && props.onKeyDown(event); }; - const _keyUp = (event: KeyEvent) => { + const _onKeyUp = (event: KeyEvent) => { if (props.keyUpEvents && event.isPropagationStopped() !== true) { const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( @@ -1705,6 +1706,7 @@ function InternalTextInput(props: TextInputProps): React.Node { } props.onKeyUp && props.onKeyUp(event); }; + // macOS] let textInput = null; @@ -1861,8 +1863,8 @@ function InternalTextInput(props: TextInputProps): React.Node { onChange={_onChange} onContentSizeChange={props.onContentSizeChange} onFocus={_onFocus} - onKeyDown={_keyDown} // [macOS] - onKeyUp={_keyUp} // [macOS] + onKeyDown={_onKeyDown} // [macOS] + onKeyUp={_onKeyUp} // [macOS] onScroll={_onScroll} onSelectionChange={_onSelectionChange} onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue} diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 48ab76c170be44..ac2dbe9c1a8662 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -13,7 +13,7 @@ import type {ViewProps} from './ViewPropTypes'; import TextAncestor from '../../Text/TextAncestor'; import ViewNativeComponent from './ViewNativeComponent'; import * as React from 'react'; -import type { KeyEvent, HandledKeyEvent } from '../../Types/CoreEventTypes'; +import type { KeyEvent, HandledKeyEvent } from '../../Types/CoreEventTypes'; // [macOS] export type Props = ViewProps; @@ -95,7 +95,8 @@ const View: component( }; } - const _keyDown = (event: KeyEvent) => { + // [macOS + const _onKeyDown = (event: KeyEvent) => { if (otherProps.keyDownEvents && event.isPropagationStopped() !== true) { const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( @@ -113,7 +114,7 @@ const View: component( otherProps.onKeyDown && otherProps.onKeyDown(event); }; - const _keyUp = (event: KeyEvent) => { + const _onKeyUp = (event: KeyEvent) => { if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) { const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( @@ -130,6 +131,7 @@ const View: component( } otherProps.onKeyUp && otherProps.onKeyUp(event); }; + // macOS] const actualView = ( ); From fd7eea2e263ff01e6a5ad04597918a042f8f6803 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 7 Oct 2025 17:24:07 -0700 Subject: [PATCH 03/20] fix space --- .../react-native/Libraries/Components/Pressable/Pressable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 985ed3d535074d..f8ae55fe436c18 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -353,7 +353,7 @@ function Pressable( const accessibilityLabel = ariaLabel ?? props.accessibilityLabel; - const keyDownEvents = keyDownEvents ?? [{key: 'Space'}, {key: 'Enter'}]; + const _keyDownEvents = keyDownEvents ?? [{key: ' '}, {key: 'Enter'}]; const restPropsWithDefaults: React.ElementConfig = { ...restProps, @@ -370,7 +370,7 @@ function Pressable( // [macOS acceptsFirstMouse: acceptsFirstMouse !== false && !disabled, enableFocusRing: enableFocusRing !== false && !disabled, - keyDownEvents, + keyDownEvents: _keyDownEvents, mouseDownCanMoveWindow: false, // macOS] }; From 5b485b883b9545b5e66ff2451971b5d8e7a9dc1d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:43:28 -0700 Subject: [PATCH 04/20] Update TextInput.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../react-native/Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 92155c865f74c0..3882f665ecd196 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1672,7 +1672,7 @@ function InternalTextInput(props: TextInputProps): React.Node { // [macOS const _onKeyDown = (event: KeyEvent) => { - if (props.keyDownEvents && event.isPropagationStopped() !== true) { + if (props.keyDownEvents && !event.isPropagationStopped()) { const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && From 630d8a46b37d471a167fe38d1d1300135302eb94 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:43:39 -0700 Subject: [PATCH 05/20] Update TextInput.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../react-native/Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 3882f665ecd196..420c00b22e6966 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1686,7 +1686,7 @@ function InternalTextInput(props: TextInputProps): React.Node { event.stopPropagation(); } } - props.onKeyDown && props.onKeyDown(event); + props.onKeyDown?.(event); }; const _onKeyUp = (event: KeyEvent) => { From b0cdbe0ca56279e3f7824aba5a3f2b4fbeebb229 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:43:49 -0700 Subject: [PATCH 06/20] Update TextInput.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../react-native/Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 420c00b22e6966..7d155c998621f9 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1690,7 +1690,7 @@ function InternalTextInput(props: TextInputProps): React.Node { }; const _onKeyUp = (event: KeyEvent) => { - if (props.keyUpEvents && event.isPropagationStopped() !== true) { + if (props.keyUpEvents && !event.isPropagationStopped()) { const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && From e65780a5701bd63b59d4c4ae0db69ce30f2249a1 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:43:57 -0700 Subject: [PATCH 07/20] Update TextInput.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../react-native/Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 7d155c998621f9..6517a2fd572996 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1704,7 +1704,7 @@ function InternalTextInput(props: TextInputProps): React.Node { event.stopPropagation(); } } - props.onKeyUp && props.onKeyUp(event); + props.onKeyUp?.(event); }; // macOS] From 50bc924002bc30c9aaa94f13d9266d52959ebfec Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:44:09 -0700 Subject: [PATCH 08/20] Update View.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- packages/react-native/Libraries/Components/View/View.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index ac2dbe9c1a8662..76a0de2e2c820e 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -97,7 +97,7 @@ const View: component( // [macOS const _onKeyDown = (event: KeyEvent) => { - if (otherProps.keyDownEvents && event.isPropagationStopped() !== true) { + if (otherProps.keyDownEvents && !event.isPropagationStopped()) { const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && From 4e2d56590312fc338b29d449f2773a910dca3b57 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:44:35 -0700 Subject: [PATCH 09/20] Update View.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- packages/react-native/Libraries/Components/View/View.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 76a0de2e2c820e..b98ec751307c83 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -115,7 +115,7 @@ const View: component( }; const _onKeyUp = (event: KeyEvent) => { - if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) { + if (otherProps.keyUpEvents && !event.isPropagationStopped()) { const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && From 50332dc3286b589852bec151352deeae56e48c1b Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:44:44 -0700 Subject: [PATCH 10/20] Update View.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- packages/react-native/Libraries/Components/View/View.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index b98ec751307c83..5416c6903de708 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -111,7 +111,7 @@ const View: component( event.stopPropagation(); } } - otherProps.onKeyDown && otherProps.onKeyDown(event); + otherProps.onKeyDown?.(event); }; const _onKeyUp = (event: KeyEvent) => { From a5bd962667f48c24162f097af69dbf3ea5c257d6 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 07:44:53 -0700 Subject: [PATCH 11/20] Update View.js Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- packages/react-native/Libraries/Components/View/View.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 5416c6903de708..31201c4cf406ff 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -129,7 +129,7 @@ const View: component( event.stopPropagation(); } } - otherProps.onKeyUp && otherProps.onKeyUp(event); + otherProps.onKeyUp?.(event); }; // macOS] From 3d6203f0a6423910ceb1b37fd731540eea0e2daa Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 09:23:09 -0700 Subject: [PATCH 12/20] Update Pressable.js --- .../react-native/Libraries/Components/Pressable/Pressable.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index f8ae55fe436c18..f9b9fb7db8c98c 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -308,7 +308,6 @@ function Pressable( onKeyDown, onKeyUp, keyDownEvents, - keyUpEvents, acceptsFirstMouse, mouseDownCanMoveWindow, enableFocusRing, @@ -353,8 +352,6 @@ function Pressable( const accessibilityLabel = ariaLabel ?? props.accessibilityLabel; - const _keyDownEvents = keyDownEvents ?? [{key: ' '}, {key: 'Enter'}]; - const restPropsWithDefaults: React.ElementConfig = { ...restProps, ...android_rippleConfig?.viewProps, @@ -370,7 +367,7 @@ function Pressable( // [macOS acceptsFirstMouse: acceptsFirstMouse !== false && !disabled, enableFocusRing: enableFocusRing !== false && !disabled, - keyDownEvents: _keyDownEvents, + keyDownEvents: keyDownEvents ?? [{key: ' '}, {key: 'Enter'}], mouseDownCanMoveWindow: false, // macOS] }; From 68dcc40551df6fc2ef8d3e4a97fe93833ef850b7 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 09:23:28 -0700 Subject: [PATCH 13/20] Add bubbling explainer --- .../KeyboardEventsExample/KeyboardEventsExample.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index a897f9d7fc04da..36c98a5daee20d 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -87,6 +87,20 @@ function BubblingExample(): React.Node { return ( + + + Event Bubbling Behavior: + + + • Pressable won't bubble Space or Enter keys + + + • Keys 'f' and 'g' won't bubble past Box 2 (handled by keyDownEvents) + + + • If "Stop Propagation" is enabled, no events bubble past 'f' and 'g' in JS + + Stop Propagation in Box 2: From d66681437495d9a11f6cdbdd8b5a6d72c1b81a2f Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 10:34:22 -0700 Subject: [PATCH 14/20] fix comparison --- .../Libraries/Components/TextInput/TextInput.js | 16 ++++++++-------- .../Libraries/Components/View/View.js | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 6517a2fd572996..cde712f9fc6b98 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1676,10 +1676,10 @@ function InternalTextInput(props: TextInputProps): React.Node { const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && - (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && - (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && - (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + (metaKey ?? false) === event.nativeEvent.metaKey && + (ctrlKey ?? false) === event.nativeEvent.ctrlKey && + (altKey ?? false) === event.nativeEvent.altKey && + (shiftKey ?? false) === event.nativeEvent.shiftKey ); }); if (isHandled) { @@ -1694,10 +1694,10 @@ function InternalTextInput(props: TextInputProps): React.Node { const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && - (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && - (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && - (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + (metaKey ?? false) === event.nativeEvent.metaKey && + (ctrlKey ?? false) === event.nativeEvent.ctrlKey && + (altKey ?? false) === event.nativeEvent.altKey && + (shiftKey ?? false) === event.nativeEvent.shiftKey ); }); if (isHandled) { diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 31201c4cf406ff..7e25cd9fa93efb 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -101,10 +101,10 @@ const View: component( const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && - (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && - (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && - (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + (metaKey ?? false) === event.nativeEvent.metaKey && + (ctrlKey ?? false) === event.nativeEvent.ctrlKey && + (altKey ?? false) === event.nativeEvent.altKey && + (shiftKey ?? false) === event.nativeEvent.shiftKey ); }); if (isHandled) { @@ -119,10 +119,10 @@ const View: component( const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? event.nativeEvent.metaKey) === event.nativeEvent.metaKey && - (ctrlKey ?? event.nativeEvent.ctrlKey) === event.nativeEvent.ctrlKey && - (altKey ?? event.nativeEvent.altKey) === event.nativeEvent.altKey && - (shiftKey ?? event.nativeEvent.shiftKey) === event.nativeEvent.shiftKey + (metaKey ?? false) === event.nativeEvent.metaKey && + (ctrlKey ?? false) === event.nativeEvent.ctrlKey && + (altKey ?? false) === event.nativeEvent.altKey && + (shiftKey ?? false) === event.nativeEvent.shiftKey ); }); if (isHandled) { From 7d9bebcc5df62d6b599b976eb284923ae39b31ab Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 12:07:09 -0700 Subject: [PATCH 15/20] Update example more --- .../KeyboardEventsExample.js | 134 +++++++++--------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index 36c98a5daee20d..f6db868e22fbf6 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -1,3 +1,5 @@ +// @flow + /** * Copyright (c) 2015-present, Facebook, Inc. * @@ -10,38 +12,49 @@ 'use strict'; // [macOS] -import type {KeyEvent} from 'react-native/Libraries/Types/CoreEventTypes'; - import * as React from 'react'; -import { - Pressable, - StyleSheet, - Switch, - Text, - TextInput, - View, -} from 'react-native'; +import type { + HandledKeyEvent, + KeyEvent, +} from 'react-native/Libraries/Types/CoreEventTypes'; -function formatKeyEvent(event: any) { +import {Pressable, StyleSheet, Text, TextInput, View} from 'react-native'; + +function formatKeyEvent(event: KeyEvent) { const modifiers = []; - if (event.ctrlKey) { + if (event.nativeEvent.ctrlKey) { modifiers.push('Ctrl'); } - if (event.altKey) { + if (event.nativeEvent.altKey) { modifiers.push('Alt'); } - if (event.shiftKey) { + if (event.nativeEvent.shiftKey) { modifiers.push('Shift'); } - if (event.metaKey) { + if (event.nativeEvent.metaKey) { modifiers.push('Meta'); } const modifierPrefix = modifiers.length > 0 ? `${modifiers.join('+')}+` : ''; - return `${modifierPrefix}${event.key}`; + return `${modifierPrefix}${event.nativeEvent.key}`; +} + +function isKeyBlocked( + event: KeyEvent, + keyEvents: Array, +) { + return keyEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}) => ( + event.nativeEvent.key === key && + (metaKey ?? false) === event.nativeEvent.metaKey && + (ctrlKey ?? false) === event.nativeEvent.ctrlKey && + (altKey ?? false) === event.nativeEvent.altKey && + (shiftKey ?? false) === event.nativeEvent.shiftKey + )); } +const BOX2_KEY_DOWN_EVENTS = [{key: 'f'}, {key: 'g'}]; + function EventLog({ eventLog, onClear, @@ -74,8 +87,6 @@ function EventLog({ function BubblingExample(): React.Node { const ref = React.useRef | null>(null); const [eventLog, setEventLog] = React.useState>([]); - const [stopPropagationEnabled, setStopPropagationEnabled] = - React.useState(false); function appendEvent(eventName: string, source?: string) { const limit = 12; @@ -92,55 +103,45 @@ function BubblingExample(): React.Node { Event Bubbling Behavior: - • Pressable won't bubble Space or Enter keys + • Pressable won't bubble Space or Enter keys, it handles those by default • Keys 'f' and 'g' won't bubble past Box 2 (handled by keyDownEvents) - • If "Stop Propagation" is enabled, no events bubble past 'f' and 'g' in JS + • Shift+f and Shift+g will bubble - - Stop Propagation in Box 2: - setStopPropagationEnabled(value)} - /> - { - const keyDisplay = formatKeyEvent(ev.nativeEvent); + onKeyDown={event => { + const keyDisplay = formatKeyEvent(event); appendEvent(`keyDown: ${keyDisplay}`, 'Box 3'); }}> + Box 3 { - const blockedKeys = ['f', 'g']; - const isBlocked = blockedKeys.includes(ev.nativeEvent.key); + onKeyDown={event => { + const isBlocked = isKeyBlocked(event, BOX2_KEY_DOWN_EVENTS); const suffix = isBlocked ? ' (blocked)' : ''; - const keyDisplay = formatKeyEvent(ev.nativeEvent); + const keyDisplay = formatKeyEvent(event); appendEvent( `keyDown: ${keyDisplay}${suffix}`, 'Box 2 keyDownEvents=[f,g]', ); - if (stopPropagationEnabled) { - ev.stopPropagation(); - appendEvent('stopPropagation called', 'Box 2'); - } }} - keyDownEvents={[{key: 'f'}, {key: 'g'}]}> + keyDownEvents={BOX2_KEY_DOWN_EVENTS}> + Box 2 (keyDownEvents: f, g) { - const keyDisplay = formatKeyEvent(ev.nativeEvent); + onKeyDown={event => { + const keyDisplay = formatKeyEvent(event); appendEvent(`keyDown: ${keyDisplay}`, 'Box 1'); }}> + Box 1 { ref.current?.focus(); }} - onKeyDown={ev => { - const keyDisplay = formatKeyEvent(ev.nativeEvent); + onKeyDown={event => { + const keyDisplay = formatKeyEvent(event); appendEvent(`keyDown: ${keyDisplay}`, 'Focusable'); if ( - ev.nativeEvent.key === 'k' && - ev.nativeEvent.metaKey === true + event.nativeEvent.key === 'k' && + event.nativeEvent.metaKey === true ) { appendEvent('Key command: Clear event log', 'Focusable'); setTimeout(() => { @@ -189,56 +190,44 @@ function KeyboardEventExample(): React.Node { }); } - function isKeyBlocked(event: any, keyEvents: Array) { - return keyEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}) => { - return ( - event.key === key && - (metaKey ?? event.metaKey) === event.metaKey && - (ctrlKey ?? event.ctrlKey) === event.ctrlKey && - (altKey ?? event.altKey) === event.altKey && - (shiftKey ?? event.shiftKey) === event.shiftKey - ); - }); - } - - const handleSingleLineKeyDown = React.useCallback((e: KeyEvent) => { + const handleSingleLineKeyDown = React.useCallback((event: KeyEvent) => { const keyDownEvents = [ {key: 'g'}, {key: 'Escape'}, {key: 'Enter'}, {key: 'ArrowLeft'}, ]; - const isBlocked = isKeyBlocked(e.nativeEvent, keyDownEvents); + const isBlocked = isKeyBlocked(event, keyDownEvents); const suffix = isBlocked ? ' (blocked)' : ''; - const keyDisplay = formatKeyEvent(e.nativeEvent); + const keyDisplay = formatKeyEvent(event); appendEvent(`keyDown: ${keyDisplay}${suffix}`, 'Single-line TextInput'); }, []); - const handleSingleLineKeyUp = React.useCallback((e: KeyEvent) => { + const handleSingleLineKeyUp = React.useCallback((event: KeyEvent) => { const keyUpEvents = [{key: 'c'}, {key: 'd'}]; - const isBlocked = isKeyBlocked(e.nativeEvent, keyUpEvents); + const isBlocked = isKeyBlocked(event, keyUpEvents); const suffix = isBlocked ? ' (blocked)' : ''; - const keyDisplay = formatKeyEvent(e.nativeEvent); + const keyDisplay = formatKeyEvent(event); appendEvent(`keyUp: ${keyDisplay}${suffix}`, 'Single-line TextInput'); }, []); - const handleMultiLineKeyDown = React.useCallback((e: KeyEvent) => { + const handleMultiLineKeyDown = React.useCallback((event: KeyEvent) => { const keyDownEvents = [ {key: 'ArrowRight'}, {key: 'ArrowDown'}, {key: 'Enter', metaKey: true}, ]; - const isBlocked = isKeyBlocked(e.nativeEvent, keyDownEvents); + const isBlocked = isKeyBlocked(event, keyDownEvents); const suffix = isBlocked ? ' (blocked)' : ''; - const keyDisplay = formatKeyEvent(e.nativeEvent); + const keyDisplay = formatKeyEvent(event); appendEvent(`keyDown: ${keyDisplay}${suffix}`, 'Multi-line TextInput'); }, []); - const handleMultiLineKeyUp = React.useCallback((e: KeyEvent) => { + const handleMultiLineKeyUp = React.useCallback((event: KeyEvent) => { const keyUpEvents = [{key: 'Escape'}, {key: 'Enter'}]; - const isBlocked = isKeyBlocked(e.nativeEvent, keyUpEvents); + const isBlocked = isKeyBlocked(event, keyUpEvents); const suffix = isBlocked ? ' (blocked)' : ''; - const keyDisplay = formatKeyEvent(e.nativeEvent); + const keyDisplay = formatKeyEvent(event); appendEvent(`keyUp: ${keyDisplay}${suffix}`, 'Multi-line TextInput'); }, []); @@ -350,6 +339,13 @@ const styles = StyleSheet.create({ borderRadius: 6, backgroundColor: '#f6fff6', }, + boxLabel: { + fontSize: 12, + fontWeight: '600', + color: '#8b5e3c', + marginHorizontal: 4, + marginTop: 4, + }, row: { flexDirection: 'row', marginVertical: 10, From 06fe614f16181db4581f7a29d3632431ef4c1add Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 12:11:45 -0700 Subject: [PATCH 16/20] More updates --- .../KeyboardEventsExample/KeyboardEventsExample.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index f6db868e22fbf6..1ff591f9b8c2a8 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -46,10 +46,10 @@ function isKeyBlocked( ) { return keyEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}) => ( event.nativeEvent.key === key && - (metaKey ?? false) === event.nativeEvent.metaKey && - (ctrlKey ?? false) === event.nativeEvent.ctrlKey && - (altKey ?? false) === event.nativeEvent.altKey && - (shiftKey ?? false) === event.nativeEvent.shiftKey + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey )); } @@ -109,7 +109,7 @@ function BubblingExample(): React.Node { • Keys 'f' and 'g' won't bubble past Box 2 (handled by keyDownEvents) - • Shift+f and Shift+g will bubble + • Ctrl+f and Ctrl+g will bubble Date: Wed, 8 Oct 2025 12:11:58 -0700 Subject: [PATCH 17/20] use Boolean more --- .../Libraries/Components/TextInput/TextInput.js | 16 ++++++++-------- .../Libraries/Components/View/View.js | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index cde712f9fc6b98..902f6ff6528afe 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1676,10 +1676,10 @@ function InternalTextInput(props: TextInputProps): React.Node { const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? false) === event.nativeEvent.metaKey && - (ctrlKey ?? false) === event.nativeEvent.ctrlKey && - (altKey ?? false) === event.nativeEvent.altKey && - (shiftKey ?? false) === event.nativeEvent.shiftKey + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); if (isHandled) { @@ -1694,10 +1694,10 @@ function InternalTextInput(props: TextInputProps): React.Node { const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? false) === event.nativeEvent.metaKey && - (ctrlKey ?? false) === event.nativeEvent.ctrlKey && - (altKey ?? false) === event.nativeEvent.altKey && - (shiftKey ?? false) === event.nativeEvent.shiftKey + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); if (isHandled) { diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 7e25cd9fa93efb..d43fe13c322859 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -101,10 +101,10 @@ const View: component( const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? false) === event.nativeEvent.metaKey && - (ctrlKey ?? false) === event.nativeEvent.ctrlKey && - (altKey ?? false) === event.nativeEvent.altKey && - (shiftKey ?? false) === event.nativeEvent.shiftKey + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); if (isHandled) { @@ -119,10 +119,10 @@ const View: component( const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && - (metaKey ?? false) === event.nativeEvent.metaKey && - (ctrlKey ?? false) === event.nativeEvent.ctrlKey && - (altKey ?? false) === event.nativeEvent.altKey && - (shiftKey ?? false) === event.nativeEvent.shiftKey + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); if (isHandled) { From 805e87f96559886da57f87878e72c203457e4c53 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 13:25:15 -0700 Subject: [PATCH 18/20] Only pass in modified onKeyDown and onKeyUp if specified. --- .../__snapshots__/Pressable-test.js.snap | 100 ++++++++++++++++++ .../Components/TextInput/TextInput.js | 4 +- .../Libraries/Components/View/View.js | 4 +- .../__snapshots__/public-api-test.js.snap | 4 + 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap b/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap index 09d5b17ea73d5f..7e55856057ecf7 100644 --- a/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap +++ b/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap @@ -24,6 +24,16 @@ exports[` should render as expected: should deep render when mocked collapsable={false} enableFocusRing={true} focusable={true} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -63,6 +73,16 @@ exports[` should render as expected: should deep render when not mo collapsable={false} enableFocusRing={true} focusable={true} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -102,6 +122,16 @@ exports[` should be disabled when disabled is true: collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -141,6 +171,16 @@ exports[` should be disabled when disabled is true: collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -180,6 +220,16 @@ exports[` should be disable collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -219,6 +269,16 @@ exports[` should be disable collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -258,6 +318,16 @@ exports[` shou collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -297,6 +367,16 @@ exports[` shou collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -336,6 +416,16 @@ exports[` sh collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} @@ -375,6 +465,16 @@ exports[` sh collapsable={false} enableFocusRing={false} focusable={false} + keyDownEvents={ + Array [ + Object { + "key": " ", + }, + Object { + "key": "Enter", + }, + ] + } mouseDownCanMoveWindow={false} onBlur={[Function]} onClick={[Function]} diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 902f6ff6528afe..f700880a080d63 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1863,8 +1863,8 @@ function InternalTextInput(props: TextInputProps): React.Node { onChange={_onChange} onContentSizeChange={props.onContentSizeChange} onFocus={_onFocus} - onKeyDown={_onKeyDown} // [macOS] - onKeyUp={_onKeyUp} // [macOS] + {...(otherProps.onKeyDown && {onKeyDown: _onKeyDown})} // [macOS] + {...(otherProps.onKeyUp && {onKeyUp: _onKeyUp})} // [macOS] onScroll={_onScroll} onSelectionChange={_onSelectionChange} onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue} diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index d43fe13c322859..71b929e4c4133e 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -151,8 +151,8 @@ const View: component( : importantForAccessibility } nativeID={id ?? nativeID} - onKeyDown={_onKeyDown} // [macOS] - onKeyUp={_onKeyUp} // [macOS] + {...(otherProps.onKeyDown && {onKeyDown: _onKeyDown})} // [macOS] + {...(otherProps.onKeyUp && {onKeyUp: _onKeyUp})} // [macOS] ref={forwardedRef} /> ); diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index c00e8ab99622f3..880b2f3d7a3b6c 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -3429,6 +3429,10 @@ export type TextInputProps = $ReadOnly<{ submitBehavior?: ?SubmitBehavior, style?: ?TextStyleProp, value?: ?Stringish, + keyDownEvents?: ?$ReadOnlyArray, + keyUpEvents?: ?$ReadOnlyArray, + onKeyDown?: ?(e: KeyEvent) => mixed, + onKeyUp?: ?(e: KeyEvent) => mixed, }>; export type TextInputComponentStatics = $ReadOnly<{ State: $ReadOnly<{ From 844ff67a82ef228f5bef260052f4ef562ac6495f Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 14:09:13 -0700 Subject: [PATCH 19/20] fix flow errors --- .../Libraries/Components/TextInput/TextInput.js | 15 +++++++++------ .../Libraries/Components/View/View.js | 15 +++++++++------ .../KeyboardEventsExample.js | 2 -- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index f700880a080d63..fde90ff10d8154 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1672,8 +1672,9 @@ function InternalTextInput(props: TextInputProps): React.Node { // [macOS const _onKeyDown = (event: KeyEvent) => { - if (props.keyDownEvents && !event.isPropagationStopped()) { - const isHandled = props.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + const keyDownEvents = props.keyDownEvents; + if (keyDownEvents != null && !event.isPropagationStopped()) { + const isHandled = keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && Boolean(metaKey) === event.nativeEvent.metaKey && @@ -1682,7 +1683,7 @@ function InternalTextInput(props: TextInputProps): React.Node { Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); - if (isHandled) { + if (isHandled === true) { event.stopPropagation(); } } @@ -1690,8 +1691,9 @@ function InternalTextInput(props: TextInputProps): React.Node { }; const _onKeyUp = (event: KeyEvent) => { - if (props.keyUpEvents && !event.isPropagationStopped()) { - const isHandled = props.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + const keyUpEvents = props.keyUpEvents; + if (keyUpEvents != null && !event.isPropagationStopped()) { + const isHandled = keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && Boolean(metaKey) === event.nativeEvent.metaKey && @@ -1700,7 +1702,7 @@ function InternalTextInput(props: TextInputProps): React.Node { Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); - if (isHandled) { + if (isHandled === true) { event.stopPropagation(); } } @@ -1863,6 +1865,7 @@ function InternalTextInput(props: TextInputProps): React.Node { onChange={_onChange} onContentSizeChange={props.onContentSizeChange} onFocus={_onFocus} + // $FlowFixMe[exponential-spread] {...(otherProps.onKeyDown && {onKeyDown: _onKeyDown})} // [macOS] {...(otherProps.onKeyUp && {onKeyUp: _onKeyUp})} // [macOS] onScroll={_onScroll} diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index 71b929e4c4133e..ad629a35a7c113 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -97,8 +97,9 @@ const View: component( // [macOS const _onKeyDown = (event: KeyEvent) => { - if (otherProps.keyDownEvents && !event.isPropagationStopped()) { - const isHandled = otherProps.keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + const keyDownEvents = otherProps.keyDownEvents; + if (keyDownEvents != null && !event.isPropagationStopped()) { + const isHandled = keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && Boolean(metaKey) === event.nativeEvent.metaKey && @@ -107,7 +108,7 @@ const View: component( Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); - if (isHandled) { + if (isHandled === true) { event.stopPropagation(); } } @@ -115,8 +116,9 @@ const View: component( }; const _onKeyUp = (event: KeyEvent) => { - if (otherProps.keyUpEvents && !event.isPropagationStopped()) { - const isHandled = otherProps.keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + const keyUpEvents = otherProps.keyUpEvents; + if (keyUpEvents != null && !event.isPropagationStopped()) { + const isHandled = keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { return ( event.nativeEvent.key === key && Boolean(metaKey) === event.nativeEvent.metaKey && @@ -125,7 +127,7 @@ const View: component( Boolean(shiftKey) === event.nativeEvent.shiftKey ); }); - if (isHandled) { + if (isHandled === true) { event.stopPropagation(); } } @@ -151,6 +153,7 @@ const View: component( : importantForAccessibility } nativeID={id ?? nativeID} + // $FlowFixMe[exponential-spread] {...(otherProps.onKeyDown && {onKeyDown: _onKeyDown})} // [macOS] {...(otherProps.onKeyUp && {onKeyUp: _onKeyUp})} // [macOS] ref={forwardedRef} diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index 1ff591f9b8c2a8..e0227d95e7bdc8 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -1,5 +1,3 @@ -// @flow - /** * Copyright (c) 2015-present, Facebook, Inc. * From a8cf49e2d08167227c731853c4207dd1d166a07a Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Oct 2025 14:24:13 -0700 Subject: [PATCH 20/20] prettier fix --- .../Components/TextInput/TextInput.js | 40 ++++++++++-------- .../Libraries/Components/View/View.js | 42 ++++++++++--------- .../KeyboardEventsExample.js | 41 +++++++++++------- 3 files changed, 72 insertions(+), 51 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index fde90ff10d8154..c1cc2df1480621 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1674,15 +1674,17 @@ function InternalTextInput(props: TextInputProps): React.Node { const _onKeyDown = (event: KeyEvent) => { const keyDownEvents = props.keyDownEvents; if (keyDownEvents != null && !event.isPropagationStopped()) { - const isHandled = keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { - return ( - event.nativeEvent.key === key && - Boolean(metaKey) === event.nativeEvent.metaKey && - Boolean(ctrlKey) === event.nativeEvent.ctrlKey && - Boolean(altKey) === event.nativeEvent.altKey && - Boolean(shiftKey) === event.nativeEvent.shiftKey - ); - }); + const isHandled = keyDownEvents.some( + ({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey + ); + }, + ); if (isHandled === true) { event.stopPropagation(); } @@ -1693,15 +1695,17 @@ function InternalTextInput(props: TextInputProps): React.Node { const _onKeyUp = (event: KeyEvent) => { const keyUpEvents = props.keyUpEvents; if (keyUpEvents != null && !event.isPropagationStopped()) { - const isHandled = keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { - return ( - event.nativeEvent.key === key && - Boolean(metaKey) === event.nativeEvent.metaKey && - Boolean(ctrlKey) === event.nativeEvent.ctrlKey && - Boolean(altKey) === event.nativeEvent.altKey && - Boolean(shiftKey) === event.nativeEvent.shiftKey - ); - }); + const isHandled = keyUpEvents.some( + ({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey + ); + }, + ); if (isHandled === true) { event.stopPropagation(); } diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index ad629a35a7c113..0b898205e26f68 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -13,7 +13,7 @@ import type {ViewProps} from './ViewPropTypes'; import TextAncestor from '../../Text/TextAncestor'; import ViewNativeComponent from './ViewNativeComponent'; import * as React from 'react'; -import type { KeyEvent, HandledKeyEvent } from '../../Types/CoreEventTypes'; // [macOS] +import type {KeyEvent, HandledKeyEvent} from '../../Types/CoreEventTypes'; // [macOS] export type Props = ViewProps; @@ -99,15 +99,17 @@ const View: component( const _onKeyDown = (event: KeyEvent) => { const keyDownEvents = otherProps.keyDownEvents; if (keyDownEvents != null && !event.isPropagationStopped()) { - const isHandled = keyDownEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { - return ( - event.nativeEvent.key === key && - Boolean(metaKey) === event.nativeEvent.metaKey && - Boolean(ctrlKey) === event.nativeEvent.ctrlKey && - Boolean(altKey) === event.nativeEvent.altKey && - Boolean(shiftKey) === event.nativeEvent.shiftKey - ); - }); + const isHandled = keyDownEvents.some( + ({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey + ); + }, + ); if (isHandled === true) { event.stopPropagation(); } @@ -118,15 +120,17 @@ const View: component( const _onKeyUp = (event: KeyEvent) => { const keyUpEvents = otherProps.keyUpEvents; if (keyUpEvents != null && !event.isPropagationStopped()) { - const isHandled = keyUpEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { - return ( - event.nativeEvent.key === key && - Boolean(metaKey) === event.nativeEvent.metaKey && - Boolean(ctrlKey) === event.nativeEvent.ctrlKey && - Boolean(altKey) === event.nativeEvent.altKey && - Boolean(shiftKey) === event.nativeEvent.shiftKey - ); - }); + const isHandled = keyUpEvents.some( + ({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => { + return ( + event.nativeEvent.key === key && + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey + ); + }, + ); if (isHandled === true) { event.stopPropagation(); } diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index e0227d95e7bdc8..1ae94b84e055c8 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -38,17 +38,15 @@ function formatKeyEvent(event: KeyEvent) { return `${modifierPrefix}${event.nativeEvent.key}`; } -function isKeyBlocked( - event: KeyEvent, - keyEvents: Array, -) { - return keyEvents.some(({key, metaKey, ctrlKey, altKey, shiftKey}) => ( - event.nativeEvent.key === key && - Boolean(metaKey) === event.nativeEvent.metaKey && - Boolean(ctrlKey) === event.nativeEvent.ctrlKey && - Boolean(altKey) === event.nativeEvent.altKey && - Boolean(shiftKey) === event.nativeEvent.shiftKey - )); +function isKeyBlocked(event: KeyEvent, keyEvents: Array) { + return keyEvents.some( + ({key, metaKey, ctrlKey, altKey, shiftKey}) => + event.nativeEvent.key === key && + Boolean(metaKey) === event.nativeEvent.metaKey && + Boolean(ctrlKey) === event.nativeEvent.ctrlKey && + Boolean(altKey) === event.nativeEvent.altKey && + Boolean(shiftKey) === event.nativeEvent.shiftKey, + ); } const BOX2_KEY_DOWN_EVENTS = [{key: 'f'}, {key: 'g'}]; @@ -96,12 +94,27 @@ function BubblingExample(): React.Node { return ( - - + + Event Bubbling Behavior: - • Pressable won't bubble Space or Enter keys, it handles those by default + • Pressable won't bubble Space or Enter keys, it handles those by + default • Keys 'f' and 'g' won't bubble past Box 2 (handled by keyDownEvents)