@@ -15,6 +15,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
15
15
import type { PluginModule } from 'legacy-events/PluginModuleType' ;
16
16
import type { ReactSyntheticEvent } from 'legacy-events/ReactSyntheticEventType' ;
17
17
import type { TopLevelType } from 'legacy-events/TopLevelEventTypes' ;
18
+ import forEachAccumulated from 'legacy-events/forEachAccumulated' ;
18
19
19
20
import {
20
21
HostRoot ,
@@ -45,6 +46,7 @@ import {
45
46
} from './DOMTopLevelEventTypes' ;
46
47
import { addTrappedEventListener } from './ReactDOMEventListener' ;
47
48
import { batchedEventUpdates } from './ReactDOMUpdateBatching' ;
49
+ import getListener from './getListener' ;
48
50
49
51
/**
50
52
* Summary of `DOMEventPluginSystem` event handling:
@@ -396,3 +398,203 @@ export function legacyTrapCapturedEvent(
396
398
) ;
397
399
listenerMap . set ( topLevelType , { passive : undefined , listener} ) ;
398
400
}
401
+
402
+ function getParent ( inst : Object | null ) : Object | null {
403
+ if ( ! inst ) {
404
+ return null ;
405
+ }
406
+ do {
407
+ inst = inst . return ;
408
+ // TODO: If this is a HostRoot we might want to bail out.
409
+ // That is depending on if we want nested subtrees (layers) to bubble
410
+ // events to their parent. We could also go through parentNode on the
411
+ // host node but that wouldn't work for React Native and doesn't let us
412
+ // do the portal feature.
413
+ } while ( inst && inst . tag !== HostComponent ) ;
414
+ if ( inst ) {
415
+ return inst ;
416
+ }
417
+ return null ;
418
+ }
419
+
420
+ /**
421
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
422
+ */
423
+ function traverseTwoPhase (
424
+ inst : Object ,
425
+ fn : Function ,
426
+ arg : ReactSyntheticEvent ,
427
+ ) {
428
+ const path = [ ] ;
429
+ while ( inst ) {
430
+ path . push ( inst ) ;
431
+ inst = getParent ( inst ) ;
432
+ }
433
+ let i ;
434
+ for ( i = path . length ; i -- > 0 ; ) {
435
+ fn ( path [ i ] , 'captured' , arg ) ;
436
+ }
437
+ for ( i = 0 ; i < path . length ; i ++ ) {
438
+ fn ( path [ i ] , 'bubbled' , arg ) ;
439
+ }
440
+ }
441
+
442
+ function listenerAtPhase ( inst , event , propagationPhase ) {
443
+ const registrationName =
444
+ event . dispatchConfig . phasedRegistrationNames [ propagationPhase ] ;
445
+ return getListener ( inst , registrationName ) ;
446
+ }
447
+
448
+ /**
449
+ * Return the lowest common ancestor of A and B, or null if they are in
450
+ * different trees.
451
+ */
452
+ export function getLowestCommonAncestor (
453
+ instA : Object ,
454
+ instB : Object ,
455
+ ) : Object | null {
456
+ let depthA = 0 ;
457
+ for ( let tempA = instA ; tempA ; tempA = getParent ( tempA ) ) {
458
+ depthA ++ ;
459
+ }
460
+ let depthB = 0 ;
461
+ for ( let tempB = instB ; tempB ; tempB = getParent ( tempB ) ) {
462
+ depthB ++ ;
463
+ }
464
+
465
+ // If A is deeper, crawl up.
466
+ while ( depthA - depthB > 0 ) {
467
+ instA = getParent ( instA ) ;
468
+ depthA -- ;
469
+ }
470
+
471
+ // If B is deeper, crawl up.
472
+ while ( depthB - depthA > 0 ) {
473
+ instB = getParent ( instB ) ;
474
+ depthB -- ;
475
+ }
476
+
477
+ // Walk in lockstep until we find a match.
478
+ let depth = depthA ;
479
+ while ( depth -- ) {
480
+ if ( instA === instB || instA === instB . alternate ) {
481
+ return instA ;
482
+ }
483
+ instA = getParent ( instA ) ;
484
+ instB = getParent ( instB ) ;
485
+ }
486
+ return null ;
487
+ }
488
+
489
+ /**
490
+ * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
491
+ * should would receive a `mouseEnter` or `mouseLeave` event.
492
+ *
493
+ * Does not invoke the callback on the nearest common ancestor because nothing
494
+ * "entered" or "left" that element.
495
+ */
496
+ export function traverseEnterLeave (
497
+ from : Object ,
498
+ to : Object ,
499
+ fn : Function ,
500
+ argFrom : ReactSyntheticEvent ,
501
+ argTo : ReactSyntheticEvent ,
502
+ ) {
503
+ const common = from && to ? getLowestCommonAncestor ( from , to ) : null ;
504
+ const pathFrom = [ ] ;
505
+ while ( true ) {
506
+ if ( ! from ) {
507
+ break ;
508
+ }
509
+ if ( from === common ) {
510
+ break;
511
+ }
512
+ const alternate = from . alternate ;
513
+ if ( alternate !== null && alternate === common ) {
514
+ break;
515
+ }
516
+ pathFrom . push ( from ) ;
517
+ from = getParent ( from ) ;
518
+ }
519
+ const pathTo = [ ] ;
520
+ while ( true ) {
521
+ if ( ! to ) {
522
+ break ;
523
+ }
524
+ if ( to === common ) {
525
+ break;
526
+ }
527
+ const alternate = to . alternate ;
528
+ if ( alternate !== null && alternate === common ) {
529
+ break;
530
+ }
531
+ pathTo . push ( to ) ;
532
+ to = getParent ( to ) ;
533
+ }
534
+ for ( let i = 0 ; i < pathFrom . length ; i ++ ) {
535
+ fn ( pathFrom [ i ] , 'bubbled' , argFrom ) ;
536
+ }
537
+ for ( let i = pathTo . length ; i -- > 0 ; ) {
538
+ fn ( pathTo [ i ] , 'captured' , argTo ) ;
539
+ }
540
+ }
541
+
542
+ function accumulateDirectionalDispatches ( inst , phase , event ) {
543
+ if ( __DEV__ ) {
544
+ if ( ! inst ) {
545
+ console . error ( 'Dispatching inst must not be null' ) ;
546
+ }
547
+ }
548
+ const listener = listenerAtPhase ( inst , event , phase ) ;
549
+ if ( listener ) {
550
+ event . _dispatchListeners = accumulateInto (
551
+ event . _dispatchListeners ,
552
+ listener ,
553
+ ) ;
554
+ event . _dispatchInstances = accumulateInto ( event . _dispatchInstances , inst ) ;
555
+ }
556
+ }
557
+
558
+ function accumulateTwoPhaseDispatchesSingle ( event ) {
559
+ if ( event && event . dispatchConfig . phasedRegistrationNames ) {
560
+ traverseTwoPhase ( event . _targetInst , accumulateDirectionalDispatches , event ) ;
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Accumulates without regard to direction, does not look for phased
566
+ * registration names. Same as `accumulateDirectDispatchesSingle` but without
567
+ * requiring that the `dispatchMarker` be the same as the dispatched ID.
568
+ */
569
+ function accumulateDispatches (
570
+ inst : Object ,
571
+ ignoredDirection : ?boolean ,
572
+ event : Object ,
573
+ ) : void {
574
+ if ( inst && event && event . dispatchConfig . registrationName ) {
575
+ const registrationName = event . dispatchConfig . registrationName ;
576
+ const listener = getListener ( inst , registrationName ) ;
577
+ if ( listener ) {
578
+ event . _dispatchListeners = accumulateInto (
579
+ event . _dispatchListeners ,
580
+ listener ,
581
+ ) ;
582
+ event . _dispatchInstances = accumulateInto ( event . _dispatchInstances , inst ) ;
583
+ }
584
+ }
585
+ }
586
+
587
+ export function accumulateTwoPhaseDispatches (
588
+ events : ReactSyntheticEvent | Array < ReactSyntheticEvent > ,
589
+ ) : void {
590
+ forEachAccumulated ( events , accumulateTwoPhaseDispatchesSingle ) ;
591
+ }
592
+
593
+ export function accumulateEnterLeaveDispatches (
594
+ leave : ReactSyntheticEvent ,
595
+ enter : ReactSyntheticEvent ,
596
+ from : Fiber ,
597
+ to : Fiber ,
598
+ ) {
599
+ traverseEnterLeave ( from , to , accumulateDispatches , leave , enter ) ;
600
+ }
0 commit comments