Pressable click fix (#18625)
* Update press-legacy to use native click events * update tests for pressable change * fix formatting issue * Address comments. Bring back some tests, remove others. Cleanup * Fix flow and lint errors * formatting fix missed by yarn lint
Naman Goel committed
Apr 20, 2020 at 15:13 UTC
5f7b175b35fa6faccb0da14e897dcda9c13d6031
3 files changed
+14
-193
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 | Touch;
19
+type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent;
20
21
export type PointerType =
22
| ''
packages/react-interactions/events/src/dom/PressLegacy.js
+7
-77
@@ -64,7 +64,6 @@ type PressState = {
64
|}>,
65
ignoreEmulatedMouseEvents: boolean,
66
activePointerId: null | number,
67
- shouldPreventClick: boolean,
67
touchEvent: null | Touch,
68
...
69
};
@@ -199,7 +198,6 @@ function createPressEvent(
198
x: clientX,
199
y: clientY,
200
preventDefault() {
202
- state.shouldPreventClick = true;
201
if (nativeEvent) {
202
pressEvent.defaultPrevented = true;
203
nativeEvent.preventDefault();
@@ -229,8 +227,7 @@ function dispatchEvent(
227
const target = ((state.pressTarget: any): Element | Document);
228
const pointerType = state.pointerType;
229
const defaultPrevented =
232
- (event != null && event.nativeEvent.defaultPrevented === true) ||
233
- (name === 'press' && state.shouldPreventClick);
230
+ event != null && event.nativeEvent.defaultPrevented === true;
231
const touchEvent = state.touchEvent;
232
const syntheticEvent = createPressEvent(
233
context,
@@ -529,7 +526,6 @@ const pressResponderImpl = {
526
responderRegionOnDeactivation: null,
527
ignoreEmulatedMouseEvents: false,
528
activePointerId: null,
532
- shouldPreventClick: false,
529
touchEvent: null,
530
};
531
},
@@ -567,7 +563,6 @@ const pressResponderImpl = {
563
return;
564
}
565
570
- state.shouldPreventClick = false;
566
if (isTouchEvent) {
567
state.ignoreEmulatedMouseEvents = true;
568
} else if (isKeyboardEvent) {
@@ -587,7 +582,6 @@ const pressResponderImpl = {
582
!altKey
583
) {
584
nativeEvent.preventDefault();
590
- state.shouldPreventClick = true;
585
}
586
} else {
587
return;
@@ -645,9 +639,6 @@ const pressResponderImpl = {
639
}
640
641
case 'click': {
648
- if (state.shouldPreventClick) {
649
- nativeEvent.preventDefault();
650
- }
642
const onPress = props.onPress;
643
644
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
@@ -751,7 +742,6 @@ const pressResponderImpl = {
742
case 'touchend': {
743
if (isPressed) {
744
const buttons = state.buttons;
754
- let isKeyboardEvent = false;
745
let touchEvent;
746
if (
747
type === 'pointerup' &&
@@ -770,79 +760,13 @@ const pressResponderImpl = {
760
if (!isValidKeyboardEvent(nativeEvent)) {
761
return;
762
}
773
- isKeyboardEvent = true;
763
removeRootEventTypes(context, state);
764
} else if (buttons === 4) {
765
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
766
removeRootEventTypes(context, state);
767
}
768
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;
769
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
- }
770
state.touchEvent = null;
771
} else if (type === 'mouseup') {
772
state.ignoreEmulatedMouseEvents = false;
@@ -855,6 +779,12 @@ const pressResponderImpl = {
779
if (previousPointerType !== 'keyboard') {
780
removeRootEventTypes(context, state);
781
}
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
+ }
788
break;
789
}
790
packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js
+6
-115
@@ -469,15 +469,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
469
});
470
471
// @gate experimental
472
- it('is called after valid "keyup" event', () => {
472
+ it('is called after valid "click" event', () => {
473
componentInit();
474
const target = createEventTarget(ref.current);
475
- target.keydown({key: 'Enter'});
476
- target.keyup({key: 'Enter'});
475
+ target.pointerdown();
476
+ target.pointerup();
477
expect(onPress).toHaveBeenCalledTimes(1);
478
- expect(onPress).toHaveBeenCalledWith(
479
- expect.objectContaining({pointerType: 'keyboard', type: 'press'}),
480
- );
478
});
479
480
// @gate experimental
@@ -804,40 +801,6 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
801
});
802
});
803
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
-
804
// @gate experimental
805
it('"onPress" is called on re-entry to hit rect', () => {
806
componentInit();
@@ -926,8 +889,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
889
'pointerdown',
890
'inner: onPressEnd',
891
'inner: onPressChange',
929
- 'inner: onPress',
892
'pointerup',
893
+ 'inner: onPress',
894
]);
895
});
896
}
@@ -1023,28 +986,6 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
986
// @gate experimental
987
it('prevents native behavior by default', () => {
988
const onPress = jest.fn();
1026
- const preventDefault = jest.fn();
1027
- const ref = React.createRef();
1028
-
1029
- const Component = () => {
1030
- const listener = usePress({onPress});
1031
- return <a href="#" ref={ref} DEPRECATED_flareListeners={listener} />;
1032
- };
1033
- ReactDOM.render(<Component />, container);
1034
-
1035
- const target = createEventTarget(ref.current);
1036
- target.pointerdown();
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();
989
const ref = React.createRef();
990
991
const Component = () => {
@@ -1054,59 +995,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
995
ReactDOM.render(<Component />, container);
996
997
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);
998
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
- );
999
+ target.pointerup();
1000
+ expect(onPress).toBeCalled();
1001
});
1002
1003
// @gate experimental