[Flare] Press events include defaultPrevented (#15916)

* Rename `disableContextMenu` to `preventContextMenu`
* Change the behaviour of `preventContextMenu` so that `onContextMenu` is still called when the native context menu is prevented.
This commit is contained in:
Nicolas Gallagher
2019-06-19 11:00:44 -07:00
committed by GitHub
parent 4a7a39b594
commit f4e1ac8caf
5 changed files with 89 additions and 56 deletions
@@ -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,
@@ -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(<Test />, 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('<button>Click me!</button>');
});
+32 -7
View File
@@ -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
+39 -25
View File
@@ -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;
}
@@ -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 = (
<Press disableContextMenu={true} onContextMenu={onContextMenu}>
<Press onContextMenu={onContextMenu} preventContextMenu={true}>
<div ref={ref} />
</Press>
);
@@ -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}),
);
});
});