[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.
Nicolas Gallagher committed
Jun 19, 2019 at 11:00 UTC
f4e1ac8cafb75dfab129b95f0eb39591579f3d03
5 files changed
+89
-56
packages/react-dom/src/events/DOMEventResponderSystem.js
-8
@@ -171,14 +171,6 @@ const eventResponderContext: ReactDOMResponderContext = {
171
}
172
},
173
});
174
- // $FlowFixMe: we don't need value, Flow thinks we do
175
- Object.defineProperty(possibleEventObject, 'defaultPrevented', {
176
- get() {
177
- if (__DEV__) {
178
- showWarning('defaultPrevented');
179
- }
180
- },
181
- });
174
175
const eventObject = ((possibleEventObject: any): $Shape<
176
PartialEventObject,
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
-13
@@ -925,19 +925,6 @@ describe('DOMEventResponderSystem', () => {
925
' Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.nativeEvent }`',
926
{withoutStack: true},
927
);
928
- expect(() => {
929
- handler = event => {
930
- return event.defaultPrevented;
931
- };
932
- ReactDOM.render(<Test />, container);
933
- dispatchClickEvent(document.body);
934
- }).toWarnDev(
935
- 'Warning: defaultPrevented is not available on event objects created from event responder modules ' +
936
- '(React Flare).' +
937
- ' Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.defaultPrevented }`',
938
- {withoutStack: true},
939
- );
940
-
928
expect(container.innerHTML).toBe('<button>Click me!</button>');
929
});
930
packages/react-events/docs/Press.md
+32
-7
@@ -37,9 +37,34 @@ const Button = (props) => (
37
38
```js
39
type PressEvent = {
40
- pointerType: 'mouse' | 'touch' | 'pen' | 'trackpad' | 'keyboard',
40
+ altKey: boolean,
41
+ ctrlKey: boolean,
42
+ defaultPrevented: boolean,
43
+ metaKey: boolean,
44
+ pageX: number,
45
+ pageY: number,
46
+ pointerType:
47
+ | 'mouse'
48
+ | 'touch'
49
+ | 'pen'
50
+ | 'trackpad'
51
+ | 'keyboard',
52
+ screenX: number,
53
+ screenY: number,
54
+ shiftKey: boolean,
55
target: Element,
42
- type: 'press' | 'pressstart' | 'pressend' | 'presschange' | 'pressmove' | 'longpress' | 'longpresschange' | 'contextmenu'
56
+ timeStamp: number,
57
+ type:
58
+ | 'press'
59
+ | 'pressstart'
60
+ | 'pressend'
61
+ | 'presschange'
62
+ | 'pressmove'
63
+ | 'longpress'
64
+ | 'longpresschange'
65
+ | 'contextmenu',
66
+ x: number,
67
+ y: number
68
}
69
70
type PressOffset = {
@@ -71,11 +96,6 @@ released before the threshold is exceeded.
96
97
Disables all `Press` events.
98
74
-### disableContextMenu: boolean = false
75
-
76
-Disables the native context menu so that it is never shown and `onContextMenu`
77
-is never called.
78
-
99
### onContextMenu: (e: PressEvent) => void
100
101
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.
155
Ensure you pass in a constant to reduce memory allocations. Default is `20` for
156
each offset.
157
158
+### preventContextMenu: boolean = false
159
+
160
+Prevents the native context menu from being shown, but `onContextMenu`
161
+is still called.
162
+
163
### preventDefault: boolean = true
164
165
Whether to `preventDefault()` native events. Native behavior is prevented by
packages/react-events/src/Press.js
+39
-25
@@ -19,7 +19,6 @@ import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
19
20
type PressProps = {
21
disabled: boolean,
22
- disableContextMenu: boolean,
22
delayLongPress: number,
23
delayPressEnd: number,
24
delayPressStart: number,
@@ -38,6 +37,7 @@ type PressProps = {
37
bottom: number,
38
left: number,
39
},
40
+ preventContextMenu: boolean,
41
preventDefault: boolean,
42
stopPropagation: boolean,
43
};
@@ -72,6 +72,7 @@ type PressState = {
72
|}>,
73
ignoreEmulatedMouseEvents: boolean,
74
activePointerId: null | number,
75
+ shouldPreventClick: boolean,
76
};
77
78
type PressEventType =
@@ -85,6 +86,7 @@ type PressEventType =
86
| 'contextmenu';
87
88
type PressEvent = {|
89
+ defaultPrevented: boolean,
90
target: Element | Document,
91
type: PressEventType,
92
pointerType: PointerType,
@@ -155,6 +157,7 @@ function createPressEvent(
157
target: Element | Document,
158
pointerType: PointerType,
159
event: ?ReactDOMResponderEvent,
160
+ defaultPrevented: boolean,
161
): PressEvent {
162
const timeStamp = context.getTimeStamp();
163
let clientX = null;
@@ -184,6 +187,7 @@ function createPressEvent(
187
}
188
}
189
return {
190
+ defaultPrevented,
191
target,
192
type,
193
pointerType,
@@ -213,12 +217,16 @@ function dispatchEvent(
217
): void {
218
const target = ((state.pressTarget: any): Element | Document);
219
const pointerType = state.pointerType;
220
+ const defaultPrevented =
221
+ (event != null && event.nativeEvent.defaultPrevented === true) ||
222
+ (name === 'press' && state.shouldPreventClick);
223
const syntheticEvent = createPressEvent(
224
context,
225
name,
226
target,
227
pointerType,
228
event,
229
+ defaultPrevented,
230
);
231
context.dispatchEvent(syntheticEvent, listener, eventPriority);
232
}
@@ -631,6 +639,7 @@ const PressResponder = {
639
responderRegionOnDeactivation: null,
640
ignoreEmulatedMouseEvents: false,
641
activePointerId: null,
642
+ shouldPreventClick: false,
643
};
644
},
645
allowMultipleHostChildren: false,
@@ -728,14 +737,13 @@ const PressResponder = {
737
}
738
739
case 'contextmenu': {
731
- if (props.disableContextMenu) {
740
+ if (props.preventContextMenu) {
741
// Skip dispatching of onContextMenu below
742
nativeEvent.preventDefault();
734
- return;
743
}
744
745
if (isPressed) {
738
- if (props.preventDefault !== false) {
746
+ if (props.preventDefault !== false && !nativeEvent.defaultPrevented) {
747
// Skip dispatching of onContextMenu below
748
nativeEvent.preventDefault();
749
return;
@@ -863,6 +871,31 @@ const PressResponder = {
871
isKeyboardEvent = true;
872
}
873
874
+ // Determine whether to call preventDefault on subsequent native events.
875
+ state.shouldPreventClick = false;
876
+ if (
877
+ context.isTargetWithinEventComponent(target) &&
878
+ context.isTargetWithinHostComponent(target, 'a', true)
879
+ ) {
880
+ const {
881
+ altKey,
882
+ ctrlKey,
883
+ metaKey,
884
+ shiftKey,
885
+ } = (nativeEvent: MouseEvent);
886
+ // Check "open in new window/tab" and "open context menu" key modifiers
887
+ const preventDefault = props.preventDefault;
888
+ if (
889
+ preventDefault !== false &&
890
+ !shiftKey &&
891
+ !metaKey &&
892
+ !ctrlKey &&
893
+ !altKey
894
+ ) {
895
+ state.shouldPreventClick = true;
896
+ }
897
+ }
898
+
899
const wasLongPressed = state.isLongPressed;
900
dispatchPressEndEvents(event, context, props, state);
901
@@ -906,27 +939,8 @@ const PressResponder = {
939
940
case 'click': {
941
removeRootEventTypes(context, state);
909
- if (
910
- context.isTargetWithinEventComponent(target) &&
911
- context.isTargetWithinHostComponent(target, 'a', true)
912
- ) {
913
- const {
914
- altKey,
915
- ctrlKey,
916
- metaKey,
917
- shiftKey,
918
- } = (nativeEvent: MouseEvent);
919
- // Check "open in new window/tab" and "open context menu" key modifiers
920
- const preventDefault = props.preventDefault;
921
- if (
922
- preventDefault !== false &&
923
- !shiftKey &&
924
- !metaKey &&
925
- !ctrlKey &&
926
- !altKey
927
- ) {
928
- nativeEvent.preventDefault();
929
- }
942
+ if (state.shouldPreventClick) {
943
+ nativeEvent.preventDefault();
944
}
945
break;
946
}
packages/react-events/src/__tests__/Press-test.internal.js
+18
-3
@@ -2221,6 +2221,9 @@ describe('Event responder: Press', () => {
2221
ref.current.dispatchEvent(createEvent('pointerup'));
2222
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2223
expect(preventDefault).toBeCalled();
2224
+ expect(onPress).toHaveBeenCalledWith(
2225
+ expect.objectContaining({defaultPrevented: true}),
2226
+ );
2227
});
2228
2229
it('deeply prevents native behaviour by default', () => {
@@ -2259,6 +2262,9 @@ describe('Event responder: Press', () => {
2262
ref.current.dispatchEvent(createEvent('pointerup'));
2263
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2264
expect(preventDefault).toBeCalled();
2265
+ expect(onPress).toHaveBeenCalledWith(
2266
+ expect.objectContaining({defaultPrevented: true}),
2267
+ );
2268
});
2269
2270
it('uses native behaviour for interactions with modifier keys', () => {
@@ -2283,6 +2289,9 @@ describe('Event responder: Press', () => {
2289
createEvent('click', {[modifierKey]: true, preventDefault}),
2290
);
2291
expect(preventDefault).not.toBeCalled();
2292
+ expect(onPress).toHaveBeenCalledWith(
2293
+ expect.objectContaining({defaultPrevented: false}),
2294
+ );
2295
});
2296
});
2297
@@ -2301,6 +2310,9 @@ describe('Event responder: Press', () => {
2310
ref.current.dispatchEvent(createEvent('pointerup'));
2311
ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2312
expect(preventDefault).not.toBeCalled();
2313
+ expect(onPress).toHaveBeenCalledWith(
2314
+ expect.objectContaining({defaultPrevented: false}),
2315
+ );
2316
});
2317
});
2318
@@ -2839,11 +2851,11 @@ describe('Event responder: Press', () => {
2851
expect(onContextMenu).toHaveBeenCalledTimes(0);
2852
});
2853
2842
- it('is not called if "disableContextMenu" is true', () => {
2854
+ it('is still called if "preventContextMenu" is true', () => {
2855
const onContextMenu = jest.fn();
2856
const ref = React.createRef();
2857
const element = (
2846
- <Press disableContextMenu={true} onContextMenu={onContextMenu}>
2858
+ <Press onContextMenu={onContextMenu} preventContextMenu={true}>
2859
<div ref={ref} />
2860
</Press>
2861
);
@@ -2852,7 +2864,10 @@ describe('Event responder: Press', () => {
2864
createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
2865
);
2866
ref.current.dispatchEvent(createEvent('contextmenu'));
2855
- expect(onContextMenu).toHaveBeenCalledTimes(0);
2867
+ expect(onContextMenu).toHaveBeenCalledTimes(1);
2868
+ expect(onContextMenu).toHaveBeenCalledWith(
2869
+ expect.objectContaining({defaultPrevented: true}),
2870
+ );
2871
});
2872
});
2873