Skip to content

Commit 0b34311

Browse files
authored
React events: fix press end event dispatching (#15500)
This patch fixes an issue related to determining whether the end event occurs within the responder region. Previously we only checked if the event target was within the responder region for moves, otherwise we checked if the target was within the event component. Since the dimensions of the child element can change after activation, we need to recalculate the responder region before deactivation as well if the target is not within the event component.
1 parent d1f667a commit 0b34311

File tree

3 files changed

+262
-53
lines changed

3 files changed

+262
-53
lines changed

packages/react-events/src/Press.js

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ type PressState = {
5252
isPressWithinResponderRegion: boolean,
5353
longPressTimeout: null | Symbol,
5454
pointerType: PointerType,
55-
pressTarget: null | Element | Document,
55+
pressTarget: null | Element,
5656
pressEndTimeout: null | Symbol,
5757
pressStartTimeout: null | Symbol,
58-
responderRegion: null | $ReadOnly<{|
58+
responderRegionOnActivation: null | $ReadOnly<{|
59+
bottom: number,
60+
left: number,
61+
right: number,
62+
top: number,
63+
|}>,
64+
responderRegionOnDeactivation: null | $ReadOnly<{|
5965
bottom: number,
6066
left: number,
6167
right: number,
@@ -312,7 +318,7 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
312318
}
313319

314320
// TODO: account for touch hit slop
315-
function calculateResponderRegion(target, props) {
321+
function calculateResponderRegion(target: Element, props: PressProps) {
316322
const pressRetentionOffset = {
317323
...DEFAULT_PRESS_RETENTION_OFFSET,
318324
...props.pressRetentionOffset,
@@ -352,15 +358,33 @@ function isPressWithinResponderRegion(
352358
nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
353359
state: PressState,
354360
): boolean {
355-
const {responderRegion} = state;
361+
const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
356362
const event = (nativeEvent: any);
363+
let left, top, right, bottom;
364+
365+
if (responderRegionOnActivation != null) {
366+
left = responderRegionOnActivation.left;
367+
top = responderRegionOnActivation.top;
368+
right = responderRegionOnActivation.right;
369+
bottom = responderRegionOnActivation.bottom;
370+
371+
if (responderRegionOnDeactivation != null) {
372+
left = Math.min(left, responderRegionOnDeactivation.left);
373+
top = Math.min(top, responderRegionOnDeactivation.top);
374+
right = Math.max(right, responderRegionOnDeactivation.right);
375+
bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
376+
}
377+
}
357378

358379
return (
359-
responderRegion != null &&
360-
(event.pageX >= responderRegion.left &&
361-
event.pageX <= responderRegion.right &&
362-
event.pageY >= responderRegion.top &&
363-
event.pageY <= responderRegion.bottom)
380+
left != null &&
381+
right != null &&
382+
top != null &&
383+
bottom != null &&
384+
(event.pageX >= left &&
385+
event.pageX <= right &&
386+
event.pageY >= top &&
387+
event.pageY <= bottom)
364388
);
365389
}
366390

@@ -408,7 +432,8 @@ const PressResponder = {
408432
pressEndTimeout: null,
409433
pressStartTimeout: null,
410434
pressTarget: null,
411-
responderRegion: null,
435+
responderRegionOnActivation: null,
436+
responderRegionOnDeactivation: null,
412437
ignoreEmulatedMouseEvents: false,
413438
};
414439
},
@@ -469,7 +494,11 @@ const PressResponder = {
469494
}
470495

471496
state.pointerType = pointerType;
472-
state.pressTarget = target;
497+
state.pressTarget = getEventCurrentTarget(event, context);
498+
state.responderRegionOnActivation = calculateResponderRegion(
499+
state.pressTarget,
500+
props,
501+
);
473502
state.isPressWithinResponderRegion = true;
474503
dispatchPressStartEvents(context, props, state);
475504
context.addRootEventTypes(rootEventTypes);
@@ -519,26 +548,34 @@ const PressResponder = {
519548
case 'touchmove': {
520549
if (state.isPressed) {
521550
// Ignore emulated events (pointermove will dispatch touch and mouse events)
522-
// Ignore pointermove events during a keyboard press
551+
// Ignore pointermove events during a keyboard press.
523552
if (state.pointerType !== pointerType) {
524553
return;
525554
}
526555

527-
if (state.responderRegion == null) {
528-
state.responderRegion = calculateResponderRegion(
529-
getEventCurrentTarget(event, context),
556+
// Calculate the responder region we use for deactivation, as the
557+
// element dimensions may have changed since activation.
558+
if (
559+
state.pressTarget !== null &&
560+
state.responderRegionOnDeactivation == null
561+
) {
562+
state.responderRegionOnDeactivation = calculateResponderRegion(
563+
state.pressTarget,
530564
props,
531565
);
532566
}
533-
if (isPressWithinResponderRegion(nativeEvent, state)) {
534-
state.isPressWithinResponderRegion = true;
567+
state.isPressWithinResponderRegion = isPressWithinResponderRegion(
568+
nativeEvent,
569+
state,
570+
);
571+
572+
if (state.isPressWithinResponderRegion) {
535573
if (props.onPressMove) {
536574
dispatchEvent(context, state, 'pressmove', props.onPressMove, {
537575
discrete: false,
538576
});
539577
}
540578
} else {
541-
state.isPressWithinResponderRegion = false;
542579
dispatchPressEndEvents(context, props, state);
543580
}
544581
}
@@ -551,18 +588,38 @@ const PressResponder = {
551588
case 'mouseup':
552589
case 'touchend': {
553590
if (state.isPressed) {
554-
// Ignore unrelated keyboard events
591+
// Ignore unrelated keyboard events and verify press is within
592+
// responder region for non-keyboard events.
555593
if (pointerType === 'keyboard') {
556594
if (!isValidKeyPress(nativeEvent.key)) {
557595
return;
558596
}
597+
// If the event target isn't within the press target, check if we're still
598+
// within the responder region. The region may have changed if the
599+
// element's layout was modified after activation.
600+
} else if (
601+
state.pressTarget != null &&
602+
!context.isTargetWithinElement(target, state.pressTarget)
603+
) {
604+
// Calculate the responder region we use for deactivation if not
605+
// already done during move event.
606+
if (state.responderRegionOnDeactivation == null) {
607+
state.responderRegionOnDeactivation = calculateResponderRegion(
608+
state.pressTarget,
609+
props,
610+
);
611+
}
612+
state.isPressWithinResponderRegion = isPressWithinResponderRegion(
613+
nativeEvent,
614+
state,
615+
);
559616
}
560617

561618
const wasLongPressed = state.isLongPressed;
562619
dispatchPressEndEvents(context, props, state);
563620

564621
if (state.pressTarget !== null && props.onPress) {
565-
if (context.isTargetWithinElement(target, state.pressTarget)) {
622+
if (state.isPressWithinResponderRegion) {
566623
if (
567624
!(
568625
wasLongPressed &&

0 commit comments

Comments
 (0)