Skip to content

Commit 142cf56

Browse files
authored
[Flare] Adds onContextMenu and fixes some contextmenu related issues (#15761)
1 parent 556cc6f commit 142cf56

2 files changed

Lines changed: 145 additions & 25 deletions

File tree

packages/react-events/src/Press.js

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type PressProps = {
2020
delayLongPress: number,
2121
delayPressEnd: number,
2222
delayPressStart: number,
23+
onContextMenu: (e: PressEvent) => void,
2324
onLongPress: (e: PressEvent) => void,
2425
onLongPressChange: boolean => void,
2526
onLongPressShouldCancelPress: () => boolean,
@@ -78,7 +79,8 @@ type PressEventType =
7879
| 'pressend'
7980
| 'presschange'
8081
| 'longpress'
81-
| 'longpresschange';
82+
| 'longpresschange'
83+
| 'contextmenu';
8284

8385
type PressEvent = {|
8486
target: Element | Document,
@@ -99,6 +101,7 @@ type PressEvent = {|
99101
shiftKey: boolean,
100102
|};
101103

104+
const isMac = /^Mac/.test(navigator.platform);
102105
const DEFAULT_PRESS_END_DELAY_MS = 0;
103106
const DEFAULT_PRESS_START_DELAY_MS = 0;
104107
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
@@ -400,17 +403,10 @@ function dispatchCancel(
400403
props: PressProps,
401404
state: PressState,
402405
): void {
403-
const nativeEvent: any = event.nativeEvent;
404-
const type = event.type;
405-
406406
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);
414410
} else if (state.allowPressReentry) {
415411
removeRootEventTypes(context, state);
416412
}
@@ -683,23 +679,32 @@ const PressResponder = {
683679
return;
684680
}
685681
// Ignore mouse/pen pressing on touch hit target area
682+
const isMouseType = pointerType === 'mouse';
686683
if (
687-
(pointerType === 'mouse' || pointerType === 'pen') &&
684+
(isMouseType || pointerType === 'pen') &&
688685
isEventPositionWithinTouchHitTarget(event, context)
689686
) {
690687
// We need to prevent the native event to block the focus
691688
nativeEvent.preventDefault();
692689
return;
693690
}
694691

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+
) {
697704
return;
698705
}
699706

700707
state.allowPressReentry = true;
701-
state.pointerType = pointerType;
702-
state.pressTarget = context.getEventCurrentTarget(event);
703708
state.responderRegionOnActivation = calculateResponderRegion(
704709
context,
705710
state.pressTarget,
@@ -717,9 +722,25 @@ const PressResponder = {
717722
break;
718723
}
719724

720-
// CANCEL
721725
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+
}
723744
break;
724745
}
725746

packages/react-events/src/__tests__/Press-test.internal.js

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,21 @@ const createKeyboardEvent = (type, data) => {
3636
});
3737
};
3838

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+
3948
describe('Event responder: Press', () => {
4049
let container;
4150

4251
beforeEach(() => {
4352
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();
5154
container = document.createElement('div');
5255
document.body.appendChild(container);
5356
});
@@ -2579,4 +2582,100 @@ describe('Event responder: Press', () => {
25792582
Scheduler.flushAll();
25802583
document.body.removeChild(newContainer);
25812584
});
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+
});
25822681
});

0 commit comments

Comments
 (0)