diff --git a/fixtures/view-transition/server/render.js b/fixtures/view-transition/server/render.js
index 11d352eabdd72..716290212e5a2 100644
--- a/fixtures/view-transition/server/render.js
+++ b/fixtures/view-transition/server/render.js
@@ -23,6 +23,8 @@ export default function render(url, res) {
const {pipe, abort} = renderToPipeableStream(
,
{
+ // TODO: Temporary hack. Detect from attributes instead.
+ bootstrapScriptContent: 'window._useVT = true;',
bootstrapScripts: [assets['main.js']],
onShellReady() {
// If something errored before we started streaming, we set the error code appropriately.
diff --git a/fixtures/view-transition/src/components/Page.js b/fixtures/view-transition/src/components/Page.js
index db2cd0aff08c7..061b1edb2cbf6 100644
--- a/fixtures/view-transition/src/components/Page.js
+++ b/fixtures/view-transition/src/components/Page.js
@@ -8,6 +8,7 @@ import React, {
useId,
useOptimistic,
startTransition,
+ Suspense,
} from 'react';
import {createPortal} from 'react-dom';
@@ -60,6 +61,12 @@ function Id() {
return ;
}
+let wait;
+function Suspend() {
+ if (!wait) wait = sleep(500);
+ return React.use(wait);
+}
+
export default function Page({url, navigate}) {
const [renderedUrl, optimisticNavigate] = useOptimistic(
url,
@@ -93,7 +100,7 @@ export default function Page({url, navigate}) {
// a flushSync will.
// Promise.resolve().then(() => {
// flushSync(() => {
- setCounter(c => c + 10);
+ // setCounter(c => c + 10);
// });
// });
}, [show]);
@@ -193,18 +200,23 @@ export default function Page({url, navigate}) {
!!
- these
- rows
- exist
- to
- test
- scrolling
- content
- out
- of
- {portal}
- the
- viewport
+
+
+ these
+ rows
+ exist
+ to
+ test
+ scrolling
+ content
+ out
+ of
+ {portal}
+ the
+ viewport
+
+
+
{show ? : null}
diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
index 8dbc6831e1fca..ef69bd4582a87 100644
--- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
@@ -80,6 +80,7 @@ import isArray from 'shared/isArray';
import {
clientRenderBoundary as clientRenderFunction,
completeBoundary as completeBoundaryFunction,
+ completeBoundaryUpgradeToViewTransitions as upgradeToViewTransitionsInstruction,
completeBoundaryWithStyles as styleInsertionFunction,
completeSegment as completeSegmentFunction,
formReplaying as formReplayingRuntime,
@@ -123,14 +124,16 @@ const ScriptStreamingFormat: StreamingFormat = 0;
const DataStreamingFormat: StreamingFormat = 1;
export type InstructionState = number;
-const NothingSent /* */ = 0b0000000;
-const SentCompleteSegmentFunction /* */ = 0b0000001;
-const SentCompleteBoundaryFunction /* */ = 0b0000010;
-const SentClientRenderFunction /* */ = 0b0000100;
-const SentStyleInsertionFunction /* */ = 0b0001000;
-const SentFormReplayingRuntime /* */ = 0b0010000;
-const SentCompletedShellId /* */ = 0b0100000;
-const SentMarkShellTime /* */ = 0b1000000;
+const NothingSent /* */ = 0b000000000;
+const SentCompleteSegmentFunction /* */ = 0b000000001;
+const SentCompleteBoundaryFunction /* */ = 0b000000010;
+const SentClientRenderFunction /* */ = 0b000000100;
+const SentStyleInsertionFunction /* */ = 0b000001000;
+const SentFormReplayingRuntime /* */ = 0b000010000;
+const SentCompletedShellId /* */ = 0b000100000;
+const SentMarkShellTime /* */ = 0b001000000;
+const NeedUpgradeToViewTransitions /* */ = 0b010000000;
+const SentUpgradeToViewTransitions /* */ = 0b100000000;
// Per request, global state that is not contextual to the rendering subtree.
// This cannot be resumed and therefore should only contain things that are
@@ -742,12 +745,13 @@ const HTML_COLGROUP_MODE = 9;
type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
-const NO_SCOPE = /* */ 0b00000;
-const NOSCRIPT_SCOPE = /* */ 0b00001;
-const PICTURE_SCOPE = /* */ 0b00010;
-const FALLBACK_SCOPE = /* */ 0b00100;
-const EXIT_SCOPE = /* */ 0b01000; // A direct Instance below a Suspense fallback is the only thing that can "exit"
-const ENTER_SCOPE = /* */ 0b10000; // A direct Instance below Suspense content is the only thing that can "enter"
+const NO_SCOPE = /* */ 0b000000;
+const NOSCRIPT_SCOPE = /* */ 0b000001;
+const PICTURE_SCOPE = /* */ 0b000010;
+const FALLBACK_SCOPE = /* */ 0b000100;
+const EXIT_SCOPE = /* */ 0b001000; // A direct Instance below a Suspense fallback is the only thing that can "exit"
+const ENTER_SCOPE = /* */ 0b010000; // A direct Instance below Suspense content is the only thing that can "enter"
+const UPDATE_SCOPE = /* */ 0b100000; // Inside a scope that applies "update" ViewTransitions if anything mutates here.
// Everything not listed here are tracked for the whole subtree as opposed to just
// until the next Instance.
@@ -926,8 +930,15 @@ function getSuspenseViewTransition(
}
export function getSuspenseFallbackFormatContext(
+ resumableState: ResumableState,
parentContext: FormatContext,
): FormatContext {
+ if (parentContext.tagScope & UPDATE_SCOPE) {
+ // If we're rendering a Suspense in fallback mode and that is inside a ViewTransition,
+ // which hasn't disabled updates, then revealing it might animate the parent so we need
+ // the ViewTransition instructions.
+ resumableState.instructions |= NeedUpgradeToViewTransitions;
+ }
return createFormatContext(
parentContext.insertionMode,
parentContext.selectedValue,
@@ -937,6 +948,7 @@ export function getSuspenseFallbackFormatContext(
}
export function getSuspenseContentFormatContext(
+ resumableState: ResumableState,
parentContext: FormatContext,
): FormatContext {
return createFormatContext(
@@ -948,6 +960,7 @@ export function getSuspenseContentFormatContext(
}
export function getViewTransitionFormatContext(
+ resumableState: ResumableState,
parentContext: FormatContext,
update: ?string,
enter: ?string,
@@ -983,14 +996,26 @@ export function getViewTransitionFormatContext(
// exit because enter/exit will take precedence and if it's deeply nested
// it just animates along whatever the parent does when disabled.
share = null;
- } else if (share == null) {
- share = 'auto';
+ } else {
+ if (share == null) {
+ share = 'auto';
+ }
+ if (parentContext.tagScope & FALLBACK_SCOPE) {
+ // If we have an explicit name and share is not disabled, and we're inside
+ // a fallback, then that fallback might pair with content and so we might need
+ // the ViewTransition instructions to animate between them.
+ resumableState.instructions |= NeedUpgradeToViewTransitions;
+ }
}
if (!(parentContext.tagScope & EXIT_SCOPE)) {
exit = null; // exit is only relevant for the first ViewTransition inside fallback
+ } else {
+ resumableState.instructions |= NeedUpgradeToViewTransitions;
}
if (!(parentContext.tagScope & ENTER_SCOPE)) {
enter = null; // enter is only relevant for the first ViewTransition inside content
+ } else {
+ resumableState.instructions |= NeedUpgradeToViewTransitions;
}
const viewTransition: ViewTransitionContext = {
update,
@@ -1001,7 +1026,12 @@ export function getViewTransitionFormatContext(
autoName,
nameIdx: 0,
};
- const subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;
+ let subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;
+ if (update !== 'none') {
+ subtreeScope |= UPDATE_SCOPE;
+ } else {
+ subtreeScope &= ~UPDATE_SCOPE;
+ }
return createFormatContext(
parentContext.insertionMode,
parentContext.selectedValue,
@@ -4780,9 +4810,8 @@ export function writeCompletedSegmentInstruction(
const completeBoundaryScriptFunctionOnly = stringToPrecomputedChunk(
completeBoundaryFunction,
);
-const completeBoundaryScript1Full = stringToPrecomputedChunk(
- completeBoundaryFunction + '$RC("',
-);
+const completeBoundaryUpgradeToViewTransitionsInstruction =
+ stringToPrecomputedChunk(upgradeToViewTransitionsInstruction);
const completeBoundaryScript1Partial = stringToPrecomputedChunk('$RC("');
const completeBoundaryWithStylesScript1FullPartial = stringToPrecomputedChunk(
@@ -4814,6 +4843,10 @@ export function writeCompletedBoundaryInstruction(
hoistableState: HoistableState,
): boolean {
const requiresStyleInsertion = renderState.stylesToHoist;
+ const requiresViewTransitions =
+ enableViewTransition &&
+ (resumableState.instructions & NeedUpgradeToViewTransitions) !==
+ NothingSent;
// If necessary stylesheets will be flushed with this instruction.
// Any style tags not yet hoisted in the Document will also be hoisted.
// We reset this state since after this instruction executes all styles
@@ -4842,6 +4875,17 @@ export function writeCompletedBoundaryInstruction(
resumableState.instructions |= SentCompleteBoundaryFunction;
writeChunk(destination, completeBoundaryScriptFunctionOnly);
}
+ if (
+ requiresViewTransitions &&
+ (resumableState.instructions & SentUpgradeToViewTransitions) ===
+ NothingSent
+ ) {
+ resumableState.instructions |= SentUpgradeToViewTransitions;
+ writeChunk(
+ destination,
+ completeBoundaryUpgradeToViewTransitionsInstruction,
+ );
+ }
if (
(resumableState.instructions & SentStyleInsertionFunction) ===
NothingSent
@@ -4857,10 +4901,20 @@ export function writeCompletedBoundaryInstruction(
NothingSent
) {
resumableState.instructions |= SentCompleteBoundaryFunction;
- writeChunk(destination, completeBoundaryScript1Full);
- } else {
- writeChunk(destination, completeBoundaryScript1Partial);
+ writeChunk(destination, completeBoundaryScriptFunctionOnly);
+ }
+ if (
+ requiresViewTransitions &&
+ (resumableState.instructions & SentUpgradeToViewTransitions) ===
+ NothingSent
+ ) {
+ resumableState.instructions |= SentUpgradeToViewTransitions;
+ writeChunk(
+ destination,
+ completeBoundaryUpgradeToViewTransitionsInstruction,
+ );
}
+ writeChunk(destination, completeBoundaryScript1Partial);
}
} else {
if (requiresStyleInsertion) {
diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
index 74787f9ee26b5..fd1791ff2c8a7 100644
--- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
+++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
@@ -181,6 +181,7 @@ export {
import escapeTextForBrowser from './escapeTextForBrowser';
export function getViewTransitionFormatContext(
+ resumableState: ResumableState,
parentContext: FormatContext,
update: void | null | 'none' | 'auto' | string,
enter: void | null | 'none' | 'auto' | string,
diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js
index 44a81c64468e7..761a8a18a958f 100644
--- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js
+++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js
@@ -1,7 +1,12 @@
-import {completeBoundary} from './ReactDOMFizzInstructionSetShared';
+import {
+ revealCompletedBoundaries,
+ completeBoundary,
+} from './ReactDOMFizzInstructionSetShared';
// This is a string so Closure's advanced compilation mode doesn't mangle it.
// eslint-disable-next-line dot-notation
window['$RB'] = [];
// eslint-disable-next-line dot-notation
+window['$RV'] = revealCompletedBoundaries;
+// eslint-disable-next-line dot-notation
window['$RC'] = completeBoundary;
diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryUpgradeToViewTransitions.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryUpgradeToViewTransitions.js
new file mode 100644
index 0000000000000..e033b9cc9e82b
--- /dev/null
+++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryUpgradeToViewTransitions.js
@@ -0,0 +1,10 @@
+import {revealCompletedBoundariesWithViewTransitions} from './ReactDOMFizzInstructionSetShared';
+
+// Upgrade the revealCompletedBoundaries instruction to support ViewTransitions.
+// This is a string so Closure's advanced compilation mode doesn't mangle it.
+// eslint-disable-next-line dot-notation
+window['$RV'] = revealCompletedBoundariesWithViewTransitions.bind(
+ null,
+ // eslint-disable-next-line dot-notation
+ window['$RV'],
+);
diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js
index 845ea5b5666e3..ce503f815d168 100644
--- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js
+++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js
@@ -8,6 +8,8 @@ import {
completeBoundaryWithStyles,
completeSegment,
listenToFormSubmissionsForReplaying,
+ revealCompletedBoundaries,
+ revealCompletedBoundariesWithViewTransitions,
} from './ReactDOMFizzInstructionSetShared';
// This is a string so Closure's advanced compilation mode doesn't mangle it.
@@ -15,6 +17,10 @@ import {
window['$RM'] = new Map();
window['$RB'] = [];
window['$RX'] = clientRenderBoundary;
+window['$RV'] = revealCompletedBoundariesWithViewTransitions.bind(
+ null,
+ revealCompletedBoundaries,
+);
window['$RC'] = completeBoundary;
window['$RR'] = completeBoundaryWithStyles;
window['$RS'] = completeSegment;
diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
index 470698b27c0c5..aa7209fadac1d 100644
--- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
+++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
@@ -6,7 +6,9 @@ export const markShellTime =
export const clientRenderBoundary =
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
export const completeBoundary =
- '$RB=[];$RC=function(d,c){function m(){$RT=performance.now();var f=$RB;$RB=[];for(var e=0;e {
+ if (document['__reactViewTransition'] === transition) {
+ document['__reactViewTransition'] = null;
+ }
+ });
+ return;
+ }
+ // Fall through to reveal.
+ } catch (x) {
+ // Fall through to reveal.
+ }
+ // ViewTransitions v2 not supported or no ViewTransitions found. Reveal immediately.
+ revealBoundaries();
+}
+
export function clientRenderBoundary(
suspenseBoundaryID,
errorDigest,
@@ -71,69 +165,6 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
return;
}
- function revealCompletedBoundaries() {
- window['$RT'] = performance.now();
- const batch = window['$RB'];
- window['$RB'] = [];
- for (let i = 0; i < batch.length; i += 2) {
- const suspenseIdNode = batch[i];
- const contentNode = batch[i + 1];
-
- // Clear all the existing children. This is complicated because
- // there can be embedded Suspense boundaries in the fallback.
- // This is similar to clearSuspenseBoundary in ReactFiberConfigDOM.
- // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
- // They never hydrate anyway. However, currently we support incrementally loading the fallback.
- const parentInstance = suspenseIdNode.parentNode;
- if (!parentInstance) {
- // We may have client-rendered this boundary already. Skip it.
- continue;
- }
-
- // Find the boundary around the fallback. This is always the previous node.
- const suspenseNode = suspenseIdNode.previousSibling;
-
- let node = suspenseIdNode;
- let depth = 0;
- do {
- if (node && node.nodeType === COMMENT_NODE) {
- const data = node.data;
- if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
- if (depth === 0) {
- break;
- } else {
- depth--;
- }
- } else if (
- data === SUSPENSE_START_DATA ||
- data === SUSPENSE_PENDING_START_DATA ||
- data === SUSPENSE_QUEUED_START_DATA ||
- data === SUSPENSE_FALLBACK_START_DATA ||
- data === ACTIVITY_START_DATA
- ) {
- depth++;
- }
- }
-
- const nextNode = node.nextSibling;
- parentInstance.removeChild(node);
- node = nextNode;
- } while (node);
-
- const endOfBoundary = node;
-
- // Insert all the children from the contentNode between the start and end of suspense boundary.
- while (contentNode.firstChild) {
- parentInstance.insertBefore(contentNode.firstChild, endOfBoundary);
- }
-
- suspenseNode.data = SUSPENSE_START_DATA;
- if (suspenseNode['_reactRetry']) {
- suspenseNode['_reactRetry']();
- }
- }
- }
-
// Mark this Suspense boundary as queued so we know not to client render it
// at the end of document load.
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
@@ -151,7 +182,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
// We always schedule the flush in a timer even if it's very low or negative to allow
// for multiple completeBoundary calls that are already queued to have a chance to
// make the batch.
- setTimeout(revealCompletedBoundaries, msUntilTimeout);
+ setTimeout(window['$RV'], msUntilTimeout);
}
}
diff --git a/packages/react-markup/src/ReactFizzConfigMarkup.js b/packages/react-markup/src/ReactFizzConfigMarkup.js
index 3ed4e61190a00..bbca0d4ddf2b9 100644
--- a/packages/react-markup/src/ReactFizzConfigMarkup.js
+++ b/packages/react-markup/src/ReactFizzConfigMarkup.js
@@ -89,6 +89,7 @@ export {
import escapeTextForBrowser from 'react-dom-bindings/src/server/escapeTextForBrowser';
export function getViewTransitionFormatContext(
+ resumableState: ResumableState,
parentContext: FormatContext,
update: void | null | 'none' | 'auto' | string,
enter: void | null | 'none' | 'auto' | string,
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 2e76e5188ec5f..69bc84038dac9 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -14,6 +14,9 @@ import type {
ViewTransitionProps,
ActivityProps,
SuspenseProps,
+ SuspenseListProps,
+ SuspenseListRevealOrder,
+ SuspenseListTailMode,
TracingMarkerProps,
CacheProps,
ProfilerProps,
@@ -26,7 +29,6 @@ import type {ActivityState} from './ReactFiberActivityComponent';
import type {
SuspenseState,
SuspenseListRenderState,
- SuspenseListTailMode,
} from './ReactFiberSuspenseComponent';
import type {SuspenseContext} from './ReactFiberSuspenseContext';
import type {
@@ -3222,8 +3224,6 @@ function findLastContentRow(firstChild: null | Fiber): null | Fiber {
return lastContentRow;
}
-type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together' | void;
-
function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
if (__DEV__) {
if (
@@ -3410,7 +3410,7 @@ function updateSuspenseListComponent(
workInProgress: Fiber,
renderLanes: Lanes,
) {
- const nextProps = workInProgress.pendingProps;
+ const nextProps: SuspenseListProps = workInProgress.pendingProps;
const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
const tailMode: SuspenseListTailMode = nextProps.tail;
const newChildren = nextProps.children;
diff --git a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js
index 07a7df2ec5b56..f7ae78ed6d313 100644
--- a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js
+++ b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {Wakeable} from 'shared/ReactTypes';
+import type {Wakeable, SuspenseListTailMode} from 'shared/ReactTypes';
import type {Fiber} from './ReactInternalTypes';
import type {SuspenseInstance} from './ReactFiberConfig';
import type {Lane} from './ReactFiberLane';
@@ -42,8 +42,6 @@ export type SuspenseState = {
hydrationErrors: Array> | null,
};
-export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
-
export type SuspenseListRenderState = {
isBackwards: boolean,
// The currently rendering tail row.
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index a159771421b7d..247e21076e5b6 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -1146,7 +1146,10 @@ function renderSuspenseBoundary(
const prevKeyPath = someTask.keyPath;
const prevContext = someTask.formatContext;
someTask.keyPath = keyPath;
- someTask.formatContext = getSuspenseContentFormatContext(prevContext);
+ someTask.formatContext = getSuspenseContentFormatContext(
+ request.resumableState,
+ prevContext,
+ );
const content: ReactNodeList = props.children;
try {
renderNode(request, someTask, content, -1);
@@ -1239,7 +1242,10 @@ function renderSuspenseBoundary(
task.blockedSegment = boundarySegment;
task.blockedPreamble = newBoundary.fallbackPreamble;
task.keyPath = fallbackKeyPath;
- task.formatContext = getSuspenseFallbackFormatContext(prevContext);
+ task.formatContext = getSuspenseFallbackFormatContext(
+ request.resumableState,
+ prevContext,
+ );
boundarySegment.status = RENDERING;
try {
renderNode(request, task, fallback, -1);
@@ -1278,7 +1284,10 @@ function renderSuspenseBoundary(
newBoundary.contentState,
task.abortSet,
keyPath,
- getSuspenseContentFormatContext(task.formatContext),
+ getSuspenseContentFormatContext(
+ request.resumableState,
+ task.formatContext,
+ ),
task.context,
task.treeContext,
task.componentStack,
@@ -1305,7 +1314,10 @@ function renderSuspenseBoundary(
task.hoistableState = newBoundary.contentState;
task.blockedSegment = contentRootSegment;
task.keyPath = keyPath;
- task.formatContext = getSuspenseContentFormatContext(prevContext);
+ task.formatContext = getSuspenseContentFormatContext(
+ request.resumableState,
+ prevContext,
+ );
contentRootSegment.status = RENDERING;
try {
@@ -1409,7 +1421,10 @@ function renderSuspenseBoundary(
newBoundary.fallbackState,
fallbackAbortSet,
fallbackKeyPath,
- getSuspenseFallbackFormatContext(task.formatContext),
+ getSuspenseFallbackFormatContext(
+ request.resumableState,
+ task.formatContext,
+ ),
task.context,
task.treeContext,
task.componentStack,
@@ -1471,7 +1486,10 @@ function replaySuspenseBoundary(
task.blockedBoundary = resumedBoundary;
task.hoistableState = resumedBoundary.contentState;
task.keyPath = keyPath;
- task.formatContext = getSuspenseContentFormatContext(prevContext);
+ task.formatContext = getSuspenseContentFormatContext(
+ request.resumableState,
+ prevContext,
+ );
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
try {
@@ -1569,7 +1587,10 @@ function replaySuspenseBoundary(
resumedBoundary.fallbackState,
fallbackAbortSet,
fallbackKeyPath,
- getSuspenseFallbackFormatContext(task.formatContext),
+ getSuspenseFallbackFormatContext(
+ request.resumableState,
+ task.formatContext,
+ ),
task.context,
task.treeContext,
task.componentStack,
@@ -2284,6 +2305,7 @@ function renderViewTransition(
request.resumableState,
);
task.formatContext = getViewTransitionFormatContext(
+ request.resumableState,
prevContext,
getViewTransitionClassName(props.default, props.update),
getViewTransitionClassName(props.default, props.enter),
diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js
index 4c8ca77bb160b..9bd64cd81d608 100644
--- a/packages/shared/ReactTypes.js
+++ b/packages/shared/ReactTypes.js
@@ -290,6 +290,30 @@ export type SuspenseProps = {
name?: string,
};
+export type SuspenseListRevealOrder =
+ | 'forwards'
+ | 'backwards'
+ | 'together'
+ | void;
+
+export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
+
+type DirectionalSuspenseListProps = {
+ children?: ReactNodeList,
+ revealOrder: 'forwards' | 'backwards',
+ tail?: SuspenseListTailMode,
+};
+
+type NonDirectionalSuspenseListProps = {
+ children?: ReactNodeList,
+ revealOrder?: 'together' | void,
+ tail?: void,
+};
+
+export type SuspenseListProps =
+ | DirectionalSuspenseListProps
+ | NonDirectionalSuspenseListProps;
+
export type TracingMarkerProps = {
name: string,
children?: ReactNodeList,
diff --git a/scripts/rollup/generate-inline-fizz-runtime.js b/scripts/rollup/generate-inline-fizz-runtime.js
index 3d097f63e216a..c531240e0c586 100644
--- a/scripts/rollup/generate-inline-fizz-runtime.js
+++ b/scripts/rollup/generate-inline-fizz-runtime.js
@@ -25,6 +25,10 @@ const config = [
entry: 'ReactDOMFizzInlineCompleteBoundary.js',
exportName: 'completeBoundary',
},
+ {
+ entry: 'ReactDOMFizzInlineCompleteBoundaryUpgradeToViewTransitions.js',
+ exportName: 'completeBoundaryUpgradeToViewTransitions',
+ },
{
entry: 'ReactDOMFizzInlineCompleteBoundaryWithStyles.js',
exportName: 'completeBoundaryWithStyles',