@@ -37,6 +37,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
37
37
import hasOwnProperty from 'shared/hasOwnProperty' ;
38
38
import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
39
39
import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
40
+ import {
41
+ isFiberContainedBy ,
42
+ isFiberFollowing ,
43
+ isFiberPreceding ,
44
+ } from 'react-reconciler/src/ReactFiberTreeReflection' ;
40
45
41
46
export {
42
47
setCurrentUpdatePriority ,
@@ -60,8 +65,9 @@ import {
60
65
} from './ReactDOMComponentTree' ;
61
66
import {
62
67
traverseFragmentInstance ,
63
- getFragmentParentHostInstance ,
64
- getNextSiblingHostInstance ,
68
+ getFragmentParentHostFiber ,
69
+ getNextSiblingHostFiber ,
70
+ getInstanceFromHostFiber ,
65
71
} from 'react-reconciler/src/ReactFiberTreeReflection' ;
66
72
67
73
export { detachDeletedInstance } ;
@@ -2629,7 +2635,6 @@ FragmentInstance.prototype.addEventListener = function (
2629
2635
listeners . push ( { type, listener, optionsOrUseCapture} ) ;
2630
2636
traverseFragmentInstance (
2631
2637
this . _fragmentFiber ,
2632
- false ,
2633
2638
addEventListenerToChild ,
2634
2639
type ,
2635
2640
listener ,
@@ -2639,12 +2644,13 @@ FragmentInstance.prototype.addEventListener = function (
2639
2644
this . _eventListeners = listeners ;
2640
2645
} ;
2641
2646
function addEventListenerToChild (
2642
- child : Instance ,
2647
+ child : Fiber ,
2643
2648
type : string ,
2644
2649
listener : EventListener ,
2645
2650
optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
2646
2651
) : boolean {
2647
- child . addEventListener ( type , listener , optionsOrUseCapture ) ;
2652
+ const instance = getInstanceFromHostFiber ( child ) ;
2653
+ instance . addEventListener ( type , listener , optionsOrUseCapture ) ;
2648
2654
return false ;
2649
2655
}
2650
2656
// $FlowFixMe[prop-missing]
@@ -2661,7 +2667,6 @@ FragmentInstance.prototype.removeEventListener = function (
2661
2667
if ( typeof listeners !== 'undefined' && listeners . length > 0 ) {
2662
2668
traverseFragmentInstance (
2663
2669
this . _fragmentFiber ,
2664
- false ,
2665
2670
removeEventListenerFromChild ,
2666
2671
type ,
2667
2672
listener ,
@@ -2679,12 +2684,13 @@ FragmentInstance.prototype.removeEventListener = function (
2679
2684
}
2680
2685
} ;
2681
2686
function removeEventListenerFromChild (
2682
- child : Instance ,
2687
+ child : Fiber ,
2683
2688
type : string ,
2684
2689
listener : EventListener ,
2685
2690
optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
2686
2691
) : boolean {
2687
- child . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2692
+ const instance = getInstanceFromHostFiber ( child ) ;
2693
+ instance . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2688
2694
return false ;
2689
2695
}
2690
2696
// $FlowFixMe[prop-missing]
@@ -2694,34 +2700,32 @@ FragmentInstance.prototype.focus = function (
2694
2700
) : void {
2695
2701
traverseFragmentInstance (
2696
2702
this . _fragmentFiber ,
2697
- false ,
2698
- setFocusIfFocusable ,
2703
+ setFocusOnFiberIfFocusable ,
2699
2704
focusOptions ,
2700
2705
) ;
2701
2706
} ;
2707
+ function setFocusOnFiberIfFocusable (
2708
+ fiber : Fiber ,
2709
+ focusOptions ?: FocusOptions ,
2710
+ ) : boolean {
2711
+ const instance = getInstanceFromHostFiber ( fiber ) ;
2712
+ return setFocusIfFocusable ( instance , focusOptions ) ;
2713
+ }
2702
2714
// $FlowFixMe[prop-missing]
2703
2715
FragmentInstance . prototype . focusLast = function (
2704
2716
this : FragmentInstanceType ,
2705
2717
focusOptions ?: FocusOptions ,
2706
2718
) : void {
2707
- const children : Array < Instance > = [ ] ;
2708
- traverseFragmentInstance (
2709
- this . _fragmentFiber ,
2710
- false ,
2711
- collectChildren ,
2712
- children ,
2713
- ) ;
2719
+ const children : Array < Fiber > = [ ] ;
2720
+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
2714
2721
for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
2715
2722
const child = children [ i ] ;
2716
- if ( setFocusIfFocusable ( child , focusOptions ) ) {
2723
+ if ( setFocusOnFiberIfFocusable ( child , focusOptions ) ) {
2717
2724
break;
2718
2725
}
2719
2726
}
2720
2727
} ;
2721
- function collectChildren (
2722
- child : Instance ,
2723
- collection : Array < Instance > ,
2724
- ) : boolean {
2728
+ function collectChildren ( child : Fiber , collection : Array < Fiber > ) : boolean {
2725
2729
collection . push ( child ) ;
2726
2730
return false ;
2727
2731
}
@@ -2731,16 +2735,16 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
2731
2735
// does not contain document.activeElement
2732
2736
traverseFragmentInstance (
2733
2737
this . _fragmentFiber ,
2734
- false ,
2735
2738
blurActiveElementWithinFragment ,
2736
2739
) ;
2737
2740
} ;
2738
- function blurActiveElementWithinFragment ( child : Instance ) : boolean {
2741
+ function blurActiveElementWithinFragment ( child : Fiber ) : boolean {
2739
2742
// TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2740
- const ownerDocument = child . ownerDocument ;
2741
- if ( child === ownerDocument . activeElement ) {
2743
+ const instance = getInstanceFromHostFiber ( child ) ;
2744
+ const ownerDocument = instance . ownerDocument ;
2745
+ if ( instance === ownerDocument . activeElement ) {
2742
2746
// $FlowFixMe[prop-missing]
2743
- child . blur ( ) ;
2747
+ instance . blur ( ) ;
2744
2748
return true ;
2745
2749
}
2746
2750
return false ;
@@ -2754,13 +2758,14 @@ FragmentInstance.prototype.observeUsing = function (
2754
2758
this . _observers = new Set ( ) ;
2755
2759
}
2756
2760
this . _observers . add ( observer ) ;
2757
- traverseFragmentInstance ( this . _fragmentFiber , false , observeChild , observer ) ;
2761
+ traverseFragmentInstance ( this . _fragmentFiber , observeChild , observer ) ;
2758
2762
} ;
2759
2763
function observeChild (
2760
- child : Instance ,
2764
+ child : Fiber ,
2761
2765
observer : IntersectionObserver | ResizeObserver ,
2762
2766
) {
2763
- observer . observe ( child ) ;
2767
+ const instance = getInstanceFromHostFiber ( child ) ;
2768
+ observer . observe ( instance ) ;
2764
2769
return false ;
2765
2770
}
2766
2771
// $FlowFixMe[prop-missing]
@@ -2777,48 +2782,41 @@ FragmentInstance.prototype.unobserveUsing = function (
2777
2782
}
2778
2783
} else {
2779
2784
this . _observers . delete ( observer ) ;
2780
- traverseFragmentInstance (
2781
- this . _fragmentFiber ,
2782
- false ,
2783
- unobserveChild ,
2784
- observer ,
2785
- ) ;
2785
+ traverseFragmentInstance ( this . _fragmentFiber , unobserveChild , observer ) ;
2786
2786
}
2787
2787
} ;
2788
2788
function unobserveChild (
2789
- child : Instance ,
2789
+ child : Fiber ,
2790
2790
observer : IntersectionObserver | ResizeObserver ,
2791
2791
) {
2792
- observer . unobserve ( child ) ;
2792
+ const instance = getInstanceFromHostFiber ( child ) ;
2793
+ observer . unobserve ( instance ) ;
2793
2794
return false ;
2794
2795
}
2795
2796
// $FlowFixMe[prop-missing]
2796
2797
FragmentInstance . prototype . getClientRects = function (
2797
2798
this : FragmentInstanceType ,
2798
2799
) : Array < DOMRect > {
2799
2800
const rects : Array < DOMRect > = [ ] ;
2800
- traverseFragmentInstance (
2801
- this . _fragmentFiber ,
2802
- true ,
2803
- collectClientRects ,
2804
- rects ,
2805
- ) ;
2801
+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
2806
2802
return rects ;
2807
2803
} ;
2808
- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2804
+ function collectClientRects(child: Fiber, rects: Array< DOMRect > ): boolean {
2805
+ const instance = getInstanceFromHostFiber ( child ) ;
2809
2806
// $FlowFixMe[method-unbinding]
2810
- rects . push . apply ( rects , child . getClientRects ( ) ) ;
2807
+ rects . push . apply ( rects , instance . getClientRects ( ) ) ;
2811
2808
return false ;
2812
2809
}
2813
2810
// $FlowFixMe[prop-missing]
2814
2811
FragmentInstance.prototype.getRootNode = function (
2815
2812
this: FragmentInstanceType,
2816
2813
getRootNodeOptions?: { composed : boolean } ,
2817
2814
): Document | ShadowRoot | FragmentInstanceType {
2818
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2819
- if ( parentHostInstance === null ) {
2815
+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2816
+ if ( parentHostFiber === null ) {
2820
2817
return this ;
2821
2818
}
2819
+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
2822
2820
const rootNode =
2823
2821
// $FlowFixMe[incompatible-cast] Flow expects Node
2824
2822
( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
@@ -2829,56 +2827,54 @@ FragmentInstance.prototype.compareDocumentPosition = function (
2829
2827
this: FragmentInstanceType,
2830
2828
otherNode: Instance,
2831
2829
): number {
2832
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2833
- if ( parentHostInstance === null ) {
2830
+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2831
+ if ( parentHostFiber === null ) {
2834
2832
return Node . DOCUMENT_POSITION_DISCONNECTED ;
2835
2833
}
2834
+ const children : Array < Fiber > = [ ] ;
2835
+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
2836
2836
2837
- const children : Array < Instance > = [ ] ;
2838
- traverseFragmentInstance (
2839
- this . _fragmentFiber ,
2840
- true ,
2841
- collectChildren ,
2842
- children ,
2843
- ) ;
2844
-
2837
+ let result = Node . DOCUMENT_POSITION_DISCONNECTED ;
2845
2838
if ( children . length === 0 ) {
2846
2839
// If the fragment has no children, we can use the parent and
2847
2840
// siblings to determine a position.
2848
- if ( parentHostInstance === otherNode ) {
2849
- return Node . DOCUMENT_POSITION_CONTAINS ;
2850
- }
2841
+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
2851
2842
const parentResult = parentHostInstance . compareDocumentPosition ( otherNode ) ;
2852
- if (parentResult & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2853
- // otherNode is one of the fragment's siblings. Use the next
2854
- // sibling to determine if its preceding or following.
2855
- const nextSiblingInstance = getNextSiblingHostInstance (
2856
- this . _fragmentFiber ,
2857
- ) ;
2858
- if ( nextSiblingInstance === null ) {
2859
- return Node . DOCUMENT_POSITION_PRECEDING ;
2860
- }
2861
- if (
2862
- nextSiblingInstance === otherNode ||
2863
- nextSiblingInstance . compareDocumentPosition ( otherNode ) &
2864
- Node . DOCUMENT_POSITION_FOLLOWING
2865
- ) {
2866
- return Node . DOCUMENT_POSITION_FOLLOWING ;
2867
- } else {
2868
- return Node . DOCUMENT_POSITION_PRECEDING ;
2843
+ result = parentResult ;
2844
+ if ( parentHostInstance === otherNode ) {
2845
+ result = Node . DOCUMENT_POSITION_CONTAINS ;
2846
+ } else {
2847
+ if ( parentResult & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2848
+ // otherNode is one of the fragment's siblings. Use the next
2849
+ // sibling to determine if its preceding or following.
2850
+ const nextSiblingFiber = getNextSiblingHostFiber ( this . _fragmentFiber ) ;
2851
+ if ( nextSiblingFiber === null ) {
2852
+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2853
+ } else {
2854
+ const nextSiblingInstance =
2855
+ getInstanceFromHostFiber ( nextSiblingFiber ) ;
2856
+ const nextSiblingResult =
2857
+ nextSiblingInstance . compareDocumentPosition ( otherNode ) ;
2858
+ if (
2859
+ nextSiblingResult === 0 ||
2860
+ nextSiblingResult & Node . DOCUMENT_POSITION_FOLLOWING
2861
+ ) {
2862
+ result = Node . DOCUMENT_POSITION_FOLLOWING ;
2863
+ } else {
2864
+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2865
+ }
2866
+ }
2869
2867
}
2870
2868
}
2871
- return parentResult ;
2869
+
2870
+ result |= Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC ;
2871
+ return result ;
2872
2872
}
2873
2873
2874
- const firstElement = children [ 0 ] ;
2875
- const lastElement = children [ children . length - 1 ] ;
2874
+ const firstElement = getInstanceFromHostFiber ( children [ 0 ] ) ;
2875
+ const lastElement = getInstanceFromHostFiber ( children [ children . length - 1 ] ) ;
2876
2876
const firstResult = firstElement . compareDocumentPosition ( otherNode ) ;
2877
2877
const lastResult = lastElement . compareDocumentPosition ( otherNode ) ;
2878
- let result ;
2879
-
2880
- // If otherNode is a child of the Fragment, it should only be
2881
- // Node.DOCUMENT_POSITION_CONTAINED_BY
2882
2878
if (
2883
2879
( firstResult & Node . DOCUMENT_POSITION_FOLLOWING &&
2884
2880
lastResult & Node . DOCUMENT_POSITION_PRECEDING ) ||
@@ -2890,9 +2886,67 @@ FragmentInstance.prototype.compareDocumentPosition = function (
2890
2886
result = firstResult ;
2891
2887
}
2892
2888
2893
- return result;
2889
+ if (
2890
+ result & Node . DOCUMENT_POSITION_DISCONNECTED ||
2891
+ result & Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
2892
+ ) {
2893
+ return result ;
2894
+ }
2895
+
2896
+ // Now that we have the result from the DOM API, we double check it matches
2897
+ // the state of the React tree. If it doesn't, we have a case of portaled or
2898
+ // otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
2899
+ const documentPositionMatchesFiberPosition =
2900
+ validateDocumentPositionWithFiberTree(
2901
+ result,
2902
+ this._fragmentFiber,
2903
+ children[0],
2904
+ children[children.length - 1],
2905
+ otherNode,
2906
+ );
2907
+ if (documentPositionMatchesFiberPosition) {
2908
+ return result ;
2909
+ }
2910
+ return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
2894
2911
} ;
2895
2912
2913
+ function validateDocumentPositionWithFiberTree (
2914
+ documentPosition : number ,
2915
+ fragmentFiber : Fiber ,
2916
+ precedingBoundaryFiber : Fiber ,
2917
+ followingBoundaryFiber : Fiber ,
2918
+ otherNode : Instance ,
2919
+ ) : boolean {
2920
+ const otherFiber = getClosestInstanceFromNode ( otherNode ) ;
2921
+ if ( documentPosition & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2922
+ return ! ! otherFiber && isFiberContainedBy ( fragmentFiber , otherFiber ) ;
2923
+ }
2924
+ if (documentPosition & Node . DOCUMENT_POSITION_CONTAINS ) {
2925
+ if ( otherFiber === null ) {
2926
+ // otherFiber could be null if its the document or body element
2927
+ const ownerDocument = otherNode . ownerDocument ;
2928
+ return otherNode === ownerDocument || otherNode === ownerDocument . body ;
2929
+ }
2930
+ return isFiberContainedBy(otherFiber, fragmentFiber);
2931
+ }
2932
+ if ( documentPosition & Node . DOCUMENT_POSITION_PRECEDING ) {
2933
+ return (
2934
+ ! ! otherFiber &&
2935
+ ( otherFiber === precedingBoundaryFiber ||
2936
+ isFiberPreceding ( precedingBoundaryFiber , otherFiber ) )
2937
+ ) ;
2938
+ }
2939
+ if (documentPosition & Node . DOCUMENT_POSITION_FOLLOWING ) {
2940
+ return (
2941
+ ! ! otherFiber &&
2942
+ ( otherFiber === followingBoundaryFiber ||
2943
+ isFiberFollowing ( followingBoundaryFiber , otherFiber ) )
2944
+ ) ;
2945
+ }
2946
+
2947
+ return false;
2948
+ }
2949
+
2896
2950
function normalizeListenerOptions (
2897
2951
opts : ?EventListenerOptionsOrUseCapture ,
2898
2952
) : string {
0 commit comments