@@ -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