782
783
type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
784
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.
793
794
// Everything not listed here are tracked for the whole subtree as opposed to just
795
// until the next Instance.
988
resumableState: ResumableState,
989
parentContext: FormatContext,
990
): 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
+ }
1000
return createFormatContext(
1001
parentContext.insertionMode,
1002
parentContext.selectedValue,
993
- parentContext.tagScope | ENTER_SCOPE,
994
- getSuspenseViewTransition(parentContext.viewTransition),
1003
+ subtreeScope,
1004
+ viewTransition,
1005
);
1006
}
1007
1073
} else {
1074
subtreeScope &= ~UPDATE_SCOPE;
1075
}
1076
+ if (enter !== 'none') {
1077
+ subtreeScope |= APPEARING_SCOPE;
1078
+ }
1079
return createFormatContext(
1080
parentContext.insertionMode,
1081
parentContext.selectedValue,
3302
props: Object,
3303
resumableState: ResumableState,
3304
renderState: RenderState,
3305
+ hoistableState: null | HoistableState,
3306
formatContext: FormatContext,
3307
): null {
3308
const pictureOrNoScriptTagInScope =
3335
) {
3336
// We have a suspensey image and ought to preload it to optimize the loading of display blocking
3337
// 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
+
3351
const sizes = typeof props.sizes === 'string' ? props.sizes : undefined;
3352
const key = getImageResourceKey(src, srcSet, sizes);
3353
4282
return pushStartPreformattedElement(target, props, type, formatContext);
4283
}
4284
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
+ );
4293
}
4294
// Omitted close tags
4295
case 'base':
6159
export type HoistableState = {
6160
styles: Set<StyleQueue>,
6161
stylesheets: Set<StylesheetResource>,
6162
+ suspenseyImages: boolean,
6163
};
6164
6165
export type StyleQueue = {
6173
return {
6174
styles: new Set(),
6175
stylesheets: new Set(),
6176
+ suspenseyImages: false,
6177
};
6178
}
6179
7031
): void {
7032
childState.styles.forEach(hoistStyleQueueDependency, parentState);
7033
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;
7046
}
7047
7048
// This function is called at various times depending on whether we are rendering