@@ -35,6 +35,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
3535import hasOwnProperty from 'shared/hasOwnProperty' ;
3636import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
3737import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
38+ import {
39+ isFiberContainedBy ,
40+ isFiberFollowing ,
41+ isFiberPreceding ,
42+ } from 'react-reconciler/src/ReactFiberTreeReflection' ;
3843
3944export {
4045 setCurrentUpdatePriority ,
@@ -58,8 +63,9 @@ import {
5863} from './ReactDOMComponentTree' ;
5964import {
6065 traverseFragmentInstance ,
61- getFragmentParentHostInstance ,
62- getNextSiblingHostInstance ,
66+ getFragmentParentHostFiber ,
67+ getNextSiblingHostFiber ,
68+ getInstanceFromHostFiber ,
6369} from 'react-reconciler/src/ReactFiberTreeReflection' ;
6470
6571export { detachDeletedInstance } ;
@@ -2448,7 +2454,6 @@ FragmentInstance.prototype.addEventListener = function (
24482454 listeners . push ( { type, listener, optionsOrUseCapture} ) ;
24492455 traverseFragmentInstance (
24502456 this . _fragmentFiber ,
2451- false ,
24522457 addEventListenerToChild ,
24532458 type ,
24542459 listener ,
@@ -2458,12 +2463,13 @@ FragmentInstance.prototype.addEventListener = function (
24582463 this . _eventListeners = listeners ;
24592464} ;
24602465function addEventListenerToChild (
2461- child : Instance ,
2466+ child : Fiber ,
24622467 type : string ,
24632468 listener : EventListener ,
24642469 optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
24652470) : boolean {
2466- child . addEventListener ( type , listener , optionsOrUseCapture ) ;
2471+ const instance = getInstanceFromHostFiber ( child ) ;
2472+ instance . addEventListener ( type , listener , optionsOrUseCapture ) ;
24672473 return false ;
24682474}
24692475// $FlowFixMe[prop-missing]
@@ -2480,7 +2486,6 @@ FragmentInstance.prototype.removeEventListener = function (
24802486 if ( typeof listeners !== 'undefined' && listeners . length > 0 ) {
24812487 traverseFragmentInstance (
24822488 this . _fragmentFiber ,
2483- false ,
24842489 removeEventListenerFromChild ,
24852490 type ,
24862491 listener ,
@@ -2498,12 +2503,13 @@ FragmentInstance.prototype.removeEventListener = function (
24982503 }
24992504} ;
25002505function removeEventListenerFromChild (
2501- child : Instance ,
2506+ child : Fiber ,
25022507 type : string ,
25032508 listener : EventListener ,
25042509 optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
25052510) : boolean {
2506- child . removeEventListener ( type , listener , optionsOrUseCapture ) ;
2511+ const instance = getInstanceFromHostFiber ( child ) ;
2512+ instance . removeEventListener ( type , listener , optionsOrUseCapture ) ;
25072513 return false ;
25082514}
25092515// $FlowFixMe[prop-missing]
@@ -2513,34 +2519,32 @@ FragmentInstance.prototype.focus = function (
25132519) : void {
25142520 traverseFragmentInstance (
25152521 this . _fragmentFiber ,
2516- false ,
2517- setFocusIfFocusable ,
2522+ setFocusOnFiberIfFocusable ,
25182523 focusOptions ,
25192524 ) ;
25202525} ;
2526+ function setFocusOnFiberIfFocusable (
2527+ fiber : Fiber ,
2528+ focusOptions ?: FocusOptions ,
2529+ ) : boolean {
2530+ const instance = getInstanceFromHostFiber ( fiber ) ;
2531+ return setFocusIfFocusable ( instance , focusOptions ) ;
2532+ }
25212533// $FlowFixMe[prop-missing]
25222534FragmentInstance . prototype . focusLast = function (
25232535 this : FragmentInstanceType ,
25242536 focusOptions ?: FocusOptions ,
25252537) : 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 ) ;
25332540 for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
25342541 const child = children [ i ] ;
2535- if ( setFocusIfFocusable ( child , focusOptions ) ) {
2542+ if ( setFocusOnFiberIfFocusable ( child , focusOptions ) ) {
25362543 break;
25372544 }
25382545 }
25392546} ;
2540- function collectChildren (
2541- child : Instance ,
2542- collection : Array < Instance > ,
2543- ) : boolean {
2547+ function collectChildren ( child : Fiber , collection : Array < Fiber > ) : boolean {
25442548 collection . push ( child ) ;
25452549 return false ;
25462550}
@@ -2550,16 +2554,16 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
25502554 // does not contain document.activeElement
25512555 traverseFragmentInstance (
25522556 this . _fragmentFiber ,
2553- false ,
25542557 blurActiveElementWithinFragment ,
25552558 ) ;
25562559} ;
2557- function blurActiveElementWithinFragment ( child : Instance ) : boolean {
2560+ function blurActiveElementWithinFragment ( child : Fiber ) : boolean {
25582561 // 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 ) {
25612565 // $FlowFixMe[prop-missing]
2562- child . blur ( ) ;
2566+ instance . blur ( ) ;
25632567 return true ;
25642568 }
25652569 return false ;
@@ -2573,13 +2577,14 @@ FragmentInstance.prototype.observeUsing = function (
25732577 this . _observers = new Set ( ) ;
25742578 }
25752579 this . _observers . add ( observer ) ;
2576- traverseFragmentInstance ( this . _fragmentFiber , false , observeChild , observer ) ;
2580+ traverseFragmentInstance ( this . _fragmentFiber , observeChild , observer ) ;
25772581} ;
25782582function observeChild (
2579- child : Instance ,
2583+ child : Fiber ,
25802584 observer : IntersectionObserver | ResizeObserver ,
25812585) {
2582- observer . observe ( child ) ;
2586+ const instance = getInstanceFromHostFiber ( child ) ;
2587+ observer . observe ( instance ) ;
25832588 return false ;
25842589}
25852590// $FlowFixMe[prop-missing]
@@ -2596,48 +2601,41 @@ FragmentInstance.prototype.unobserveUsing = function (
25962601 }
25972602 } else {
25982603 this . _observers . delete ( observer ) ;
2599- traverseFragmentInstance (
2600- this . _fragmentFiber ,
2601- false ,
2602- unobserveChild ,
2603- observer ,
2604- ) ;
2604+ traverseFragmentInstance ( this . _fragmentFiber , unobserveChild , observer ) ;
26052605 }
26062606} ;
26072607function unobserveChild (
2608- child : Instance ,
2608+ child : Fiber ,
26092609 observer : IntersectionObserver | ResizeObserver ,
26102610) {
2611- observer . unobserve ( child ) ;
2611+ const instance = getInstanceFromHostFiber ( child ) ;
2612+ observer . unobserve ( instance ) ;
26122613 return false ;
26132614}
26142615// $FlowFixMe[prop-missing]
26152616FragmentInstance . prototype . getClientRects = function (
26162617 this : FragmentInstanceType ,
26172618) : Array < DOMRect > {
26182619 const rects : Array < DOMRect > = [ ] ;
2619- traverseFragmentInstance (
2620- this . _fragmentFiber ,
2621- true ,
2622- collectClientRects ,
2623- rects ,
2624- ) ;
2620+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
26252621 return rects ;
26262622} ;
2627- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2623+ function collectClientRects(child: Fiber, rects: Array< DOMRect > ): boolean {
2624+ const instance = getInstanceFromHostFiber ( child ) ;
26282625 // $FlowFixMe[method-unbinding]
2629- rects . push . apply ( rects , child . getClientRects ( ) ) ;
2626+ rects . push . apply ( rects , instance . getClientRects ( ) ) ;
26302627 return false ;
26312628}
26322629// $FlowFixMe[prop-missing]
26332630FragmentInstance.prototype.getRootNode = function (
26342631 this: FragmentInstanceType,
26352632 getRootNodeOptions?: { composed : boolean } ,
26362633): Document | ShadowRoot | FragmentInstanceType {
2637- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2638- if ( parentHostInstance === null ) {
2634+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2635+ if ( parentHostFiber === null ) {
26392636 return this ;
26402637 }
2638+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
26412639 const rootNode =
26422640 // $FlowFixMe[incompatible-cast] Flow expects Node
26432641 ( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
@@ -2648,56 +2646,54 @@ FragmentInstance.prototype.compareDocumentPosition = function (
26482646 this: FragmentInstanceType,
26492647 otherNode: Instance,
26502648): number {
2651- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2652- if ( parentHostInstance === null ) {
2649+ const parentHostFiber = getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2650+ if ( parentHostFiber === null ) {
26532651 return Node . DOCUMENT_POSITION_DISCONNECTED ;
26542652 }
2653+ const children : Array < Fiber > = [ ] ;
2654+ traverseFragmentInstance ( this . _fragmentFiber , collectChildren , children ) ;
26552655
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 ;
26642657 if ( children . length === 0 ) {
26652658 // If the fragment has no children, we can use the parent and
26662659 // siblings to determine a position.
2667- if ( parentHostInstance === otherNode ) {
2668- return Node . DOCUMENT_POSITION_CONTAINS ;
2669- }
2660+ const parentHostInstance = getInstanceFromHostFiber ( parentHostFiber ) ;
26702661 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+ }
26882686 }
26892687 }
2690- return parentResult ;
2688+
2689+ result |= Node . DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC ;
2690+ return result ;
26912691 }
26922692
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 ] ) ;
26952695 const firstResult = firstElement . compareDocumentPosition ( otherNode ) ;
26962696 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
27012697 if (
27022698 ( firstResult & Node . DOCUMENT_POSITION_FOLLOWING &&
27032699 lastResult & Node . DOCUMENT_POSITION_PRECEDING ) ||
@@ -2709,9 +2705,67 @@ FragmentInstance.prototype.compareDocumentPosition = function (
27092705 result = firstResult ;
27102706 }
27112707
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;
27132730} ;
27142731
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+
27152769function normalizeListenerOptions (
27162770 opts : ?EventListenerOptionsOrUseCapture ,
27172771) : string {
0 commit comments