@@ -55,7 +55,10 @@ import {
55
55
} from './ReactDOMComponentTree' ;
56
56
import {
57
57
traverseFragmentInstance ,
58
- getFragmentParentHostInstance ,
58
+ getFragmentInstanceHostParent ,
59
+ getFragmentInstanceSiblings ,
60
+ getHostNodeFromHostFiber ,
61
+ groupFragmentChildrenByScrollContainer ,
59
62
} from 'react-reconciler/src/ReactFiberTreeReflection' ;
60
63
61
64
export { detachDeletedInstance } ;
@@ -2247,6 +2250,7 @@ export type FragmentInstanceType = {
2247
2250
composed : boolean ,
2248
2251
} ) : Document | ShadowRoot | FragmentInstanceType ,
2249
2252
compareDocumentPosition ( otherNode : Instance ) : number ,
2253
+ scrollIntoView ( alignToTop ? : boolean ) : void ,
2250
2254
} ;
2251
2255
2252
2256
function FragmentInstance ( this : FragmentInstanceType , fragmentFiber : Fiber ) {
@@ -2336,8 +2340,8 @@ FragmentInstance.prototype.dispatchEvent = function (
2336
2340
this : FragmentInstanceType ,
2337
2341
event : Event ,
2338
2342
) : boolean {
2339
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2340
- if ( parentHostInstance === null ) {
2343
+ const parentHostFiber = getFragmentInstanceHostParent ( this . _fragmentFiber ) ;
2344
+ if ( parentHostFiber === null ) {
2341
2345
if ( __DEV__ ) {
2342
2346
console . error (
2343
2347
'You are attempting to dispatch an event on a disconnected ' +
@@ -2346,6 +2350,7 @@ FragmentInstance.prototype.dispatchEvent = function (
2346
2350
}
2347
2351
return true ;
2348
2352
}
2353
+ const parentHostInstance = getHostNodeFromHostFiber ( parentHostFiber ) ;
2349
2354
return parentHostInstance . dispatchEvent ( event ) ;
2350
2355
} ;
2351
2356
// $FlowFixMe[prop-missing]
@@ -2446,10 +2451,13 @@ FragmentInstance.prototype.getClientRects = function (
2446
2451
this : FragmentInstanceType ,
2447
2452
) : Array < DOMRect > {
2448
2453
const rects : Array < DOMRect > = [ ] ;
2449
- traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
2454
+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRectsFlat , rects ) ;
2450
2455
return rects ;
2451
2456
} ;
2452
- function collectClientRects(child: Instance, rects: Array< DOMRect > ): boolean {
2457
+ function collectClientRectsFlat(
2458
+ child: Instance,
2459
+ rects: Array< DOMRect > ,
2460
+ ): boolean {
2453
2461
// $FlowFixMe[method-unbinding]
2454
2462
rects . push . apply ( rects , child . getClientRects ( ) ) ;
2455
2463
return false ;
@@ -2459,10 +2467,11 @@ FragmentInstance.prototype.getRootNode = function (
2459
2467
this: FragmentInstanceType,
2460
2468
getRootNodeOptions?: { composed : boolean } ,
2461
2469
): Document | ShadowRoot | FragmentInstanceType {
2462
- const parentHostInstance = getFragmentParentHostInstance ( this . _fragmentFiber ) ;
2463
- if ( parentHostInstance === null ) {
2470
+ const parentHostFiber = getFragmentInstanceHostParent ( this . _fragmentFiber ) ;
2471
+ if ( parentHostFiber === null ) {
2464
2472
return this ;
2465
2473
}
2474
+ const parentHostInstance = getHostNodeFromHostFiber ( parentHostFiber ) ;
2466
2475
const rootNode =
2467
2476
// $FlowFixMe[incompatible-cast] Flow expects Node
2468
2477
( parentHostInstance . getRootNode ( getRootNodeOptions ) : Document | ShadowRoot ) ;
@@ -2503,6 +2512,113 @@ FragmentInstance.prototype.compareDocumentPosition = function (
2503
2512
2504
2513
return result ;
2505
2514
} ;
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
+ }
2506
2622
2507
2623
function normalizeListenerOptions (
2508
2624
opts : ?EventListenerOptionsOrUseCapture ,
0 commit comments