diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionCase.js
new file mode 100644
index 00000000000..aada4e33fa4
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionCase.js
@@ -0,0 +1,53 @@
+import TestCase from '../../TestCase';
+import Fixture from '../../Fixture';
+import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
+
+const React = window.React;
+
+export default function CompareDocumentPositionCase() {
+ return (
+
+
+ Click the "Compare All Positions" button
+
+
+ The compareDocumentPosition method compares the position of the fragment
+ relative to other elements in the DOM. The "Before Element" should be
+ PRECEDING the fragment, and the "After Element" should be FOLLOWING.
+ Elements inside the fragment should be CONTAINED_BY.
+
+
+
+
+
+ First child element
+
+
+ Second child element
+
+
+ Third child element
+
+
+
+
+
+ );
+}
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionFragmentContainer.js b/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionFragmentContainer.js
new file mode 100644
index 00000000000..380bed49cb4
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionFragmentContainer.js
@@ -0,0 +1,246 @@
+const React = window.React;
+const {Fragment, useRef, useState} = React;
+
+const POSITION_FLAGS = {
+ DISCONNECTED: 0x01,
+ PRECEDING: 0x02,
+ FOLLOWING: 0x04,
+ CONTAINS: 0x08,
+ CONTAINED_BY: 0x10,
+ IMPLEMENTATION_SPECIFIC: 0x20,
+};
+
+function getPositionDescription(bitmask) {
+ const flags = [];
+ if (bitmask & POSITION_FLAGS.DISCONNECTED) flags.push('DISCONNECTED');
+ if (bitmask & POSITION_FLAGS.PRECEDING) flags.push('PRECEDING');
+ if (bitmask & POSITION_FLAGS.FOLLOWING) flags.push('FOLLOWING');
+ if (bitmask & POSITION_FLAGS.CONTAINS) flags.push('CONTAINS');
+ if (bitmask & POSITION_FLAGS.CONTAINED_BY) flags.push('CONTAINED_BY');
+ if (bitmask & POSITION_FLAGS.IMPLEMENTATION_SPECIFIC)
+ flags.push('IMPLEMENTATION_SPECIFIC');
+ return flags.length > 0 ? flags.join(' | ') : 'SAME';
+}
+
+function ResultRow({label, result, color}) {
+ if (!result) return null;
+
+ return (
+
+
+ {label}
+
+
+ Raw value:
+ {result.raw}
+ Flags:
+
+ {getPositionDescription(result.raw)}
+
+
+
+ );
+}
+
+export default function CompareDocumentPositionFragmentContainer({children}) {
+ const fragmentRef = useRef(null);
+ const beforeRef = useRef(null);
+ const afterRef = useRef(null);
+ const insideRef = useRef(null);
+ const [results, setResults] = useState(null);
+
+ const compareAll = () => {
+ const fragment = fragmentRef.current;
+ const beforePos = fragment.compareDocumentPosition(beforeRef.current);
+ const afterPos = fragment.compareDocumentPosition(afterRef.current);
+ const insidePos = insideRef.current
+ ? fragment.compareDocumentPosition(insideRef.current)
+ : null;
+
+ setResults({
+ before: {raw: beforePos},
+ after: {raw: afterPos},
+ inside: insidePos !== null ? {raw: insidePos} : null,
+ });
+ };
+
+ return (
+
+
+
+ Compare All Positions
+
+ {results && (
+
+ Comparison complete
+
+ )}
+
+
+
+
+
+
+ Before Element
+
+
+
+
+ FRAGMENT
+
+
+ {children}
+
+
+
+
+ After Element
+
+
+
+
+
+
+ Comparison Results
+
+
+ {!results && (
+
+ Click "Compare All Positions" to see results
+
+ )}
+
+ {results && (
+
+
+
+ {results.inside && (
+
+ )}
+
+
+
Flag Reference:
+
+ 0x01
+ DISCONNECTED
+ 0x02
+ PRECEDING (other is before fragment)
+ 0x04
+ FOLLOWING (other is after fragment)
+ 0x08
+ CONTAINS (other contains fragment)
+ 0x10
+ CONTAINED_BY (other is inside fragment)
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/EventFragmentContainer.js b/fixtures/dom/src/components/fixtures/fragment-refs/EventFragmentContainer.js
new file mode 100644
index 00000000000..33c99390a4d
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/EventFragmentContainer.js
@@ -0,0 +1,112 @@
+const React = window.React;
+const {Fragment, useRef, useState} = React;
+
+export default function EventFragmentContainer({children}) {
+ const fragmentRef = useRef(null);
+ const [eventLog, setEventLog] = useState([]);
+ const [listenerAdded, setListenerAdded] = useState(false);
+ const [bubblesState, setBubblesState] = useState(true);
+
+ const logEvent = message => {
+ setEventLog(prev => [...prev, message]);
+ };
+
+ const fragmentClickHandler = () => {
+ logEvent('Fragment event listener fired');
+ };
+
+ const addListener = () => {
+ fragmentRef.current.addEventListener('click', fragmentClickHandler);
+ setListenerAdded(true);
+ logEvent('Added click listener to fragment');
+ };
+
+ const removeListener = () => {
+ fragmentRef.current.removeEventListener('click', fragmentClickHandler);
+ setListenerAdded(false);
+ logEvent('Removed click listener from fragment');
+ };
+
+ const dispatchClick = () => {
+ fragmentRef.current.dispatchEvent(
+ new MouseEvent('click', {bubbles: bubblesState})
+ );
+ logEvent(`Dispatched click event (bubbles: ${bubblesState})`);
+ };
+
+ const clearLog = () => {
+ setEventLog([]);
+ };
+
+ return (
+
+
+ setBubblesState(e.target.value === 'true')}
+ style={{padding: '6px 10px'}}>
+ Bubbles: true
+ Bubbles: false
+
+
+ Dispatch click event
+
+
+ Add event listener
+
+
+ Remove event listener
+
+
+ Clear log
+
+
+
+ logEvent('Parent div clicked')}
+ style={{
+ padding: '12px',
+ border: '1px dashed #ccc',
+ borderRadius: '4px',
+ backgroundColor: '#fff',
+ }}>
+ {children}
+
+
+ {eventLog.length > 0 && (
+
+
Event Log:
+
+ {eventLog.map((msg, i) => (
+ {msg}
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js
index 125b67cf39a..a6e32422bc2 100644
--- a/fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js
@@ -1,46 +1,35 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
+import EventFragmentContainer from './EventFragmentContainer';
const React = window.React;
-const {Fragment, useEffect, useRef, useState} = React;
+const {useState} = React;
function WrapperComponent(props) {
return props.children;
}
-function handler(e) {
- const text = e.currentTarget.innerText;
- alert('You clicked: ' + text);
-}
-
export default function EventListenerCase() {
- const fragmentRef = useRef(null);
const [extraChildCount, setExtraChildCount] = useState(0);
- useEffect(() => {
- fragmentRef.current.addEventListener('click', handler);
-
- const lastFragmentRefValue = fragmentRef.current;
- return () => {
- lastFragmentRefValue.removeEventListener('click', handler);
- };
- });
-
return (
- Click one of the children, observe the alert
- Add a new child, click it, observe the alert
- Remove the event listeners, click a child, observe no alert
- Add the event listeners back, click a child, observe the alert
+
+ Click "Add event listener" to attach a click handler to the fragment
+
+ Click "Dispatch click event" to dispatch a click event
+ Observe the event log showing the event fired
+ Add a new child, dispatch again to see it still works
+
+ Click "Remove event listener" and dispatch again to see no event fires
+
Fragment refs can manage event listeners on the first level of host
- children. This page loads with an effect that sets up click event
- hanndlers on each child card. Clicking on a card will show an alert
- with the card's text.
+ children. The event log shows when events are dispatched and handled.
New child nodes will also have event listeners applied. Removed nodes
@@ -50,28 +39,17 @@ export default function EventListenerCase() {
- Target count: {extraChildCount + 3}
- {
- setExtraChildCount(prev => prev + 1);
- }}>
- Add Child
-
- {
- fragmentRef.current.addEventListener('click', handler);
- }}>
- Add click event listeners
-
- {
- fragmentRef.current.removeEventListener('click', handler);
- }}>
- Remove click event listeners
-
-
-
-
+
+ Target count: {extraChildCount + 3}
+ {
+ setExtraChildCount(prev => prev + 1);
+ }}
+ style={{marginLeft: '10px'}}>
+ Add Child
+
+
+
Child A
@@ -88,8 +66,8 @@ export default function EventListenerCase() {
))}
-
-
+
+
);
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js
index 563f2ad0542..4d9b2803dde 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() {
- Print Rects
-
-
+
- {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/GetRootNodeFragmentContainer.js b/fixtures/dom/src/components/fixtures/fragment-refs/GetRootNodeFragmentContainer.js
new file mode 100644
index 00000000000..01244b3c87a
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/GetRootNodeFragmentContainer.js
@@ -0,0 +1,79 @@
+const React = window.React;
+const {Fragment, useRef, useState} = React;
+
+export default function GetRootNodeFragmentContainer({children}) {
+ const fragmentRef = useRef(null);
+ const [rootNodeInfo, setRootNodeInfo] = useState(null);
+
+ const getRootNodeInfo = () => {
+ const rootNode = fragmentRef.current.getRootNode();
+ setRootNodeInfo({
+ nodeName: rootNode.nodeName,
+ nodeType: rootNode.nodeType,
+ nodeTypeLabel: getNodeTypeLabel(rootNode.nodeType),
+ isDocument: rootNode === document,
+ });
+ };
+
+ const getNodeTypeLabel = nodeType => {
+ const types = {
+ 1: 'ELEMENT_NODE',
+ 3: 'TEXT_NODE',
+ 9: 'DOCUMENT_NODE',
+ 11: 'DOCUMENT_FRAGMENT_NODE',
+ };
+ return types[nodeType] || `UNKNOWN (${nodeType})`;
+ };
+
+ return (
+
+
+
+ Get Root Node
+
+
+
+ {rootNodeInfo && (
+
+
+ Node Name: {rootNodeInfo.nodeName}
+
+
+ Node Type: {rootNodeInfo.nodeType} (
+ {rootNodeInfo.nodeTypeLabel})
+
+
+ Is Document: {' '}
+ {rootNodeInfo.isDocument ? 'Yes' : 'No'}
+
+
+ )}
+
+
+ {children}
+
+
+ );
+}
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 00000000000..a084932a512
--- /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 (
+
+
+
+ Print Rects
+
+ {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 00000000000..c6a359ac887
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js
@@ -0,0 +1,470 @@
+import TestCase from '../../TestCase';
+import Fixture from '../../Fixture';
+import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';
+import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
+import EventFragmentContainer from './EventFragmentContainer';
+import GetRootNodeFragmentContainer from './GetRootNodeFragmentContainer';
+
+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.
+
+
+
+ focus()
+ focusLast()
+ {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.
+
+
+
+ tryScrollIntoView(true)}>
+ scrollIntoView(true)
+
+ tryScrollIntoView(false)}>
+ scrollIntoView(false)
+
+ {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.
+
+
+
+ tryScrollIntoView(true)}>
+ scrollIntoView(true)
+
+ tryScrollIntoView(false)}>
+ scrollIntoView(false)
+
+ {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 CompareDocumentPositionTextNodes() {
+ return (
+
+
+ Click the "Compare All Positions" button
+
+
+ compareDocumentPosition should work correctly even when the fragment
+ contains only text nodes. The "Before" element should be PRECEDING the
+ fragment, and the "After" element should be FOLLOWING.
+
+
+
+
+ This is text-only content inside 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.
+
+
+
+
+ observeUsing(IntersectionObserver)
+
+ {message && (
+ {message}
+ )}
+
+
+
+ This fragment contains only text. Text nodes cannot be observed.
+
+
+
+
+ );
+}
+
+function EventTextOnly() {
+ return (
+
+
+
+ Click "Add event listener" to attach a click handler to the fragment
+
+ Click "Dispatch click event" to dispatch a click event
+ Observe that the fragment's event listener fires
+ Click "Remove event listener" and dispatch again
+
+
+ Event operations (addEventListener, removeEventListener, dispatchEvent)
+ work on fragments with text-only content. The event is dispatched on the
+ fragment's parent element since text nodes cannot be event targets.
+
+
+
+
+ This fragment contains only text. Events are handled via the parent.
+
+
+
+
+ );
+}
+
+function EventMixed() {
+ return (
+
+
+
+ Click "Add event listener" to attach a click handler to the fragment
+
+ Click "Dispatch click event" to dispatch a click event
+ Observe that the fragment's event listener fires
+ Click directly on the element or text content to see bubbling
+
+
+ Event operations work on fragments with mixed text and element content.
+ dispatchEvent forwards to the parent element. Clicks on child elements
+ or text bubble up through the DOM as normal.
+
+
+
+
+ Text node before element.
+
+ Element
+
+ Text node after element.
+
+
+
+
+ );
+}
+
+function GetRootNodeTextOnly() {
+ return (
+
+
+ Click the "Get Root Node" button
+
+
+ getRootNode should return the root of the DOM tree containing the
+ fragment's text content. For a fragment in the main document, this
+ should return the Document node.
+
+
+
+
+ This fragment contains only text. getRootNode returns the document.
+
+
+
+
+ );
+}
+
+function GetRootNodeMixed() {
+ return (
+
+
+ Click the "Get Root Node" button
+
+
+ getRootNode should return the root of the DOM tree for fragments with
+ mixed text and element content. The result is the same whether checking
+ from text nodes or element nodes within the fragment.
+
+
+
+
+ Text before element.
+
+ Element
+
+ Text after element.
+
+
+
+
+ );
+}
+
+export default function TextNodesCase() {
+ return (
+
+
+
+ This section demonstrates how various FragmentInstance methods work
+ with text nodes.
+
+
+ Supported: getClientRects, compareDocumentPosition,
+ scrollIntoView, getRootNode, addEventListener, removeEventListener,
+ dispatchEvent
+
+
+ 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 c560b59fbec..91160618c80 100644
--- a/fixtures/dom/src/components/fixtures/fragment-refs/index.js
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/index.js
@@ -5,7 +5,9 @@ import IntersectionObserverCase from './IntersectionObserverCase';
import ResizeObserverCase from './ResizeObserverCase';
import FocusCase from './FocusCase';
import GetClientRectsCase from './GetClientRectsCase';
+import CompareDocumentPositionCase from './CompareDocumentPositionCase';
import ScrollIntoViewCase from './ScrollIntoViewCase';
+import TextNodesCase from './TextNodesCase';
const React = window.React;
@@ -18,7 +20,9 @@ export default function FragmentRefsPage() {
+
+
);
}
diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js
index 2246f74697e..4dc316de366 100644
--- a/packages/react-client/src/ReactFlightClient.js
+++ b/packages/react-client/src/ReactFlightClient.js
@@ -3730,6 +3730,14 @@ function createFakeFunction(
fn = function (_) {
return _();
};
+ // Using the usual {[name]: _() => _()}.bind() trick to avoid minifiers
+ // doesn't work here since this will produce `Object.*` names.
+ Object.defineProperty(
+ fn,
+ // $FlowFixMe[cannot-write] -- `name` is configurable though.
+ 'name',
+ {value: name},
+ );
}
return fn;
}
diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index 9897c28d7e8..f62ec321501 100644
--- a/packages/react-client/src/__tests__/ReactFlight-test.js
+++ b/packages/react-client/src/__tests__/ReactFlight-test.js
@@ -3677,6 +3677,56 @@ describe('ReactFlight', () => {
expect(caughtError.digest).toBe('digest("my-error")');
});
+ it('can transport function names in stackframes in dev even without eval', async () => {
+ function a() {
+ return b();
+ }
+ function b() {
+ return c();
+ }
+ function c() {
+ return new Error('boom');
+ }
+
+ // eslint-disable-next-line no-eval
+ const previousEval = globalThis.eval.bind(globalThis);
+ // eslint-disable-next-line no-eval
+ globalThis.eval = () => {
+ throw new Error('eval is disabled');
+ };
+
+ try {
+ const transport = ReactNoopFlightServer.render(
+ {model: a()},
+ {onError: () => 'digest'},
+ );
+
+ const root = await ReactNoopFlightClient.read(transport);
+ const receivedError = await root.model;
+
+ if (__DEV__) {
+ const normalizedErrorStack = normalizeCodeLocInfo(
+ receivedError.stack.split('\n').slice(0, 4).join('\n'),
+ );
+
+ expect(normalizedErrorStack).toEqual(
+ 'Error: boom' +
+ '\n in c (at **)' +
+ '\n in b (at **)' +
+ '\n in a (at **)',
+ );
+ } else {
+ expect(receivedError.message).toEqual(
+ 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
+ );
+ expect(receivedError).not.toHaveProperty('digest');
+ }
+ } finally {
+ // eslint-disable-next-line no-eval
+ globalThis.eval = previousEval;
+ }
+ });
+
// @gate __DEV__ && enableComponentPerformanceTrack
it('can render deep but cut off JSX in debug info', async () => {
function createDeepJSX(n) {
diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
index 026f4ae0c8c..09653552aaf 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__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
index cd1d055c09d..af128d180e8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
@@ -9,6 +9,13 @@
'use strict';
+// Fix JSDOM. setAttribute is supposed to throw on things that can't be implicitly toStringed.
+const setAttribute = Element.prototype.setAttribute;
+Element.prototype.setAttribute = function (name, value) {
+ // eslint-disable-next-line react-internal/safe-string-coercion
+ return setAttribute.call(this, name, '' + value);
+};
+
describe('ReactDOM unknown attribute', () => {
let React;
let ReactDOMClient;
diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
index 1766db2de8b..1caa5ed8d6e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
@@ -602,6 +602,14 @@ describe('ReactDOMFloat', () => {
'> ');
+ if (gate('enableTrustedTypesIntegration')) {
+ assertConsoleErrorDev([
+ 'Encountered a script tag while rendering React component. ' +
+ 'Scripts inside React components are never executed when rendering on the client. ' +
+ 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
+ ' in script (at **)\n' +
+ ' in TogglingComponent (at **)',
+ ]);
+ }
+
const container2 = document.createElement('div');
const root2 = ReactDOMClient.createRoot(container2);
expect(() => {
@@ -189,6 +202,7 @@ describe('ReactEmptyComponent', () => {
'mount SCRIPT',
'update undefined',
]);
+ expect(container2.innerHTML).toBe('');
});
it(
diff --git a/packages/react-dom/src/__tests__/utils/IntersectionMocks.js b/packages/react-dom/src/__tests__/utils/IntersectionMocks.js
index d89a683e8e9..9d36f46ae94 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-dom/src/client/__tests__/trustedTypes-test.internal.js b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
index 5a43a9ec2f0..06744581ae9 100644
--- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
+++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
@@ -12,7 +12,6 @@
describe('when Trusted Types are available in global object', () => {
let React;
let ReactDOMClient;
- let ReactFeatureFlags;
let act;
let assertConsoleErrorDev;
let container;
@@ -33,8 +32,6 @@ describe('when Trusted Types are available in global object', () => {
isScript: () => false,
isScriptURL: () => false,
};
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.enableTrustedTypesIntegration = true;
React = require('react');
ReactDOMClient = require('react-dom/client');
({act, assertConsoleErrorDev} = require('internal-test-utils'));
@@ -118,7 +115,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('data-foo');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Hi ');
+ }
setAttributeCalls.length = 0;
await act(() => {
@@ -129,7 +130,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('data-foo');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Bye ');
+ }
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -153,7 +158,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('class');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Hi ');
+ }
setAttributeCalls.length = 0;
await act(() => {
@@ -164,7 +173,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('class');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Bye ');
+ }
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -189,7 +202,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
+ } else {
+ expect(setAttributeNSCalls[0][3]).toBe('Hi ');
+ }
setAttributeNSCalls.length = 0;
await act(() => {
@@ -201,7 +218,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
+ } else {
+ expect(setAttributeNSCalls[0][3]).toBe('Bye ');
+ }
} finally {
Element.prototype.setAttributeNS = setAttributeNS;
}
@@ -212,13 +233,15 @@ describe('when Trusted Types are available in global object', () => {
await act(() => {
root.render();
});
- assertConsoleErrorDev([
- 'Encountered a script tag while rendering React component. ' +
- 'Scripts inside React components are never executed when rendering ' +
- 'on the client. Consider using template tag instead ' +
- '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
- ' in script (at **)',
- ]);
+ if (gate('enableTrustedTypesIntegration')) {
+ assertConsoleErrorDev([
+ 'Encountered a script tag while rendering React component. ' +
+ 'Scripts inside React components are never executed when rendering ' +
+ 'on the client. Consider using template tag instead ' +
+ '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
+ ' in script (at **)',
+ ]);
+ }
// check that the warning is printed only once
await act(() => {
diff --git a/packages/react-reconciler/src/ReactFiberTreeReflection.js b/packages/react-reconciler/src/ReactFiberTreeReflection.js
index 530b65d6995..8b4c52b4ea2 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 6542fc9da6a..113370d1eb7 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 fe7777eda71..8082e77e1d6 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 d516581486e..dcee1c3b15e 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 52c4204ef00..f64a2165d0d 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 4a1ded43c7d..3db934f13de 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 67ae35f04b9..a80ca9eedda 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 4434cfa6c64..d8a1e797d85 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 a8d829ee3e3..391c50e9956 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -34,13 +34,13 @@ 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__;
+export const enableTrustedTypesIntegration: boolean = __VARIANT__;
// TODO: These flags are hard-coded to the default values used in open source.
// Update the tests so that they pass in either mode, then set these
// to __VARIANT__.
-export const enableTrustedTypesIntegration: boolean = false;
// You probably *don't* want to add more hardcoded ones.
// Instead, try to add them above with the __VARIANT__ value.
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 8a710cc429d..5e206f489fe 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;