@@ -55,7 +55,10 @@ import {
5555} from './ReactDOMComponentTree' ;
5656import {
5757 traverseFragmentInstance ,
58- getFragmentParentHostInstance ,
58+ getFragmentInstanceHostParent ,
59+ getFragmentInstanceSiblings ,
60+ getHostNodeFromHostFiber ,
61+ groupFragmentChildrenByScrollContainer ,
5962} from 'react-reconciler/src/ReactFiberTreeReflection' ;
6063
6164export { detachDeletedInstance } ;
@@ -2247,6 +2250,7 @@ export type FragmentInstanceType = {
22472250 composed : boolean ,
22482251 } ) : Document | ShadowRoot | FragmentInstanceType ,
22492252 compareDocumentPosition ( otherNode : Instance ) : number ,
2253+ scrollIntoView ( alignToTop ? : boolean ) : void ,
22502254} ;
22512255
22522256function FragmentInstance ( this : FragmentInstanceType , fragmentFiber : Fiber ) {
@@ -2336,8 +2340,8 @@ FragmentInstance.prototype.dispatchEvent = function (
23362340 this : FragmentInstanceType ,
23372341 event : Event ,
23382342) : boolean {
2339- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2340- if ( parentHostInstance === null ) {
2343+ const parentHostFiber = getFragmentInstanceHostParent ( this . _fragmentFiber ) ;
2344+ if ( parentHostFiber === null ) {
23412345 if ( __DEV__ ) {
23422346 console . error (
23432347 'You are attempting to dispatch an event on a disconnected ' +
@@ -2346,6 +2350,7 @@ FragmentInstance.prototype.dispatchEvent = function (
23462350 }
23472351 return true ;
23482352 }
2353+ const parentHostInstance = getHostNodeFromHostFiber ( parentHostFiber ) ;
23492354 return parentHostInstance . dispatchEvent ( event ) ;
23502355} ;
23512356// $FlowFixMe[prop-missing]
@@ -2446,10 +2451,13 @@ FragmentInstance.prototype.getClientRects = function (
24462451 this : FragmentInstanceType ,
24472452) : Array < DOMRect > {
24482453 const rects : Array < DOMRect > = [ ] ;
2449- traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
2454+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRectsFlat , rects ) ;
24502455 return rects ;
24512456} ;
2452- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2457+ function collectClientRectsFlat(
2458+ child: Instance,
2459+ rects: Array< DOMRect > ,
2460+ ): boolean {
24532461 // $FlowFixMe[method-unbinding]
24542462 rects . push . apply ( rects , child . getClientRects ( ) ) ;
24552463 return false ;
@@ -2459,10 +2467,11 @@ FragmentInstance.prototype.getRootNode = function (
24592467 this: FragmentInstanceType,
24602468 getRootNodeOptions?: { composed : boolean } ,
24612469): Document | ShadowRoot | FragmentInstanceType {
2462- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2463- if ( parentHostInstance === null ) {
2470+ const parentHostFiber = getFragmentInstanceHostParent ( this . _fragmentFiber ) ;
2471+ if ( parentHostFiber === null ) {
24642472 return this ;
24652473 }
2474+ const parentHostInstance = getHostNodeFromHostFiber ( parentHostFiber ) ;
24662475 const rootNode =
24672476 // $FlowFixMe[incompatible-cast] Flow expects Node
24682477 ( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
@@ -2503,6 +2512,113 @@ FragmentInstance.prototype.compareDocumentPosition = function (
25032512
25042513 return result ;
25052514} ;
2515+ // $FlowFixMe[prop-missing]
2516+ FragmentInstance . prototype . scrollIntoView = function (
2517+ this : FragmentInstanceType ,
2518+ alignToTop ?: boolean ,
2519+ ) : void {
2520+ if ( typeof alignToTop === 'object' ) {
2521+ throw new Error (
2522+ 'FragmentInstance.scrollIntoView() does not support ' +
2523+ 'scrollIntoViewOptions. Use the alignToTop boolean instead.' ,
2524+ ) ;
2525+ }
2526+
2527+ const childrenByScrollContainer = groupFragmentChildrenByScrollContainer (
2528+ this . _fragmentFiber ,
2529+ fiber => {
2530+ const hostNode = getHostNodeFromHostFiber ( fiber ) ;
2531+ const position = getComputedStyle ( hostNode ) . position ;
2532+ return position === 'sticky' || position === 'fixed' ;
2533+ } ,
2534+ ) ;
2535+
2536+ // If there are no children, go off the previous or next sibling
2537+ if ( childrenByScrollContainer [ 0 ] . length === 0 ) {
2538+ const hostSiblings = getFragmentInstanceSiblings ( this . _fragmentFiber ) ;
2539+ const targetFiber =
2540+ ( alignToTop === false
2541+ ? hostSiblings [ 0 ] || hostSiblings [ 1 ]
2542+ : hostSiblings [ 1 ] || hostSiblings [ 0 ] ) ||
2543+ getFragmentInstanceHostParent ( this . _fragmentFiber ) ;
2544+ if ( targetFiber === null ) {
2545+ if ( __DEV__ ) {
2546+ console . error (
2547+ 'You are attempting to scroll a FragmentInstance that has no ' +
2548+ 'children, siblings, or parent. No scroll was performed.' ,
2549+ ) ;
2550+ }
2551+ return ;
2552+ }
2553+ const target = getHostNodeFromHostFiber ( targetFiber ) ;
2554+ target . scrollIntoView ( alignToTop ) ;
2555+ } else {
2556+ iterateFragmentChildrenScrollContainers (
2557+ childrenByScrollContainer ,
2558+ alignToTop !== false ,
2559+ ( targetFiber , alignToTopArg , scrollState ) => {
2560+ if ( targetFiber ) {
2561+ const target = getHostNodeFromHostFiber ( targetFiber ) ;
2562+ const targetPosition = getComputedStyle ( target ) . position ;
2563+ const isStickyOrFixed =
2564+ targetPosition === 'sticky' || targetPosition === 'fixed' ;
2565+ const targetRect = target . getBoundingClientRect ( ) ;
2566+ const distanceToTargetEdge = Math . abs ( targetRect . bottom ) ;
2567+ const hasNotScrolled =
2568+ scrollState . nextScrollThreshold === Number . MAX_SAFE_INTEGER ;
2569+ const ownerDocument = target . ownerDocument ;
2570+ const documentElement = ownerDocument . documentElement ;
2571+ const targetWithinViewport =
2572+ documentElement &&
2573+ ( targetRect . top >= 0 ||
2574+ targetRect . bottom <= documentElement . clientHeight ) ;
2575+ // If we've already scrolled, only scroll again if
2576+ // 1) The previous scroll target was sticky or fixed OR
2577+ // 2) Scrolling to the next target won't remove previous target from viewport AND
2578+ // 3) The next target is not already in the viewport
2579+ if (
2580+ hasNotScrolled ||
2581+ scrollState . prevWasStickyOrFixed ||
2582+ ( distanceToTargetEdge < scrollState . nextScrollThreshold &&
2583+ ! targetWithinViewport )
2584+ ) {
2585+ target . scrollIntoView ( alignToTopArg ) ;
2586+ scrollState . nextScrollThreshold = targetRect . height ;
2587+ scrollState . prevWasStickyOrFixed = isStickyOrFixed ;
2588+ }
2589+ }
2590+ } ,
2591+ ) ;
2592+ }
2593+ } ;
2594+
2595+ function iterateFragmentChildrenScrollContainers (
2596+ childrenByScrollContainer : Array < Array < Fiber >> ,
2597+ alignToTop : boolean ,
2598+ callback : (
2599+ child : Fiber | null ,
2600+ arg : boolean ,
2601+ scrollState : { nextScrollThreshold : number , prevWasStickyOrFixed : boolean } ,
2602+ ) = > void ,
2603+ ) {
2604+ const scrollState = {
2605+ nextScrollThreshold : Number . MAX_SAFE_INTEGER ,
2606+ prevWasStickyOrFixed : false ,
2607+ } ;
2608+ if ( alignToTop ) {
2609+ for ( let i = 0 ; i < childrenByScrollContainer . length ; i ++ ) {
2610+ const children = childrenByScrollContainer [ i ] ;
2611+ const child = children [ 0 ] ;
2612+ callback ( child , alignToTop , scrollState ) ;
2613+ }
2614+ } else {
2615+ for ( let i = childrenByScrollContainer . length - 1 ; i >= 0 ; i -- ) {
2616+ const children = childrenByScrollContainer [ i ] ;
2617+ const child = children [ children . length - 1 ] ;
2618+ callback ( child , alignToTop , scrollState ) ;
2619+ }
2620+ }
2621+ }
25062622
25072623function normalizeListenerOptions (
25082624 opts : ?EventListenerOptionsOrUseCapture ,
0 commit comments