Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call event.stopPropagation() with keyDownEvents specified. #8077

Merged
merged 5 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Match native View.keyDownEvents behavior in JS.",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -350,61 +350,47 @@ function PressWithOnKeyDown() {

function PressWithKeyCapture() {
const [eventLog, setEventLog] = useState([]);
const [shouldStopPropagation, setShouldStopPropagation] = useState(false);
const toggleSwitch2 = () =>
setShouldStopPropagation(shouldStopPropagation => !shouldStopPropagation);
const [timesPressed, setTimesPressed] = useState(0);

function appendEvent(eventName) {
function logEvent(eventName) {
const limit = 6;
setEventLog(current => {
return [eventName].concat(current.slice(0, limit - 1));
});
}

function myKeyDown(event) {
appendEvent('keyDown ' + event.nativeEvent.code);

if (shouldStopPropagation) {
event.stopPropagation();
}
}

function myKeyUp(event) {
appendEvent('keyUp ' + event.nativeEvent.code);

if (shouldStopPropagation) {
event.stopPropagation();
}
}

function myKeyDownCapture(event) {
appendEvent('keyDownCapture ' + event.nativeEvent.code);

if (shouldStopPropagation) {
event.stopPropagation();
}
console.log(eventName);
}

return (
<>
<View testID="pressable_feedback_events">
<View
style={[styles.row, styles.centered]}
onKeyDownCapture={event => myKeyDownCapture(event)}>
<Pressable
style={styles.wrapper}
onKeyDown={event => myKeyDown(event)}
onKeyUp={event => myKeyUp(event)}
onPress={() => appendEvent('press')}>
<Text style={styles.button}>Press Me</Text>
</Pressable>
</View>
<View style={styles.eventLogBox}>
{eventLog.map((e, ii) => (
<Text key={ii}>{e}</Text>
))}
</View>
<Switch onValueChange={toggleSwitch2} value={shouldStopPropagation} />
<View
style={styles.row}
onKeyDown={event => logEvent('outer keyDown ' + event.nativeEvent.code)}
onKeyDownCapture={event =>
logEvent('outer keyDownCapture ' + event.nativeEvent.code)
}>
<Pressable
keyDownEvents={[
{code: 'KeyW', handledEventPhase: 3},
{code: 'KeyE', handledEventPhase: 1},
]}
onKeyDown={event => logEvent('keyDown ' + event.nativeEvent.code)}
onKeyDownCapture={event =>
logEvent('keyDownCapture ' + event.nativeEvent.code)
}
onPress={() => {
setTimesPressed(current => current + 1);
logEvent('pressed ' + timesPressed);
}}>
{({pressed}) => (
<Text style={styles.text}>{pressed ? 'Pressed!' : 'Press Me'}</Text>
)}
</Pressable>
</View>

<View style={styles.eventLogBox}>
{eventLog.map((e, ii) => (
<Text key={ii}>{e}</Text>
))}
</View>
</>
);
Expand Down Expand Up @@ -658,9 +644,9 @@ exports.examples = [
{
title: 'OnKeyDownCapture on Pressable (View)',
description: ('You can intercept routed KeyDown/KeyUp events by specifying the onKeyDownCapture/onKeyUpCapture callbacks.' +
" In the example below, set focus to the 'Press me' element (by tabbing into it), and start pressing letters (or any other keys) on the keyboard to observe the event log below." +
" Additionally, it's possible to control whether the intercepted event will continue down the route to its originating element, by toggling the switch below." +
" This specifies that the stopPropagation() method will be called on the event. When the switch is on, you'll see the KeyDown event doesn't show up after the KeyDownCapture for a given key.": string),
" In the example below, a <Pressable> is wrapper in a <View>, and each specifies onKeyDown and onKeyDownCapture callbacks. Set focus to the 'Press me' element by tabbing into it, and start pressing letters on the keyboard to observe the event log below." +
" Additionally, because the keyDownEvents prop is specified - keyDownEvents={[{code: 'KeyW', handledEventPhase: 3}, {code: 'KeyE', handledEventPhase: 1}]} - " +
'for these keys the event routing will be interrupted at the phase specified (3 - bubbling, 1 - capturing) to match processing on the native side.': string),
rectified95 marked this conversation as resolved.
Show resolved Hide resolved
render: function(): React.Node {
return <PressWithKeyCapture />;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ module.exports = ([
<TextInput
autoFocus={true}
style={styles.default}
keyDownEvents={[{code: 'KeyC'}, {code: 'KeyD', handledEventPhase: 1}]}
accessibilityLabel="I am the accessibility label for text input"
/>
);
Expand Down
21 changes: 21 additions & 0 deletions vnext/src/Libraries/Components/Pressable/Pressable.windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
FocusEvent,
KeyEvent, // Windows]
} from '../../Types/CoreEventTypes';
import type {HandledKeyboardEvent} from '../../Components/View/ViewPropTypes';
import View from '../View/View';
import TextInputState from '../TextInput/TextInputState';

Expand Down Expand Up @@ -137,6 +138,26 @@ type Props = $ReadOnly<{|
*/
onKeyUp?: ?(event: KeyEvent) => mixed,

/*
* List of keys handled only by JS.
*/
keyDownEvents?: ?$ReadOnlyArray<HandledKeyboardEvent>,

/*
* List of keys to be handled only by JS.
*/
keyUpEvents?: ?$ReadOnlyArray<HandledKeyboardEvent>,

/*
* Called in the tunneling phase after a key up event is detected.
*/
onKeyDownCapture?: ?(event: KeyEvent) => void,

/*
* Called in the tunneling phase after a key up event is detected.
*/
onKeyUpCapture?: ?(event: KeyEvent) => void,

/**
* Either view styles or a function that receives a boolean reflecting whether
* the component is currently pressed and returns view styles.
Expand Down
55 changes: 54 additions & 1 deletion vnext/src/Libraries/Components/View/View.windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,50 @@ const View: React.AbstractComponent<
ViewProps,
React.ElementRef<typeof ViewNativeComponent>,
> = React.forwardRef((props: ViewProps, forwardedRef) => {
const _keyDown = (event: KeyEvent) => {
if (props.keyDownEvents && !event.isPropagationStopped()) {
for (const el of props.keyDownEvents) {
if (event.nativeEvent.code == el.code && el.handledEventPhase == 3) {
event.stopPropagation();
}
}
}
props.onKeyDown && props.onKeyDown(event);
};

const _keyUp = (event: KeyEvent) => {
if (props.keyUpEvents && !event.isPropagationStopped()) {
for (const el of props.keyUpEvents) {
if (event.nativeEvent.code == el.code && el.handledEventPhase == 3) {
Copy link
Contributor

@jonthysell jonthysell Jun 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a constant somewhere we can use (or define) so as to not require a magic number here (and elsewhere)?

event.stopPropagation();
}
}
}
props.onKeyUp && props.onKeyUp(event);
};

const _keyDownCapture = (event: KeyEvent) => {
if (props.keyDownEvents && !event.isPropagationStopped()) {
for (const el of props.keyDownEvents) {
if (event.nativeEvent.code == el.code && el.handledEventPhase == 1) {
event.stopPropagation();
}
}
}
props.onKeyDownCapture && props.onKeyDownCapture(event);
};

const _keyUpCapture = (event: KeyEvent) => {
if (props.keyUpEvents) {
for (const el of props.keyUpEvents && !event.isPropagationStopped()) {
if (event.nativeEvent.code == el.code && el.handledEventPhase == 1) {
event.stopPropagation();
}
}
}
props.onKeyUpCapture && props.onKeyUpCapture(event);
};

return (
// [Windows
// In core this is a TextAncestor.Provider value={false} See
Expand All @@ -39,7 +83,16 @@ const View: React.AbstractComponent<
!hasTextAncestor,
'Nesting of <View> within <Text> is not currently supported.',
);
return <ViewNativeComponent {...props} ref={forwardedRef} />;
return (
<ViewNativeComponent
{...props}
ref={forwardedRef}
onKeyDown={_keyDown}
onKeyDownCapture={_keyDownCapture}
onKeyUp={_keyUp}
onKeyUpCapture={_keyUpCapture}
/>
);
}}
</TextAncestor.Consumer>
// Windows]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,11 @@ type WindowsViewProps = $ReadOnly<{|
* @platform windows
*/
onKeyUp?: ?(e: KeyEvent) => void,
onKeyUpCapture?: ?(e: KeyEvent) => void,
keyUpEvents?: ?$ReadOnlyArray<HandledKeyboardEvent>,

onKeyDown?: ?(e: KeyEvent) => void,
onKeyDownCapture?: ?(e: KeyEvent) => void,
keyDownEvents?: ?$ReadOnlyArray<HandledKeyboardEvent>,
/**
* Specifies the Tooltip for the view
Expand Down