@@ -35,6 +35,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
35
35
import hasOwnProperty from 'shared/hasOwnProperty' ;
36
36
import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
37
37
import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
38
+ import {
39
+ isFiberContainedBy ,
40
+ isFiberFollowing ,
41
+ isFiberPreceding ,
42
+ } from 'react-reconciler/src/ReactFiberTreeReflection' ;
38
43
39
44
export {
40
45
setCurrentUpdatePriority ,
@@ -58,8 +63,9 @@ import {
58
63
} from './ReactDOMComponentTree' ;
59
64
import {
60
65
traverseFragmentInstance ,
61
- getFragmentParentHostInstance ,
62
- getNextSiblingHostInstance ,
66
+ getFragmentParentHostFiber ,
67
+ getNextSiblingHostFiber ,
68
+ getInstanceFromHostFiber ,
63
69
} from 'react-reconciler/src/ReactFiberTreeReflection' ;
64
70
65
71
export { detachDeletedInstance } ;
@@ -2448,7 +2454,6 @@ FragmentInstance.prototype.addEventListener = function (
2448
2454
listeners . push ( { type, listener, optionsOrUseCapture} ) ;
2449
2455
traverseFragmentInstance (
2450
2456
this . _fragmentFiber ,
2451
- false ,
2452
2457
addEventListenerToChild ,
2453
2458
type ,
2454
2459
listener ,
@@ -2458,12 +2463,13 @@ FragmentInstance.prototype.addEventListener = function (
2458
2463
this . _eventListeners = listeners ;
2459
2464
} ;
2460
2465
function addEventListenerToChild (
2461
- child : Instance ,
2466
+ child : Fiber ,
2462
2467
type : string ,
2463
2468
listener : EventListener ,
2464
2469
optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
2465
2470
) : boolean {
2466
- child . addEventListener ( type , listener , optionsOrUseCapture ) ;
2471
+ const instance = getInstanceFromHostFiber ( child ) ;
2472
+ instance . addEventListener ( type , listener , optionsOrUseCapture ) ;
2467
2473
return false ;
2468
2474
}
2469
2475
// $FlowFixMe[prop-missing]
@@ -2480,7 +2486,6 @@ FragmentInstance.prototype.removeEventListener = function (
2480
2486
if ( typeof listeners !== 'undefined' && listeners . length > 0 ) {
2481
2487
traverseFragmentInstance (
2482
2488
this . _fragmentFiber ,
2483
- false ,
2484
2489
removeEventListenerFromChild ,
2485
2490
type ,
2486
2491
listener ,
@@ -2498,12 +2503,13 @@ FragmentInstance.prototype.removeEventListener = function (
2498
2503
}
2499
2504
} ;
2500
2505
function removeEventListenerFromChild (
2501
- child : Instance ,
2506
+ child : Fiber ,
2502
2507
type : string ,
2503
2508
listener : EventListener ,
2504
2509
optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
2505
2510
) : boolean {
2506
- child . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2511
+ const instance = getInstanceFromHostFiber ( child ) ;
2512
+ instance . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2507
2513
return false ;
2508
2514
}
2509
2515
// $FlowFixMe[prop-missing]
@@ -2513,34 +2519,32 @@ FragmentInstance.prototype.focus = function (
2513
2519
) : void {
2514
2520
traverseFragmentInstance (
2515
2521
this . _fragmentFiber ,
2516
- false ,
2517
- setFocusIfFocusable ,
2522
+ setFocusOnFiberIfFocusable ,
2518
2523
focusOptions ,
2519
2524
) ;
2520
2525
} ;
2526
+ function setFocusOnFiberIfFocusable (
2527
+ fiber : Fiber ,
2528
+ focusOptions ?: FocusOptions ,
2529
+ ) : boolean {
2530
+ const instance = getInstanceFromHostFiber ( fiber ) ;
2531
+ return setFocusIfFocusable ( instance , focusOptions ) ;
2532
+ }
2521
2533
// $FlowFixMe[prop-missing]
2522
2534
FragmentInstance . prototype . focusLast = function (
2523
2535
this : FragmentInstanceType ,
2524
2536
focusOptions ?: FocusOptions ,
2525
2537
) : void {
2526
- const children : Array < Instance > = [ ] ;
2527
- traverseFragmentInstance (
2528
- this . _fragmentFiber ,
2529
- false ,
2530
- collectChildren ,
2531
- children ,
2532
- ) ;
2538
+ const children : Array < Fiber > = [ ] ;
2539
+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
2533
2540
for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
2534
2541
const child = children [ i ] ;
2535
- if ( setFocusIfFocusable ( child , focusOptions ) ) {
2542
+ if ( setFocusOnFiberIfFocusable ( child , focusOptions ) ) {
2536
2543
break;
2537
2544
}
2538
2545
}
2539
2546
} ;
2540
- function collectChildren (
2541
- child : Instance ,
2542
- collection : Array < Instance > ,
2543
- ) : boolean {
2547
+ function collectChildren ( child : Fiber , collection : Array < Fiber > ) : boolean {
2544
2548
collection . push ( child ) ;
2545
2549
return false ;
2546
2550
}
@@ -2550,16 +2554,16 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
2550
2554
// does not contain document.activeElement
2551
2555
traverseFragmentInstance (
2552
2556
this . _fragmentFiber ,
2553
- false ,
2554
2557
blurActiveElementWithinFragment ,
2555
2558
) ;
2556
2559
} ;
2557
- function blurActiveElementWithinFragment ( child : Instance ) : boolean {
2560
+ function blurActiveElementWithinFragment ( child : Fiber ) : boolean {
2558
2561
// TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2559
- const ownerDocument = child . ownerDocument ;
2560
- if ( child === ownerDocument . activeElement ) {
2562
+ const instance = getInstanceFromHostFiber ( child ) ;
2563
+ const ownerDocument = instance . ownerDocument ;
2564
+ if ( instance === ownerDocument . activeElement ) {
2561
2565
// $FlowFixMe[prop-missing]
2562
- child . blur ( ) ;
2566
+ instance . blur ( ) ;
2563
2567
return true ;
2564
2568
}
2565
2569
return false ;
@@ -2573,13 +2577,14 @@ FragmentInstance.prototype.observeUsing = function (
2573
2577
this . _observers = new Set ( ) ;
2574
2578
}
2575
2579
this . _observers . add ( observer ) ;
2576
- traverseFragmentInstance ( this . _fragmentFiber , false , observeChild , observer ) ;
2580
+ traverseFragmentInstance ( this . _fragmentFiber , observeChild , observer ) ;
2577
2581
} ;
2578
2582
function observeChild (
2579
- child : Instance ,
2583
+ child : Fiber ,
2580
2584
observer : IntersectionObserver | ResizeObserver ,
2581
2585
) {
2582
- observer . observe ( child ) ;
2586
+ const instance = getInstanceFromHostFiber ( child ) ;
2587
+ observer . observe ( instance ) ;
2583
2588
return false ;
2584
2589
}
2585
2590
// $FlowFixMe[prop-missing]
@@ -2596,48 +2601,41 @@ FragmentInstance.prototype.unobserveUsing = function (
2596
2601
}
2597
2602
} else {
2598
2603
this . _observers . delete ( observer ) ;
2599
- traverseFragmentInstance (
2600
- this . _fragmentFiber ,
2601
- false ,
2602
- unobserveChild ,
2603
- observer ,
2604
- ) ;
2604
+ traverseFragmentInstance ( this . _fragmentFiber , unobserveChild , observer ) ;
2605
2605
}
2606
2606
} ;
2607
2607
function unobserveChild (
2608
- child : Instance ,
2608
+ child : Fiber ,
2609
2609
observer : IntersectionObserver | ResizeObserver ,
2610
2610
) {
2611
- observer . unobserve ( child ) ;
2611
+ const instance = getInstanceFromHostFiber ( child ) ;
2612
+ observer . unobserve ( instance ) ;
2612
2613
return false ;
2613
2614
}
2614
2615
// $FlowFixMe[prop-missing]
2615
2616
FragmentInstance . prototype . getClientRects = function (
2616
2617
this : FragmentInstanceType ,
2617
2618
) : Array < DOMRect > {
2618
2619
const rects : Array < DOMRect > = [ ] ;
2619
- traverseFragmentInstance (
2620
- this . _fragmentFiber ,
2621
- true ,
2622
- collectClientRects ,
2623
- rects ,
2624
- ) ;
2620
+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
2625
2621
return rects ;
2626
2622
} ;
2627
- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2623
+ function collectClientRects(child: Fiber, rects: Array< DOMRect > ): boolean {
2624
+ const instance = getInstanceFromHostFiber ( child ) ;
2628
2625
// $FlowFixMe[method-unbinding]
2629
- rects . push . apply ( rects , child . getClientRects ( ) ) ;
2626
+ rects . push . apply ( rects , instance . getClientRects ( ) ) ;
2630
2627
return false ;
2631
2628
}
2632
2629
// $FlowFixMe[prop-missing]
2633
2630
FragmentInstance.prototype.getRootNode = function (
2634
2631
this: FragmentInstanceType,
2635
2632
getRootNodeOptions?: { composed : boolean } ,
2636
2633
): Document | ShadowRoot | FragmentInstanceType {
2637
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2638
- if ( parentHostInstance === null ) {
2634
+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2635
+ if ( parentHostFiber === null ) {
2639
2636
return this ;
2640
2637
}
2638
+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
2641
2639
const rootNode =
2642
2640
// $FlowFixMe[incompatible-cast] Flow expects Node
2643
2641
( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
@@ -2648,56 +2646,54 @@ FragmentInstance.prototype.compareDocumentPosition = function (
2648
2646
this: FragmentInstanceType,
2649
2647
otherNode: Instance,
2650
2648
): number {
2651
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2652
- if ( parentHostInstance === null ) {
2649
+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2650
+ if ( parentHostFiber === null ) {
2653
2651
return Node . DOCUMENT_POSITION_DISCONNECTED ;
2654
2652
}
2653
+ const children : Array < Fiber > = [ ] ;
2654
+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
2655
2655
2656
- const children : Array < Instance > = [ ] ;
2657
- traverseFragmentInstance (
2658
- this . _fragmentFiber ,
2659
- true ,
2660
- collectChildren ,
2661
- children ,
2662
- ) ;
2663
-
2656
+ let result = Node . DOCUMENT_POSITION_DISCONNECTED ;
2664
2657
if ( children . length === 0 ) {
2665
2658
// If the fragment has no children, we can use the parent and
2666
2659
// siblings to determine a position.
2667
- if ( parentHostInstance === otherNode ) {
2668
- return Node . DOCUMENT_POSITION_CONTAINS ;
2669
- }
2660
+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
2670
2661
const parentResult = parentHostInstance . compareDocumentPosition ( otherNode ) ;
2671
- if (parentResult & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2672
- // otherNode is one of the fragment's siblings. Use the next
2673
- // sibling to determine if its preceding or following.
2674
- const nextSiblingInstance = getNextSiblingHostInstance (
2675
- this . _fragmentFiber ,
2676
- ) ;
2677
- if ( nextSiblingInstance === null ) {
2678
- return Node . DOCUMENT_POSITION_PRECEDING ;
2679
- }
2680
- if (
2681
- nextSiblingInstance === otherNode ||
2682
- nextSiblingInstance . compareDocumentPosition ( otherNode ) &
2683
- Node . DOCUMENT_POSITION_FOLLOWING
2684
- ) {
2685
- return Node . DOCUMENT_POSITION_FOLLOWING ;
2686
- } else {
2687
- return Node . DOCUMENT_POSITION_PRECEDING ;
2662
+ result = parentResult ;
2663
+ if ( parentHostInstance === otherNode ) {
2664
+ result = Node . DOCUMENT_POSITION_CONTAINS ;
2665
+ } else {
2666
+ if ( parentResult & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2667
+ // otherNode is one of the fragment's siblings. Use the next
2668
+ // sibling to determine if its preceding or following.
2669
+ const nextSiblingFiber = getNextSiblingHostFiber ( this . _fragmentFiber ) ;
2670
+ if ( nextSiblingFiber === null ) {
2671
+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2672
+ } else {
2673
+ const nextSiblingInstance =
2674
+ getInstanceFromHostFiber ( nextSiblingFiber ) ;
2675
+ const nextSiblingResult =
2676
+ nextSiblingInstance . compareDocumentPosition ( otherNode ) ;
2677
+ if (
2678
+ nextSiblingResult === 0 ||
2679
+ nextSiblingResult & Node . DOCUMENT_POSITION_FOLLOWING
2680
+ ) {
2681
+ result = Node . DOCUMENT_POSITION_FOLLOWING ;
2682
+ } else {
2683
+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2684
+ }
2685
+ }
2688
2686
}
2689
2687
}
2690
- return parentResult ;
2688
+
2689
+ result |= Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC ;
2690
+ return result ;
2691
2691
}
2692
2692
2693
- const firstElement = children [ 0 ] ;
2694
- const lastElement = children [ children . length - 1 ] ;
2693
+ const firstElement = getInstanceFromHostFiber ( children [ 0 ] ) ;
2694
+ const lastElement = getInstanceFromHostFiber ( children [ children . length - 1 ] ) ;
2695
2695
const firstResult = firstElement . compareDocumentPosition ( otherNode ) ;
2696
2696
const lastResult = lastElement . compareDocumentPosition ( otherNode ) ;
2697
- let result ;
2698
-
2699
- // If otherNode is a child of the Fragment, it should only be
2700
- // Node.DOCUMENT_POSITION_CONTAINED_BY
2701
2697
if (
2702
2698
( firstResult & Node . DOCUMENT_POSITION_FOLLOWING &&
2703
2699
lastResult & Node . DOCUMENT_POSITION_PRECEDING ) ||
@@ -2709,9 +2705,67 @@ FragmentInstance.prototype.compareDocumentPosition = function (
2709
2705
result = firstResult ;
2710
2706
}
2711
2707
2712
- return result;
2708
+ if (
2709
+ result & Node . DOCUMENT_POSITION_DISCONNECTED ||
2710
+ result & Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
2711
+ ) {
2712
+ return result ;
2713
+ }
2714
+
2715
+ // Now that we have the result from the DOM API, we double check it matches
2716
+ // the state of the React tree. If it doesn't, we have a case of portaled or
2717
+ // otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
2718
+ const documentPositionMatchesFiberPosition =
2719
+ validateDocumentPositionWithFiberTree(
2720
+ result,
2721
+ this._fragmentFiber,
2722
+ children[0],
2723
+ children[children.length - 1],
2724
+ otherNode,
2725
+ );
2726
+ if (documentPositionMatchesFiberPosition) {
2727
+ return result ;
2728
+ }
2729
+ return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
2713
2730
} ;
2714
2731
2732
+ function validateDocumentPositionWithFiberTree (
2733
+ documentPosition : number ,
2734
+ fragmentFiber : Fiber ,
2735
+ precedingBoundaryFiber : Fiber ,
2736
+ followingBoundaryFiber : Fiber ,
2737
+ otherNode : Instance ,
2738
+ ) : boolean {
2739
+ const otherFiber = getClosestInstanceFromNode ( otherNode ) ;
2740
+ if ( documentPosition & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2741
+ return ! ! otherFiber && isFiberContainedBy ( fragmentFiber , otherFiber ) ;
2742
+ }
2743
+ if (documentPosition & Node . DOCUMENT_POSITION_CONTAINS ) {
2744
+ if ( otherFiber === null ) {
2745
+ // otherFiber could be null if its the document or body element
2746
+ const ownerDocument = otherNode . ownerDocument ;
2747
+ return otherNode === ownerDocument || otherNode === ownerDocument . body ;
2748
+ }
2749
+ return isFiberContainedBy(otherFiber, fragmentFiber);
2750
+ }
2751
+ if ( documentPosition & Node . DOCUMENT_POSITION_PRECEDING ) {
2752
+ return (
2753
+ ! ! otherFiber &&
2754
+ ( otherFiber === precedingBoundaryFiber ||
2755
+ isFiberPreceding ( precedingBoundaryFiber , otherFiber ) )
2756
+ ) ;
2757
+ }
2758
+ if (documentPosition & Node . DOCUMENT_POSITION_FOLLOWING ) {
2759
+ return (
2760
+ ! ! otherFiber &&
2761
+ ( otherFiber === followingBoundaryFiber ||
2762
+ isFiberFollowing ( followingBoundaryFiber , otherFiber ) )
2763
+ ) ;
2764
+ }
2765
+
2766
+ return false;
2767
+ }
2768
+
2715
2769
function normalizeListenerOptions (
2716
2770
opts : ?EventListenerOptionsOrUseCapture ,
2717
2771
) : string {
0 commit comments