[Flare] Press: fix keyboard interactions (#16179)
Prevents Spacebar from scrolling the window. Prevents Enter from triggering a navigation if preventDefault is true. Fixes the emulated mouse events test.
Nicolas Gallagher committed
Jul 22, 2019 at 18:16 UTC
bbd21066e634c03db67f0b9570de60fdb4879dc0
2 files changed
+79
-15
packages/react-events/src/dom/Press.js
+25
-5
@@ -685,14 +685,35 @@ const PressResponder: ReactDOMEventResponder = {
685
const isKeyboardEvent = pointerType === 'keyboard';
686
const isMouseEvent = pointerType === 'mouse';
687
688
+ // Ignore emulated mouse events
689
+ if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
690
+ return;
691
+ }
692
+
693
+ state.shouldPreventClick = false;
694
if (isPointerEvent || isTouchEvent) {
695
state.ignoreEmulatedMouseEvents = true;
690
- } else if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
691
- // Ignore emulated mouse events
692
- return;
696
} else if (isKeyboardEvent) {
697
// Ignore unrelated key events
695
- if (!isValidKeyboardEvent(nativeEvent)) {
698
+ if (isValidKeyboardEvent(nativeEvent)) {
699
+ const {
700
+ altKey,
701
+ ctrlKey,
702
+ metaKey,
703
+ shiftKey,
704
+ } = (nativeEvent: MouseEvent);
705
+ if (nativeEvent.key === ' ') {
706
+ nativeEvent.preventDefault();
707
+ } else if (
708
+ props.preventDefault !== false &&
709
+ !shiftKey &&
710
+ !metaKey &&
711
+ !ctrlKey &&
712
+ !altKey
713
+ ) {
714
+ state.shouldPreventClick = true;
715
+ }
716
+ } else {
717
return;
718
}
719
}
@@ -920,7 +941,6 @@ const PressResponder: ReactDOMEventResponder = {
941
}
942
943
// Determine whether to call preventDefault on subsequent native events.
923
- state.shouldPreventClick = false;
944
if (
945
context.isTargetWithinEventComponent(target) &&
946
context.isTargetWithinHostComponent(target, 'a', true)
packages/react-events/src/dom/__tests__/Press-test.internal.js
+54
-10
@@ -46,11 +46,7 @@ function createTouchEvent(type, id, data) {
46
}
47
48
const createKeyboardEvent = (type, data) => {
49
- return new KeyboardEvent(type, {
50
- bubbles: true,
51
- cancelable: true,
52
- ...data,
53
- });
49
+ return createEvent(type, data);
50
};
51
52
function init() {
@@ -216,13 +212,20 @@ describe('Event responder: Press', () => {
212
});
213
214
it('is called once after "keydown" events for Spacebar', () => {
219
- ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
215
+ const preventDefault = jest.fn();
216
+ ref.current.dispatchEvent(
217
+ createKeyboardEvent('keydown', {key: ' ', preventDefault}),
218
+ );
219
+ expect(preventDefault).toBeCalled();
220
ref.current.dispatchEvent(createKeyboardEvent('keypress', {key: ' '}));
221
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
222
ref.current.dispatchEvent(createKeyboardEvent('keypress', {key: ' '}));
223
expect(onPressStart).toHaveBeenCalledTimes(1);
224
expect(onPressStart).toHaveBeenCalledWith(
225
- expect.objectContaining({pointerType: 'keyboard', type: 'pressstart'}),
225
+ expect.objectContaining({
226
+ pointerType: 'keyboard',
227
+ type: 'pressstart',
228
+ }),
229
);
230
});
231
@@ -411,7 +414,6 @@ describe('Event responder: Press', () => {
414
target: ref.current,
415
}),
416
);
414
- ref.current.dispatchEvent(createEvent('mousedown'));
417
ref.current.dispatchEvent(
418
createEvent('pointerup', {pointerType: 'touch'}),
419
);
@@ -420,7 +422,9 @@ describe('Event responder: Press', () => {
422
target: ref.current,
423
}),
424
);
425
+ ref.current.dispatchEvent(createEvent('mousedown'));
426
ref.current.dispatchEvent(createEvent('mouseup'));
427
+ ref.current.dispatchEvent(createEvent('click'));
428
expect(onPressEnd).toHaveBeenCalledTimes(1);
429
expect(onPressEnd).toHaveBeenCalledWith(
430
expect.objectContaining({pointerType: 'touch', type: 'pressend'}),
@@ -2419,7 +2423,7 @@ describe('Event responder: Press', () => {
2423
});
2424
2425
describe('link components', () => {
2422
- it('prevents native behaviour by default', () => {
2426
+ it('prevents native behaviour for pointer events by default', () => {
2427
const onPress = jest.fn();
2428
const preventDefault = jest.fn();
2429
const ref = React.createRef();
@@ -2444,6 +2448,26 @@ describe('Event responder: Press', () => {
2448
);
2449
});
2450
2451
+ it('prevents native behaviour for keyboard events by default', () => {
2452
+ const onPress = jest.fn();
2453
+ const preventDefault = jest.fn();
2454
+ const ref = React.createRef();
2455
+ const element = (
2456
+ <Press onPress={onPress}>
2457
+ <a href="#" ref={ref} />
2458
+ </Press>
2459
+ );
2460
+ ReactDOM.render(element, container);
2461
+
2462
+ ref.current.dispatchEvent(createEvent('keydown', {key: 'Enter'}));
2463
+ ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2464
+ ref.current.dispatchEvent(createEvent('keyup', {key: 'Enter'}));
2465
+ expect(preventDefault).toBeCalled();
2466
+ expect(onPress).toHaveBeenCalledWith(
2467
+ expect.objectContaining({defaultPrevented: true}),
2468
+ );
2469
+ });
2470
+
2471
it('deeply prevents native behaviour by default', () => {
2472
const onPress = jest.fn();
2473
const preventDefault = jest.fn();
@@ -2527,7 +2551,7 @@ describe('Event responder: Press', () => {
2551
});
2552
});
2553
2530
- it('uses native behaviour if preventDefault is false', () => {
2554
+ it('uses native behaviour for pointer events if preventDefault is false', () => {
2555
const onPress = jest.fn();
2556
const preventDefault = jest.fn();
2557
const ref = React.createRef();
@@ -2552,6 +2576,26 @@ describe('Event responder: Press', () => {
2576
);
2577
});
2578
2579
+ it('uses native behaviour for keyboard events if preventDefault is false', () => {
2580
+ const onPress = jest.fn();
2581
+ const preventDefault = jest.fn();
2582
+ const ref = React.createRef();
2583
+ const element = (
2584
+ <Press onPress={onPress} preventDefault={false}>
2585
+ <a href="#" ref={ref} />
2586
+ </Press>
2587
+ );
2588
+ ReactDOM.render(element, container);
2589
+
2590
+ ref.current.dispatchEvent(createEvent('keydown', {key: 'Enter'}));
2591
+ ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2592
+ ref.current.dispatchEvent(createEvent('keyup', {key: 'Enter'}));
2593
+ expect(preventDefault).not.toBeCalled();
2594
+ expect(onPress).toHaveBeenCalledWith(
2595
+ expect.objectContaining({defaultPrevented: false}),
2596
+ );
2597
+ });
2598
+
2599
it('warns when preventDefault is used in an event hook', () => {
2600
const onPress = jest.fn();
2601
const preventDefault = jest.fn();