@@ -499,8 +499,11 @@ export class DynamicFragment extends VaporFragment {
499499 let parentNode : Node | null
500500 let nextNode : Node | null
501501 if ( forwardedSlot ) {
502- parentNode = slotAnchor ! . parentNode
503- nextNode = slotAnchor ! . nextSibling
502+ // Keep the forwarded slot close marker structural for parent cleanup,
503+ // even though this fragment uses a runtime anchor after it.
504+ const anchor = markHydrationAnchor ( slotAnchor ! )
505+ parentNode = anchor . parentNode
506+ nextNode = anchor . nextSibling
504507 } else {
505508 const node = findBlockNode ( this . nodes )
506509 parentNode = node . parentNode
@@ -511,11 +514,13 @@ export class DynamicFragment extends VaporFragment {
511514 // Otherwise detached anchors could be observed too early by traversal
512515 // logic such as `findLastChild()`.
513516 queuePostFlushCb ( ( ) => {
517+ const anchor =
518+ nextNode && nextNode . parentNode === parentNode ? nextNode : null
514519 parentNode ! . insertBefore (
515520 ( this . anchor = markHydrationAnchor (
516521 __DEV__ ? createComment ( this . anchorLabel ! ) : createTextNode ( ) ,
517522 ) ) ,
518- nextNode ,
523+ anchor ,
519524 )
520525 } )
521526 } finally {
@@ -566,6 +571,7 @@ export let currentEmptyFragment: DynamicFragment | null | undefined
566571
567572export class SlotFragment extends DynamicFragment {
568573 forwarded = false
574+ deferredHydrationBoundary ?: ( ) => void
569575
570576 constructor ( ) {
571577 super ( isHydrating || __DEV__ ? 'slot' : undefined , false , false )
@@ -579,6 +585,7 @@ export class SlotFragment extends DynamicFragment {
579585 let prevEndAnchor : Node | null = null
580586 let pushedEndAnchor = false
581587 let exitHydrationBoundary : ( ( ) => void ) | undefined
588+ let deferHydrationBoundary = false
582589 if ( isHydrating ) {
583590 locateHydrationNode ( )
584591 if ( isComment ( currentHydrationNode ! , '[' ) ) {
@@ -624,12 +631,24 @@ export class SlotFragment extends DynamicFragment {
624631 // once against the final block.
625632 if ( isHydrating ) {
626633 this . hydrate ( render == null , true )
634+ // Empty slots rendered while resolving an outer slot fallback can be
635+ // filled by that fallback immediately after render() returns.
636+ deferHydrationBoundary =
637+ ! ! exitHydrationBoundary &&
638+ currentEmptyFragment !== undefined &&
639+ ! isValidBlock ( this . nodes )
627640 }
628641 } finally {
629- if ( isHydrating && pushedEndAnchor ) {
630- setCurrentSlotEndAnchor ( prevEndAnchor )
642+ if ( isHydrating ) {
643+ if ( pushedEndAnchor ) {
644+ setCurrentSlotEndAnchor ( prevEndAnchor )
645+ }
646+ if ( deferHydrationBoundary ) {
647+ this . deferredHydrationBoundary = exitHydrationBoundary
648+ } else {
649+ exitHydrationBoundary && exitHydrationBoundary ( )
650+ }
631651 }
632- exitHydrationBoundary && exitHydrationBoundary ( )
633652 }
634653 }
635654}
@@ -657,6 +676,15 @@ export function renderSlotFallback(
657676 frag . nodes [ 0 ] = [ fallback ( ) || [ ] ] as Block [ ]
658677 } else if ( frag instanceof DynamicFragment ) {
659678 frag . update ( fallback )
679+ if ( isHydrating && frag instanceof SlotFragment ) {
680+ const deferredHydrationBoundary = frag . deferredHydrationBoundary
681+ if ( deferredHydrationBoundary ) {
682+ frag . deferredHydrationBoundary = undefined
683+ // The fallback has now had a chance to hydrate the SSR nodes that
684+ // originally belonged to the empty forwarded slot.
685+ deferredHydrationBoundary ( )
686+ }
687+ }
660688 }
661689 return block
662690 }
0 commit comments