diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js
index ff6ab2737b..1e34b5079b 100644
--- a/packages/react-dom/src/events/DOMEventResponderSystem.js
+++ b/packages/react-dom/src/events/DOMEventResponderSystem.js
@@ -171,14 +171,6 @@ const eventResponderContext: ReactDOMResponderContext = {
}
},
});
- // $FlowFixMe: we don't need value, Flow thinks we do
- Object.defineProperty(possibleEventObject, 'defaultPrevented', {
- get() {
- if (__DEV__) {
- showWarning('defaultPrevented');
- }
- },
- });
const eventObject = ((possibleEventObject: any): $Shape<
PartialEventObject,
diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
index 07f8828b53..1903f02b56 100644
--- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
@@ -925,19 +925,6 @@ describe('DOMEventResponderSystem', () => {
' Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.nativeEvent }`',
{withoutStack: true},
);
- expect(() => {
- handler = event => {
- return event.defaultPrevented;
- };
- ReactDOM.render(, container);
- dispatchClickEvent(document.body);
- }).toWarnDev(
- 'Warning: defaultPrevented is not available on event objects created from event responder modules ' +
- '(React Flare).' +
- ' Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.defaultPrevented }`',
- {withoutStack: true},
- );
-
expect(container.innerHTML).toBe('');
});
diff --git a/packages/react-events/docs/Press.md b/packages/react-events/docs/Press.md
index 71d269b3c3..d618d7121c 100644
--- a/packages/react-events/docs/Press.md
+++ b/packages/react-events/docs/Press.md
@@ -37,9 +37,34 @@ const Button = (props) => (
```js
type PressEvent = {
- pointerType: 'mouse' | 'touch' | 'pen' | 'trackpad' | 'keyboard',
+ altKey: boolean,
+ ctrlKey: boolean,
+ defaultPrevented: boolean,
+ metaKey: boolean,
+ pageX: number,
+ pageY: number,
+ pointerType:
+ | 'mouse'
+ | 'touch'
+ | 'pen'
+ | 'trackpad'
+ | 'keyboard',
+ screenX: number,
+ screenY: number,
+ shiftKey: boolean,
target: Element,
- type: 'press' | 'pressstart' | 'pressend' | 'presschange' | 'pressmove' | 'longpress' | 'longpresschange' | 'contextmenu'
+ timeStamp: number,
+ type:
+ | 'press'
+ | 'pressstart'
+ | 'pressend'
+ | 'presschange'
+ | 'pressmove'
+ | 'longpress'
+ | 'longpresschange'
+ | 'contextmenu',
+ x: number,
+ y: number
}
type PressOffset = {
@@ -71,11 +96,6 @@ released before the threshold is exceeded.
Disables all `Press` events.
-### disableContextMenu: boolean = false
-
-Disables the native context menu so that it is never shown and `onContextMenu`
-is never called.
-
### onContextMenu: (e: PressEvent) => void
Called when the context menu is shown. When a press is active, the context menu
@@ -135,6 +155,11 @@ down) can be moved back within the bounds of the element to reactivate it.
Ensure you pass in a constant to reduce memory allocations. Default is `20` for
each offset.
+### preventContextMenu: boolean = false
+
+Prevents the native context menu from being shown, but `onContextMenu`
+is still called.
+
### preventDefault: boolean = true
Whether to `preventDefault()` native events. Native behavior is prevented by
diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js
index 4af1a798a7..2a6f788aef 100644
--- a/packages/react-events/src/Press.js
+++ b/packages/react-events/src/Press.js
@@ -19,7 +19,6 @@ import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
type PressProps = {
disabled: boolean,
- disableContextMenu: boolean,
delayLongPress: number,
delayPressEnd: number,
delayPressStart: number,
@@ -38,6 +37,7 @@ type PressProps = {
bottom: number,
left: number,
},
+ preventContextMenu: boolean,
preventDefault: boolean,
stopPropagation: boolean,
};
@@ -72,6 +72,7 @@ type PressState = {
|}>,
ignoreEmulatedMouseEvents: boolean,
activePointerId: null | number,
+ shouldPreventClick: boolean,
};
type PressEventType =
@@ -85,6 +86,7 @@ type PressEventType =
| 'contextmenu';
type PressEvent = {|
+ defaultPrevented: boolean,
target: Element | Document,
type: PressEventType,
pointerType: PointerType,
@@ -155,6 +157,7 @@ function createPressEvent(
target: Element | Document,
pointerType: PointerType,
event: ?ReactDOMResponderEvent,
+ defaultPrevented: boolean,
): PressEvent {
const timeStamp = context.getTimeStamp();
let clientX = null;
@@ -184,6 +187,7 @@ function createPressEvent(
}
}
return {
+ defaultPrevented,
target,
type,
pointerType,
@@ -213,12 +217,16 @@ function dispatchEvent(
): void {
const target = ((state.pressTarget: any): Element | Document);
const pointerType = state.pointerType;
+ const defaultPrevented =
+ (event != null && event.nativeEvent.defaultPrevented === true) ||
+ (name === 'press' && state.shouldPreventClick);
const syntheticEvent = createPressEvent(
context,
name,
target,
pointerType,
event,
+ defaultPrevented,
);
context.dispatchEvent(syntheticEvent, listener, eventPriority);
}
@@ -631,6 +639,7 @@ const PressResponder = {
responderRegionOnDeactivation: null,
ignoreEmulatedMouseEvents: false,
activePointerId: null,
+ shouldPreventClick: false,
};
},
allowMultipleHostChildren: false,
@@ -728,14 +737,13 @@ const PressResponder = {
}
case 'contextmenu': {
- if (props.disableContextMenu) {
+ if (props.preventContextMenu) {
// Skip dispatching of onContextMenu below
nativeEvent.preventDefault();
- return;
}
if (isPressed) {
- if (props.preventDefault !== false) {
+ if (props.preventDefault !== false && !nativeEvent.defaultPrevented) {
// Skip dispatching of onContextMenu below
nativeEvent.preventDefault();
return;
@@ -863,6 +871,31 @@ const PressResponder = {
isKeyboardEvent = true;
}
+ // Determine whether to call preventDefault on subsequent native events.
+ state.shouldPreventClick = false;
+ if (
+ context.isTargetWithinEventComponent(target) &&
+ context.isTargetWithinHostComponent(target, 'a', true)
+ ) {
+ const {
+ altKey,
+ ctrlKey,
+ metaKey,
+ shiftKey,
+ } = (nativeEvent: MouseEvent);
+ // Check "open in new window/tab" and "open context menu" key modifiers
+ const preventDefault = props.preventDefault;
+ if (
+ preventDefault !== false &&
+ !shiftKey &&
+ !metaKey &&
+ !ctrlKey &&
+ !altKey
+ ) {
+ state.shouldPreventClick = true;
+ }
+ }
+
const wasLongPressed = state.isLongPressed;
dispatchPressEndEvents(event, context, props, state);
@@ -906,27 +939,8 @@ const PressResponder = {
case 'click': {
removeRootEventTypes(context, state);
- if (
- context.isTargetWithinEventComponent(target) &&
- context.isTargetWithinHostComponent(target, 'a', true)
- ) {
- const {
- altKey,
- ctrlKey,
- metaKey,
- shiftKey,
- } = (nativeEvent: MouseEvent);
- // Check "open in new window/tab" and "open context menu" key modifiers
- const preventDefault = props.preventDefault;
- if (
- preventDefault !== false &&
- !shiftKey &&
- !metaKey &&
- !ctrlKey &&
- !altKey
- ) {
- nativeEvent.preventDefault();
- }
+ if (state.shouldPreventClick) {
+ nativeEvent.preventDefault();
}
break;
}
diff --git a/packages/react-events/src/__tests__/Press-test.internal.js b/packages/react-events/src/__tests__/Press-test.internal.js
index b2c49c2b6f..42510a2458 100644
--- a/packages/react-events/src/__tests__/Press-test.internal.js
+++ b/packages/react-events/src/__tests__/Press-test.internal.js
@@ -2221,6 +2221,9 @@ describe('Event responder: Press', () => {
ref.current.dispatchEvent(createEvent('pointerup'));
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
expect(preventDefault).toBeCalled();
+ expect(onPress).toHaveBeenCalledWith(
+ expect.objectContaining({defaultPrevented: true}),
+ );
});
it('deeply prevents native behaviour by default', () => {
@@ -2259,6 +2262,9 @@ describe('Event responder: Press', () => {
ref.current.dispatchEvent(createEvent('pointerup'));
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
expect(preventDefault).toBeCalled();
+ expect(onPress).toHaveBeenCalledWith(
+ expect.objectContaining({defaultPrevented: true}),
+ );
});
it('uses native behaviour for interactions with modifier keys', () => {
@@ -2283,6 +2289,9 @@ describe('Event responder: Press', () => {
createEvent('click', {[modifierKey]: true, preventDefault}),
);
expect(preventDefault).not.toBeCalled();
+ expect(onPress).toHaveBeenCalledWith(
+ expect.objectContaining({defaultPrevented: false}),
+ );
});
});
@@ -2301,6 +2310,9 @@ describe('Event responder: Press', () => {
ref.current.dispatchEvent(createEvent('pointerup'));
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
expect(preventDefault).not.toBeCalled();
+ expect(onPress).toHaveBeenCalledWith(
+ expect.objectContaining({defaultPrevented: false}),
+ );
});
});
@@ -2839,11 +2851,11 @@ describe('Event responder: Press', () => {
expect(onContextMenu).toHaveBeenCalledTimes(0);
});
- it('is not called if "disableContextMenu" is true', () => {
+ it('is still called if "preventContextMenu" is true', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const element = (
-
+
);
@@ -2852,7 +2864,10 @@ describe('Event responder: Press', () => {
createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
);
ref.current.dispatchEvent(createEvent('contextmenu'));
- expect(onContextMenu).toHaveBeenCalledTimes(0);
+ expect(onContextMenu).toHaveBeenCalledTimes(1);
+ expect(onContextMenu).toHaveBeenCalledWith(
+ expect.objectContaining({defaultPrevented: true}),
+ );
});
});