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',