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

This commit is contained in:
Dominic Gannaway
2019-05-29 17:53:55 +01:00
committed by GitHub
parent 556cc6fe19
commit 142cf56cbf
2 changed files with 145 additions and 25 deletions

View File

@@ -20,6 +20,7 @@ type PressProps = {
delayLongPress: number,
delayPressEnd: number,
delayPressStart: number,
onContextMenu: (e: PressEvent) => void,
onLongPress: (e: PressEvent) => void,
onLongPressChange: boolean => void,
onLongPressShouldCancelPress: () => boolean,
@@ -78,7 +79,8 @@ type PressEventType =
| 'pressend'
| 'presschange'
| 'longpress'
| 'longpresschange';
| 'longpresschange'
| 'contextmenu';
type PressEvent = {|
target: Element | Document,
@@ -99,6 +101,7 @@ type PressEvent = {|
shiftKey: boolean,
|};
const isMac = /^Mac/.test(navigator.platform);
const DEFAULT_PRESS_END_DELAY_MS = 0;
const DEFAULT_PRESS_START_DELAY_MS = 0;
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
@@ -400,17 +403,10 @@ function dispatchCancel(
props: PressProps,
state: PressState,
): void {
const nativeEvent: any = event.nativeEvent;
const type = event.type;
if (state.isPressed) {
if (type === 'contextmenu' && props.preventDefault !== false) {
nativeEvent.preventDefault();
} else {
state.ignoreEmulatedMouseEvents = false;
removeRootEventTypes(context, state);
dispatchPressEndEvents(event, context, props, state);
}
state.ignoreEmulatedMouseEvents = false;
removeRootEventTypes(context, state);
dispatchPressEndEvents(event, context, props, state);
} else if (state.allowPressReentry) {
removeRootEventTypes(context, state);
}
@@ -683,8 +679,9 @@ const PressResponder = {
return;
}
// Ignore mouse/pen pressing on touch hit target area
const isMouseType = pointerType === 'mouse';
if (
(pointerType === 'mouse' || pointerType === 'pen') &&
(isMouseType || pointerType === 'pen') &&
isEventPositionWithinTouchHitTarget(event, context)
) {
// We need to prevent the native event to block the focus
@@ -692,14 +689,22 @@ const PressResponder = {
return;
}
// Ignore any device buttons except left-mouse and touch/pen contact
if (nativeEvent.button > 0) {
// We set these here, before the button check so we have this
// data around for handling of the context menu
state.pointerType = pointerType;
state.pressTarget = context.getEventCurrentTarget(event);
// Ignore any device buttons except left-mouse and touch/pen contact.
// Additionally we ignore left-mouse + ctrl-key with Macs as that
// acts like right-click and opens the contextmenu.
if (
nativeEvent.button > 0 ||
(isMac && isMouseType && nativeEvent.ctrlKey)
) {
return;
}
state.allowPressReentry = true;
state.pointerType = pointerType;
state.pressTarget = context.getEventCurrentTarget(event);
state.responderRegionOnActivation = calculateResponderRegion(
context,
state.pressTarget,
@@ -717,9 +722,25 @@ const PressResponder = {
break;
}
// CANCEL
case 'contextmenu': {
dispatchCancel(event, context, props, state);
if (state.isPressed) {
dispatchCancel(event, context, props, state);
if (props.preventDefault !== false) {
// Skip dispatching of onContextMenu below
nativeEvent.preventDefault();
return;
}
}
if (props.onContextMenu) {
dispatchEvent(
event,
context,
state,
'contextmenu',
props.onContextMenu,
true,
);
}
break;
}

View File

@@ -36,18 +36,21 @@ const createKeyboardEvent = (type, data) => {
});
};
function init() {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableEventAPI = true;
React = require('react');
ReactDOM = require('react-dom');
Press = require('react-events/press');
Scheduler = require('scheduler');
}
describe('Event responder: Press', () => {
let container;
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableEventAPI = true;
React = require('react');
ReactDOM = require('react-dom');
Press = require('react-events/press');
Scheduler = require('scheduler');
init();
container = document.createElement('div');
document.body.appendChild(container);
});
@@ -2579,4 +2582,100 @@ describe('Event responder: Press', () => {
Scheduler.flushAll();
document.body.removeChild(newContainer);
});
describe('onContextMenu', () => {
it('is called after a right mouse click', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const element = (
<Press onContextMenu={onContextMenu}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.dispatchEvent(
createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
);
ref.current.dispatchEvent(createEvent('contextmenu'));
expect(onContextMenu).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
);
});
it('is called after a left mouse click + ctrl key on Mac', () => {
jest.resetModules();
const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
platformGetter.mockReturnValue('MacIntel');
init();
const onContextMenu = jest.fn();
const ref = React.createRef();
const element = (
<Press onContextMenu={onContextMenu}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.dispatchEvent(
createEvent('pointerdown', {
pointerType: 'mouse',
button: 0,
ctrlKey: true,
}),
);
ref.current.dispatchEvent(createEvent('contextmenu'));
expect(onContextMenu).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
);
platformGetter.mockClear();
});
it('is not called after a left mouse click + ctrl key on Windows', () => {
jest.resetModules();
const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
platformGetter.mockReturnValue('Win32');
init();
const onContextMenu = jest.fn();
const ref = React.createRef();
const element = (
<Press onContextMenu={onContextMenu}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.dispatchEvent(
createEvent('pointerdown', {
pointerType: 'mouse',
button: 0,
ctrlKey: true,
}),
);
ref.current.dispatchEvent(createEvent('contextmenu'));
expect(onContextMenu).toHaveBeenCalledTimes(0);
platformGetter.mockClear();
});
it('is not called after a right mouse click occurs during an active press', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const element = (
<Press onContextMenu={onContextMenu}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.dispatchEvent(
createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
);
ref.current.dispatchEvent(createEvent('contextmenu'));
expect(onContextMenu).toHaveBeenCalledTimes(0);
});
});
});