@samitouri / QOS-React-2 / commits / 7b3934b34f

Revert "Pressable click fix (#18625)" (#18688)

This reverts commit 5f7b175b35fa6faccb0da14e897dcda9c13d6031.

Dominic Gannaway committed Apr 21, 2020 at 11:08 UTC 7b3934b34fe51ceb115ed9bb7836386bd8a7abc2
3 files changed +193 -14
packages/react-dom/src/shared/ReactDOMTypes.js
+1 -1
@@ -16,7 +16,7 @@ import type {
16 } from 'shared/ReactTypes';
17 import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
18
19 -type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent;
19 +type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
20
21 export type PointerType =
22 | ''
packages/react-interactions/events/src/dom/PressLegacy.js
+77 -7
@@ -64,6 +64,7 @@ type PressState = {
64 |}>,
65 ignoreEmulatedMouseEvents: boolean,
66 activePointerId: null | number,
67 + shouldPreventClick: boolean,
68 touchEvent: null | Touch,
69 ...
70 };
@@ -198,6 +199,7 @@ function createPressEvent(
199 x: clientX,
200 y: clientY,
201 preventDefault() {
202 + state.shouldPreventClick = true;
203 if (nativeEvent) {
204 pressEvent.defaultPrevented = true;
205 nativeEvent.preventDefault();
@@ -227,7 +229,8 @@ function dispatchEvent(
229 const target = ((state.pressTarget: any): Element | Document);
230 const pointerType = state.pointerType;
231 const defaultPrevented =
230 - event != null && event.nativeEvent.defaultPrevented === true;
232 + (event != null && event.nativeEvent.defaultPrevented === true) ||
233 + (name === 'press' && state.shouldPreventClick);
234 const touchEvent = state.touchEvent;
235 const syntheticEvent = createPressEvent(
236 context,
@@ -526,6 +529,7 @@ const pressResponderImpl = {
529 responderRegionOnDeactivation: null,
530 ignoreEmulatedMouseEvents: false,
531 activePointerId: null,
532 + shouldPreventClick: false,
533 touchEvent: null,
534 };
535 },
@@ -563,6 +567,7 @@ const pressResponderImpl = {
567 return;
568 }
569
570 + state.shouldPreventClick = false;
571 if (isTouchEvent) {
572 state.ignoreEmulatedMouseEvents = true;
573 } else if (isKeyboardEvent) {
@@ -582,6 +587,7 @@ const pressResponderImpl = {
587 !altKey
588 ) {
589 nativeEvent.preventDefault();
590 + state.shouldPreventClick = true;
591 }
592 } else {
593 return;
@@ -639,6 +645,9 @@ const pressResponderImpl = {
645 }
646
647 case 'click': {
648 + if (state.shouldPreventClick) {
649 + nativeEvent.preventDefault();
650 + }
651 const onPress = props.onPress;
652
653 if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
@@ -742,6 +751,7 @@ const pressResponderImpl = {
751 case 'touchend': {
752 if (isPressed) {
753 const buttons = state.buttons;
754 + let isKeyboardEvent = false;
755 let touchEvent;
756 if (
757 type === 'pointerup' &&
@@ -760,13 +770,79 @@ const pressResponderImpl = {
770 if (!isValidKeyboardEvent(nativeEvent)) {
771 return;
772 }
773 + isKeyboardEvent = true;
774 removeRootEventTypes(context, state);
775 } else if (buttons === 4) {
776 // Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
777 removeRootEventTypes(context, state);
778 }
779
780 + // Determine whether to call preventDefault on subsequent native events.
781 + if (
782 + target !== null &&
783 + context.isTargetWithinResponder(target) &&
784 + context.isTargetWithinHostComponent(target, 'a')
785 + ) {
786 + const {
787 + altKey,
788 + ctrlKey,
789 + metaKey,
790 + shiftKey,
791 + } = (nativeEvent: MouseEvent);
792 + // Check "open in new window/tab" and "open context menu" key modifiers
793 + const preventDefault = props.preventDefault;
794 +
795 + if (
796 + preventDefault !== false &&
797 + !shiftKey &&
798 + !metaKey &&
799 + !ctrlKey &&
800 + !altKey
801 + ) {
802 + state.shouldPreventClick = true;
803 + }
804 + }
805 +
806 + const pressTarget = state.pressTarget;
807 dispatchPressEndEvents(event, context, props, state);
808 + const onPress = props.onPress;
809 +
810 + if (pressTarget !== null && isFunction(onPress)) {
811 + if (
812 + !isKeyboardEvent &&
813 + pressTarget !== null &&
814 + target !== null &&
815 + !targetIsDocument(pressTarget)
816 + ) {
817 + if (
818 + pointerType === 'mouse' &&
819 + context.isTargetWithinNode(target, pressTarget)
820 + ) {
821 + state.isPressWithinResponderRegion = true;
822 + } else {
823 + // If the event target isn't within the press target, check if we're still
824 + // within the responder region. The region may have changed if the
825 + // element's layout was modified after activation.
826 + updateIsPressWithinResponderRegion(
827 + touchEvent || nativeEvent,
828 + context,
829 + props,
830 + state,
831 + );
832 + }
833 + }
834 +
835 + if (state.isPressWithinResponderRegion && buttons !== 4) {
836 + dispatchEvent(
837 + event,
838 + onPress,
839 + context,
840 + state,
841 + 'press',
842 + DiscreteEvent,
843 + );
844 + }
845 + }
846 state.touchEvent = null;
847 } else if (type === 'mouseup') {
848 state.ignoreEmulatedMouseEvents = false;
@@ -779,12 +855,6 @@ const pressResponderImpl = {
855 if (previousPointerType !== 'keyboard') {
856 removeRootEventTypes(context, state);
857 }
782 -
783 - const pressTarget = state.pressTarget;
784 - const onPress = props.onPress;
785 - if (pressTarget !== null && isFunction(onPress)) {
786 - dispatchEvent(event, onPress, context, state, 'press', DiscreteEvent);
787 - }
858 break;
859 }
860
packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js
+115 -6
@@ -469,12 +469,15 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
469 });
470
471 // @gate experimental
472 - it('is called after valid "click" event', () => {
472 + it('is called after valid "keyup" event', () => {
473 componentInit();
474 const target = createEventTarget(ref.current);
475 - target.pointerdown();
476 - target.pointerup();
475 + target.keydown({key: 'Enter'});
476 + target.keyup({key: 'Enter'});
477 expect(onPress).toHaveBeenCalledTimes(1);
478 + expect(onPress).toHaveBeenCalledWith(
479 + expect.objectContaining({pointerType: 'keyboard', type: 'press'}),
480 + );
481 });
482
483 // @gate experimental
@@ -801,6 +804,40 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
804 });
805 });
806
807 + describe('beyond bounds of hit rect', () => {
808 + /** ┌──────────────────┐
809 + * │ ┌────────────┐ │
810 + * │ │ VisualRect │ │
811 + * │ └────────────┘ │
812 + * │ HitRect │
813 + * └──────────────────┘
814 + * X <= Move to X and release
815 + */
816 + // @gate experimental
817 + it('"onPress" is not called on release', () => {
818 + componentInit();
819 + const target = createEventTarget(ref.current);
820 + const targetContainer = createEventTarget(container);
821 + target.setBoundingClientRect(rectMock);
822 + target.pointerdown({pointerType});
823 + target.pointermove({pointerType, ...coordinatesInside});
824 + if (pointerType === 'mouse') {
825 + // TODO: use setPointerCapture so this is only true for fallback mouse events.
826 + targetContainer.pointermove({pointerType, ...coordinatesOutside});
827 + targetContainer.pointerup({pointerType, ...coordinatesOutside});
828 + } else {
829 + target.pointermove({pointerType, ...coordinatesOutside});
830 + target.pointerup({pointerType, ...coordinatesOutside});
831 + }
832 + expect(events.filter(removePressMoveStrings)).toEqual([
833 + 'onPressStart',
834 + 'onPressChange',
835 + 'onPressEnd',
836 + 'onPressChange',
837 + ]);
838 + });
839 + });
840 +
841 // @gate experimental
842 it('"onPress" is called on re-entry to hit rect', () => {
843 componentInit();
@@ -889,8 +926,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
926 'pointerdown',
927 'inner: onPressEnd',
928 'inner: onPressChange',
892 - 'pointerup',
929 'inner: onPress',
930 + 'pointerup',
931 ]);
932 });
933 }
@@ -986,6 +1023,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
1023 // @gate experimental
1024 it('prevents native behavior by default', () => {
1025 const onPress = jest.fn();
1026 + const preventDefault = jest.fn();
1027 const ref = React.createRef();
1028
1029 const Component = () => {
@@ -996,8 +1034,79 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
1034
1035 const target = createEventTarget(ref.current);
1036 target.pointerdown();
999 - target.pointerup();
1000 - expect(onPress).toBeCalled();
1037 + target.pointerup({preventDefault});
1038 + expect(preventDefault).toBeCalled();
1039 + expect(onPress).toHaveBeenCalledWith(
1040 + expect.objectContaining({defaultPrevented: true}),
1041 + );
1042 + });
1043 +
1044 + // @gate experimental
1045 + it('prevents native behaviour for keyboard events by default', () => {
1046 + const onPress = jest.fn();
1047 + const preventDefault = jest.fn();
1048 + const ref = React.createRef();
1049 +
1050 + const Component = () => {
1051 + const listener = usePress({onPress});
1052 + return <a href="#" ref={ref} DEPRECATED_flareListeners={listener} />;
1053 + };
1054 + ReactDOM.render(<Component />, container);
1055 +
1056 + const target = createEventTarget(ref.current);
1057 + target.keydown({key: 'Enter', preventDefault});
1058 + target.keyup({key: 'Enter'});
1059 + expect(preventDefault).toBeCalled();
1060 + expect(onPress).toHaveBeenCalledWith(
1061 + expect.objectContaining({defaultPrevented: true}),
1062 + );
1063 + });
1064 +
1065 + // @gate experimental
1066 + it('deeply prevents native behaviour by default', () => {
1067 + const onPress = jest.fn();
1068 + const preventDefault = jest.fn();
1069 + const buttonRef = React.createRef();
1070 +
1071 + const Component = () => {
1072 + const listener = usePress({onPress});
1073 + return (
1074 + <a href="#">
1075 + <button ref={buttonRef} DEPRECATED_flareListeners={listener} />
1076 + </a>
1077 + );
1078 + };
1079 + ReactDOM.render(<Component />, container);
1080 +
1081 + const target = createEventTarget(buttonRef.current);
1082 + target.pointerdown();
1083 + target.pointerup({preventDefault});
1084 + expect(preventDefault).toBeCalled();
1085 + });
1086 +
1087 + // @gate experimental
1088 + it('prevents native behaviour by default with nested elements', () => {
1089 + const onPress = jest.fn();
1090 + const preventDefault = jest.fn();
1091 + const ref = React.createRef();
1092 +
1093 + const Component = () => {
1094 + const listener = usePress({onPress});
1095 + return (
1096 + <a href="#" DEPRECATED_flareListeners={listener}>
1097 + <div ref={ref} />
1098 + </a>
1099 + );
1100 + };
1101 + ReactDOM.render(<Component />, container);
1102 +
1103 + const target = createEventTarget(ref.current);
1104 + target.pointerdown();
1105 + target.pointerup({preventDefault});
1106 + expect(preventDefault).toBeCalled();
1107 + expect(onPress).toHaveBeenCalledWith(
1108 + expect.objectContaining({defaultPrevented: true}),
1109 + );
1110 });
1111
1112 // @gate experimental