@@ -37,6 +37,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
3737import hasOwnProperty from 'shared/hasOwnProperty' ;
3838import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
3939import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
40+ import {
41+ isFiberContainedBy ,
42+ isFiberFollowing ,
43+ isFiberPreceding ,
44+ } from 'react-reconciler/src/ReactFiberTreeReflection' ;
4045
4146export {
4247 setCurrentUpdatePriority ,
@@ -60,7 +65,9 @@ import {
6065} from './ReactDOMComponentTree' ;
6166import {
6267 traverseFragmentInstance ,
63- getFragmentParentHostInstance ,
68+ getFragmentParentHostFiber ,
69+ getNextSiblingHostFiber ,
70+ getInstanceFromHostFiber ,
6471} from 'react-reconciler/src/ReactFiberTreeReflection' ;
6572
6673export { detachDeletedInstance } ;
@@ -2599,6 +2606,7 @@ export type FragmentInstanceType = {
25992606 getRootNode ( getRootNodeOptions ? : {
26002607 composed : boolean ,
26012608 } ) : Document | ShadowRoot | FragmentInstanceType ,
2609+ compareDocumentPosition ( otherNode : Instance ) : number ,
26022610} ;
26032611
26042612function FragmentInstance ( this : FragmentInstanceType , fragmentFiber : Fiber ) {
@@ -2636,12 +2644,13 @@ FragmentInstance.prototype.addEventListener = function (
26362644 this . _eventListeners = listeners ;
26372645} ;
26382646function addEventListenerToChild (
2639- child : Instance ,
2647+ child : Fiber ,
26402648 type : string ,
26412649 listener : EventListener ,
26422650 optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
26432651) : boolean {
2644- child . addEventListener ( type , listener , optionsOrUseCapture ) ;
2652+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
2653+ instance . addEventListener ( type , listener , optionsOrUseCapture ) ;
26452654 return false ;
26462655}
26472656// $FlowFixMe[prop-missing]
@@ -2675,12 +2684,13 @@ FragmentInstance.prototype.removeEventListener = function (
26752684 }
26762685} ;
26772686function removeEventListenerFromChild (
2678- child : Instance ,
2687+ child : Fiber ,
26792688 type : string ,
26802689 listener : EventListener ,
26812690 optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
26822691) : boolean {
2683- child . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2692+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
2693+ instance . removeEventListener ( type , listener , optionsOrUseCapture ) ;
26842694 return false ;
26852695}
26862696// $FlowFixMe[prop-missing]
@@ -2690,28 +2700,32 @@ FragmentInstance.prototype.focus = function (
26902700) : void {
26912701 traverseFragmentInstance (
26922702 this . _fragmentFiber ,
2693- setFocusIfFocusable ,
2703+ setFocusOnFiberIfFocusable ,
26942704 focusOptions ,
26952705 ) ;
26962706} ;
2707+ function setFocusOnFiberIfFocusable (
2708+ fiber : Fiber ,
2709+ focusOptions ?: FocusOptions ,
2710+ ) : boolean {
2711+ const instance = getInstanceFromHostFiber < Instance > ( fiber ) ;
2712+ return setFocusIfFocusable ( instance , focusOptions ) ;
2713+ }
26972714// $FlowFixMe[prop-missing]
26982715FragmentInstance . prototype . focusLast = function (
26992716 this : FragmentInstanceType ,
27002717 focusOptions ?: FocusOptions ,
27012718) : void {
2702- const children : Array < Instance > = [ ] ;
2719+ const children : Array < Fiber > = [ ] ;
27032720 traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
27042721 for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
27052722 const child = children [ i ] ;
2706- if ( setFocusIfFocusable ( child , focusOptions ) ) {
2723+ if ( setFocusOnFiberIfFocusable ( child , focusOptions ) ) {
27072724 break;
27082725 }
27092726 }
27102727} ;
2711- function collectChildren (
2712- child : Instance ,
2713- collection : Array < Instance > ,
2714- ) : boolean {
2728+ function collectChildren ( child : Fiber , collection : Array < Fiber > ) : boolean {
27152729 collection . push ( child ) ;
27162730 return false ;
27172731}
@@ -2724,12 +2738,13 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
27242738 blurActiveElementWithinFragment ,
27252739 ) ;
27262740} ;
2727- function blurActiveElementWithinFragment ( child : Instance ) : boolean {
2741+ function blurActiveElementWithinFragment ( child : Fiber ) : boolean {
27282742 // TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2729- const ownerDocument = child . ownerDocument ;
2730- if ( child === ownerDocument . activeElement ) {
2743+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
2744+ const ownerDocument = instance . ownerDocument ;
2745+ if ( instance === ownerDocument . activeElement ) {
27312746 // $FlowFixMe[prop-missing]
2732- child . blur ( ) ;
2747+ instance . blur ( ) ;
27332748 return true ;
27342749 }
27352750 return false ;
@@ -2746,10 +2761,11 @@ FragmentInstance.prototype.observeUsing = function (
27462761 traverseFragmentInstance ( this . _fragmentFiber , observeChild , observer ) ;
27472762} ;
27482763function observeChild (
2749- child : Instance ,
2764+ child : Fiber ,
27502765 observer : IntersectionObserver | ResizeObserver ,
27512766) {
2752- observer . observe ( child ) ;
2767+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
2768+ observer . observe ( instance ) ;
27532769 return false ;
27542770}
27552771// $FlowFixMe[prop-missing]
@@ -2770,10 +2786,11 @@ FragmentInstance.prototype.unobserveUsing = function (
27702786 }
27712787} ;
27722788function unobserveChild (
2773- child : Instance ,
2789+ child : Fiber ,
27742790 observer : IntersectionObserver | ResizeObserver ,
27752791) {
2776- observer . unobserve ( child ) ;
2792+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
2793+ observer . unobserve ( instance ) ;
27772794 return false ;
27782795}
27792796// $FlowFixMe[prop-missing]
@@ -2784,25 +2801,155 @@ FragmentInstance.prototype.getClientRects = function (
27842801 traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
27852802 return rects ;
27862803} ;
2787- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2804+ function collectClientRects(child: Fiber, rects: Array< DOMRect > ): boolean {
2805+ const instance = getInstanceFromHostFiber < Instance > ( child ) ;
27882806 // $FlowFixMe[method-unbinding]
2789- rects . push . apply ( rects , child . getClientRects ( ) ) ;
2807+ rects . push . apply ( rects , instance . getClientRects ( ) ) ;
27902808 return false ;
27912809}
27922810// $FlowFixMe[prop-missing]
27932811FragmentInstance.prototype.getRootNode = function (
27942812 this: FragmentInstanceType,
27952813 getRootNodeOptions?: { composed : boolean } ,
27962814): Document | ShadowRoot | FragmentInstanceType {
2797- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2798- if ( parentHostInstance === null ) {
2815+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2816+ if ( parentHostFiber === null ) {
27992817 return this ;
28002818 }
2819+ const parentHostInstance =
2820+ getInstanceFromHostFiber < Instance > ( parentHostFiber ) ;
28012821 const rootNode =
28022822 // $FlowFixMe[incompatible-cast] Flow expects Node
28032823 ( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
28042824 return rootNode ;
28052825} ;
2826+ // $FlowFixMe[prop-missing]
2827+ FragmentInstance.prototype.compareDocumentPosition = function (
2828+ this: FragmentInstanceType,
2829+ otherNode: Instance,
2830+ ): number {
2831+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2832+ if ( parentHostFiber === null ) {
2833+ return Node . DOCUMENT_POSITION_DISCONNECTED ;
2834+ }
2835+ const children : Array < Fiber > = [ ] ;
2836+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
2837+
2838+ let result = Node . DOCUMENT_POSITION_DISCONNECTED ;
2839+ if ( children . length === 0 ) {
2840+ // If the fragment has no children, we can use the parent and
2841+ // siblings to determine a position.
2842+ const parentHostInstance =
2843+ getInstanceFromHostFiber < Instance > ( parentHostFiber ) ;
2844+ const parentResult = parentHostInstance . compareDocumentPosition ( otherNode ) ;
2845+ result = parentResult ;
2846+ if ( parentHostInstance === otherNode ) {
2847+ result = Node . DOCUMENT_POSITION_CONTAINS ;
2848+ } else {
2849+ if ( parentResult & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2850+ // otherNode is one of the fragment's siblings. Use the next
2851+ // sibling to determine if its preceding or following.
2852+ const nextSiblingFiber = getNextSiblingHostFiber ( this . _fragmentFiber ) ;
2853+ if ( nextSiblingFiber === null ) {
2854+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2855+ } else {
2856+ const nextSiblingInstance =
2857+ getInstanceFromHostFiber < Instance > ( nextSiblingFiber ) ;
2858+ const nextSiblingResult =
2859+ nextSiblingInstance . compareDocumentPosition ( otherNode ) ;
2860+ if (
2861+ nextSiblingResult === 0 ||
2862+ nextSiblingResult & Node . DOCUMENT_POSITION_FOLLOWING
2863+ ) {
2864+ result = Node . DOCUMENT_POSITION_FOLLOWING ;
2865+ } else {
2866+ result = Node . DOCUMENT_POSITION_PRECEDING ;
2867+ }
2868+ }
2869+ }
2870+ }
2871+
2872+ result |= Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC ;
2873+ return result ;
2874+ }
2875+
2876+ const firstElement = getInstanceFromHostFiber < Instance > ( children [ 0 ] ) ;
2877+ const lastElement = getInstanceFromHostFiber < Instance > (
2878+ children [ children . length - 1 ] ,
2879+ ) ;
2880+ const firstResult = firstElement . compareDocumentPosition ( otherNode ) ;
2881+ const lastResult = lastElement . compareDocumentPosition ( otherNode ) ;
2882+ if (
2883+ ( firstResult & Node . DOCUMENT_POSITION_FOLLOWING &&
2884+ lastResult & Node . DOCUMENT_POSITION_PRECEDING ) ||
2885+ otherNode === firstElement ||
2886+ otherNode === lastElement
2887+ ) {
2888+ result = Node . DOCUMENT_POSITION_CONTAINED_BY ;
2889+ } else {
2890+ result = firstResult ;
2891+ }
2892+
2893+ if (
2894+ result & Node . DOCUMENT_POSITION_DISCONNECTED ||
2895+ result & Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
2896+ ) {
2897+ return result ;
2898+ }
2899+
2900+ // Now that we have the result from the DOM API, we double check it matches
2901+ // the state of the React tree. If it doesn't, we have a case of portaled or
2902+ // otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
2903+ const documentPositionMatchesFiberPosition =
2904+ validateDocumentPositionWithFiberTree(
2905+ result,
2906+ this._fragmentFiber,
2907+ children[0],
2908+ children[children.length - 1],
2909+ otherNode,
2910+ );
2911+ if (documentPositionMatchesFiberPosition) {
2912+ return result ;
2913+ }
2914+ return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
2915+ } ;
2916+
2917+ function validateDocumentPositionWithFiberTree (
2918+ documentPosition : number ,
2919+ fragmentFiber : Fiber ,
2920+ precedingBoundaryFiber : Fiber ,
2921+ followingBoundaryFiber : Fiber ,
2922+ otherNode : Instance ,
2923+ ) : boolean {
2924+ const otherFiber = getClosestInstanceFromNode ( otherNode ) ;
2925+ if ( documentPosition & Node . DOCUMENT_POSITION_CONTAINED_BY ) {
2926+ return ! ! otherFiber && isFiberContainedBy ( fragmentFiber , otherFiber ) ;
2927+ }
2928+ if (documentPosition & Node . DOCUMENT_POSITION_CONTAINS ) {
2929+ if ( otherFiber === null ) {
2930+ // otherFiber could be null if its the document or body element
2931+ const ownerDocument = otherNode . ownerDocument ;
2932+ return otherNode === ownerDocument || otherNode === ownerDocument . body ;
2933+ }
2934+ return isFiberContainedBy(otherFiber, fragmentFiber);
2935+ }
2936+ if ( documentPosition & Node . DOCUMENT_POSITION_PRECEDING ) {
2937+ return (
2938+ ! ! otherFiber &&
2939+ ( otherFiber === precedingBoundaryFiber ||
2940+ isFiberPreceding ( precedingBoundaryFiber , otherFiber ) )
2941+ ) ;
2942+ }
2943+ if (documentPosition & Node . DOCUMENT_POSITION_FOLLOWING ) {
2944+ return (
2945+ ! ! otherFiber &&
2946+ ( otherFiber === followingBoundaryFiber ||
2947+ isFiberFollowing ( followingBoundaryFiber , otherFiber ) )
2948+ ) ;
2949+ }
2950+
2951+ return false;
2952+ }
28062953
28072954function normalizeListenerOptions (
28082955 opts : ?EventListenerOptionsOrUseCapture ,
0 commit comments