Skip to content

Commit 6b66e62

Browse files
committed
Add flag enableFragmentRefsScrollIntoView, prefix with experimental_
1 parent 716ccf8 commit 6b66e62

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
@@ -124,6 +124,7 @@ import {
124124
enableSrcObject,
125125
enableViewTransition,
126126
enableHydrationChangeEvent,
127+
enableFragmentRefsScrollIntoView,
127128
} from 'shared/ReactFeatureFlags';
128129
import {
129130
HostComponent,
@@ -3248,46 +3249,48 @@ function validateDocumentPositionWithFiberTree(
32483249
return false;
32493250
}
32503251

3251-
// $FlowFixMe[prop-missing]
3252-
FragmentInstance.prototype.scrollIntoView = function (
3253-
this: FragmentInstanceType,
3254-
alignToTop?: boolean,
3255-
): void {
3256-
if (typeof alignToTop === 'object') {
3257-
throw new Error(
3258-
'FragmentInstance.scrollIntoView() does not support ' +
3259-
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
3260-
);
3261-
}
3262-
// First, get the children nodes
3263-
const children: Array<Fiber> = [];
3264-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
3265-
3266-
// If there are no children, we can use the parent and siblings to determine a position
3267-
if (children.length === 0) {
3268-
const hostSiblings = getFragmentInstanceSiblings(this._fragmentFiber);
3269-
const targetFiber =
3270-
(alignToTop === false
3271-
? hostSiblings[0] || hostSiblings[1]
3272-
: hostSiblings[1] || hostSiblings[0]) ||
3273-
getFragmentParentHostFiber(this._fragmentFiber);
3274-
if (targetFiber === null) {
3275-
if (__DEV__) {
3276-
console.error(
3277-
'You are attempting to scroll a FragmentInstance that has no ' +
3278-
'children, siblings, or parent. No scroll was performed.',
3279-
);
3252+
if (enableFragmentRefsScrollIntoView) {
3253+
// $FlowFixMe[prop-missing]
3254+
FragmentInstance.prototype.experimental_scrollIntoView = function (
3255+
this: FragmentInstanceType,
3256+
alignToTop?: boolean,
3257+
): void {
3258+
if (typeof alignToTop === 'object') {
3259+
throw new Error(
3260+
'FragmentInstance.experimental_scrollIntoView() does not support ' +
3261+
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
3262+
);
3263+
}
3264+
// First, get the children nodes
3265+
const children: Array<Fiber> = [];
3266+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
3267+
3268+
// If there are no children, we can use the parent and siblings to determine a position
3269+
if (children.length === 0) {
3270+
const hostSiblings = getFragmentInstanceSiblings(this._fragmentFiber);
3271+
const targetFiber =
3272+
(alignToTop === false
3273+
? hostSiblings[0] || hostSiblings[1]
3274+
: hostSiblings[1] || hostSiblings[0]) ||
3275+
getFragmentParentHostFiber(this._fragmentFiber);
3276+
if (targetFiber === null) {
3277+
if (__DEV__) {
3278+
console.error(
3279+
'You are attempting to scroll a FragmentInstance that has no ' +
3280+
'children, siblings, or parent. No scroll was performed.',
3281+
);
3282+
}
3283+
return;
32803284
}
3285+
const target = getInstanceFromHostFiber<Instance>(targetFiber);
3286+
target.scrollIntoView(alignToTop);
32813287
return;
32823288
}
3283-
const target = getInstanceFromHostFiber<Instance>(targetFiber);
3284-
target.scrollIntoView(alignToTop);
3285-
return;
3286-
}
32873289

3288-
// If there are children, handle them per scroll container
3289-
scrollIntoViewByScrollContainer(children, alignToTop !== false);
3290-
};
3290+
// If there are children, handle them per scroll container
3291+
scrollIntoViewByScrollContainer(children, alignToTop !== false);
3292+
};
3293+
}
32913294

32923295
function isInstanceScrollable(inst: Instance): 0 | 1 | 2 {
32933296
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
@@ -1844,7 +1844,7 @@ describe('FragmentRefs', () => {
18441844
});
18451845

18461846
describe('scrollIntoView', () => {
1847-
// @gate enableFragmentRefs
1847+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
18481848
it('does not yet support options', async () => {
18491849
const fragmentRef = React.createRef();
18501850
const root = ReactDOMClient.createRoot(container);
@@ -1853,15 +1853,15 @@ describe('FragmentRefs', () => {
18531853
});
18541854

18551855
expect(() => {
1856-
fragmentRef.current.scrollIntoView({block: 'start'});
1856+
fragmentRef.current.experimental_scrollIntoView({block: 'start'});
18571857
}).toThrowError(
1858-
'FragmentInstance.scrollIntoView() does not support ' +
1858+
'FragmentInstance.experimental_scrollIntoView() does not support ' +
18591859
'scrollIntoViewOptions. Use the alignToTop boolean instead.',
18601860
);
18611861
});
18621862

18631863
describe('with children', () => {
1864-
// @gate enableFragmentRefs
1864+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
18651865
it('calls scrollIntoView on the first child by default, or if alignToTop=true', async () => {
18661866
const fragmentRef = React.createRef();
18671867
const childARef = React.createRef();
@@ -1883,19 +1883,19 @@ describe('FragmentRefs', () => {
18831883
childBRef.current.scrollIntoView = jest.fn();
18841884

18851885
// Default call
1886-
fragmentRef.current.scrollIntoView();
1886+
fragmentRef.current.experimental_scrollIntoView();
18871887
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
18881888
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
18891889

18901890
childARef.current.scrollIntoView.mockClear();
18911891

18921892
// alignToTop=true
1893-
fragmentRef.current.scrollIntoView(true);
1893+
fragmentRef.current.experimental_scrollIntoView(true);
18941894
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
18951895
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
18961896
});
18971897

1898-
// @gate enableFragmentRefs
1898+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
18991899
it('calls scrollIntoView on the last child if alignToTop is false', async () => {
19001900
const fragmentRef = React.createRef();
19011901
const childARef = React.createRef();
@@ -1913,12 +1913,12 @@ describe('FragmentRefs', () => {
19131913
childARef.current.scrollIntoView = jest.fn();
19141914
childBRef.current.scrollIntoView = jest.fn();
19151915

1916-
fragmentRef.current.scrollIntoView(false);
1916+
fragmentRef.current.experimental_scrollIntoView(false);
19171917
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
19181918
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
19191919
});
19201920

1921-
// @gate enableFragmentRefs
1921+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
19221922
it('handles portaled elements -- same scroll container', async () => {
19231923
const fragmentRef = React.createRef();
19241924
const childARef = React.createRef();
@@ -1950,12 +1950,12 @@ describe('FragmentRefs', () => {
19501950
childBRef.current.scrollIntoView = jest.fn();
19511951

19521952
// Default call
1953-
fragmentRef.current.scrollIntoView();
1953+
fragmentRef.current.experimental_scrollIntoView();
19541954
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
19551955
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
19561956
});
19571957

1958-
// @gate enableFragmentRefs
1958+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
19591959
it('handles portaled elements -- different scroll container', async () => {
19601960
const fragmentRef = React.createRef();
19611961
const headerChildRef = React.createRef();
@@ -2086,7 +2086,7 @@ describe('FragmentRefs', () => {
20862086
});
20872087

20882088
// Default call
2089-
fragmentRef.current.scrollIntoView();
2089+
fragmentRef.current.experimental_scrollIntoView();
20902090
expect(childCRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
20912091
// In the same group as A, we use the first child
20922092
expect(childBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
@@ -2102,7 +2102,7 @@ describe('FragmentRefs', () => {
21022102
logs = [];
21032103

21042104
// // alignToTop=false
2105-
fragmentRef.current.scrollIntoView(false);
2105+
fragmentRef.current.experimental_scrollIntoView(false);
21062106
expect(headerChildRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
21072107
// In the same group as B, only attempt B which is the last child
21082108
expect(childARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
@@ -2116,7 +2116,7 @@ describe('FragmentRefs', () => {
21162116
});
21172117

21182118
describe('without children', () => {
2119-
// @gate enableFragmentRefs
2119+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
21202120
it('calls scrollIntoView on the next sibling by default, or if alignToTop=true', async () => {
21212121
const fragmentRef = React.createRef();
21222122
const siblingARef = React.createRef();
@@ -2138,19 +2138,19 @@ describe('FragmentRefs', () => {
21382138
siblingBRef.current.scrollIntoView = jest.fn();
21392139

21402140
// Default call
2141-
fragmentRef.current.scrollIntoView();
2141+
fragmentRef.current.experimental_scrollIntoView();
21422142
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
21432143
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
21442144

21452145
siblingBRef.current.scrollIntoView.mockClear();
21462146

21472147
// alignToTop=true
2148-
fragmentRef.current.scrollIntoView(true);
2148+
fragmentRef.current.experimental_scrollIntoView(true);
21492149
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
21502150
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
21512151
});
21522152

2153-
// @gate enableFragmentRefs
2153+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
21542154
it('calls scrollIntoView on the prev sibling if alignToTop is false', async () => {
21552155
const fragmentRef = React.createRef();
21562156
const siblingARef = React.createRef();
@@ -2182,12 +2182,12 @@ describe('FragmentRefs', () => {
21822182
siblingBRef.current.scrollIntoView = jest.fn();
21832183

21842184
// alignToTop=false
2185-
fragmentRef.current.scrollIntoView(false);
2185+
fragmentRef.current.experimental_scrollIntoView(false);
21862186
expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
21872187
expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
21882188
});
21892189

2190-
// @gate enableFragmentRefs
2190+
// @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
21912191
it('calls scrollIntoView on the parent if there are no siblings', async () => {
21922192
const fragmentRef = React.createRef();
21932193
const parentRef = React.createRef();
@@ -2203,7 +2203,7 @@ describe('FragmentRefs', () => {
22032203
});
22042204

22052205
parentRef.current.scrollIntoView = jest.fn();
2206-
fragmentRef.current.scrollIntoView();
2206+
fragmentRef.current.experimental_scrollIntoView();
22072207
expect(parentRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
22082208
});
22092209
});

packages/shared/ReactFeatureFlags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export const transitionLaneExpirationMs = 5000;
152152
export const enableInfiniteRenderLoopDetection: boolean = false;
153153

154154
export const enableFragmentRefs = __EXPERIMENTAL__;
155+
export const enableFragmentRefsScrollIntoView = __EXPERIMENTAL__;
155156

156157
// -----------------------------------------------------------------------------
157158
// 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
@@ -25,4 +25,5 @@ export const enableEagerAlternateStateNodeCleanup = __VARIANT__;
2525
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
2626
export const renameElementSymbol = __VARIANT__;
2727
export const enableFragmentRefs = __VARIANT__;
28+
export const enableFragmentRefsScrollIntoView = __VARIANT__;
2829
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
@@ -27,6 +27,7 @@ export const {
2727
passChildrenWhenCloningPersistedNodes,
2828
renameElementSymbol,
2929
enableFragmentRefs,
30+
enableFragmentRefsScrollIntoView,
3031
} = dynamicFlags;
3132

3233
// 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
@@ -73,6 +73,7 @@ export const enableDefaultTransitionIndicator: boolean = false;
7373
export const ownerStackLimit = 1e4;
7474

7575
export const enableFragmentRefs: boolean = false;
76+
export const enableFragmentRefsScrollIntoView: boolean = false;
7677

7778
// Profiling Only
7879
export const enableProfilerTimer: boolean = __PROFILE__;

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

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

7777
export const enableFragmentRefs: boolean = false;
78+
export const enableFragmentRefsScrollIntoView: boolean = false;
7879

7980
// TODO: This must be in sync with the main ReactFeatureFlags file because
8081
// 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
@@ -68,6 +68,7 @@ export const enableSrcObject = false;
6868
export const enableHydrationChangeEvent = false;
6969
export const enableDefaultTransitionIndicator = false;
7070
export const enableFragmentRefs = false;
71+
export const enableFragmentRefsScrollIntoView = false;
7172
export const ownerStackLimit = 1e4;
7273

7374
// 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
@@ -82,6 +82,7 @@ export const enableHydrationChangeEvent: boolean = false;
8282
export const enableDefaultTransitionIndicator: boolean = false;
8383

8484
export const enableFragmentRefs: boolean = false;
85+
export const enableFragmentRefsScrollIntoView: boolean = false;
8586
export const ownerStackLimit = 1e4;
8687

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

0 commit comments

Comments
 (0)