Skip to content

Commit

Permalink
[Flare] Adds onContextMenu and fixes some contextmenu related issues (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored May 29, 2019
1 parent 556cc6f commit 142cf56
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 25 deletions.
57 changes: 39 additions & 18 deletions packages/react-events/src/Press.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -78,7 +79,8 @@ type PressEventType =
| 'pressend'
| 'presschange'
| 'longpress'
| 'longpresschange';
| 'longpresschange'
| 'contextmenu';

type PressEvent = {|
target: Element | Document,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -683,23 +679,32 @@ 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
nativeEvent.preventDefault();
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,
Expand All @@ -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;
}

Expand Down
113 changes: 106 additions & 7 deletions packages/react-events/src/__tests__/Press-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
});
});

0 comments on commit 142cf56

Please sign in to comment.