@samitouri / QOS-React-2 / commits / f4e1ac8caf

[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