Skip to content

Commit 7284602

Browse files
authored
fix(fabric): Add back compat layer for validKeysDown and validKeysUp (#2879)
1 parent 92a0916 commit 7284602

File tree

6 files changed

+345
-20
lines changed

6 files changed

+345
-20
lines changed

packages/react-native/Libraries/Components/Pressable/Pressable.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,14 @@ function Pressable({
381381
// [macOS
382382
acceptsFirstMouse: acceptsFirstMouse !== false && !disabled,
383383
enableFocusRing: enableFocusRing !== false && !disabled,
384-
keyDownEvents: keyDownEvents ?? [{key: ' '}, {key: 'Enter'}],
384+
keyDownEvents:
385+
keyDownEvents ??
386+
// $FlowFixMe[unclear-type] Legacy props not in type definitions
387+
(((props: any).validKeysDown: mixed) == null &&
388+
// $FlowFixMe[unclear-type]
389+
((props: any).passthroughAllKeyEvents: mixed) !== true
390+
? [{key: ' '}, {key: 'Enter'}]
391+
: undefined),
385392
mouseDownCanMoveWindow: false,
386393
// macOS]
387394
};

packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
5252
captured: 'onSubmitEditingCapture',
5353
},
5454
},
55+
topKeyDown: {
56+
phasedRegistrationNames: {
57+
bubbled: 'onKeyDown',
58+
captured: 'onKeyDownCapture',
59+
},
60+
},
61+
topKeyUp: {
62+
phasedRegistrationNames: {
63+
bubbled: 'onKeyUp',
64+
captured: 'onKeyUpCapture',
65+
},
66+
},
5567
topTouchCancel: {
5668
phasedRegistrationNames: {
5769
bubbled: 'onTouchCancel',
@@ -173,6 +185,8 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
173185
clearTextOnSubmit: true,
174186
grammarCheck: true,
175187
hideVerticalScrollIndicator: true,
188+
keyDownEvents: true,
189+
keyUpEvents: true,
176190
pastedTypes: true,
177191
submitKeyEvents: true,
178192
tooltip: true,
@@ -191,6 +205,8 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
191205
onAutoCorrectChange: true,
192206
onSpellCheckChange: true,
193207
onGrammarCheckChange: true,
208+
onKeyDown: true,
209+
onKeyUp: true,
194210
// macOS]
195211
}),
196212
disableKeyboardShortcuts: true,

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ import flattenStyle from '../../StyleSheet/flattenStyle';
6060
import StyleSheet, {type TextStyleProp} from '../../StyleSheet/StyleSheet';
6161
import Text from '../../Text/Text';
6262
import TextAncestorContext from '../../Text/TextAncestorContext';
63+
// [macOS
64+
import processLegacyKeyProps, {
65+
hasLegacyKeyProps,
66+
stripLegacyKeyProps,
67+
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
6368
import Platform from '../../Utilities/Platform';
69+
// macOS]
6470
import useMergeRefs from '../../Utilities/useMergeRefs';
6571
import TextInputState from './TextInputState';
6672
import invariant from 'invariant';
@@ -386,6 +392,11 @@ function useTextInputStateSynchronization({
386392
*
387393
*/
388394
function InternalTextInput(props: TextInputProps): React.Node {
395+
// [macOS
396+
const legacyKeyOverrides = hasLegacyKeyProps(props)
397+
? processLegacyKeyProps(props)
398+
: null;
399+
// macOS]
389400
const {
390401
'aria-busy': ariaBusy,
391402
'aria-checked': ariaChecked,
@@ -400,7 +411,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
400411
selectionHandleColor,
401412
cursorColor,
402413
...otherProps
403-
} = props;
414+
// $FlowFixMe[unclear-type]
415+
} = ({...props, ...legacyKeyOverrides}: any); // [macOS]
416+
stripLegacyKeyProps(otherProps); // [macOS]
404417

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

@@ -581,10 +594,15 @@ function InternalTextInput(props: TextInputProps): React.Node {
581594
};
582595

583596
// [macOS
597+
const _keyDownEvents =
598+
legacyKeyOverrides?.keyDownEvents ?? props.keyDownEvents;
599+
const _keyUpEvents = legacyKeyOverrides?.keyUpEvents ?? props.keyUpEvents;
600+
const _origOnKeyDown = legacyKeyOverrides?.onKeyDown ?? props.onKeyDown;
601+
const _origOnKeyUp = legacyKeyOverrides?.onKeyUp ?? props.onKeyUp;
602+
584603
const _onKeyDown = (event: KeyEvent) => {
585-
const keyDownEvents = props.keyDownEvents;
586-
if (keyDownEvents != null && !event.isPropagationStopped()) {
587-
const isHandled = keyDownEvents.some(
604+
if (_keyDownEvents != null && !event.isPropagationStopped()) {
605+
const isHandled = _keyDownEvents.some(
588606
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
589607
return (
590608
event.nativeEvent.key === key &&
@@ -599,13 +617,12 @@ function InternalTextInput(props: TextInputProps): React.Node {
599617
event.stopPropagation();
600618
}
601619
}
602-
props.onKeyDown?.(event);
620+
_origOnKeyDown?.(event);
603621
};
604622

605623
const _onKeyUp = (event: KeyEvent) => {
606-
const keyUpEvents = props.keyUpEvents;
607-
if (keyUpEvents != null && !event.isPropagationStopped()) {
608-
const isHandled = keyUpEvents.some(
624+
if (_keyUpEvents != null && !event.isPropagationStopped()) {
625+
const isHandled = _keyUpEvents.some(
609626
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
610627
return (
611628
event.nativeEvent.key === key &&
@@ -620,7 +637,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
620637
event.stopPropagation();
621638
}
622639
}
623-
props.onKeyUp?.(event);
640+
_origOnKeyUp?.(event);
624641
};
625642
// macOS]
626643

packages/react-native/Libraries/Components/View/View.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import type {ViewProps} from './ViewPropTypes';
1313

1414
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
1515
import TextAncestorContext from '../../Text/TextAncestorContext';
16+
// [macOS
17+
import processLegacyKeyProps, {
18+
hasLegacyKeyProps,
19+
stripLegacyKeyProps,
20+
} from '../../Utilities/normalizeLegacyHandledKeyEvents';
21+
// macOS]
1622
import ViewNativeComponent from './ViewNativeComponent';
1723
import * as React from 'react';
1824
import {use} from 'react';
@@ -30,13 +36,24 @@ export default component View(
3036
) {
3137
const hasTextAncestor = use(TextAncestorContext);
3238

39+
// [macOS
40+
const legacyKeyOverrides = hasLegacyKeyProps(props)
41+
? processLegacyKeyProps(props)
42+
: null;
43+
// macOS]
44+
3345
let actualView;
3446

3547
// [macOS
48+
const _keyDownEvents =
49+
legacyKeyOverrides?.keyDownEvents ?? props.keyDownEvents;
50+
const _keyUpEvents = legacyKeyOverrides?.keyUpEvents ?? props.keyUpEvents;
51+
const _origOnKeyDown = legacyKeyOverrides?.onKeyDown ?? props.onKeyDown;
52+
const _origOnKeyUp = legacyKeyOverrides?.onKeyUp ?? props.onKeyUp;
53+
3654
const _onKeyDown = (event: KeyEvent) => {
37-
const keyDownEvents = props.keyDownEvents;
38-
if (keyDownEvents != null && !event.isPropagationStopped()) {
39-
const isHandled = keyDownEvents.some(
55+
if (_keyDownEvents != null && !event.isPropagationStopped()) {
56+
const isHandled = _keyDownEvents.some(
4057
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
4158
return (
4259
event.nativeEvent.key === key &&
@@ -51,13 +68,12 @@ export default component View(
5168
event.stopPropagation();
5269
}
5370
}
54-
props.onKeyDown?.(event);
71+
_origOnKeyDown?.(event);
5572
};
5673

5774
const _onKeyUp = (event: KeyEvent) => {
58-
const keyUpEvents = props.keyUpEvents;
59-
if (keyUpEvents != null && !event.isPropagationStopped()) {
60-
const isHandled = keyUpEvents.some(
75+
if (_keyUpEvents != null && !event.isPropagationStopped()) {
76+
const isHandled = _keyUpEvents.some(
6177
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) => {
6278
return (
6379
event.nativeEvent.key === key &&
@@ -72,7 +88,7 @@ export default component View(
7288
event.stopPropagation();
7389
}
7490
}
75-
props.onKeyUp?.(event);
91+
_origOnKeyUp?.(event);
7692
};
7793
// macOS]
7894

@@ -96,10 +112,12 @@ export default component View(
96112
id,
97113
tabIndex,
98114
...otherProps
99-
} = props;
115+
// $FlowFixMe[unclear-type]
116+
} = ({...props, ...legacyKeyOverrides}: any); // [macOS]
100117

101118
// Since we destructured props, we can now treat it as mutable
102119
const processedProps = otherProps as {...ViewProps};
120+
stripLegacyKeyProps(processedProps); // [macOS]
103121

104122
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
105123
if (parsedAriaLabelledBy !== undefined) {
@@ -195,7 +213,9 @@ export default component View(
195213
nativeID,
196214
tabIndex,
197215
...otherProps
198-
} = props;
216+
// $FlowFixMe[unclear-type]
217+
} = ({...props, ...legacyKeyOverrides}: any); // [macOS]
218+
stripLegacyKeyProps(otherProps); // [macOS]
199219
const _accessibilityLabelledBy =
200220
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;
201221

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
// [macOS]
12+
// Legacy validKeysDown/validKeysUp/passthroughAllKeyEvents compat layer.
13+
// When removing legacy support, delete this file and its call sites.
14+
15+
import type {HandledKeyEvent, KeyEvent} from '../Types/CoreEventTypes';
16+
17+
type LegacyHandledKeyEvent = string | HandledKeyEvent;
18+
19+
function expandKey(entry: LegacyHandledKeyEvent): Array<HandledKeyEvent> {
20+
if (typeof entry !== 'string') {
21+
return [entry];
22+
}
23+
const out: Array<HandledKeyEvent> = [];
24+
const bools: Array<boolean> = [false, true];
25+
for (const metaKey of bools) {
26+
for (const ctrlKey of bools) {
27+
for (const altKey of bools) {
28+
for (const shiftKey of bools) {
29+
out.push({altKey, ctrlKey, key: entry, metaKey, shiftKey});
30+
}
31+
}
32+
}
33+
}
34+
return out;
35+
}
36+
37+
function normalize(
38+
legacy: ?$ReadOnlyArray<LegacyHandledKeyEvent>,
39+
): void | Array<HandledKeyEvent> {
40+
if (legacy == null) {
41+
return undefined;
42+
}
43+
const result: Array<HandledKeyEvent> = [];
44+
for (const entry of legacy) {
45+
result.push(...expandKey(entry));
46+
}
47+
return result;
48+
}
49+
50+
function matchesEvent(
51+
events: $ReadOnlyArray<HandledKeyEvent>,
52+
event: KeyEvent,
53+
): boolean {
54+
return events.some(
55+
({key, metaKey, ctrlKey, altKey, shiftKey}: HandledKeyEvent) =>
56+
event.nativeEvent.key === key &&
57+
Boolean(metaKey) === event.nativeEvent.metaKey &&
58+
Boolean(ctrlKey) === event.nativeEvent.ctrlKey &&
59+
Boolean(altKey) === event.nativeEvent.altKey &&
60+
Boolean(shiftKey) === event.nativeEvent.shiftKey,
61+
);
62+
}
63+
64+
export type LegacyKeyResult = {
65+
keyDownEvents: void | Array<HandledKeyEvent>,
66+
keyUpEvents: void | Array<HandledKeyEvent>,
67+
onKeyDown: void | ((event: KeyEvent) => void),
68+
onKeyUp: void | ((event: KeyEvent) => void),
69+
};
70+
71+
/**
72+
* Returns true if the props contain legacy key props that need processing.
73+
*/
74+
export function hasLegacyKeyProps(props: mixed): boolean {
75+
// $FlowFixMe[unclear-type]
76+
const p = (props: any);
77+
return (
78+
p.validKeysDown != null ||
79+
p.validKeysUp != null ||
80+
p.passthroughAllKeyEvents != null
81+
);
82+
}
83+
84+
/**
85+
* Strips legacy props from a props object (mutates).
86+
*/
87+
export function stripLegacyKeyProps(props: {+[string]: mixed}): void {
88+
// $FlowFixMe[unclear-type]
89+
const p = (props: any);
90+
delete p.validKeysDown;
91+
delete p.validKeysUp;
92+
delete p.passthroughAllKeyEvents;
93+
}
94+
95+
/**
96+
* Processes legacy validKeysDown/validKeysUp/passthroughAllKeyEvents props
97+
* and returns the equivalent modern keyDownEvents/keyUpEvents and wrapped
98+
* onKeyDown/onKeyUp handlers.
99+
*
100+
* Usage in component:
101+
* if (hasLegacyKeyProps(props)) {
102+
* const legacy = processLegacyKeyProps(props);
103+
* // use legacy.keyDownEvents, legacy.onKeyDown, etc.
104+
* }
105+
*/
106+
export default function processLegacyKeyProps(
107+
// $FlowFixMe[unclear-type]
108+
props: any,
109+
): LegacyKeyResult {
110+
const validKeysDown: ?$ReadOnlyArray<LegacyHandledKeyEvent> =
111+
props.validKeysDown;
112+
const validKeysUp: ?$ReadOnlyArray<LegacyHandledKeyEvent> = props.validKeysUp;
113+
const passthroughAllKeyEvents: ?boolean = props.passthroughAllKeyEvents;
114+
115+
const hasModernKeyDown = props.keyDownEvents != null;
116+
const hasModernKeyUp = props.keyUpEvents != null;
117+
const legacyPassthrough =
118+
passthroughAllKeyEvents === true && !hasModernKeyDown;
119+
120+
const gateKeyDown =
121+
!hasModernKeyDown && validKeysDown != null && !legacyPassthrough;
122+
const gateKeyUp =
123+
!hasModernKeyUp && validKeysUp != null && !legacyPassthrough;
124+
125+
const normalizedDown = props.keyDownEvents ?? normalize(validKeysDown);
126+
const normalizedUp = props.keyUpEvents ?? normalize(validKeysUp);
127+
128+
const keyDownEvents = legacyPassthrough ? undefined : normalizedDown;
129+
const keyUpEvents = legacyPassthrough ? undefined : normalizedUp;
130+
131+
const onKeyDown =
132+
props.onKeyDown != null
133+
? (event: KeyEvent) => {
134+
let isHandled = false;
135+
if (normalizedDown != null && !event.isPropagationStopped()) {
136+
isHandled = matchesEvent(normalizedDown, event);
137+
if (isHandled && hasModernKeyDown) {
138+
event.stopPropagation();
139+
}
140+
}
141+
if (!gateKeyDown || isHandled) {
142+
props.onKeyDown?.(event);
143+
}
144+
}
145+
: undefined;
146+
147+
const onKeyUp =
148+
props.onKeyUp != null
149+
? (event: KeyEvent) => {
150+
let isHandled = false;
151+
if (normalizedUp != null && !event.isPropagationStopped()) {
152+
isHandled = matchesEvent(normalizedUp, event);
153+
if (isHandled && hasModernKeyUp) {
154+
event.stopPropagation();
155+
}
156+
}
157+
if (!gateKeyUp || isHandled) {
158+
props.onKeyUp?.(event);
159+
}
160+
}
161+
: undefined;
162+
163+
return {keyDownEvents, keyUpEvents, onKeyDown, onKeyUp};
164+
}

0 commit comments

Comments
 (0)