Skip to content

Commit

Permalink
[react-events] Add ContextMenu responder (#16296)
Browse files Browse the repository at this point in the history
A module for responding to contextmenu events. This functionality will be
removed from the Press responder in the future.
  • Loading branch information
necolas authored Aug 6, 2019
1 parent 606f76b commit 23405c9
Show file tree
Hide file tree
Showing 5 changed files with 414 additions and 17 deletions.
12 changes: 12 additions & 0 deletions packages/react-events/context-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

module.exports = require('./src/dom/ContextMenu');
11 changes: 6 additions & 5 deletions packages/react-events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
"files": [
"LICENSE",
"README.md",
"press.js",
"hover.js",
"focus.js",
"swipe.js",
"context-menu.js",
"drag.js",
"scroll.js",
"focus.js",
"hover.js",
"input.js",
"keyboard.js",
"press.js",
"scroll.js",
"swipe.js",
"build-info.json",
"cjs/",
"umd/"
Expand Down
128 changes: 128 additions & 0 deletions packages/react-events/src/dom/ContextMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {
ReactDOMResponderEvent,
ReactDOMResponderContext,
PointerType,
} from 'shared/ReactDOMTypes';
import type {ReactEventResponderListener} from 'shared/ReactTypes';

import React from 'react';
import {DiscreteEvent} from 'shared/ReactTypes';

type ContextMenuProps = {|
disabled: boolean,
onContextMenu: (e: ContextMenuEvent) => void,
preventDefault: boolean,
|};

type ContextMenuState = {
pointerType: PointerType,
};

type ContextMenuEvent = {|
target: Element | Document,
type: 'contextmenu',
pointerType: PointerType,
timeStamp: number,
clientX: null | number,
clientY: null | number,
pageX: null | number,
pageY: null | number,
x: null | number,
y: null | number,
altKey: boolean,
ctrlKey: boolean,
metaKey: boolean,
shiftKey: boolean,
|};

const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent != null;

function dispatchContextMenuEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: ContextMenuProps,
state: ContextMenuState,
): void {
const nativeEvent: any = event.nativeEvent;
const target = event.target;
const timeStamp = context.getTimeStamp();
const pointerType = state.pointerType;

const gestureState = {
altKey: nativeEvent.altKey,
button: nativeEvent.button === 0 ? 'primary' : 'auxillary',
ctrlKey: nativeEvent.altKey,
metaKey: nativeEvent.altKey,
pageX: nativeEvent.altKey,
pageY: nativeEvent.altKey,
pointerType,
shiftKey: nativeEvent.altKey,
target,
timeStamp,
type: 'contextmenu',
x: nativeEvent.clientX,
y: nativeEvent.clientY,
};

context.dispatchEvent(gestureState, props.onContextMenu, DiscreteEvent);
}

const contextMenuImpl = {
targetEventTypes: hasPointerEvents
? ['contextmenu_active', 'pointerdown']
: ['contextmenu_active', 'touchstart', 'mousedown'],
getInitialState(): ContextMenuState {
return {
pointerType: '',
};
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: ContextMenuProps,
state: ContextMenuState,
): void {
const nativeEvent: any = event.nativeEvent;
const pointerType = event.pointerType;
const type = event.type;

if (props.disabled) {
return;
}

if (type === 'contextmenu') {
const onContextMenu = props.onContextMenu;
const preventDefault = props.preventDefault;
if (preventDefault !== false && !nativeEvent.defaultPrevented) {
nativeEvent.preventDefault();
}
if (typeof onContextMenu === 'function') {
dispatchContextMenuEvent(event, context, props, state);
}
state.pointerType = '';
} else {
state.pointerType = pointerType;
}
},
};

export const ContextMenuResponder = React.unstable_createResponder(
'ContextMenu',
contextMenuImpl,
);

export function useContextMenuResponder(
props: ContextMenuProps,
): ReactEventResponderListener<any, any> {
return React.unstable_useResponder(ContextMenuResponder, props);
}
Loading

0 comments on commit 23405c9

Please sign in to comment.