@samitouri / QOS-React-2 / commits / 142cf56cbf

[Flare] Adds onContextMenu and fixes some contextmenu related issues (#15761)

Dominic Gannaway committed May 29, 2019 at 17:53 UTC 142cf56cbfe6d470f57e675e4e7079e904832cdd
2 files changed +145 -25
packages/react-events/src/Press.js
+39 -18
@@ -20,6 +20,7 @@ type PressProps = {
20 delayLongPress: number,
21 delayPressEnd: number,
22 delayPressStart: number,
23 + onContextMenu: (e: PressEvent) => void,
24 onLongPress: (e: PressEvent) => void,
25 onLongPressChange: boolean => void,
26 onLongPressShouldCancelPress: () => boolean,
@@ -78,7 +79,8 @@ type PressEventType =
79 | 'pressend'
80 | 'presschange'
81 | 'longpress'
81 - | 'longpresschange';
82 + | 'longpresschange'
83 + | 'contextmenu';
84
85 type PressEvent = {|
86 target: Element | Document,
@@ -99,6 +101,7 @@ type PressEvent = {|
101 shiftKey: boolean,
102 |};
103
104 +const isMac = /^Mac/.test(navigator.platform);
105 const DEFAULT_PRESS_END_DELAY_MS = 0;
106 const DEFAULT_PRESS_START_DELAY_MS = 0;
107 const DEFAULT_LONG_PRESS_DELAY_MS = 500;
@@ -400,17 +403,10 @@ function dispatchCancel(
403 props: PressProps,
404 state: PressState,
405 ): void {
403 - const nativeEvent: any = event.nativeEvent;
404 - const type = event.type;
405 -
406 if (state.isPressed) {
407 - if (type === 'contextmenu' && props.preventDefault !== false) {
408 - nativeEvent.preventDefault();
409 - } else {
410 - state.ignoreEmulatedMouseEvents = false;
411 - removeRootEventTypes(context, state);
412 - dispatchPressEndEvents(event, context, props, state);
413 - }
407 + state.ignoreEmulatedMouseEvents = false;
408 + removeRootEventTypes(context, state);
409 + dispatchPressEndEvents(event, context, props, state);
410 } else if (state.allowPressReentry) {
411 removeRootEventTypes(context, state);
412 }
@@ -683,8 +679,9 @@ const PressResponder = {
679 return;
680 }
681 // Ignore mouse/pen pressing on touch hit target area
682 + const isMouseType = pointerType === 'mouse';
683 if (
687 - (pointerType === 'mouse' || pointerType === 'pen') &&
684 + (isMouseType || pointerType === 'pen') &&
685 isEventPositionWithinTouchHitTarget(event, context)
686 ) {
687 // We need to prevent the native event to block the focus
@@ -692,14 +689,22 @@ const PressResponder = {
689 return;
690 }
691
695 - // Ignore any device buttons except left-mouse and touch/pen contact
696 - if (nativeEvent.button > 0) {
692 + // We set these here, before the button check so we have this
693 + // data around for handling of the context menu
694 + state.pointerType = pointerType;
695 + state.pressTarget = context.getEventCurrentTarget(event);
696 +
697 + // Ignore any device buttons except left-mouse and touch/pen contact.
698 + // Additionally we ignore left-mouse + ctrl-key with Macs as that
699 + // acts like right-click and opens the contextmenu.
700 + if (
701 + nativeEvent.button > 0 ||
702 + (isMac && isMouseType && nativeEvent.ctrlKey)
703 + ) {
704 return;
705 }
706
707 state.allowPressReentry = true;
701 - state.pointerType = pointerType;
702 - state.pressTarget = context.getEventCurrentTarget(event);
708 state.responderRegionOnActivation = calculateResponderRegion(
709 context,
710 state.pressTarget,
@@ -717,9 +722,25 @@ const PressResponder = {
722 break;
723 }
724
720 - // CANCEL
725 case 'contextmenu': {
722 - dispatchCancel(event, context, props, state);
726 + if (state.isPressed) {
727 + dispatchCancel(event, context, props, state);
728 + if (props.preventDefault !== false) {
729 + // Skip dispatching of onContextMenu below
730 + nativeEvent.preventDefault();
731 + return;
732 + }
733 + }
734 + if (props.onContextMenu) {
735 + dispatchEvent(
736 + event,
737 + context,
738 + state,
739 + 'contextmenu',
740 + props.onContextMenu,
741 + true,
742 + );
743 + }
744 break;
745 }
746
packages/react-events/src/__tests__/Press-test.internal.js
+106 -7
@@ -36,18 +36,21 @@ const createKeyboardEvent = (type, data) => {
36 });
37 };
38
39 +function init() {
40 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
41 + ReactFeatureFlags.enableEventAPI = true;
42 + React = require('react');
43 + ReactDOM = require('react-dom');
44 + Press = require('react-events/press');
45 + Scheduler = require('scheduler');
46 +}
47 +
48 describe('Event responder: Press', () => {
49 let container;
50
51 beforeEach(() => {
52 jest.resetModules();
44 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
45 - ReactFeatureFlags.enableEventAPI = true;
46 - React = require('react');
47 - ReactDOM = require('react-dom');
48 - Press = require('react-events/press');
49 - Scheduler = require('scheduler');
50 -
53 + init();
54 container = document.createElement('div');
55 document.body.appendChild(container);
56 });
@@ -2579,4 +2582,100 @@ describe('Event responder: Press', () => {
2582 Scheduler.flushAll();
2583 document.body.removeChild(newContainer);
2584 });
2585 +
2586 + describe('onContextMenu', () => {
2587 + it('is called after a right mouse click', () => {
2588 + const onContextMenu = jest.fn();
2589 + const ref = React.createRef();
2590 + const element = (
2591 + <Press onContextMenu={onContextMenu}>
2592 + <div ref={ref} />
2593 + </Press>
2594 + );
2595 + ReactDOM.render(element, container);
2596 +
2597 + ref.current.dispatchEvent(
2598 + createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
2599 + );
2600 + ref.current.dispatchEvent(createEvent('contextmenu'));
2601 + expect(onContextMenu).toHaveBeenCalledTimes(1);
2602 + expect(onContextMenu).toHaveBeenCalledWith(
2603 + expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
2604 + );
2605 + });
2606 +
2607 + it('is called after a left mouse click + ctrl key on Mac', () => {
2608 + jest.resetModules();
2609 + const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
2610 + platformGetter.mockReturnValue('MacIntel');
2611 + init();
2612 +
2613 + const onContextMenu = jest.fn();
2614 + const ref = React.createRef();
2615 + const element = (
2616 + <Press onContextMenu={onContextMenu}>
2617 + <div ref={ref} />
2618 + </Press>
2619 + );
2620 + ReactDOM.render(element, container);
2621 +
2622 + ref.current.dispatchEvent(
2623 + createEvent('pointerdown', {
2624 + pointerType: 'mouse',
2625 + button: 0,
2626 + ctrlKey: true,
2627 + }),
2628 + );
2629 + ref.current.dispatchEvent(createEvent('contextmenu'));
2630 + expect(onContextMenu).toHaveBeenCalledTimes(1);
2631 + expect(onContextMenu).toHaveBeenCalledWith(
2632 + expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
2633 + );
2634 + platformGetter.mockClear();
2635 + });
2636 +
2637 + it('is not called after a left mouse click + ctrl key on Windows', () => {
2638 + jest.resetModules();
2639 + const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
2640 + platformGetter.mockReturnValue('Win32');
2641 + init();
2642 +
2643 + const onContextMenu = jest.fn();
2644 + const ref = React.createRef();
2645 + const element = (
2646 + <Press onContextMenu={onContextMenu}>
2647 + <div ref={ref} />
2648 + </Press>
2649 + );
2650 + ReactDOM.render(element, container);
2651 +
2652 + ref.current.dispatchEvent(
2653 + createEvent('pointerdown', {
2654 + pointerType: 'mouse',
2655 + button: 0,
2656 + ctrlKey: true,
2657 + }),
2658 + );
2659 + ref.current.dispatchEvent(createEvent('contextmenu'));
2660 + expect(onContextMenu).toHaveBeenCalledTimes(0);
2661 + platformGetter.mockClear();
2662 + });
2663 +
2664 + it('is not called after a right mouse click occurs during an active press', () => {
2665 + const onContextMenu = jest.fn();
2666 + const ref = React.createRef();
2667 + const element = (
2668 + <Press onContextMenu={onContextMenu}>
2669 + <div ref={ref} />
2670 + </Press>
2671 + );
2672 + ReactDOM.render(element, container);
2673 +
2674 + ref.current.dispatchEvent(
2675 + createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
2676 + );
2677 + ref.current.dispatchEvent(createEvent('contextmenu'));
2678 + expect(onContextMenu).toHaveBeenCalledTimes(0);
2679 + });
2680 + });
2681 });