Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,11 @@ function Pressable({
// [macOS
acceptsFirstMouse: acceptsFirstMouse !== false && !disabled,
enableFocusRing: enableFocusRing !== false && !disabled,
keyDownEvents: keyDownEvents ?? [{key: ' '}, {key: 'Enter'}],
keyDownEvents:
keyDownEvents ??
(((props: any).validKeysDown: mixed) == null
? [{key: ' '}, {key: 'Enter'}]
: undefined),
mouseDownCanMoveWindow: false,
// macOS]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
captured: 'onSubmitEditingCapture',
},
},
topKeyDown: {
phasedRegistrationNames: {
bubbled: 'onKeyDown',
captured: 'onKeyDownCapture',
},
},
topKeyUp: {
phasedRegistrationNames: {
bubbled: 'onKeyUp',
captured: 'onKeyUpCapture',
},
},
topTouchCancel: {
phasedRegistrationNames: {
bubbled: 'onTouchCancel',
Expand Down Expand Up @@ -173,6 +185,8 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
clearTextOnSubmit: true,
grammarCheck: true,
hideVerticalScrollIndicator: true,
keyDownEvents: true,
keyUpEvents: true,
pastedTypes: true,
submitKeyEvents: true,
tooltip: true,
Expand All @@ -191,6 +205,8 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
onAutoCorrectChange: true,
onSpellCheckChange: true,
onGrammarCheckChange: true,
onKeyDown: true,
onKeyUp: true,
// macOS]
}),
disableKeyboardShortcuts: true,
Expand Down
48 changes: 37 additions & 11 deletions packages/react-native/Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ import StyleSheet, {type TextStyleProp} from '../../StyleSheet/StyleSheet';
import Text from '../../Text/Text';
import TextAncestorContext from '../../Text/TextAncestorContext';
import Platform from '../../Utilities/Platform';
import normalizeLegacyHandledKeyEvents, {
type LegacyHandledKeyEvent,
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
import useMergeRefs from '../../Utilities/useMergeRefs';
import TextInputState from './TextInputState';
import invariant from 'invariant';
Expand Down Expand Up @@ -386,6 +389,13 @@ function useTextInputStateSynchronization({
*
*/
function InternalTextInput(props: TextInputProps): React.Node {
const validKeysDown =
((props: any).validKeysDown: ?$ReadOnlyArray<LegacyHandledKeyEvent>);
const validKeysUp =
((props: any).validKeysUp: ?$ReadOnlyArray<LegacyHandledKeyEvent>);
const propsWithoutLegacyKeyProps = ({...props}: any);
delete propsWithoutLegacyKeyProps.validKeysDown;
delete propsWithoutLegacyKeyProps.validKeysUp;
const {
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
Expand All @@ -400,7 +410,17 @@ function InternalTextInput(props: TextInputProps): React.Node {
selectionHandleColor,
cursorColor,
...otherProps
} = props;
} = propsWithoutLegacyKeyProps;
const normalizedKeyDownEvents =
propsWithoutLegacyKeyProps.keyDownEvents ??
normalizeLegacyHandledKeyEvents(validKeysDown);
const normalizedKeyUpEvents =
propsWithoutLegacyKeyProps.keyUpEvents ??
normalizeLegacyHandledKeyEvents(validKeysUp);
const isUsingLegacyKeyDownProp =
propsWithoutLegacyKeyProps.keyDownEvents == null && validKeysDown != null;
const isUsingLegacyKeyUpProp =
propsWithoutLegacyKeyProps.keyUpEvents == null && validKeysUp != null;

const inputRef = useRef<null | TextInputInstance>(null);

Expand Down Expand Up @@ -582,9 +602,9 @@ function InternalTextInput(props: TextInputProps): React.Node {

// [macOS
const _onKeyDown = (event: KeyEvent) => {
const keyDownEvents = props.keyDownEvents;
if (keyDownEvents != null && !event.isPropagationStopped()) {
const isHandled = keyDownEvents.some(
let isHandled = false;
if (normalizedKeyDownEvents != null && !event.isPropagationStopped()) {
isHandled = normalizedKeyDownEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
return (
event.nativeEvent.key === key &&
Expand All @@ -595,17 +615,19 @@ function InternalTextInput(props: TextInputProps): React.Node {
);
},
);
if (isHandled === true) {
if (isHandled === true && !isUsingLegacyKeyDownProp) {
event.stopPropagation();
}
}
props.onKeyDown?.(event);
if (!isUsingLegacyKeyDownProp || isHandled) {
propsWithoutLegacyKeyProps.onKeyDown?.(event);
}
};

const _onKeyUp = (event: KeyEvent) => {
const keyUpEvents = props.keyUpEvents;
if (keyUpEvents != null && !event.isPropagationStopped()) {
const isHandled = keyUpEvents.some(
let isHandled = false;
if (normalizedKeyUpEvents != null && !event.isPropagationStopped()) {
isHandled = normalizedKeyUpEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
return (
event.nativeEvent.key === key &&
Expand All @@ -616,11 +638,13 @@ function InternalTextInput(props: TextInputProps): React.Node {
);
},
);
if (isHandled === true) {
if (isHandled === true && !isUsingLegacyKeyUpProp) {
event.stopPropagation();
}
}
props.onKeyUp?.(event);
if (!isUsingLegacyKeyUpProp || isHandled) {
propsWithoutLegacyKeyProps.onKeyUp?.(event);
}
};
// macOS]

Expand Down Expand Up @@ -773,6 +797,8 @@ function InternalTextInput(props: TextInputProps): React.Node {
caretHidden={caretHidden}
dataDetectorTypes={props.dataDetectorTypes}
focusable={tabIndex !== undefined ? !tabIndex : focusable}
keyDownEvents={normalizedKeyDownEvents}
keyUpEvents={normalizedKeyUpEvents}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
Expand Down
59 changes: 47 additions & 12 deletions packages/react-native/Libraries/Components/View/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import type {ViewProps} from './ViewPropTypes';

import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import TextAncestorContext from '../../Text/TextAncestorContext';
import normalizeLegacyHandledKeyEvents, {
type LegacyHandledKeyEvent,
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
import ViewNativeComponent from './ViewNativeComponent';
import * as React from 'react';
import {use} from 'react';
Expand All @@ -29,14 +32,31 @@ export default component View(
...props: ViewProps
) {
const hasTextAncestor = use(TextAncestorContext);
const validKeysDown =
((props: any).validKeysDown: ?$ReadOnlyArray<LegacyHandledKeyEvent>);
const validKeysUp =
((props: any).validKeysUp: ?$ReadOnlyArray<LegacyHandledKeyEvent>);
const propsWithoutLegacyKeyProps = ({...props}: any);
delete propsWithoutLegacyKeyProps.validKeysDown;
delete propsWithoutLegacyKeyProps.validKeysUp;
const normalizedKeyDownEvents =
propsWithoutLegacyKeyProps.keyDownEvents ??
normalizeLegacyHandledKeyEvents(validKeysDown);
const normalizedKeyUpEvents =
propsWithoutLegacyKeyProps.keyUpEvents ??
normalizeLegacyHandledKeyEvents(validKeysUp);
const isUsingLegacyKeyDownProp =
propsWithoutLegacyKeyProps.keyDownEvents == null && validKeysDown != null;
const isUsingLegacyKeyUpProp =
propsWithoutLegacyKeyProps.keyUpEvents == null && validKeysUp != null;

let actualView;

// [macOS
const _onKeyDown = (event: KeyEvent) => {
const keyDownEvents = props.keyDownEvents;
if (keyDownEvents != null && !event.isPropagationStopped()) {
const isHandled = keyDownEvents.some(
let isHandled = false;
if (normalizedKeyDownEvents != null && !event.isPropagationStopped()) {
isHandled = normalizedKeyDownEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
return (
event.nativeEvent.key === key &&
Expand All @@ -47,17 +67,19 @@ export default component View(
);
},
);
if (isHandled === true) {
if (isHandled === true && !isUsingLegacyKeyDownProp) {
event.stopPropagation();
}
}
props.onKeyDown?.(event);
if (!isUsingLegacyKeyDownProp || isHandled) {
propsWithoutLegacyKeyProps.onKeyDown?.(event);
}
};

const _onKeyUp = (event: KeyEvent) => {
const keyUpEvents = props.keyUpEvents;
if (keyUpEvents != null && !event.isPropagationStopped()) {
const isHandled = keyUpEvents.some(
let isHandled = false;
if (normalizedKeyUpEvents != null && !event.isPropagationStopped()) {
isHandled = normalizedKeyUpEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
return (
event.nativeEvent.key === key &&
Expand All @@ -68,11 +90,13 @@ export default component View(
);
},
);
if (isHandled === true) {
if (isHandled === true && !isUsingLegacyKeyUpProp) {
event.stopPropagation();
}
}
props.onKeyUp?.(event);
if (!isUsingLegacyKeyUpProp || isHandled) {
propsWithoutLegacyKeyProps.onKeyUp?.(event);
}
};
// macOS]

Expand All @@ -96,11 +120,20 @@ export default component View(
id,
tabIndex,
...otherProps
} = props;
} = propsWithoutLegacyKeyProps;

// Since we destructured props, we can now treat it as mutable
const processedProps = otherProps as {...ViewProps};

processedProps.keyDownEvents = normalizedKeyDownEvents;
processedProps.keyUpEvents = normalizedKeyUpEvents;
if (processedProps.onKeyDown != null) {
processedProps.onKeyDown = _onKeyDown;
}
if (processedProps.onKeyUp != null) {
processedProps.onKeyUp = _onKeyUp;
}

const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
if (parsedAriaLabelledBy !== undefined) {
processedProps.accessibilityLabelledBy = parsedAriaLabelledBy;
Expand Down Expand Up @@ -195,7 +228,7 @@ export default component View(
nativeID,
tabIndex,
...otherProps
} = props;
} = propsWithoutLegacyKeyProps;
const _accessibilityLabelledBy =
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;

Expand Down Expand Up @@ -247,6 +280,8 @@ export default component View(
: importantForAccessibility
}
nativeID={id ?? nativeID}
keyDownEvents={normalizedKeyDownEvents}
keyUpEvents={normalizedKeyUpEvents}
// $FlowFixMe[exponential-spread]
{...(otherProps.onKeyDown && {onKeyDown: _onKeyDown})} // [macOS]
{...(otherProps.onKeyUp && {onKeyUp: _onKeyUp})} // [macOS]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import type {HandledKeyEvent} from '../Types/CoreEventTypes';

export type LegacyHandledKeyEvent = string | HandledKeyEvent;

function expandLegacyHandledKeyEvent(
legacyHandledKeyEvent: LegacyHandledKeyEvent,
): Array<HandledKeyEvent> {
if (typeof legacyHandledKeyEvent !== 'string') {
return [legacyHandledKeyEvent];
}

const expandedHandledKeyEvents = [];
for (const metaKey of [false, true]) {
for (const ctrlKey of [false, true]) {
for (const altKey of [false, true]) {
for (const shiftKey of [false, true]) {
expandedHandledKeyEvents.push({
altKey,
ctrlKey,
key: legacyHandledKeyEvent,
metaKey,
shiftKey,
});
}
}
}
}

return expandedHandledKeyEvents;
}

export default function normalizeLegacyHandledKeyEvents(
legacyHandledKeyEvents: ?$ReadOnlyArray<LegacyHandledKeyEvent>,
): void | Array<HandledKeyEvent> {
if (legacyHandledKeyEvents == null) {
return undefined;
}

const normalizedHandledKeyEvents = [];
for (const legacyHandledKeyEvent of legacyHandledKeyEvents) {
normalizedHandledKeyEvents.push(
...expandLegacyHandledKeyEvent(legacyHandledKeyEvent),
);
}

return normalizedHandledKeyEvents;
}
Loading