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,14 @@ function Pressable({
// [macOS
acceptsFirstMouse: acceptsFirstMouse !== false && !disabled,
enableFocusRing: enableFocusRing !== false && !disabled,
keyDownEvents: keyDownEvents ?? [{key: ' '}, {key: 'Enter'}],
keyDownEvents:
keyDownEvents ??
// $FlowFixMe[unclear-type] Legacy props not in type definitions
(((props: any).validKeysDown: mixed) == null &&
// $FlowFixMe[unclear-type]
((props: any).passthroughAllKeyEvents: mixed) !== true
? [{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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @format
*/

import type {HostInstance} from '../../../src/private/types/HostInstance';

Check warning on line 11 in packages/react-native/Libraries/Components/TextInput/TextInput.js

View workflow job for this annotation

GitHub Actions / JavaScript Tests

Requires should be sorted alphabetically, with at least one line between imports/requires and code
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
import type {
BlurEvent,
Expand Down Expand Up @@ -61,6 +61,12 @@
import Text from '../../Text/Text';
import TextAncestorContext from '../../Text/TextAncestorContext';
import Platform from '../../Utilities/Platform';
// [macOS
import processLegacyKeyProps, {
hasLegacyKeyProps,
stripLegacyKeyProps,
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
// macOS]
import useMergeRefs from '../../Utilities/useMergeRefs';
import TextInputState from './TextInputState';
import invariant from 'invariant';
Expand Down Expand Up @@ -386,6 +392,23 @@
*
*/
function InternalTextInput(props: TextInputProps): React.Node {
// [macOS Legacy keyboard event compat — to remove, delete this block and its import
const usingLegacyKeyboardProps = hasLegacyKeyProps(props);
// $FlowFixMe[unclear-type]
const effectiveProps: any = usingLegacyKeyboardProps ? ({...props}: any) : props;
if (usingLegacyKeyboardProps) {
stripLegacyKeyProps(effectiveProps);
const legacy = processLegacyKeyProps(props);
effectiveProps.keyDownEvents = legacy.keyDownEvents;
effectiveProps.keyUpEvents = legacy.keyUpEvents;
if (legacy.onKeyDown != null) {
effectiveProps.onKeyDown = legacy.onKeyDown;
}
if (legacy.onKeyUp != null) {
effectiveProps.onKeyUp = legacy.onKeyUp;
}
}
// macOS]
const {
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
Expand All @@ -400,7 +423,7 @@
selectionHandleColor,
cursorColor,
...otherProps
} = props;
} = effectiveProps;

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

Expand Down Expand Up @@ -582,7 +605,7 @@

// [macOS
const _onKeyDown = (event: KeyEvent) => {
const keyDownEvents = props.keyDownEvents;
const keyDownEvents = effectiveProps.keyDownEvents;
if (keyDownEvents != null && !event.isPropagationStopped()) {
const isHandled = keyDownEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
Expand All @@ -599,11 +622,11 @@
event.stopPropagation();
}
}
props.onKeyDown?.(event);
effectiveProps.onKeyDown?.(event);
};

const _onKeyUp = (event: KeyEvent) => {
const keyUpEvents = props.keyUpEvents;
const keyUpEvents = effectiveProps.keyUpEvents;
if (keyUpEvents != null && !event.isPropagationStopped()) {
const isHandled = keyUpEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
Expand All @@ -620,7 +643,7 @@
event.stopPropagation();
}
}
props.onKeyUp?.(event);
effectiveProps.onKeyUp?.(event);
};
// macOS]

Expand Down
36 changes: 30 additions & 6 deletions packages/react-native/Libraries/Components/View/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import type {ViewProps} from './ViewPropTypes';

import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import TextAncestorContext from '../../Text/TextAncestorContext';
// [macOS
import processLegacyKeyProps, {
hasLegacyKeyProps,
stripLegacyKeyProps,
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
// macOS]
import ViewNativeComponent from './ViewNativeComponent';
import * as React from 'react';
import {use} from 'react';
Expand All @@ -30,11 +36,29 @@ export default component View(
) {
const hasTextAncestor = use(TextAncestorContext);

// [macOS Legacy keyboard event compat — to remove, delete this block and its import
const usingLegacyKeyboardProps = hasLegacyKeyProps(props);
// $FlowFixMe[unclear-type]
const effectiveProps: any = usingLegacyKeyboardProps ? ({...props}: any) : props;
if (usingLegacyKeyboardProps) {
stripLegacyKeyProps(effectiveProps);
const legacy = processLegacyKeyProps(props);
effectiveProps.keyDownEvents = legacy.keyDownEvents;
effectiveProps.keyUpEvents = legacy.keyUpEvents;
if (legacy.onKeyDown != null) {
effectiveProps.onKeyDown = legacy.onKeyDown;
}
if (legacy.onKeyUp != null) {
effectiveProps.onKeyUp = legacy.onKeyUp;
}
}
// macOS]

let actualView;

// [macOS
const _onKeyDown = (event: KeyEvent) => {
const keyDownEvents = props.keyDownEvents;
const keyDownEvents = effectiveProps.keyDownEvents;
if (keyDownEvents != null && !event.isPropagationStopped()) {
const isHandled = keyDownEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
Expand All @@ -51,11 +75,11 @@ export default component View(
event.stopPropagation();
}
}
props.onKeyDown?.(event);
effectiveProps.onKeyDown?.(event);
};

const _onKeyUp = (event: KeyEvent) => {
const keyUpEvents = props.keyUpEvents;
const keyUpEvents = effectiveProps.keyUpEvents;
if (keyUpEvents != null && !event.isPropagationStopped()) {
const isHandled = keyUpEvents.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
Expand All @@ -72,7 +96,7 @@ export default component View(
event.stopPropagation();
}
}
props.onKeyUp?.(event);
effectiveProps.onKeyUp?.(event);
};
// macOS]

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

// Since we destructured props, we can now treat it as mutable
const processedProps = otherProps as {...ViewProps};
Expand Down Expand Up @@ -195,7 +219,7 @@ export default component View(
nativeID,
tabIndex,
...otherProps
} = props;
} = effectiveProps;
const _accessibilityLabelledBy =
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* 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
*/

// [macOS]
// Legacy validKeysDown/validKeysUp/passthroughAllKeyEvents compat layer.
// When removing legacy support, delete this file and its call sites.

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

type LegacyHandledKeyEvent = string | HandledKeyEvent;

function expandKey(entry: LegacyHandledKeyEvent): Array<HandledKeyEvent> {
if (typeof entry !== 'string') {
return [entry];
}
const out: Array<HandledKeyEvent> = [];
const bools: Array<boolean> = [false, true];
for (const metaKey of bools) {
for (const ctrlKey of bools) {
for (const altKey of bools) {
for (const shiftKey of bools) {
out.push({altKey, ctrlKey, key: entry, metaKey, shiftKey});
}
}
}
}
return out;
}

function normalize(
legacy: ?$ReadOnlyArray<LegacyHandledKeyEvent>,
): void | Array<HandledKeyEvent> {
if (legacy == null) {
return undefined;
}
const result: Array<HandledKeyEvent> = [];
for (const entry of legacy) {
result.push(...expandKey(entry));
}
return result;
}

function matchesEvent(
events: $ReadOnlyArray<HandledKeyEvent>,
event: KeyEvent,
): boolean {
return events.some(
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) =>
event.nativeEvent.key === key &&
Boolean(metaKey) === event.nativeEvent.metaKey &&
Boolean(ctrlKey) === event.nativeEvent.ctrlKey &&
Boolean(altKey) === event.nativeEvent.altKey &&
Boolean(shiftKey) === event.nativeEvent.shiftKey,
);
}

export type LegacyKeyResult = {
keyDownEvents: void | Array<HandledKeyEvent>,
keyUpEvents: void | Array<HandledKeyEvent>,
onKeyDown: void | ((event: KeyEvent) => void),
onKeyUp: void | ((event: KeyEvent) => void),
};

/**
* Returns true if the props contain legacy key props that need processing.
*/
export function hasLegacyKeyProps(props: mixed): boolean {
// $FlowFixMe[unclear-type]
const p = (props: any);
return (
p.validKeysDown != null ||
p.validKeysUp != null ||
p.passthroughAllKeyEvents != null
);
}

/**
* Strips legacy props from a props object (mutates).
*/
export function stripLegacyKeyProps(props: {+[string]: mixed}): void {
// $FlowFixMe[unclear-type]
const p = (props: any);
delete p.validKeysDown;
delete p.validKeysUp;
delete p.passthroughAllKeyEvents;
}

/**
* Processes legacy validKeysDown/validKeysUp/passthroughAllKeyEvents props
* and returns the equivalent modern keyDownEvents/keyUpEvents and wrapped
* onKeyDown/onKeyUp handlers.
*
* Usage in component:
* if (hasLegacyKeyProps(props)) {
* const legacy = processLegacyKeyProps(props);
* // use legacy.keyDownEvents, legacy.onKeyDown, etc.
* }
*/
export default function processLegacyKeyProps(
// $FlowFixMe[unclear-type]
props: any,
): LegacyKeyResult {
const validKeysDown: ?$ReadOnlyArray<LegacyHandledKeyEvent> =
props.validKeysDown;
const validKeysUp: ?$ReadOnlyArray<LegacyHandledKeyEvent> =
props.validKeysUp;
const passthroughAllKeyEvents: ?boolean = props.passthroughAllKeyEvents;

const hasModernKeyDown = props.keyDownEvents != null;
const hasModernKeyUp = props.keyUpEvents != null;
const legacyPassthrough =
passthroughAllKeyEvents === true && !hasModernKeyDown;

const gateKeyDown =
!hasModernKeyDown && validKeysDown != null && !legacyPassthrough;
const gateKeyUp =
!hasModernKeyUp && validKeysUp != null && !legacyPassthrough;

const normalizedDown =
props.keyDownEvents ?? normalize(validKeysDown);
const normalizedUp =
props.keyUpEvents ?? normalize(validKeysUp);

const keyDownEvents = legacyPassthrough ? undefined : normalizedDown;
const keyUpEvents = legacyPassthrough ? undefined : normalizedUp;

const onKeyDown =
props.onKeyDown != null
? (event: KeyEvent) => {
let isHandled = false;
if (normalizedDown != null && !event.isPropagationStopped()) {
isHandled = matchesEvent(normalizedDown, event);
if (isHandled && hasModernKeyDown) {
event.stopPropagation();
}
}
if (!gateKeyDown || isHandled) {
props.onKeyDown?.(event);
}
}
: undefined;

const onKeyUp =
props.onKeyUp != null
? (event: KeyEvent) => {
let isHandled = false;
if (normalizedUp != null && !event.isPropagationStopped()) {
isHandled = matchesEvent(normalizedUp, event);
if (isHandled && hasModernKeyUp) {
event.stopPropagation();
}
}
if (!gateKeyUp || isHandled) {
props.onKeyUp?.(event);
}
}
: undefined;

return {keyDownEvents, keyUpEvents, onKeyDown, onKeyUp};
}
Loading
Loading