diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js index 563f2ad05429..4d9b2803dde0 100644 --- a/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js +++ b/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js @@ -1,17 +1,10 @@ import TestCase from '../../TestCase'; import Fixture from '../../Fixture'; +import PrintRectsFragmentContainer from './PrintRectsFragmentContainer'; const React = window.React; -const {Fragment, useRef, useState} = React; export default function GetClientRectsCase() { - const fragmentRef = useRef(null); - const [rects, setRects] = useState([]); - const getRects = () => { - const rects = fragmentRef.current.getClientRects(); - setRects(rects); - }; - return ( @@ -26,74 +19,35 @@ export default function GetClientRectsCase() { - -
-
+ - {rects.map(({x, y, width, height}, index) => { - const scale = 0.3; - - return ( -
- ); - })} -
-
- {rects.map(({x, y, width, height}, index) => { - return ( -
- {index} :: {`{`}x: {x}, y: {y}, width: {width}, height:{' '} - {height} - {`}`} -
- ); - })} -
-
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. + +
+
+
- - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. - -
-
-
); diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/PrintRectsFragmentContainer.js b/fixtures/dom/src/components/fixtures/fragment-refs/PrintRectsFragmentContainer.js new file mode 100644 index 000000000000..a084932a512d --- /dev/null +++ b/fixtures/dom/src/components/fixtures/fragment-refs/PrintRectsFragmentContainer.js @@ -0,0 +1,126 @@ +const React = window.React; +const {Fragment, useRef, useState} = React; + +const colors = [ + '#e74c3c', + '#3498db', + '#2ecc71', + '#9b59b6', + '#f39c12', + '#1abc9c', +]; + +export default function PrintRectsFragmentContainer({children}) { + const fragmentRef = useRef(null); + const [rects, setRects] = useState([]); + + const getRects = () => { + const rectsResult = fragmentRef.current.getClientRects(); + setRects(Array.from(rectsResult)); + }; + + const getColor = index => colors[index % colors.length]; + + return ( + +
+ + {rects.length > 0 && ( + + Found {rects.length} rect{rects.length !== 1 ? 's' : ''} + + )} +
+ +
+
+ {rects.length === 0 && ( +
+ Click button to visualize rects +
+ )} + {rects.map(({x, y, width, height}, index) => { + const scale = 0.3; + const color = getColor(index); + + return ( +
+ ); + })} +
+ +
+ {rects.map(({x, y, width, height}, index) => { + const color = getColor(index); + return ( +
+ #{index}{' '} + + x: {Math.round(x)}, y: {Math.round(y)}, w: {Math.round(width)} + , h: {Math.round(height)} + +
+ ); + })} +
+
+ +
+ {children} +
+ + ); +} diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js new file mode 100644 index 000000000000..4141e069eec1 --- /dev/null +++ b/fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js @@ -0,0 +1,319 @@ +import TestCase from '../../TestCase'; +import Fixture from '../../Fixture'; +import PrintRectsFragmentContainer from './PrintRectsFragmentContainer'; + +const React = window.React; +const {Fragment, useRef, useState} = React; + +function GetClientRectsTextOnly() { + return ( + + +
  • Click the "Print Rects" button
  • +
    + + The fragment contains only text nodes. getClientRects should return + bounding rectangles for the text content using the Range API. + + + + + This is text content inside a fragment with no element children. + + + +
    + ); +} + +function GetClientRectsMixed() { + return ( + + +
  • Click the "Print Rects" button
  • +
    + + The fragment contains both text nodes and elements. getClientRects + should return bounding rectangles for both text content (via Range API) + and elements. + + + + + Text before the span. + + Element + + Text after the span. +
    + More text at the end. +
    +
    +
    +
    + ); +} + +function FocusTextOnlyNoop() { + const fragmentRef = useRef(null); + const [message, setMessage] = useState(''); + + const tryFocus = () => { + fragmentRef.current.focus(); + setMessage('Called focus() - no-op for text-only fragments'); + }; + + const tryFocusLast = () => { + fragmentRef.current.focusLast(); + setMessage('Called focusLast() - no-op for text-only fragments'); + }; + + return ( + + +
  • Click either focus button
  • +
    + + Calling focus() or focusLast() on a fragment with only text children is + a no-op. Nothing happens and no warning is logged. This is because text + nodes cannot receive focus. + + + + + + {message && ( +
    {message}
    + )} +
    +
    + + This fragment contains only text. Text nodes are not focusable. + +
    +
    +
    + ); +} + +function ScrollIntoViewTextOnly() { + const fragmentRef = useRef(null); + const [message, setMessage] = useState(''); + + const tryScrollIntoView = alignToTop => { + fragmentRef.current.scrollIntoView(alignToTop); + setMessage( + `Called scrollIntoView(${alignToTop}) - page should scroll to text` + ); + }; + + return ( + + +
  • Scroll down the page so the text fragment is not visible
  • +
  • Click one of the scrollIntoView buttons
  • +
    + + The page should scroll to bring the text content into view. With + alignToTop=true, the text should appear at the top of the viewport. With + alignToTop=false, it should appear at the bottom. This uses the Range + API to calculate text node positions. + + + + + + {message && ( +
    {message}
    + )} +
    +
    + + This fragment contains only text. The scrollIntoView method uses the + Range API to calculate the text position and scroll to it. + +
    +
    +
    + ); +} + +function ScrollIntoViewMixed() { + const fragmentRef = useRef(null); + const [message, setMessage] = useState(''); + + const tryScrollIntoView = alignToTop => { + fragmentRef.current.scrollIntoView(alignToTop); + setMessage( + `Called scrollIntoView(${alignToTop}) - page should scroll to fragment` + ); + }; + + const targetStyle = { + height: 300, + marginBottom: 50, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '24px', + fontWeight: 'bold', + }; + + return ( + + +
  • Scroll down the page so the fragment is not visible
  • +
  • Click one of the scrollIntoView buttons
  • +
    + + The fragment contains raw text nodes (not wrapped in elements) and + elements in alternating order. With alignToTop=true, scroll starts from + the last child and works backwards, ending with the first text node at + the top. With alignToTop=false, scroll starts from the first child and + works forward, ending with the last text node at the bottom. Text nodes + use the Range API for scrolling. + + + + + + {message && ( +
    {message}
    + )} +
    +
    + + TEXT NODE 1 - This is a raw text node at the start of the fragment +
    + ELEMENT 1 +
    + TEXT NODE 2 - This is a raw text node between elements +
    + ELEMENT 2 +
    + TEXT NODE 3 - This is a raw text node between elements +
    + ELEMENT 3 +
    + TEXT NODE 4 - This is a raw text node at the end of the fragment +
    +
    +
    +
    + ); +} + +function ObserveTextOnlyWarning() { + const fragmentRef = useRef(null); + const [message, setMessage] = useState(''); + + const tryObserve = () => { + setMessage('Called observeUsing() - check console for warning'); + const observer = new IntersectionObserver(() => {}); + fragmentRef.current.observeUsing(observer); + }; + + return ( + + +
  • Open the browser console
  • +
  • Click the observeUsing button
  • +
    + + A warning should appear in the console because IntersectionObserver + cannot observe text nodes. The warning message should indicate that + observeUsing() was called on a FragmentInstance with only text children. + + + + + {message && ( +
    {message}
    + )} +
    +
    + + This fragment contains only text. Text nodes cannot be observed. + +
    +
    +
    + ); +} + +export default function TextNodesCase() { + return ( + + +

    + This section demonstrates how various FragmentInstance methods work + with text nodes. +

    +

    + Supported: getClientRects, compareDocumentPosition, + scrollIntoView +

    +

    + No-op (silent): focus, focusLast (text nodes cannot + receive focus) +

    +

    + Not supported (warns): observeUsing (observers cannot + observe text nodes) +

    +
    + + + + + + +
    + ); +} diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/index.js b/fixtures/dom/src/components/fixtures/fragment-refs/index.js index c560b59fbec6..ca6e8185116c 100644 --- a/fixtures/dom/src/components/fixtures/fragment-refs/index.js +++ b/fixtures/dom/src/components/fixtures/fragment-refs/index.js @@ -6,6 +6,7 @@ import ResizeObserverCase from './ResizeObserverCase'; import FocusCase from './FocusCase'; import GetClientRectsCase from './GetClientRectsCase'; import ScrollIntoViewCase from './ScrollIntoViewCase'; +import TextNodesCase from './TextNodesCase'; const React = window.React; @@ -19,6 +20,7 @@ export default function FragmentRefsPage() { + ); } diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 026f4ae0c8c3..09653552aafe 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -127,6 +127,7 @@ import { enableFragmentRefsScrollIntoView, enableProfilerTimer, enableFragmentRefsInstanceHandles, + enableFragmentRefsTextNodes, } from 'shared/ReactFeatureFlags'; import { HostComponent, @@ -2956,6 +2957,7 @@ function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { this._eventListeners = null; this._observers = null; } + // $FlowFixMe[prop-missing] FragmentInstance.prototype.addEventListener = function ( this: FragmentInstanceType, @@ -3119,6 +3121,12 @@ function setFocusOnFiberIfFocusable( fiber: Fiber, focusOptions?: FocusOptions, ): boolean { + if (enableFragmentRefsTextNodes) { + // Skip text nodes - they are not focusable + if (fiber.tag === HostText) { + return false; + } + } const instance = getInstanceFromHostFiber(fiber); return setFocusIfFocusable(instance, focusOptions); } @@ -3169,6 +3177,28 @@ FragmentInstance.prototype.observeUsing = function ( this: FragmentInstanceType, observer: IntersectionObserver | ResizeObserver, ): void { + if (__DEV__) { + if (enableFragmentRefsTextNodes) { + let hasText = false; + let hasElement = false; + traverseFragmentInstance(this._fragmentFiber, (child: Fiber) => { + if (child.tag === HostText) { + hasText = true; + } else { + // Stop traversal, found element + hasElement = true; + return true; + } + return false; + }); + if (hasText && !hasElement) { + console.error( + 'observeUsing() was called on a FragmentInstance with only text children. ' + + 'Observers do not work on text nodes.', + ); + } + } + } if (this._observers === null) { this._observers = new Set(); } @@ -3179,6 +3209,12 @@ function observeChild( child: Fiber, observer: IntersectionObserver | ResizeObserver, ) { + if (enableFragmentRefsTextNodes) { + // Skip text nodes - observers don't work on them + if (child.tag === HostText) { + return false; + } + } const instance = getInstanceFromHostFiber(child); observer.observe(instance); return false; @@ -3205,6 +3241,12 @@ function unobserveChild( child: Fiber, observer: IntersectionObserver | ResizeObserver, ) { + if (enableFragmentRefsTextNodes) { + // Skip text nodes - they were never observed + if (child.tag === HostText) { + return false; + } + } const instance = getInstanceFromHostFiber(child); observer.unobserve(instance); return false; @@ -3218,9 +3260,17 @@ FragmentInstance.prototype.getClientRects = function ( return rects; }; function collectClientRects(child: Fiber, rects: Array): boolean { - const instance = getInstanceFromHostFiber(child); - // $FlowFixMe[method-unbinding] - rects.push.apply(rects, instance.getClientRects()); + if (enableFragmentRefsTextNodes && child.tag === HostText) { + const textNode: Text = child.stateNode; + const range = textNode.ownerDocument.createRange(); + range.selectNodeContents(textNode); + // $FlowFixMe[method-unbinding] + rects.push.apply(rects, range.getClientRects()); + } else { + const instance = getInstanceFromHostFiber(child); + // $FlowFixMe[method-unbinding] + rects.push.apply(rects, instance.getClientRects()); + } return false; } // $FlowFixMe[prop-missing] @@ -3426,6 +3476,19 @@ if (enableFragmentRefsScrollIntoView) { let i = resolvedAlignToTop ? children.length - 1 : 0; while (i !== (resolvedAlignToTop ? -1 : children.length)) { const child = children[i]; + // For text nodes, use Range API to scroll to their position + if (enableFragmentRefsTextNodes && child.tag === HostText) { + const textNode: Text = child.stateNode; + const range = textNode.ownerDocument.createRange(); + range.selectNodeContents(textNode); + const rect = range.getBoundingClientRect(); + const scrollY = resolvedAlignToTop + ? window.scrollY + rect.top + : window.scrollY + rect.bottom - window.innerHeight; + window.scrollTo(window.scrollX + rect.left, scrollY); + i += resolvedAlignToTop ? -1 : 1; + continue; + } const instance = getInstanceFromHostFiber(child); instance.scrollIntoView(alignToTop); i += resolvedAlignToTop ? -1 : 1; diff --git a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js index 98c2a2aaac74..7ff088d7d312 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js @@ -20,6 +20,7 @@ let Activity; let mockIntersectionObserver; let simulateIntersection; let setClientRects; +let mockRangeClientRects; let assertConsoleErrorDev; function Wrapper({children}) { @@ -40,6 +41,7 @@ describe('FragmentRefs', () => { mockIntersectionObserver = IntersectionMocks.mockIntersectionObserver; simulateIntersection = IntersectionMocks.simulateIntersection; setClientRects = IntersectionMocks.setClientRects; + mockRangeClientRects = IntersectionMocks.mockRangeClientRects; assertConsoleErrorDev = require('internal-test-utils').assertConsoleErrorDev; @@ -2426,4 +2428,169 @@ describe('FragmentRefs', () => { }); }); }); + + describe('with text nodes', () => { + // @gate enableFragmentRefs && enableFragmentRefsTextNodes + it('getClientRects includes text node bounds', async () => { + const restoreRange = mockRangeClientRects([ + {x: 0, y: 0, width: 80, height: 16}, + ]); + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + Hello World +
    , + ), + ); + + const rects = fragmentRef.current.getClientRects(); + expect(rects.length).toBe(1); + expect(rects[0].width).toBe(80); + restoreRange(); + }); + + // @gate enableFragmentRefs && enableFragmentRefsTextNodes + it('getClientRects includes both text and element bounds', async () => { + const restoreRange = mockRangeClientRects([ + {x: 0, y: 0, width: 60, height: 16}, + ]); + const fragmentRef = React.createRef(); + const childRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + + Text before +
    Element
    + Text after +
    +
    , + ), + ); + + setClientRects(childRef.current, [ + {x: 10, y: 10, width: 100, height: 20}, + ]); + const rects = fragmentRef.current.getClientRects(); + // Should have rects from 2 text nodes + 1 element = 3 total + expect(rects.length).toBe(3); + restoreRange(); + }); + + // @gate enableFragmentRefs + it('compareDocumentPosition works with text children', async () => { + const fragmentRef = React.createRef(); + const beforeRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    +
    + Text content +
    , + ), + ); + + const position = fragmentRef.current.compareDocumentPosition( + beforeRef.current, + ); + expect(position & Node.DOCUMENT_POSITION_PRECEDING).toBeTruthy(); + }); + + // @gate enableFragmentRefs + it('focus is a no-op on text-only fragment', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + Text only content +
    , + ), + ); + + // Should not throw or warn - just a silent no-op + fragmentRef.current.focus(); + // Test passes if no error is thrown + }); + + // @gate enableFragmentRefs + it('focusLast is a no-op on text-only fragment', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + Text only content +
    , + ), + ); + + // Should not throw or warn - just a silent no-op + fragmentRef.current.focusLast(); + }); + + // @gate enableFragmentRefs && enableFragmentRefsTextNodes + it('warns when observeUsing is called on text-only fragment', async () => { + mockIntersectionObserver(); + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + Text only content +
    , + ), + ); + + const observer = new IntersectionObserver(() => {}); + fragmentRef.current.observeUsing(observer); + assertConsoleErrorDev( + [ + 'observeUsing() was called on a FragmentInstance with only text children. ' + + 'Observers do not work on text nodes.', + ], + {withoutStack: true}, + ); + }); + + // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView + it('scrollIntoView works on text-only fragment using Range API', async () => { + const restoreRange = mockRangeClientRects([ + {x: 100, y: 200, width: 80, height: 16}, + ]); + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => + root.render( +
    + Text content +
    , + ), + ); + + // Mock window.scrollTo to verify it was called + const originalScrollTo = window.scrollTo; + const scrollToMock = jest.fn(); + window.scrollTo = scrollToMock; + + fragmentRef.current.scrollIntoView(); + + // Should have called window.scrollTo for the text node + expect(scrollToMock).toHaveBeenCalled(); + + window.scrollTo = originalScrollTo; + restoreRange(); + }); + }); }); diff --git a/packages/react-dom/src/__tests__/utils/IntersectionMocks.js b/packages/react-dom/src/__tests__/utils/IntersectionMocks.js index d89a683e8e93..9d36f46ae946 100644 --- a/packages/react-dom/src/__tests__/utils/IntersectionMocks.js +++ b/packages/react-dom/src/__tests__/utils/IntersectionMocks.js @@ -93,3 +93,58 @@ export function setClientRects(target, rects) { })); }; } + +/** + * Mock Range.prototype.getClientRects and getBoundingClientRect since jsdom doesn't implement them. + * Call this in beforeEach to set up the mock. + */ +export function mockRangeClientRects( + rects = [{x: 0, y: 0, width: 100, height: 20}], +) { + const originalCreateRange = document.createRange; + document.createRange = function () { + const range = originalCreateRange.call(document); + range.getClientRects = function () { + return rects.map(({x, y, width, height}) => ({ + width, + height, + left: x, + right: x + width, + top: y, + bottom: y + height, + x, + y, + })); + }; + range.getBoundingClientRect = function () { + // Return the bounding rect that encompasses all rects + if (rects.length === 0) { + return { + width: 0, + height: 0, + left: 0, + right: 0, + top: 0, + bottom: 0, + x: 0, + y: 0, + }; + } + const first = rects[0]; + return { + width: first.width, + height: first.height, + left: first.x, + right: first.x + first.width, + top: first.y, + bottom: first.y + first.height, + x: first.x, + y: first.y, + }; + }; + return range; + }; + return function restore() { + document.createRange = originalCreateRange; + }; +} diff --git a/packages/react-reconciler/src/ReactFiberTreeReflection.js b/packages/react-reconciler/src/ReactFiberTreeReflection.js index 530b65d69953..8b4c52b4ea2d 100644 --- a/packages/react-reconciler/src/ReactFiberTreeReflection.js +++ b/packages/react-reconciler/src/ReactFiberTreeReflection.js @@ -29,6 +29,7 @@ import { Fragment, } from './ReactWorkTags'; import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; +import {enableFragmentRefsTextNodes} from 'shared/ReactFeatureFlags'; export function getNearestMountedFiber(fiber: Fiber): null | Fiber { let node = fiber; @@ -373,7 +374,10 @@ function traverseVisibleHostChildren( c: C, ): boolean { while (child !== null) { - if (child.tag === HostComponent && fn(child, a, b, c)) { + const isHostNode = + child.tag === HostComponent || + (enableFragmentRefsTextNodes && child.tag === HostText); + if (isHostNode && fn(child, a, b, c)) { return true; } else if ( child.tag === OffscreenComponent && @@ -473,6 +477,7 @@ function findFragmentInstanceSiblings( export function getInstanceFromHostFiber(fiber: Fiber): I { switch (fiber.tag) { case HostComponent: + case HostText: return fiber.stateNode; case HostRoot: return fiber.stateNode.containerInfo; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 6542fc9da6a8..113370d1eb79 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -144,6 +144,7 @@ export const enableInfiniteRenderLoopDetection: boolean = false; export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = true; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableFragmentRefsTextNodes: boolean = true; export const enableInternalInstanceMap: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js index fe7777eda713..8082e77e1d67 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -25,3 +25,4 @@ export const passChildrenWhenCloningPersistedNodes = __VARIANT__; export const enableFragmentRefs = __VARIANT__; export const enableFragmentRefsScrollIntoView = __VARIANT__; export const enableFragmentRefsInstanceHandles = __VARIANT__; +export const enableFragmentRefsTextNodes = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index d516581486e7..dcee1c3b15e6 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -27,6 +27,7 @@ export const { enableFragmentRefs, enableFragmentRefsScrollIntoView, enableFragmentRefsInstanceHandles, + enableFragmentRefsTextNodes, } = dynamicFlags; // The rest of the flags are static for better dead code elimination. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 52c4204ef000..f64a2165d0d4 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -72,6 +72,7 @@ export const ownerStackLimit = 1e4; export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = false; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableFragmentRefsTextNodes: boolean = false; export const enableInternalInstanceMap: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 4a1ded43c7d4..3db934f13def 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -73,6 +73,7 @@ export const ownerStackLimit = 1e4; export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = true; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableFragmentRefsTextNodes: boolean = true; export const enableInternalInstanceMap: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 67ae35f04b95..a80ca9eedda7 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -65,6 +65,8 @@ export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = true; export const enableFragmentRefs = false; export const enableFragmentRefsScrollIntoView = false; +export const enableFragmentRefsInstanceHandles = false; +export const enableFragmentRefsTextNodes = false; export const ownerStackLimit = 1e4; export const enableOptimisticKey = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 4434cfa6c645..d8a1e797d85a 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -78,6 +78,7 @@ export const enableDefaultTransitionIndicator: boolean = true; export const enableFragmentRefs: boolean = false; export const enableFragmentRefsScrollIntoView: boolean = false; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableFragmentRefsTextNodes: boolean = false; export const ownerStackLimit = 1e4; export const enableInternalInstanceMap: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index a8d829ee3e31..40458585cfdc 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -34,6 +34,7 @@ export const enableViewTransition: boolean = __VARIANT__; export const enableScrollEndPolyfill: boolean = __VARIANT__; export const enableFragmentRefs: boolean = __VARIANT__; export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__; +export const enableFragmentRefsTextNodes: boolean = __VARIANT__; export const enableAsyncDebugInfo: boolean = __VARIANT__; export const enableInternalInstanceMap: boolean = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 8a710cc429d6..5e206f489fec 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -32,6 +32,7 @@ export const { enableScrollEndPolyfill, enableFragmentRefs, enableFragmentRefsScrollIntoView, + enableFragmentRefsTextNodes, enableAsyncDebugInfo, enableInternalInstanceMap, } = dynamicFeatureFlags;