@@ -36,6 +36,8 @@ type PressProps = {
36
36
stopPropagation : boolean ,
37
37
} ;
38
38
39
+ type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch' ;
40
+
39
41
type PressState = {
40
42
didDispatchEvent : boolean ,
41
43
isActivePressed : boolean ,
@@ -45,6 +47,7 @@ type PressState = {
45
47
isPressed : boolean ,
46
48
isPressWithinResponderRegion : boolean ,
47
49
longPressTimeout : null | Symbol ,
50
+ pointerType : PointerType ,
48
51
pressTarget : null | Element | Document ,
49
52
pressEndTimeout : null | Symbol ,
50
53
pressStartTimeout : null | Symbol ,
@@ -70,6 +73,7 @@ type PressEvent = {|
70
73
listener : PressEvent => void ,
71
74
target : Element | Document ,
72
75
type : PressEventType ,
76
+ pointerType : PointerType ,
73
77
| } ;
74
78
75
79
const DEFAULT_PRESS_END_DELAY_MS = 0 ;
@@ -85,9 +89,10 @@ const DEFAULT_PRESS_RETENTION_OFFSET = {
85
89
const targetEventTypes = [
86
90
{ name : 'click' , passive : false } ,
87
91
{ name : 'keydown' , passive : false } ,
92
+ { name : 'keypress' , passive : false } ,
93
+ { name : 'contextmenu' , passive : false } ,
88
94
'pointerdown' ,
89
95
'pointercancel' ,
90
- 'contextmenu' ,
91
96
] ;
92
97
const rootEventTypes = [
93
98
{ name : 'keyup' , passive : false } ,
@@ -110,11 +115,13 @@ function createPressEvent(
110
115
type : PressEventType ,
111
116
target : Element | Document ,
112
117
listener : PressEvent => void ,
118
+ pointerType : PointerType ,
113
119
) : PressEvent {
114
120
return {
115
121
listener,
116
122
target,
117
123
type,
124
+ pointerType,
118
125
} ;
119
126
}
120
127
@@ -125,7 +132,8 @@ function dispatchEvent(
125
132
listener : ( e : Object ) = > void ,
126
133
) : void {
127
134
const target = ( ( state . pressTarget : any ) : Element | Document ) ;
128
- const syntheticEvent = createPressEvent ( name , target , listener ) ;
135
+ const pointerType = state . pointerType ;
136
+ const syntheticEvent = createPressEvent ( name , target , listener , pointerType ) ;
129
137
context . dispatchEvent ( syntheticEvent , {
130
138
discrete : true ,
131
139
} ) ;
@@ -137,8 +145,9 @@ function dispatchPressChangeEvent(
137
145
props : PressProps ,
138
146
state : PressState ,
139
147
) : void {
148
+ const bool = state . isActivePressed ;
140
149
const listener = ( ) => {
141
- props . onPressChange ( state . isActivePressed ) ;
150
+ props . onPressChange ( bool ) ;
142
151
} ;
143
152
dispatchEvent ( context , state , 'presschange' , listener ) ;
144
153
}
@@ -148,8 +157,9 @@ function dispatchLongPressChangeEvent(
148
157
props : PressProps ,
149
158
state : PressState ,
150
159
) : void {
160
+ const bool = state . isLongPressed ;
151
161
const listener = ( ) => {
152
- props . onLongPressChange ( state . isLongPressed ) ;
162
+ props . onLongPressChange ( bool ) ;
153
163
} ;
154
164
dispatchEvent ( context , state , 'longpresschange' , listener ) ;
155
165
}
@@ -251,6 +261,7 @@ function dispatchPressEndEvents(
251
261
state : PressState ,
252
262
) : void {
253
263
const wasActivePressStart = state . isActivePressStart ;
264
+ let activationWasForced = false ;
254
265
255
266
state . isActivePressStart = false ;
256
267
state . isPressed = false ;
@@ -267,13 +278,17 @@ function dispatchPressEndEvents(
267
278
if ( state . isPressWithinResponderRegion ) {
268
279
// if we haven't yet activated (due to delays), activate now
269
280
activate ( context , props , state ) ;
281
+ activationWasForced = true ;
270
282
}
271
283
}
272
284
273
285
if ( state . isActivePressed ) {
274
286
const delayPressEnd = calculateDelayMS (
275
287
props . delayPressEnd ,
276
- 0 ,
288
+ // if activation and deactivation occur during the same event there's no
289
+ // time for visual user feedback therefore a small delay is added before
290
+ // deactivating.
291
+ activationWasForced ? 10 : 0 ,
277
292
DEFAULT_PRESS_END_DELAY_MS ,
278
293
) ;
279
294
if ( delayPressEnd > 0 ) {
@@ -338,6 +353,23 @@ function calculateResponderRegion(target, props) {
338
353
} ;
339
354
}
340
355
356
+ function getPointerType ( nativeEvent : any ) {
357
+ const { type , pointerType } = nativeEvent ;
358
+ if ( pointerType != null ) {
359
+ return pointerType ;
360
+ }
361
+ if ( type . indexOf ( 'mouse' ) > - 1 ) {
362
+ return 'mouse ';
363
+ }
364
+ if ( type . indexOf ( 'touch' ) > - 1 ) {
365
+ return 'touch ';
366
+ }
367
+ if ( type . indexOf ( 'key' ) > - 1 ) {
368
+ return 'keyboard ';
369
+ }
370
+ return '';
371
+ }
372
+
341
373
function isPressWithinResponderRegion (
342
374
nativeEvent : $PropertyType < ReactResponderEvent , 'nativeEvent' > ,
343
375
state : PressState ,
@@ -377,6 +409,7 @@ const PressResponder = {
377
409
isPressed : false ,
378
410
isPressWithinResponderRegion : true ,
379
411
longPressTimeout : null ,
412
+ pointerType : '',
380
413
pressEndTimeout : null ,
381
414
pressStartTimeout : null ,
382
415
pressTarget : null ,
@@ -403,10 +436,10 @@ const PressResponder = {
403
436
! context . hasOwnership ( ) &&
404
437
! state . shouldSkipMouseAfterTouch
405
438
) {
406
- if (
407
- ( nativeEvent : any ) . pointerType === 'mouse' ||
408
- type === 'mousedown'
409
- ) {
439
+ const pointerType = getPointerType ( nativeEvent ) ;
440
+ state . pointerType = pointerType ;
441
+
442
+ if ( pointerType === 'mouse' || type === 'mousedown' ) {
410
443
if (
411
444
// Ignore right- and middle-clicks
412
445
nativeEvent . button === 1 ||
@@ -436,6 +469,9 @@ const PressResponder = {
436
469
return ;
437
470
}
438
471
472
+ const pointerType = getPointerType ( nativeEvent ) ;
473
+ state . pointerType = pointerType ;
474
+
439
475
if ( state . responderRegion == null ) {
440
476
let currentTarget = ( target : any ) ;
441
477
while (
@@ -470,6 +506,9 @@ const PressResponder = {
470
506
return ;
471
507
}
472
508
509
+ const pointerType = getPointerType ( nativeEvent ) ;
510
+ state . pointerType = pointerType ;
511
+
473
512
const wasLongPressed = state . isLongPressed ;
474
513
475
514
dispatchPressEndEvents ( context , props , state ) ;
@@ -506,6 +545,8 @@ const PressResponder = {
506
545
state . isAnchorTouched = true ;
507
546
return ;
508
547
}
548
+ const pointerType = getPointerType ( nativeEvent ) ;
549
+ state . pointerType = pointerType ;
509
550
state . pressTarget = target ;
510
551
state . isPressWithinResponderRegion = true ;
511
552
dispatchPressStartEvents ( context , props , state ) ;
@@ -519,6 +560,9 @@ const PressResponder = {
519
560
return ;
520
561
}
521
562
if ( state . isPressed ) {
563
+ const pointerType = getPointerType ( nativeEvent ) ;
564
+ state . pointerType = pointerType ;
565
+
522
566
const wasLongPressed = state . isLongPressed ;
523
567
524
568
dispatchPressEndEvents ( context , props , state ) ;
@@ -556,20 +600,24 @@ const PressResponder = {
556
600
* Keyboard interaction support
557
601
* TODO: determine UX for metaKey + validKeyPress interactions
558
602
*/
559
- case 'keydown' : {
603
+ case 'keydown' :
604
+ case 'keypress ': {
560
605
if (
561
- ! state . isPressed &&
562
- ! state . isLongPressed &&
563
606
! context . hasOwnership ( ) &&
564
607
isValidKeyPress ( ( nativeEvent : any ) . key )
565
608
) {
566
- // Prevent spacebar press from scrolling the window
567
- if ( ( nativeEvent : any ) . key === ' ' ) {
568
- ( nativeEvent : any ) . preventDefault ( ) ;
609
+ if ( state . isPressed ) {
610
+ // Prevent spacebar press from scrolling the window
611
+ if ( ( nativeEvent : any ) . key === ' ' ) {
612
+ ( nativeEvent : any ) . preventDefault ( ) ;
613
+ }
614
+ } else {
615
+ const pointerType = getPointerType ( nativeEvent ) ;
616
+ state . pointerType = pointerType ;
617
+ state . pressTarget = target ;
618
+ dispatchPressStartEvents ( context , props , state ) ;
619
+ context . addRootEventTypes ( target . ownerDocument , rootEventTypes ) ;
569
620
}
570
- state . pressTarget = target ;
571
- dispatchPressStartEvents ( context , props , state ) ;
572
- context . addRootEventTypes ( target . ownerDocument , rootEventTypes ) ;
573
621
}
574
622
break ;
575
623
}
@@ -593,7 +641,6 @@ const PressResponder = {
593
641
break ;
594
642
}
595
643
596
- case 'contextmenu ':
597
644
case 'pointercancel ':
598
645
case 'scroll ':
599
646
case 'touchcancel ': {
@@ -608,14 +655,29 @@ const PressResponder = {
608
655
case 'click' : {
609
656
if ( isAnchorTagElement ( target ) ) {
610
657
const { ctrlKey , metaKey , shiftKey } = ( ( nativeEvent : any ) : MouseEvent ) ;
658
+ // Check "open in new window/tab" and "open context menu" key modifiers
611
659
const preventDefault = props . preventDefault ;
612
- // Check "open in new window/tab" key modifiers
613
- if ( preventDefault !== false && ! shiftKey && ! ctrlKey && ! metaKey ) {
660
+ if ( preventDefault !== false && ! shiftKey && ! metaKey && ! ctrlKey ) {
614
661
( nativeEvent : any ) . preventDefault ( ) ;
615
662
}
616
663
}
664
+ break ;
665
+ }
666
+
667
+ case 'contextmenu ': {
668
+ if ( state . isPressed ) {
669
+ if ( props . preventDefault !== false ) {
670
+ ( nativeEvent : any ) . preventDefault ( ) ;
671
+ } else {
672
+ state . shouldSkipMouseAfterTouch = false ;
673
+ dispatchPressEndEvents ( context , props , state ) ;
674
+ context . removeRootEventTypes ( rootEventTypes ) ;
675
+ }
676
+ }
677
+ break ;
617
678
}
618
679
}
680
+
619
681
if ( state . didDispatchEvent ) {
620
682
const shouldStopPropagation =
621
683
props . stopPropagation === undefined ? true : props . stopPropagation ;
0 commit comments