Skip to content

Commit d2e6c29

Browse files
committed
[fix] Pan interactions should cancel 'click' events on the target
If a pan interaction has taken place, it is not expected that 'click' events occur on the target element when the pointer is released (as was occuring with mouse pointers). This patch cancels any 'click' that occurs within the pan target's subtree, within 250ms of the pan gesture ending. Fix #1788
1 parent 03897d3 commit d2e6c29

File tree

1 file changed

+36
-20
lines changed
  • packages/react-native-web/src/vendor/react-native/PanResponder

1 file changed

+36
-20
lines changed

packages/react-native-web/src/vendor/react-native/PanResponder/index.js

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ type ActiveCallback = (
188188
gestureState: GestureState,
189189
) => boolean;
190190

191+
type InteractionState = {handle: ?number, shouldCancelClick: boolean, timeout: ?TimeoutID};
192+
191193
type PassiveCallback = (event: PressEvent, gestureState: GestureState) => mixed;
192194

193195
type PanResponderConfig = $ReadOnly<{|
@@ -384,9 +386,12 @@ const PanResponder = {
384386
* are the responder.
385387
*/
386388
create(config: PanResponderConfig) {
387-
const interactionState = {
388-
handle: (null: ?number),
389+
const interactionState: InteractionState = {
390+
handle: null,
391+
shouldCancelClick: false,
392+
timeout: null,
389393
};
394+
390395
const gestureState: GestureState = {
391396
// Useful for debugging
392397
stateID: Math.random(),
@@ -427,15 +432,6 @@ const PanResponder = {
427432

428433
onMoveShouldSetResponderCapture(event: PressEvent): boolean {
429434
const touchHistory = event.touchHistory;
430-
// Responder system incorrectly dispatches should* to current responder
431-
// Filter out any touch moves past the first one - we would have
432-
// already processed multi-touch geometry during the first event.
433-
if (
434-
gestureState._accountsForMovesUpTo ===
435-
touchHistory.mostRecentTimeStamp
436-
) {
437-
return false;
438-
}
439435
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
440436
return config.onMoveShouldSetPanResponderCapture
441437
? config.onMoveShouldSetPanResponderCapture(event, gestureState)
@@ -446,6 +442,10 @@ const PanResponder = {
446442
if (!interactionState.handle) {
447443
interactionState.handle = InteractionManager.createInteractionHandle();
448444
}
445+
if (interactionState.timeout) {
446+
clearInteractionTimeout(interactionState);
447+
}
448+
interactionState.shouldCancelClick = true;
449449
gestureState.x0 = currentCentroidX(event.touchHistory);
450450
gestureState.y0 = currentCentroidY(event.touchHistory);
451451
gestureState.dx = 0;
@@ -475,6 +475,7 @@ const PanResponder = {
475475
event,
476476
gestureState,
477477
);
478+
setInteractionTimeout(interactionState);
478479
PanResponder._initializeGestureState(gestureState);
479480
},
480481

@@ -488,14 +489,6 @@ const PanResponder = {
488489

489490
onResponderMove(event: PressEvent): void {
490491
const touchHistory = event.touchHistory;
491-
// Guard against the dispatch of two touch moves when there are two
492-
// simultaneously changed touches.
493-
if (
494-
gestureState._accountsForMovesUpTo ===
495-
touchHistory.mostRecentTimeStamp
496-
) {
497-
return;
498-
}
499492
// Filter out any touch moves past the first one - we would have
500493
// already processed multi-touch geometry during the first event.
501494
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
@@ -522,6 +515,7 @@ const PanResponder = {
522515
event,
523516
gestureState,
524517
);
518+
setInteractionTimeout(interactionState);
525519
PanResponder._initializeGestureState(gestureState);
526520
},
527521

@@ -530,7 +524,19 @@ const PanResponder = {
530524
? true
531525
: config.onPanResponderTerminationRequest(event, gestureState);
532526
},
527+
528+
// We do not want to trigger 'click' activated gestures or native behaviors
529+
// on any pan target that is under a mouse cursor when it is released.
530+
// Browsers will natively cancel 'click' events on a target if a non-mouse
531+
// active pointer moves.
532+
onClickCapture: (event: any): void => {
533+
if (interactionState.shouldCancelClick === true) {
534+
event.stopPropagation();
535+
event.preventDefault();
536+
}
537+
},
533538
};
539+
534540
return {
535541
panHandlers,
536542
getInteractionHandle(): ?number {
@@ -541,7 +547,7 @@ const PanResponder = {
541547
};
542548

543549
function clearInteractionHandle(
544-
interactionState: {handle: ?number},
550+
interactionState: InteractionState,
545551
callback: ?(ActiveCallback | PassiveCallback),
546552
event: PressEvent,
547553
gestureState: GestureState,
@@ -555,6 +561,16 @@ function clearInteractionHandle(
555561
}
556562
}
557563

564+
function clearInteractionTimeout(interactionState: InteractionState) {
565+
clearTimeout(interactionState.timeout);
566+
}
567+
568+
function setInteractionTimeout(interactionState: InteractionState) {
569+
interactionState.timeout = setTimeout(() => {
570+
interactionState.shouldCancelClick = false;
571+
}, 250);
572+
}
573+
558574
export type PanResponderInstance = $Call<
559575
$PropertyType<typeof PanResponder, 'create'>,
560576
PanResponderConfig,

0 commit comments

Comments
 (0)