@@ -13,6 +13,11 @@ import type {
13
13
ReactResponderDispatchEventOptions ,
14
14
} from 'shared/ReactTypes' ;
15
15
import { REACT_EVENT_COMPONENT_TYPE } from 'shared/ReactSymbols' ;
16
+ import {
17
+ getEventPointerType ,
18
+ getEventCurrentTarget ,
19
+ isEventPositionWithinTouchHitTarget ,
20
+ } from './utils' ;
16
21
17
22
const CAPTURE_PHASE = 2 ;
18
23
@@ -44,7 +49,6 @@ type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
44
49
type PressState = {
45
50
isActivePressed : boolean ,
46
51
isActivePressStart : boolean ,
47
- isAnchorTouched : boolean ,
48
52
isLongPressed : boolean ,
49
53
isPressed : boolean ,
50
54
isPressWithinResponderRegion : boolean ,
@@ -59,7 +63,7 @@ type PressState = {
59
63
right : number ,
60
64
top : number ,
61
65
| } > ,
62
- shouldSkipMouseAfterTouch : boolean ,
66
+ ignoreEmulatedMouseEvents : boolean ,
63
67
} ;
64
68
65
69
type PressEventType =
@@ -355,23 +359,6 @@ function calculateResponderRegion(target, props) {
355
359
} ;
356
360
}
357
361
358
- function getPointerType ( nativeEvent : any ) {
359
- const { type , pointerType } = nativeEvent ;
360
- if ( pointerType != null ) {
361
- return pointerType ;
362
- }
363
- if ( type . indexOf ( 'mouse' ) > - 1 ) {
364
- return 'mouse ';
365
- }
366
- if ( type . indexOf ( 'touch' ) > - 1 ) {
367
- return 'touch ';
368
- }
369
- if ( type . indexOf ( 'key' ) > - 1 ) {
370
- return 'keyboard ';
371
- }
372
- return '';
373
- }
374
-
375
362
function isPressWithinResponderRegion (
376
363
nativeEvent : $PropertyType < ReactResponderEvent , 'nativeEvent' > ,
377
364
state : PressState ,
@@ -406,7 +393,6 @@ const PressResponder = {
406
393
didDispatchEvent : false ,
407
394
isActivePressed : false ,
408
395
isActivePressStart : false ,
409
- isAnchorTouched : false ,
410
396
isLongPressed : false ,
411
397
isPressed : false ,
412
398
isPressWithinResponderRegion : true ,
@@ -416,7 +402,7 @@ const PressResponder = {
416
402
pressStartTimeout : null ,
417
403
pressTarget : null ,
418
404
responderRegion : null ,
419
- shouldSkipMouseAfterTouch : false ,
405
+ ignoreEmulatedMouseEvents : false ,
420
406
} ;
421
407
} ,
422
408
onEvent (
@@ -431,70 +417,80 @@ const PressResponder = {
431
417
if ( phase === CAPTURE_PHASE ) {
432
418
return false ;
433
419
}
420
+
434
421
const nativeEvent : any = event . nativeEvent ;
422
+ const pointerType = getEventPointerType ( event ) ;
435
423
const shouldStopPropagation =
436
424
props . stopPropagation === undefined ? true : props . stopPropagation ;
437
425
438
426
switch ( type ) {
439
- /**
440
- * Respond to pointer events and fall back to mouse.
441
- */
427
+ // START
442
428
case 'pointerdown ':
443
- case 'mousedown ': {
444
- if ( ! state . isPressed && ! state . shouldSkipMouseAfterTouch ) {
445
- const pointerType = getPointerType ( nativeEvent ) ;
446
- state . pointerType = pointerType ;
429
+ case 'keydown ':
430
+ case 'keypress ':
431
+ case 'mousedown ':
432
+ case 'touchstart ': {
433
+ if ( ! state . isPressed ) {
434
+ if ( type === 'pointerdown' || type === 'touchstart' ) {
435
+ state . ignoreEmulatedMouseEvents = true ;
436
+ }
437
+
438
+ // Ignore unrelated key events
439
+ if ( pointerType === 'keyboard' ) {
440
+ if ( ! isValidKeyPress ( nativeEvent . key ) ) {
441
+ return shouldStopPropagation ;
442
+ }
443
+ }
447
444
448
- // Ignore pressing on hit slop area with mouse
449
- if (
450
- ( pointerType === 'mouse' || type === 'mousedown' ) &&
451
- context . isPositionWithinTouchHitTarget (
452
- target . ownerDocument ,
453
- nativeEvent . x ,
454
- nativeEvent . y ,
455
- )
456
- ) {
457
- return false ;
445
+ // Ignore emulated mouse events and mouse pressing on touch hit target
446
+ // area
447
+ if ( type === 'mousedown' ) {
448
+ if (
449
+ state . ignoreEmulatedMouseEvents ||
450
+ isEventPositionWithinTouchHitTarget ( event , context )
451
+ ) {
452
+ return shouldStopPropagation ;
453
+ }
458
454
}
459
455
460
456
// Ignore any device buttons except left-mouse and touch/pen contact
461
457
if ( nativeEvent . button > 0 ) {
462
458
return shouldStopPropagation ;
463
459
}
464
460
461
+ state . pointerType = pointerType ;
465
462
state . pressTarget = target ;
466
463
state . isPressWithinResponderRegion = true ;
467
464
dispatchPressStartEvents ( context , props , state ) ;
468
465
context . addRootEventTypes ( target . ownerDocument , rootEventTypes ) ;
469
466
return shouldStopPropagation ;
467
+ } else {
468
+ // Prevent spacebar press from scrolling the window
469
+ if ( isValidKeyPress ( nativeEvent . key ) && nativeEvent . key === ' ' ) {
470
+ nativeEvent . preventDefault ( ) ;
471
+ return shouldStopPropagation ;
472
+ }
470
473
}
471
- return false ;
474
+ return shouldStopPropagation ;
472
475
}
476
+
477
+ // MOVE
473
478
case 'pointermove ':
474
479
case 'mousemove ':
475
480
case 'touchmove ': {
476
481
if ( state . isPressed ) {
477
- if ( state . shouldSkipMouseAfterTouch ) {
482
+ // Ignore emulated events (pointermove will dispatch touch and mouse events)
483
+ // Ignore pointermove events during a keyboard press
484
+ if ( state . pointerType !== pointerType ) {
478
485
return shouldStopPropagation ;
479
486
}
480
487
481
- const pointerType = getPointerType ( nativeEvent ) ;
482
- state . pointerType = pointerType ;
483
-
484
488
if ( state . responderRegion == null ) {
485
- let currentTarget = ( target : any ) ;
486
- while (
487
- currentTarget . parentNode &&
488
- context . isTargetWithinEventComponent ( currentTarget . parentNode )
489
- ) {
490
- currentTarget = currentTarget . parentNode ;
491
- }
492
489
state . responderRegion = calculateResponderRegion (
493
- currentTarget ,
490
+ getEventCurrentTarget ( event , context ) ,
494
491
props ,
495
492
) ;
496
493
}
497
-
498
494
if ( isPressWithinResponderRegion ( nativeEvent , state ) ) {
499
495
state . isPressWithinResponderRegion = true ;
500
496
if ( props . onPressMove ) {
@@ -510,19 +506,21 @@ const PressResponder = {
510
506
}
511
507
return false ;
512
508
}
509
+
510
+ // END
513
511
case 'pointerup' :
514
- case 'mouseup ': {
512
+ case 'keyup ':
513
+ case 'mouseup ':
514
+ case 'touchend ': {
515
515
if ( state . isPressed ) {
516
- if ( state . shouldSkipMouseAfterTouch ) {
517
- state . shouldSkipMouseAfterTouch = false ;
518
- return shouldStopPropagation ;
516
+ // Ignore unrelated keyboard events
517
+ if ( pointerType === 'keyboard' ) {
518
+ if ( ! isValidKeyPress ( nativeEvent . key ) ) {
519
+ return false ;
520
+ }
519
521
}
520
522
521
- const pointerType = getPointerType ( nativeEvent ) ;
522
- state . pointerType = pointerType ;
523
-
524
523
const wasLongPressed = state . isLongPressed ;
525
-
526
524
dispatchPressEndEvents ( context , props , state ) ;
527
525
528
526
if ( state . pressTarget !== null && props . onPress ) {
@@ -540,128 +538,25 @@ const PressResponder = {
540
538
}
541
539
context . removeRootEventTypes ( rootEventTypes ) ;
542
540
return shouldStopPropagation ;
543
- }
544
- state . isAnchorTouched = false ;
545
- state . shouldSkipMouseAfterTouch = false ;
546
- return false ;
547
- }
548
-
549
- /**
550
- * Touch event implementations are only needed for Safari, which lacks
551
- * support for pointer events.
552
- */
553
- case 'touchstart' : {
554
- if ( ! state . isPressed ) {
555
- // We bail out of polyfilling anchor tags, given the same heuristics
556
- // explained above in regards to needing to use click events.
557
- if ( isAnchorTagElement ( target ) ) {
558
- state . isAnchorTouched = true ;
559
- return shouldStopPropagation ;
560
- }
561
- const pointerType = getPointerType ( nativeEvent ) ;
562
- state . pointerType = pointerType ;
563
- state . pressTarget = target ;
564
- state . isPressWithinResponderRegion = true ;
565
- dispatchPressStartEvents ( context , props , state ) ;
566
- context . addRootEventTypes ( target . ownerDocument , rootEventTypes ) ;
567
- return shouldStopPropagation ;
568
- }
569
- return false ;
570
- }
571
- case 'touchend' : {
572
- if ( state . isAnchorTouched ) {
573
- state . isAnchorTouched = false ;
574
- return shouldStopPropagation ;
575
- }
576
- if ( state . isPressed ) {
577
- const pointerType = getPointerType ( nativeEvent ) ;
578
- state . pointerType = pointerType ;
579
-
580
- const wasLongPressed = state . isLongPressed ;
581
-
582
- dispatchPressEndEvents ( context , props , state ) ;
583
-
584
- if ( type !== 'touchcancel' && props . onPress ) {
585
- // Find if the X/Y of the end touch is still that of the original target
586
- const changedTouch = nativeEvent . changedTouches [ 0 ] ;
587
- const doc = ( target : any ) . ownerDocument ;
588
- const fromTarget = doc . elementFromPoint (
589
- changedTouch . screenX ,
590
- changedTouch . screenY ,
591
- ) ;
592
- if (
593
- fromTarget !== null &&
594
- context . isTargetWithinEventComponent ( fromTarget )
595
- ) {
596
- if (
597
- ! (
598
- wasLongPressed &&
599
- props . onLongPressShouldCancelPress &&
600
- props . onLongPressShouldCancelPress ( )
601
- )
602
- ) {
603
- dispatchEvent ( context , state , 'press' , props . onPress ) ;
604
- }
605
- }
606
- }
607
- state . shouldSkipMouseAfterTouch = true ;
608
- context . removeRootEventTypes ( rootEventTypes ) ;
609
- return shouldStopPropagation ;
610
- }
611
- return false ;
612
- }
613
-
614
- /**
615
- * Keyboard interaction support
616
- * TODO: determine UX for metaKey + validKeyPress interactions
617
- */
618
- case 'keydown' :
619
- case 'keypress ': {
620
- if ( isValidKeyPress ( nativeEvent . key ) ) {
621
- if ( state . isPressed ) {
622
- // Prevent spacebar press from scrolling the window
623
- if ( nativeEvent . key === ' ' ) {
624
- nativeEvent . preventDefault ( ) ;
625
- }
626
- } else {
627
- const pointerType = getPointerType ( nativeEvent ) ;
628
- state . pointerType = pointerType ;
629
- state . pressTarget = target ;
630
- dispatchPressStartEvents ( context , props , state ) ;
631
- context . addRootEventTypes ( target . ownerDocument , rootEventTypes ) ;
632
- }
633
- return shouldStopPropagation ;
634
- }
635
- return false ;
636
- }
637
- case 'keyup' : {
638
- if ( state . isPressed && isValidKeyPress ( nativeEvent . key ) ) {
639
- const wasLongPressed = state . isLongPressed ;
640
- dispatchPressEndEvents ( context , props , state ) ;
641
- if ( state . pressTarget !== null && props . onPress ) {
642
- if (
643
- ! (
644
- wasLongPressed &&
645
- props . onLongPressShouldCancelPress &&
646
- props . onLongPressShouldCancelPress ( )
647
- )
648
- ) {
649
- dispatchEvent ( context , state , 'press' , props . onPress ) ;
650
- }
651
- }
652
- context . removeRootEventTypes ( rootEventTypes ) ;
653
- return shouldStopPropagation ;
541
+ } else if ( type === 'mouseup' && state . ignoreEmulatedMouseEvents ) {
542
+ state . ignoreEmulatedMouseEvents = false ;
654
543
}
655
544
return false ;
656
545
}
657
546
547
+ // CANCEL
548
+ case 'contextmenu' :
658
549
case 'pointercancel ':
659
550
case 'scroll ':
660
551
case 'touchcancel ': {
661
552
if ( state . isPressed ) {
662
- state . shouldSkipMouseAfterTouch = false ;
663
- dispatchPressEndEvents ( context , props , state ) ;
664
- context . removeRootEventTypes ( rootEventTypes ) ;
553
+ if ( type === 'contextmenu' && props . preventDefault !== false ) {
554
+ nativeEvent . preventDefault ( ) ;
555
+ } else {
556
+ state . ignoreEmulatedMouseEvents = false ;
557
+ dispatchPressEndEvents ( context , props , state ) ;
558
+ context . removeRootEventTypes ( rootEventTypes ) ;
559
+ }
665
560
return shouldStopPropagation ;
666
561
}
667
562
return false ;
@@ -679,20 +574,6 @@ const PressResponder = {
679
574
}
680
575
return false ;
681
576
}
682
-
683
- case 'contextmenu ': {
684
- if ( state . isPressed ) {
685
- if ( props . preventDefault !== false ) {
686
- nativeEvent . preventDefault ( ) ;
687
- } else {
688
- state . shouldSkipMouseAfterTouch = false ;
689
- dispatchPressEndEvents ( context , props , state ) ;
690
- context . removeRootEventTypes ( rootEventTypes ) ;
691
- }
692
- return shouldStopPropagation ;
693
- }
694
- return false ;
695
- }
696
577
}
697
578
return false ;
698
579
} ,
0 commit comments