Skip to content

Commit 03c0543

Browse files
committed
Add flag enableFragmentRefsScrollIntoView, prefix with experimental_
1 parent 22f9647 commit 03c0543

File tree

13 files changed

+71
-59
lines changed

13 files changed

+71
-59
lines changed

fixtures/dom/src/components/fixtures/fragment-refs/ScrollIntoViewCase.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export default function ScrollIntoViewCase() {
5555
const scrollContainerRef = useRef(null);
5656

5757
const scrollVertical = () => {
58-
fragmentRef.current.scrollIntoView(alignToTop);
58+
fragmentRef.current.experimental_scrollIntoView(alignToTop);
5959
};
6060

6161
const scrollVerticalNoChildren = () => {
62-
noChildRef.current.scrollIntoView(alignToTop);
62+
noChildRef.current.experimental_scrollIntoView(alignToTop);
6363
};
6464

6565
useEffect(() => {

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import {
122122
enableSrcObject,
123123
enableViewTransition,
124124
enableHydrationChangeEvent,
125+
enableFragmentRefsScrollIntoView,
125126
} from 'shared/ReactFeatureFlags';
126127
import {
127128
HostComponent,
@@ -3201,46 +3202,48 @@ function validateDocumentPositionWithFiberTree(
32013202
return false;
32023203
}
32033204

3204-
// $FlowFixMe[prop-missing]
3205-
FragmentInstance.prototype.scrollIntoView = function (
3206-
this: FragmentInstanceType,
3207-
alignToTop?: boolean,
3208-
): void {
3209-
if (typeof alignToTop === 'object') {
3210-
throw new Error(
3211-
'FragmentInstance.scrollIntoView() does not support ' +
3212-
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
3213-
);
3214-
}
3215-
// First, get the children nodes
3216-
const children: Array<Fiber> = [];
3217-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
3218-
3219-
// If there are no children, we can use the parent and siblings to determine a position
3220-
if (children.length === 0) {
3221-
const hostSiblings = getFragmentInstanceSiblings(this._fragmentFiber);
3222-
const targetFiber =
3223-
(alignToTop === false
3224-
? hostSiblings[0] || hostSiblings[1]
3225-
: hostSiblings[1] || hostSiblings[0]) ||
3226-
getFragmentParentHostFiber(this._fragmentFiber);
3227-
if (targetFiber === null) {
3228-
if (__DEV__) {
3229-
console.error(
3230-
'You are attempting to scroll a FragmentInstance that has no ' +
3231-
'children, siblings, or parent. No scroll was performed.',
3232-
);
3205+
if (enableFragmentRefsScrollIntoView) {
3206+
// $FlowFixMe[prop-missing]
3207+
FragmentInstance.prototype.experimental_scrollIntoView = function (
3208+
this: FragmentInstanceType,
3209+
alignToTop?: boolean,
3210+
): void {
3211+
if (typeof alignToTop === 'object') {
3212+
throw new Error(
3213+
'FragmentInstance.experimental_scrollIntoView() does not support ' +
3214+
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
3215+
);
3216+
}
3217+
// First, get the children nodes
3218+
const children: Array<Fiber> = [];
3219+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
3220+
3221+
// If there are no children, we can use the parent and siblings to determine a position
3222+
if (children.length === 0) {
3223+
const hostSiblings = getFragmentInstanceSiblings(this._fragmentFiber);
3224+
const targetFiber =
3225+
(alignToTop === false
3226+
? hostSiblings[0] || hostSiblings[1]
3227+
: hostSiblings[1] || hostSiblings[0]) ||
3228+
getFragmentParentHostFiber(this._fragmentFiber);
3229+
if (targetFiber === null) {
3230+
if (__DEV__) {
3231+
console.error(
3232+
'You are attempting to scroll a FragmentInstance that has no ' +
3233+
'children, siblings, or parent. No scroll was performed.',
3234+
);
3235+
}
3236+
return;
32333237
}
3238+
const target = getInstanceFromHostFiber<Instance>(targetFiber);
3239+
target.scrollIntoView(alignToTop);
32343240
return;
32353241
}
3236-
const target = getInstanceFromHostFiber<Instance>(targetFiber);
3237-
target.scrollIntoView(alignToTop);
3238-
return;
3239-
}
32403242

3241-
// If there are children, handle them per scroll container
3242-
scrollIntoViewByScrollContainer(children, alignToTop !== false);
3243-
};
3243+
// If there are children, handle them per scroll container
3244+
scrollIntoViewByScrollContainer(children, alignToTop !== false);
3245+
};
3246+
}
32443247

32453248
function isInstanceScrollable(inst: Instance): 0 | 1 | 2 {
32463249
const style = getComputedStyle(inst);

packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,7 +1741,7 @@ describe('FragmentRefs', () => {
17411741
});
17421742

17431743
describe('scrollIntoView', () => {
1744-
// @gate enableFragmentRefs
1744+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
17451745
it('does not yet support options', async () => {
17461746
const fragmentRef = React.createRef();
17471747
const root = ReactDOMClient.createRoot(container);
@@ -1750,15 +1750,15 @@ describe('FragmentRefs', () => {
17501750
});
17511751

17521752
expect(() => {
1753-
fragmentRef.current.scrollIntoView({block: 'start'});
1753+
fragmentRef.current.experimental_scrollIntoView({block: 'start'});
17541754
}).toThrowError(
1755-
'FragmentInstance.scrollIntoView() does not support ' +
1755+
'FragmentInstance.experimental_scrollIntoView() does not support ' +
17561756
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
17571757
);
17581758
});
17591759

17601760
describe('with children', () => {
1761-
// @gate enableFragmentRefs
1761+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
17621762
it('calls scrollIntoView on the first child by default, or if alignToTop=true', async () => {
17631763
const fragmentRef = React.createRef();
17641764
const childARef = React.createRef();
@@ -1780,19 +1780,19 @@ describe('FragmentRefs', () => {
17801780
childBRef.current.scrollIntoView = jest.fn();
17811781

17821782
// Default call
1783-
fragmentRef.current.scrollIntoView();
1783+
fragmentRef.current.experimental_scrollIntoView();
17841784
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
17851785
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
17861786

17871787
childARef.current.scrollIntoView.mockClear();
17881788

17891789
// alignToTop=true
1790-
fragmentRef.current.scrollIntoView(true);
1790+
fragmentRef.current.experimental_scrollIntoView(true);
17911791
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
17921792
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
17931793
});
17941794

1795-
// @gate enableFragmentRefs
1795+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
17961796
it('calls scrollIntoView on the last child if alignToTop is false', async () => {
17971797
const fragmentRef = React.createRef();
17981798
const childARef = React.createRef();
@@ -1810,12 +1810,12 @@ describe('FragmentRefs', () => {
18101810
childARef.current.scrollIntoView = jest.fn();
18111811
childBRef.current.scrollIntoView = jest.fn();
18121812

1813-
fragmentRef.current.scrollIntoView(false);
1813+
fragmentRef.current.experimental_scrollIntoView(false);
18141814
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
18151815
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
18161816
});
18171817

1818-
// @gate enableFragmentRefs
1818+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
18191819
it('handles portaled elements -- same scroll container', async () => {
18201820
const fragmentRef = React.createRef();
18211821
const childARef = React.createRef();
@@ -1847,12 +1847,12 @@ describe('FragmentRefs', () => {
18471847
childBRef.current.scrollIntoView = jest.fn();
18481848

18491849
// Default call
1850-
fragmentRef.current.scrollIntoView();
1850+
fragmentRef.current.experimental_scrollIntoView();
18511851
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
18521852
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
18531853
});
18541854

1855-
// @gate enableFragmentRefs
1855+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
18561856
it('handles portaled elements -- different scroll container', async () => {
18571857
const fragmentRef = React.createRef();
18581858
const headerChildRef = React.createRef();
@@ -1983,7 +1983,7 @@ describe('FragmentRefs', () => {
19831983
});
19841984

19851985
// Default call
1986-
fragmentRef.current.scrollIntoView();
1986+
fragmentRef.current.experimental_scrollIntoView();
19871987
expect(childCRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
19881988
// In the same group as A, we use the first child
19891989
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
@@ -1999,7 +1999,7 @@ describe('FragmentRefs', () => {
19991999
logs = [];
20002000

20012001
// // alignToTop=false
2002-
fragmentRef.current.scrollIntoView(false);
2002+
fragmentRef.current.experimental_scrollIntoView(false);
20032003
expect(headerChildRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
20042004
// In the same group as B, only attempt B which is the last child
20052005
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
@@ -2013,7 +2013,7 @@ describe('FragmentRefs', () => {
20132013
});
20142014

20152015
describe('without children', () => {
2016-
// @gate enableFragmentRefs
2016+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
20172017
it('calls scrollIntoView on the next sibling by default, or if alignToTop=true', async () => {
20182018
const fragmentRef = React.createRef();
20192019
const siblingARef = React.createRef();
@@ -2035,19 +2035,19 @@ describe('FragmentRefs', () => {
20352035
siblingBRef.current.scrollIntoView = jest.fn();
20362036

20372037
// Default call
2038-
fragmentRef.current.scrollIntoView();
2038+
fragmentRef.current.experimental_scrollIntoView();
20392039
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
20402040
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
20412041

20422042
siblingBRef.current.scrollIntoView.mockClear();
20432043

20442044
// alignToTop=true
2045-
fragmentRef.current.scrollIntoView(true);
2045+
fragmentRef.current.experimental_scrollIntoView(true);
20462046
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
20472047
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
20482048
});
20492049

2050-
// @gate enableFragmentRefs
2050+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
20512051
it('calls scrollIntoView on the prev sibling if alignToTop is false', async () => {
20522052
const fragmentRef = React.createRef();
20532053
const siblingARef = React.createRef();
@@ -2079,12 +2079,12 @@ describe('FragmentRefs', () => {
20792079
siblingBRef.current.scrollIntoView = jest.fn();
20802080

20812081
// alignToTop=false
2082-
fragmentRef.current.scrollIntoView(false);
2082+
fragmentRef.current.experimental_scrollIntoView(false);
20832083
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
20842084
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
20852085
});
20862086

2087-
// @gate enableFragmentRefs
2087+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
20882088
it('calls scrollIntoView on the parent if there are no siblings', async () => {
20892089
const fragmentRef = React.createRef();
20902090
const parentRef = React.createRef();
@@ -2100,7 +2100,7 @@ describe('FragmentRefs', () => {
21002100
});
21012101

21022102
parentRef.current.scrollIntoView = jest.fn();
2103-
fragmentRef.current.scrollIntoView();
2103+
fragmentRef.current.experimental_scrollIntoView();
21042104
expect(parentRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
21052105
});
21062106
});

packages/shared/ReactFeatureFlags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export const enableInfiniteRenderLoopDetection = false;
154154
export const enableLazyPublicInstanceInFabric = false;
155155

156156
export const enableFragmentRefs = __EXPERIMENTAL__;
157+
export const enableFragmentRefsScrollIntoView = __EXPERIMENTAL__;
157158

158159
// -----------------------------------------------------------------------------
159160
// Ready for next major.

packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
2626
export const enableLazyPublicInstanceInFabric = __VARIANT__;
2727
export const renameElementSymbol = __VARIANT__;
2828
export const enableFragmentRefs = __VARIANT__;
29+
export const enableFragmentRefsScrollIntoView = __VARIANT__;
2930
export const enableComponentPerformanceTrack = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const {
2828
enableLazyPublicInstanceInFabric,
2929
renameElementSymbol,
3030
enableFragmentRefs,
31+
enableFragmentRefsScrollIntoView,
3132
} = dynamicFlags;
3233

3334
// The rest of the flags are static for better dead code elimination.

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const enableDefaultTransitionIndicator = false;
7474
export const ownerStackLimit = 1e4;
7575

7676
export const enableFragmentRefs = false;
77+
export const enableFragmentRefsScrollIntoView = false;
7778

7879
// Profiling Only
7980
export const enableProfilerTimer = __PROFILE__;

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const enableDefaultTransitionIndicator = false;
7676
export const ownerStackLimit = 1e4;
7777

7878
export const enableFragmentRefs = false;
79+
export const enableFragmentRefsScrollIntoView = false;
7980

8081
// TODO: This must be in sync with the main ReactFeatureFlags file because
8182
// the Test Renderer's value must be the same as the one used by the

packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const enableSrcObject = false;
7070
export const enableHydrationChangeEvent = false;
7171
export const enableDefaultTransitionIndicator = false;
7272
export const enableFragmentRefs = false;
73+
export const enableFragmentRefsScrollIntoView = false;
7374
export const ownerStackLimit = 1e4;
7475

7576
// Flow magic to verify the exports of this file match the original version.

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const enableHydrationChangeEvent = false;
8383
export const enableDefaultTransitionIndicator = false;
8484

8585
export const enableFragmentRefs = false;
86+
export const enableFragmentRefsScrollIntoView = false;
8687
export const ownerStackLimit = 1e4;
8788

8889
// Flow magic to verify the exports of this file match the original version.

0 commit comments

Comments
 (0)