@@ -782,13 +782,14 @@ const HTML_COLGROUP_MODE = 9;
782782
783783type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ;
784784
785- const NO_SCOPE = /* */ 0b000000 ;
786- const NOSCRIPT_SCOPE = /* */ 0b000001 ;
787- const PICTURE_SCOPE = /* */ 0b000010 ;
788- const FALLBACK_SCOPE = /* */ 0b000100 ;
789- const EXIT_SCOPE = /* */ 0b001000 ; // A direct Instance below a Suspense fallback is the only thing that can "exit"
790- const ENTER_SCOPE = /* */ 0b010000 ; // A direct Instance below Suspense content is the only thing that can "enter"
791- const UPDATE_SCOPE = /* */ 0b100000 ; // Inside a scope that applies "update" ViewTransitions if anything mutates here.
785+ const NO_SCOPE = /* */ 0b0000000 ;
786+ const NOSCRIPT_SCOPE = /* */ 0b0000001 ;
787+ const PICTURE_SCOPE = /* */ 0b0000010 ;
788+ const FALLBACK_SCOPE = /* */ 0b0000100 ;
789+ const EXIT_SCOPE = /* */ 0b0001000 ; // A direct Instance below a Suspense fallback is the only thing that can "exit"
790+ const ENTER_SCOPE = /* */ 0b0010000 ; // A direct Instance below Suspense content is the only thing that can "enter"
791+ const UPDATE_SCOPE = /* */ 0b0100000 ; // Inside a scope that applies "update" ViewTransitions if anything mutates here.
792+ const APPEARING_SCOPE = /* */ 0b1000000 ; // Below Suspense content subtree which might appear in an "enter" animation or "shared" animation.
792793
793794// Everything not listed here are tracked for the whole subtree as opposed to just
794795// until the next Instance.
@@ -987,11 +988,20 @@ export function getSuspenseContentFormatContext(
987988 resumableState : ResumableState ,
988989 parentContext : FormatContext ,
989990) : FormatContext {
991+ const viewTransition = getSuspenseViewTransition (
992+ parentContext . viewTransition ,
993+ ) ;
994+ let subtreeScope = parentContext . tagScope | ENTER_SCOPE ;
995+ if ( viewTransition !== null && viewTransition . share !== 'none' ) {
996+ // If we have a ViewTransition wrapping Suspense then the appearing animation
997+ // will be applied just like an "enter" below. Mark it as animating.
998+ subtreeScope |= APPEARING_SCOPE ;
999+ }
9901000 return createFormatContext (
9911001 parentContext . insertionMode ,
9921002 parentContext . selectedValue ,
993- parentContext . tagScope | ENTER_SCOPE ,
994- getSuspenseViewTransition ( parentContext . viewTransition ) ,
1003+ subtreeScope ,
1004+ viewTransition ,
9951005 ) ;
9961006}
9971007
@@ -1063,6 +1073,9 @@ export function getViewTransitionFormatContext(
10631073 } else {
10641074 subtreeScope &= ~ UPDATE_SCOPE ;
10651075 }
1076+ if ( enter !== 'none' ) {
1077+ subtreeScope |= APPEARING_SCOPE ;
1078+ }
10661079 return createFormatContext (
10671080 parentContext . insertionMode ,
10681081 parentContext . selectedValue ,
@@ -3289,6 +3302,7 @@ function pushImg(
32893302 props : Object ,
32903303 resumableState : ResumableState ,
32913304 renderState : RenderState ,
3305+ hoistableState : null | HoistableState ,
32923306 formatContext : FormatContext ,
32933307) : null {
32943308 const pictureOrNoScriptTagInScope =
@@ -3321,6 +3335,19 @@ function pushImg(
33213335 ) {
33223336 // We have a suspensey image and ought to preload it to optimize the loading of display blocking
33233337 // resumableState.
3338+
3339+ if ( hoistableState !== null ) {
3340+ // Mark this boundary's state as having suspensey images.
3341+ // Only do that if we have a ViewTransition that might trigger a parent Suspense boundary
3342+ // to animate its appearing. Since that's the only case we'd actually apply suspensey images
3343+ // for SSR reveals.
3344+ const isInSuspenseWithEnterViewTransition =
3345+ formatContext . tagScope & APPEARING_SCOPE ;
3346+ if ( isInSuspenseWithEnterViewTransition ) {
3347+ hoistableState . suspenseyImages = true ;
3348+ }
3349+ }
3350+
33243351 const sizes = typeof props . sizes === 'string' ? props . sizes : undefined ;
33253352 const key = getImageResourceKey ( src , srcSet , sizes ) ;
33263353
@@ -4255,7 +4282,14 @@ export function pushStartInstance(
42554282 return pushStartPreformattedElement ( target , props , type , formatContext ) ;
42564283 }
42574284 case 'img' : {
4258- return pushImg ( target , props , resumableState , renderState , formatContext ) ;
4285+ return pushImg (
4286+ target ,
4287+ props ,
4288+ resumableState ,
4289+ renderState ,
4290+ hoistableState ,
4291+ formatContext ,
4292+ ) ;
42594293 }
42604294 // Omitted close tags
42614295 case 'base' :
@@ -6125,6 +6159,7 @@ type StylesheetResource = {
61256159export type HoistableState = {
61266160 styles : Set < StyleQueue > ,
61276161 stylesheets : Set < StylesheetResource > ,
6162+ suspenseyImages : boolean ,
61286163} ;
61296164
61306165export type StyleQueue = {
@@ -6138,6 +6173,7 @@ export function createHoistableState(): HoistableState {
61386173 return {
61396174 styles : new Set ( ) ,
61406175 stylesheets : new Set ( ) ,
6176+ suspenseyImages : false ,
61416177 } ;
61426178}
61436179
@@ -6995,6 +7031,18 @@ export function hoistHoistables(
69957031) : void {
69967032 childState . styles . forEach ( hoistStyleQueueDependency , parentState ) ;
69977033 childState . stylesheets . forEach ( hoistStylesheetDependency , parentState ) ;
7034+ if ( childState . suspenseyImages ) {
7035+ // If the child has suspensey images, the parent now does too if it's inlined.
7036+ // Similarly, if a SuspenseList row has a suspensey image then effectively
7037+ // the next row should be blocked on it as well since the next row can't show
7038+ // earlier. In practice, since the child will be outlined this transferring
7039+ // may never matter but is conceptually correct.
7040+ parentState . suspenseyImages = true ;
7041+ }
7042+ }
7043+
7044+ export function hasSuspenseyContent ( hoistableState : HoistableState ) : boolean {
7045+ return hoistableState . stylesheets . size > 0 || hoistableState . suspenseyImages ;
69987046}
69997047
70007048// This function is called at various times depending on whether we are rendering
0 commit comments