Skip to content

Commit 7b3934b

Browse files
authored
Revert "Pressable click fix (facebook#18625)" (facebook#18688)
This reverts commit 5f7b175.
1 parent 80d39d8 commit 7b3934b

File tree

3 files changed

+193
-14
lines changed

3 files changed

+193
-14
lines changed

packages/react-dom/src/shared/ReactDOMTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616
} from 'shared/ReactTypes';
1717
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
1818

19-
type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent;
19+
type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
2020

2121
export type PointerType =
2222
| ''

packages/react-interactions/events/src/dom/PressLegacy.js

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type PressState = {
6464
|}>,
6565
ignoreEmulatedMouseEvents: boolean,
6666
activePointerId: null | number,
67+
shouldPreventClick: boolean,
6768
touchEvent: null | Touch,
6869
...
6970
};
@@ -198,6 +199,7 @@ function createPressEvent(
198199
x: clientX,
199200
y: clientY,
200201
preventDefault() {
202+
state.shouldPreventClick = true;
201203
if (nativeEvent) {
202204
pressEvent.defaultPrevented = true;
203205
nativeEvent.preventDefault();
@@ -227,7 +229,8 @@ function dispatchEvent(
227229
const target = ((state.pressTarget: any): Element | Document);
228230
const pointerType = state.pointerType;
229231
const defaultPrevented =
230-
event != null && event.nativeEvent.defaultPrevented === true;
232+
(event != null && event.nativeEvent.defaultPrevented === true) ||
233+
(name === 'press' && state.shouldPreventClick);
231234
const touchEvent = state.touchEvent;
232235
const syntheticEvent = createPressEvent(
233236
context,
@@ -526,6 +529,7 @@ const pressResponderImpl = {
526529
responderRegionOnDeactivation: null,
527530
ignoreEmulatedMouseEvents: false,
528531
activePointerId: null,
532+
shouldPreventClick: false,
529533
touchEvent: null,
530534
};
531535
},
@@ -563,6 +567,7 @@ const pressResponderImpl = {
563567
return;
564568
}
565569

570+
state.shouldPreventClick = false;
566571
if (isTouchEvent) {
567572
state.ignoreEmulatedMouseEvents = true;
568573
} else if (isKeyboardEvent) {
@@ -582,6 +587,7 @@ const pressResponderImpl = {
582587
!altKey
583588
) {
584589
nativeEvent.preventDefault();
590+
state.shouldPreventClick = true;
585591
}
586592
} else {
587593
return;
@@ -639,6 +645,9 @@ const pressResponderImpl = {
639645
}
640646

641647
case 'click': {
648+
if (state.shouldPreventClick) {
649+
nativeEvent.preventDefault();
650+
}
642651
const onPress = props.onPress;
643652

644653
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
@@ -742,6 +751,7 @@ const pressResponderImpl = {
742751
case 'touchend': {
743752
if (isPressed) {
744753
const buttons = state.buttons;
754+
let isKeyboardEvent = false;
745755
let touchEvent;
746756
if (
747757
type === 'pointerup' &&
@@ -760,13 +770,79 @@ const pressResponderImpl = {
760770
if (!isValidKeyboardEvent(nativeEvent)) {
761771
return;
762772
}
773+
isKeyboardEvent = true;
763774
removeRootEventTypes(context, state);
764775
} else if (buttons === 4) {
765776
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
766777
removeRootEventTypes(context, state);
767778
}
768779

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;
769807
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+
}
770846
state.touchEvent = null;
771847
} else if (type === 'mouseup') {
772848
state.ignoreEmulatedMouseEvents = false;
@@ -779,12 +855,6 @@ const pressResponderImpl = {
779855
if (previousPointerType !== 'keyboard') {
780856
removeRootEventTypes(context, state);
781857
}
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-
}
788858
break;
789859
}
790860

packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,15 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
469469
});
470470

471471
// @gate experimental
472-
it('is called after valid "click" event', () => {
472+
it('is called after valid "keyup" event', () => {
473473
componentInit();
474474
const target = createEventTarget(ref.current);
475-
target.pointerdown();
476-
target.pointerup();
475+
target.keydown({key: 'Enter'});
476+
target.keyup({key: 'Enter'});
477477
expect(onPress).toHaveBeenCalledTimes(1);
478+
expect(onPress).toHaveBeenCalledWith(
479+
expect.objectContaining({pointerType: 'keyboard', type: 'press'}),
480+
);
478481
});
479482

480483
// @gate experimental
@@ -801,6 +804,40 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
801804
});
802805
});
803806

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+
804841
// @gate experimental
805842
it('"onPress" is called on re-entry to hit rect', () => {
806843
componentInit();
@@ -889,8 +926,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
889926
'pointerdown',
890927
'inner: onPressEnd',
891928
'inner: onPressChange',
892-
'pointerup',
893929
'inner: onPress',
930+
'pointerup',
894931
]);
895932
});
896933
}
@@ -986,6 +1023,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
9861023
// @gate experimental
9871024
it('prevents native behavior by default', () => {
9881025
const onPress = jest.fn();
1026+
const preventDefault = jest.fn();
9891027
const ref = React.createRef();
9901028

9911029
const Component = () => {
@@ -996,8 +1034,79 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
9961034

9971035
const target = createEventTarget(ref.current);
9981036
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+
);
10011110
});
10021111

10031112
// @gate experimental

0 commit comments

Comments
 (0)