16
16
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
17
17
18
18
import { disableTextSelection , restoreTextSelection } from './textSelection' ;
19
- import { DOMAttributes , FocusableElement , PointerType , PressEvents } from '@react-types/shared' ;
19
+ import { DOMAttributes , FocusableElement , PressEvent as IPressEvent , PointerType , PressEvents } from '@react-types/shared' ;
20
20
import { focusWithoutScrolling , isVirtualClick , isVirtualPointerEvent , mergeProps , useEffectEvent , useGlobalListeners , useSyncRef } from '@react-aria/utils' ;
21
21
import { PressResponderContext } from './context' ;
22
22
import { RefObject , useContext , useEffect , useMemo , useRef , useState } from 'react' ;
@@ -84,6 +84,35 @@ function usePressResponderContext(props: PressHookProps): PressHookProps {
84
84
return props ;
85
85
}
86
86
87
+ class PressEvent implements IPressEvent {
88
+ type : IPressEvent [ 'type' ] ;
89
+ pointerType : PointerType ;
90
+ target : Element ;
91
+ shiftKey : boolean ;
92
+ ctrlKey : boolean ;
93
+ metaKey : boolean ;
94
+ altKey : boolean ;
95
+ #shouldStopPropagation = true ;
96
+
97
+ constructor ( type : IPressEvent [ 'type' ] , pointerType : PointerType , originalEvent : EventBase ) {
98
+ this . type = type ;
99
+ this . pointerType = pointerType ;
100
+ this . target = originalEvent . currentTarget as Element ;
101
+ this . shiftKey = originalEvent . shiftKey ;
102
+ this . metaKey = originalEvent . metaKey ;
103
+ this . ctrlKey = originalEvent . ctrlKey ;
104
+ this . altKey = originalEvent . altKey ;
105
+ }
106
+
107
+ continuePropagation ( ) {
108
+ this . #shouldStopPropagation = false ;
109
+ }
110
+
111
+ get shouldStopPropagation ( ) {
112
+ return this . #shouldStopPropagation;
113
+ }
114
+ }
115
+
87
116
/**
88
117
* Handles press interactions across mouse, touch, keyboard, and screen readers.
89
118
* It normalizes behavior across browsers and platforms, and handles many nuances
@@ -126,16 +155,11 @@ export function usePress(props: PressHookProps): PressResult {
126
155
return ;
127
156
}
128
157
158
+ let shouldStopPropagation = true ;
129
159
if ( onPressStart ) {
130
- onPressStart ( {
131
- type : 'pressstart' ,
132
- pointerType,
133
- target : originalEvent . currentTarget as Element ,
134
- shiftKey : originalEvent . shiftKey ,
135
- metaKey : originalEvent . metaKey ,
136
- ctrlKey : originalEvent . ctrlKey ,
137
- altKey : originalEvent . altKey
138
- } ) ;
160
+ let event = new PressEvent ( 'pressstart' , pointerType , originalEvent ) ;
161
+ onPressStart ( event ) ;
162
+ shouldStopPropagation = event . shouldStopPropagation ;
139
163
}
140
164
141
165
if ( onPressChange ) {
@@ -144,6 +168,7 @@ export function usePress(props: PressHookProps): PressResult {
144
168
145
169
state . didFirePressStart = true ;
146
170
setPressed ( true ) ;
171
+ return shouldStopPropagation ;
147
172
} ) ;
148
173
149
174
let triggerPressEnd = useEffectEvent ( ( originalEvent : EventBase , pointerType : PointerType , wasPressed = true ) => {
@@ -155,16 +180,11 @@ export function usePress(props: PressHookProps): PressResult {
155
180
state . ignoreClickAfterPress = true ;
156
181
state . didFirePressStart = false ;
157
182
183
+ let shouldStopPropagation = true ;
158
184
if ( onPressEnd ) {
159
- onPressEnd ( {
160
- type : 'pressend' ,
161
- pointerType,
162
- target : originalEvent . currentTarget as Element ,
163
- shiftKey : originalEvent . shiftKey ,
164
- metaKey : originalEvent . metaKey ,
165
- ctrlKey : originalEvent . ctrlKey ,
166
- altKey : originalEvent . altKey
167
- } ) ;
185
+ let event = new PressEvent ( 'pressend' , pointerType , originalEvent ) ;
186
+ onPressEnd ( event ) ;
187
+ shouldStopPropagation = event . shouldStopPropagation ;
168
188
}
169
189
170
190
if ( onPressChange ) {
@@ -174,16 +194,12 @@ export function usePress(props: PressHookProps): PressResult {
174
194
setPressed ( false ) ;
175
195
176
196
if ( onPress && wasPressed && ! isDisabled ) {
177
- onPress ( {
178
- type : 'press' ,
179
- pointerType,
180
- target : originalEvent . currentTarget as Element ,
181
- shiftKey : originalEvent . shiftKey ,
182
- metaKey : originalEvent . metaKey ,
183
- ctrlKey : originalEvent . ctrlKey ,
184
- altKey : originalEvent . altKey
185
- } ) ;
197
+ let event = new PressEvent ( 'press' , pointerType , originalEvent ) ;
198
+ onPress ( event ) ;
199
+ shouldStopPropagation &&= event . shouldStopPropagation ;
186
200
}
201
+
202
+ return shouldStopPropagation ;
187
203
} ) ;
188
204
189
205
let triggerPressUp = useEffectEvent ( ( originalEvent : EventBase , pointerType : PointerType ) => {
@@ -192,16 +208,12 @@ export function usePress(props: PressHookProps): PressResult {
192
208
}
193
209
194
210
if ( onPressUp ) {
195
- onPressUp ( {
196
- type : 'pressup' ,
197
- pointerType,
198
- target : originalEvent . currentTarget as Element ,
199
- shiftKey : originalEvent . shiftKey ,
200
- metaKey : originalEvent . metaKey ,
201
- ctrlKey : originalEvent . ctrlKey ,
202
- altKey : originalEvent . altKey
203
- } ) ;
211
+ let event = new PressEvent ( 'pressup' , pointerType , originalEvent ) ;
212
+ onPressUp ( event ) ;
213
+ return event . shouldStopPropagation ;
204
214
}
215
+
216
+ return true ;
205
217
} ) ;
206
218
207
219
let cancel = useEffectEvent ( ( e : EventBase ) => {
@@ -235,20 +247,24 @@ export function usePress(props: PressHookProps): PressResult {
235
247
if ( shouldPreventDefaultKeyboard ( e . target as Element , e . key ) ) {
236
248
e . preventDefault ( ) ;
237
249
}
238
- e . stopPropagation ( ) ;
239
250
240
251
// If the event is repeating, it may have started on a different element
241
252
// after which focus moved to the current element. Ignore these events and
242
253
// only handle the first key down event.
254
+ let shouldStopPropagation = true ;
243
255
if ( ! state . isPressed && ! e . repeat ) {
244
256
state . target = e . currentTarget ;
245
257
state . isPressed = true ;
246
- triggerPressStart ( e , 'keyboard' ) ;
258
+ shouldStopPropagation = triggerPressStart ( e , 'keyboard' ) ;
247
259
248
260
// Focus may move before the key up event, so register the event on the document
249
261
// instead of the same element where the key down event occurred.
250
262
addGlobalListener ( document , 'keyup' , onKeyUp , false ) ;
251
263
}
264
+
265
+ if ( shouldStopPropagation ) {
266
+ e . stopPropagation ( ) ;
267
+ }
252
268
} else if ( e . key === 'Enter' && isHTMLAnchorLink ( e . currentTarget ) ) {
253
269
// If the target is a link, we won't have handled this above because we want the default
254
270
// browser behavior to open the link when pressing Enter. But we still need to prevent
@@ -267,7 +283,7 @@ export function usePress(props: PressHookProps): PressResult {
267
283
}
268
284
269
285
if ( e && e . button === 0 ) {
270
- e . stopPropagation ( ) ;
286
+ let shouldStopPropagation = true ;
271
287
if ( isDisabled ) {
272
288
e . preventDefault ( ) ;
273
289
}
@@ -280,13 +296,17 @@ export function usePress(props: PressHookProps): PressResult {
280
296
focusWithoutScrolling ( e . currentTarget ) ;
281
297
}
282
298
283
- triggerPressStart ( e , 'virtual' ) ;
284
- triggerPressUp ( e , 'virtual' ) ;
285
- triggerPressEnd ( e , 'virtual' ) ;
299
+ let stopPressStart = triggerPressStart ( e , 'virtual' ) ;
300
+ let stopPressUp = triggerPressUp ( e , 'virtual' ) ;
301
+ let stopPressEnd = triggerPressEnd ( e , 'virtual' ) ;
302
+ shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd ;
286
303
}
287
304
288
305
state . ignoreEmulatedMouseEvents = false ;
289
306
state . ignoreClickAfterPress = false ;
307
+ if ( shouldStopPropagation ) {
308
+ e . stopPropagation ( ) ;
309
+ }
290
310
}
291
311
}
292
312
} ;
@@ -296,13 +316,16 @@ export function usePress(props: PressHookProps): PressResult {
296
316
if ( shouldPreventDefaultKeyboard ( e . target as Element , e . key ) ) {
297
317
e . preventDefault ( ) ;
298
318
}
299
- e . stopPropagation ( ) ;
300
319
301
320
state . isPressed = false ;
302
321
let target = e . target as Element ;
303
- triggerPressEnd ( createEvent ( state . target , e ) , 'keyboard' , state . target . contains ( target ) ) ;
322
+ let shouldStopPropagation = triggerPressEnd ( createEvent ( state . target , e ) , 'keyboard' , state . target . contains ( target ) ) ;
304
323
removeAllGlobalListeners ( ) ;
305
324
325
+ if ( shouldStopPropagation ) {
326
+ e . stopPropagation ( ) ;
327
+ }
328
+
306
329
// If the target is a link, trigger the click method to open the URL,
307
330
// but defer triggering pressEnd until onClick event handler.
308
331
if ( state . target instanceof HTMLElement && state . target . contains ( target ) && ( isHTMLAnchorLink ( state . target ) || state . target . getAttribute ( 'role' ) === 'link' ) ) {
@@ -335,7 +358,7 @@ export function usePress(props: PressHookProps): PressResult {
335
358
336
359
state . pointerType = e . pointerType ;
337
360
338
- e . stopPropagation ( ) ;
361
+ let shouldStopPropagation = true ;
339
362
if ( ! state . isPressed ) {
340
363
state . isPressed = true ;
341
364
state . isOverTarget = true ;
@@ -350,12 +373,16 @@ export function usePress(props: PressHookProps): PressResult {
350
373
disableTextSelection ( state . target ) ;
351
374
}
352
375
353
- triggerPressStart ( e , state . pointerType ) ;
376
+ shouldStopPropagation = triggerPressStart ( e , state . pointerType ) ;
354
377
355
378
addGlobalListener ( document , 'pointermove' , onPointerMove , false ) ;
356
379
addGlobalListener ( document , 'pointerup' , onPointerUp , false ) ;
357
380
addGlobalListener ( document , 'pointercancel' , onPointerCancel , false ) ;
358
381
}
382
+
383
+ if ( shouldStopPropagation ) {
384
+ e . stopPropagation ( ) ;
385
+ }
359
386
} ;
360
387
361
388
pressProps . onMouseDown = ( e ) => {
@@ -453,8 +480,8 @@ export function usePress(props: PressHookProps): PressResult {
453
480
e . preventDefault ( ) ;
454
481
}
455
482
456
- e . stopPropagation ( ) ;
457
483
if ( state . ignoreEmulatedMouseEvents ) {
484
+ e . stopPropagation ( ) ;
458
485
return ;
459
486
}
460
487
@@ -467,7 +494,10 @@ export function usePress(props: PressHookProps): PressResult {
467
494
focusWithoutScrolling ( e . currentTarget ) ;
468
495
}
469
496
470
- triggerPressStart ( e , state . pointerType ) ;
497
+ let shouldStopPropagation = triggerPressStart ( e , state . pointerType ) ;
498
+ if ( shouldStopPropagation ) {
499
+ e . stopPropagation ( ) ;
500
+ }
471
501
472
502
addGlobalListener ( document , 'mouseup' , onMouseUp , false ) ;
473
503
} ;
@@ -477,10 +507,14 @@ export function usePress(props: PressHookProps): PressResult {
477
507
return ;
478
508
}
479
509
480
- e . stopPropagation ( ) ;
510
+ let shouldStopPropagation = true ;
481
511
if ( state . isPressed && ! state . ignoreEmulatedMouseEvents ) {
482
512
state . isOverTarget = true ;
483
- triggerPressStart ( e , state . pointerType ) ;
513
+ shouldStopPropagation = triggerPressStart ( e , state . pointerType ) ;
514
+ }
515
+
516
+ if ( shouldStopPropagation ) {
517
+ e . stopPropagation ( ) ;
484
518
}
485
519
} ;
486
520
@@ -489,12 +523,16 @@ export function usePress(props: PressHookProps): PressResult {
489
523
return ;
490
524
}
491
525
492
- e . stopPropagation ( ) ;
526
+ let shouldStopPropagation = true ;
493
527
if ( state . isPressed && ! state . ignoreEmulatedMouseEvents ) {
494
528
state . isOverTarget = false ;
495
- triggerPressEnd ( e , state . pointerType , false ) ;
529
+ shouldStopPropagation = triggerPressEnd ( e , state . pointerType , false ) ;
496
530
cancelOnPointerExit ( e ) ;
497
531
}
532
+
533
+ if ( shouldStopPropagation ) {
534
+ e . stopPropagation ( ) ;
535
+ }
498
536
} ;
499
537
500
538
pressProps . onMouseUp = ( e ) => {
@@ -535,7 +573,6 @@ export function usePress(props: PressHookProps): PressResult {
535
573
return ;
536
574
}
537
575
538
- e . stopPropagation ( ) ;
539
576
let touch = getTouchFromEvent ( e . nativeEvent ) ;
540
577
if ( ! touch ) {
541
578
return ;
@@ -557,7 +594,10 @@ export function usePress(props: PressHookProps): PressResult {
557
594
disableTextSelection ( state . target ) ;
558
595
}
559
596
560
- triggerPressStart ( e , state . pointerType ) ;
597
+ let shouldStopPropagation = triggerPressStart ( e , state . pointerType ) ;
598
+ if ( shouldStopPropagation ) {
599
+ e . stopPropagation ( ) ;
600
+ }
561
601
562
602
addGlobalListener ( window , 'scroll' , onScroll , true ) ;
563
603
} ;
@@ -567,40 +607,50 @@ export function usePress(props: PressHookProps): PressResult {
567
607
return ;
568
608
}
569
609
570
- e . stopPropagation ( ) ;
571
610
if ( ! state . isPressed ) {
611
+ e . stopPropagation ( ) ;
572
612
return ;
573
613
}
574
614
575
615
let touch = getTouchById ( e . nativeEvent , state . activePointerId ) ;
616
+ let shouldStopPropagation = true ;
576
617
if ( touch && isOverTarget ( touch , e . currentTarget ) ) {
577
618
if ( ! state . isOverTarget ) {
578
619
state . isOverTarget = true ;
579
- triggerPressStart ( e , state . pointerType ) ;
620
+ shouldStopPropagation = triggerPressStart ( e , state . pointerType ) ;
580
621
}
581
622
} else if ( state . isOverTarget ) {
582
623
state . isOverTarget = false ;
583
- triggerPressEnd ( e , state . pointerType , false ) ;
624
+ shouldStopPropagation = triggerPressEnd ( e , state . pointerType , false ) ;
584
625
cancelOnPointerExit ( e ) ;
585
626
}
627
+
628
+ if ( shouldStopPropagation ) {
629
+ e . stopPropagation ( ) ;
630
+ }
586
631
} ;
587
632
588
633
pressProps . onTouchEnd = ( e ) => {
589
634
if ( ! e . currentTarget . contains ( e . target as Element ) ) {
590
635
return ;
591
636
}
592
637
593
- e . stopPropagation ( ) ;
594
638
if ( ! state . isPressed ) {
639
+ e . stopPropagation ( ) ;
595
640
return ;
596
641
}
597
642
598
643
let touch = getTouchById ( e . nativeEvent , state . activePointerId ) ;
644
+ let shouldStopPropagation = true ;
599
645
if ( touch && isOverTarget ( touch , e . currentTarget ) ) {
600
646
triggerPressUp ( e , state . pointerType ) ;
601
- triggerPressEnd ( e , state . pointerType ) ;
647
+ shouldStopPropagation = triggerPressEnd ( e , state . pointerType ) ;
602
648
} else if ( state . isOverTarget ) {
603
- triggerPressEnd ( e , state . pointerType , false ) ;
649
+ shouldStopPropagation = triggerPressEnd ( e , state . pointerType , false ) ;
650
+ }
651
+
652
+ if ( shouldStopPropagation ) {
653
+ e . stopPropagation ( ) ;
604
654
}
605
655
606
656
state . isPressed = false ;
0 commit comments