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