@@ -35,11 +35,6 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
35
35
import hasOwnProperty from 'shared/hasOwnProperty' ;
36
36
import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
37
37
import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
38
- import {
39
- isFiberContainedBy ,
40
- isFiberFollowing ,
41
- isFiberPreceding ,
42
- } from 'react-reconciler/src/ReactFiberTreeReflection' ;
43
38
44
39
export {
45
40
setCurrentUpdatePriority ,
@@ -66,6 +61,11 @@ import {
66
61
getFragmentParentHostFiber ,
67
62
getNextSiblingHostFiber ,
68
63
getInstanceFromHostFiber ,
64
+ groupFragmentChildrenByScrollContainer ,
65
+ isFiberContainedBy ,
66
+ isFiberFollowing ,
67
+ isFiberPreceding ,
68
+ getFragmentInstanceSiblings ,
69
69
} from 'react-reconciler/src/ReactFiberTreeReflection' ;
70
70
71
71
export { detachDeletedInstance } ;
@@ -2427,6 +2427,7 @@ export type FragmentInstanceType = {
2427
2427
composed : boolean ,
2428
2428
} ) : Document | ShadowRoot | FragmentInstanceType ,
2429
2429
compareDocumentPosition ( otherNode : Instance ) : number ,
2430
+ scrollIntoView ( alignToTop ? : boolean ) : void ,
2430
2431
} ;
2431
2432
2432
2433
function FragmentInstance ( this : FragmentInstanceType , fragmentFiber : Fiber ) {
@@ -2654,10 +2655,10 @@ FragmentInstance.prototype.getClientRects = function (
2654
2655
this : FragmentInstanceType ,
2655
2656
) : Array < DOMRect > {
2656
2657
const rects : Array < DOMRect > = [ ] ;
2657
- traverseFragmentInstance ( this . _fragmentFiber , collectClientRects , rects ) ;
2658
+ traverseFragmentInstance ( this . _fragmentFiber , collectClientRectsFlat , rects ) ;
2658
2659
return rects ;
2659
2660
} ;
2660
- function collectClientRects (child: Fiber, rects: Array< DOMRect > ): boolean {
2661
+ function collectClientRectsFlat (child: Fiber, rects: Array< DOMRect > ): boolean {
2661
2662
const instance = getInstanceFromHostFiber ( child ) ;
2662
2663
// $FlowFixMe[method-unbinding]
2663
2664
rects . push . apply ( rects , instance . getClientRects ( ) ) ;
@@ -2802,6 +2803,113 @@ function validateDocumentPositionWithFiberTree(
2802
2803
2803
2804
return false;
2804
2805
}
2806
+ // $FlowFixMe[prop-missing]
2807
+ FragmentInstance . prototype . scrollIntoView = function (
2808
+ this : FragmentInstanceType ,
2809
+ alignToTop ?: boolean ,
2810
+ ) : void {
2811
+ if ( typeof alignToTop === 'object' ) {
2812
+ throw new Error (
2813
+ 'FragmentInstance.scrollIntoView() does not support ' +
2814
+ 'scrollIntoViewOptions. Use the alignToTop boolean instead.' ,
2815
+ ) ;
2816
+ }
2817
+
2818
+ const childrenByScrollContainer = groupFragmentChildrenByScrollContainer(
2819
+ this._fragmentFiber,
2820
+ fiber => {
2821
+ const instance = getInstanceFromHostFiber ( fiber ) ;
2822
+ const position = getComputedStyle ( instance ) . position ;
2823
+ return position === 'sticky' || position === 'fixed' ;
2824
+ } ,
2825
+ );
2826
+
2827
+ // If there are no children, go off the previous or next sibling
2828
+ if (childrenByScrollContainer[0].length === 0) {
2829
+ const hostSiblings = getFragmentInstanceSiblings ( this . _fragmentFiber ) ;
2830
+ const targetFiber =
2831
+ ( alignToTop === false
2832
+ ? hostSiblings [ 0 ] || hostSiblings [ 1 ]
2833
+ : hostSiblings [ 1 ] || hostSiblings [ 0 ] ) ||
2834
+ getFragmentParentHostFiber ( this . _fragmentFiber ) ;
2835
+ if ( targetFiber === null ) {
2836
+ if ( __DEV__ ) {
2837
+ console . error (
2838
+ 'You are attempting to scroll a FragmentInstance that has no ' +
2839
+ 'children, siblings, or parent. No scroll was performed.' ,
2840
+ ) ;
2841
+ }
2842
+ return ;
2843
+ }
2844
+ const target = getInstanceFromHostFiber ( targetFiber ) ;
2845
+ target . scrollIntoView ( alignToTop ) ;
2846
+ } else {
2847
+ iterateFragmentChildrenScrollContainers (
2848
+ childrenByScrollContainer ,
2849
+ alignToTop !== false ,
2850
+ ( targetFiber , alignToTopArg , scrollState ) => {
2851
+ if ( targetFiber ) {
2852
+ const target = getInstanceFromHostFiber ( targetFiber ) ;
2853
+ const targetPosition = getComputedStyle ( target ) . position ;
2854
+ const isStickyOrFixed =
2855
+ targetPosition === 'sticky' || targetPosition === 'fixed' ;
2856
+ const targetRect = target . getBoundingClientRect ( ) ;
2857
+ const distanceToTargetEdge = Math . abs ( targetRect . bottom ) ;
2858
+ const hasNotScrolled =
2859
+ scrollState . nextScrollThreshold === Number . MAX_SAFE_INTEGER ;
2860
+ const ownerDocument = target . ownerDocument ;
2861
+ const documentElement = ownerDocument . documentElement ;
2862
+ const targetWithinViewport =
2863
+ documentElement &&
2864
+ ( targetRect . top >= 0 ||
2865
+ targetRect . bottom <= documentElement . clientHeight ) ;
2866
+ // If we've already scrolled, only scroll again if
2867
+ // 1) The previous scroll target was sticky or fixed OR
2868
+ // 2) Scrolling to the next target won't remove previous target from viewport AND
2869
+ // 3) The next target is not already in the viewport
2870
+ if (
2871
+ hasNotScrolled ||
2872
+ scrollState . prevWasStickyOrFixed ||
2873
+ ( distanceToTargetEdge < scrollState . nextScrollThreshold &&
2874
+ ! targetWithinViewport )
2875
+ ) {
2876
+ target . scrollIntoView ( alignToTopArg ) ;
2877
+ scrollState . nextScrollThreshold = targetRect . height ;
2878
+ scrollState . prevWasStickyOrFixed = isStickyOrFixed ;
2879
+ }
2880
+ }
2881
+ } ,
2882
+ ) ;
2883
+ }
2884
+ } ;
2885
+
2886
+ function iterateFragmentChildrenScrollContainers (
2887
+ childrenByScrollContainer : Array < Array < Fiber > > ,
2888
+ alignToTop : boolean ,
2889
+ callback : (
2890
+ child : Fiber | null ,
2891
+ arg : boolean ,
2892
+ scrollState : { nextScrollThreshold : number , prevWasStickyOrFixed : boolean } ,
2893
+ ) => void ,
2894
+ ) {
2895
+ const scrollState = {
2896
+ nextScrollThreshold : Number . MAX_SAFE_INTEGER ,
2897
+ prevWasStickyOrFixed : false ,
2898
+ } ;
2899
+ if ( alignToTop ) {
2900
+ for ( let i = 0 ; i < childrenByScrollContainer . length ; i ++ ) {
2901
+ const children = childrenByScrollContainer [ i ] ;
2902
+ const child = children [ 0 ] ;
2903
+ callback ( child , alignToTop , scrollState ) ;
2904
+ }
2905
+ } else {
2906
+ for ( let i = childrenByScrollContainer . length - 1 ; i >= 0 ; i -- ) {
2907
+ const children = childrenByScrollContainer [ i ] ;
2908
+ const child = children [ children . length - 1 ] ;
2909
+ callback ( child , alignToTop , scrollState ) ;
2910
+ }
2911
+ }
2912
+ }
2805
2913
2806
2914
function normalizeListenerOptions (
2807
2915
opts : ?EventListenerOptionsOrUseCapture ,
0 commit comments