@@ -245,6 +245,8 @@ export class VueElement
245
245
private _childStyles ?: Map < string , HTMLStyleElement [ ] >
246
246
private _ob ?: MutationObserver | null = null
247
247
private _slots ?: Record < string , Node [ ] >
248
+ private _slotFallbacks ?: Record < string , Node [ ] >
249
+ private _slotAnchors ?: Map < string , Node >
248
250
249
251
constructor (
250
252
/**
@@ -529,8 +531,11 @@ export class VueElement
529
531
private _createVNode ( ) : VNode < any , any > {
530
532
const baseProps : VNodeProps = { }
531
533
if ( ! this . shadowRoot ) {
532
- baseProps . onVnodeMounted = baseProps . onVnodeUpdated =
533
- this . _renderSlots . bind ( this )
534
+ baseProps . onVnodeMounted = ( ) => {
535
+ this . _captureSlotFallbacks ( )
536
+ this . _renderSlots ( )
537
+ }
538
+ baseProps . onVnodeUpdated = this . _renderSlots . bind ( this )
534
539
}
535
540
const vnode = createVNode ( this . _def , extend ( baseProps , this . _props ) )
536
541
if ( ! this . _instance ) {
@@ -617,14 +622,19 @@ export class VueElement
617
622
/**
618
623
* Only called when shadowRoot is false
619
624
*/
620
- private _parseSlots ( ) {
625
+ private _parseSlots ( remove : boolean = true ) {
621
626
const slots : VueElement [ '_slots' ] = ( this . _slots = { } )
622
- let n
623
- while ( ( n = this . firstChild ) ) {
627
+ let n = this . firstChild
628
+ while ( n ) {
624
629
const slotName =
625
630
( n . nodeType === 1 && ( n as Element ) . getAttribute ( 'slot' ) ) || 'default'
626
631
; ( slots [ slotName ] || ( slots [ slotName ] = [ ] ) ) . push ( n )
627
- this . removeChild ( n )
632
+ const next = n . nextSibling
633
+ // store the parentNode reference since node will be removed
634
+ // but it is needed during patching
635
+ ; ( n as any ) . _parentNode = n . parentNode
636
+ if ( remove ) this . removeChild ( n )
637
+ n = next
628
638
}
629
639
}
630
640
@@ -634,11 +644,18 @@ export class VueElement
634
644
private _renderSlots ( ) {
635
645
const outlets = ( this . _teleportTarget || this ) . querySelectorAll ( 'slot' )
636
646
const scopeId = this . _instance ! . type . __scopeId
647
+ this . _slotAnchors = new Map ( )
637
648
for ( let i = 0 ; i < outlets . length ; i ++ ) {
638
649
const o = outlets [ i ] as HTMLSlotElement
639
650
const slotName = o . getAttribute ( 'name' ) || 'default'
640
651
const content = this . _slots ! [ slotName ]
641
652
const parent = o . parentNode !
653
+
654
+ // insert an anchor to facilitate updates
655
+ const anchor = document . createTextNode ( '' )
656
+ this . _slotAnchors . set ( slotName , anchor )
657
+ parent . insertBefore ( anchor , o )
658
+
642
659
if ( content ) {
643
660
for ( const n of content ) {
644
661
// for :slotted css
@@ -651,23 +668,91 @@ export class VueElement
651
668
; ( child as Element ) . setAttribute ( id , '' )
652
669
}
653
670
}
654
- parent . insertBefore ( n , o )
671
+ parent . insertBefore ( n , anchor )
672
+ }
673
+ } else if ( this . _slotFallbacks ) {
674
+ const nodes = this . _slotFallbacks [ slotName ]
675
+ if ( nodes ) {
676
+ for ( const n of nodes ) {
677
+ parent . insertBefore ( n , anchor )
678
+ }
655
679
}
656
- } else {
657
- while ( o . firstChild ) parent . insertBefore ( o . firstChild , o )
658
680
}
659
681
parent . removeChild ( o )
660
682
}
661
683
}
662
684
663
685
/**
664
- * @internal
686
+ * Only called when shadowRoot is false
665
687
*/
666
- _updateSlots ( children : VNode [ ] ) : void {
667
- children . forEach ( child => {
668
- // slot children are always Fragments
669
- this . _slots ! [ child . slotName ! ] = collectFragmentElements ( child )
670
- } )
688
+ _updateSlots ( n1 : VNode , n2 : VNode ) : void {
689
+ // replace v-if nodes
690
+ const prevNodes = collectNodes ( n1 . children as VNodeArrayChildren )
691
+ const newNodes = collectNodes ( n2 . children as VNodeArrayChildren )
692
+ for ( let i = 0 ; i < prevNodes . length ; i ++ ) {
693
+ const prevNode = prevNodes [ i ]
694
+ const newNode = newNodes [ i ]
695
+ if ( isComment ( prevNode , 'v-if' ) || isComment ( newNode , 'v-if' ) ) {
696
+ Object . keys ( this . _slots ! ) . forEach ( name => {
697
+ const slotNodes = this . _slots ! [ name ]
698
+ if ( slotNodes ) {
699
+ for ( const node of slotNodes ) {
700
+ if ( node === prevNode ) {
701
+ this . _slots ! [ name ] [ i ] = newNode
702
+ break
703
+ }
704
+ }
705
+ }
706
+ } )
707
+ }
708
+ }
709
+
710
+ // switch between fallback and provided content
711
+ if ( this . _slotFallbacks ) {
712
+ const oldSlotNames = Object . keys ( this . _slots ! )
713
+ // re-parse slots
714
+ this . _parseSlots ( false )
715
+ const newSlotNames = Object . keys ( this . _slots ! )
716
+ const allSlotNames = new Set ( [ ...oldSlotNames , ...newSlotNames ] )
717
+ allSlotNames . forEach ( name => {
718
+ const fallbackNodes = this . _slotFallbacks ! [ name ]
719
+ if ( fallbackNodes ) {
720
+ // render fallback nodes for removed slots
721
+ if ( ! newSlotNames . includes ( name ) ) {
722
+ const anchor = this . _slotAnchors ! . get ( name ) !
723
+ fallbackNodes . forEach ( fallbackNode =>
724
+ this . insertBefore ( fallbackNode , anchor ) ,
725
+ )
726
+ }
727
+
728
+ // remove fallback nodes for added slots
729
+ if ( ! oldSlotNames . includes ( name ) ) {
730
+ fallbackNodes . forEach ( fallbackNode =>
731
+ this . removeChild ( fallbackNode ) ,
732
+ )
733
+ }
734
+ }
735
+ } )
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Only called when shadowRoot is false
741
+ */
742
+ private _captureSlotFallbacks ( ) {
743
+ const outlets = ( this . _teleportTarget || this ) . querySelectorAll ( 'slot' )
744
+ for ( let i = 0 ; i < outlets . length ; i ++ ) {
745
+ const slotElement = outlets [ i ] as HTMLSlotElement
746
+ const slotName = slotElement . getAttribute ( 'name' ) || 'default'
747
+ const fallbackNodes : Node [ ] = [ ]
748
+ while ( slotElement . firstChild ) {
749
+ fallbackNodes . push ( slotElement . removeChild ( slotElement . firstChild ) )
750
+ }
751
+ if ( fallbackNodes . length ) {
752
+ ; ( this . _slotFallbacks || ( this . _slotFallbacks = { } ) ) [ slotName ] =
753
+ fallbackNodes
754
+ }
755
+ }
671
756
}
672
757
673
758
/**
@@ -724,26 +809,30 @@ export function useShadowRoot(): ShadowRoot | null {
724
809
return el && el . shadowRoot
725
810
}
726
811
727
- function collectFragmentElements ( child : VNode ) : Node [ ] {
812
+ function collectFragmentNodes ( child : VNode ) : Node [ ] {
728
813
return [
729
814
child . el as Node ,
730
- ...collectElements ( child . children as VNodeArrayChildren ) ,
815
+ ...collectNodes ( child . children as VNodeArrayChildren ) ,
731
816
child . anchor as Node ,
732
817
]
733
818
}
734
819
735
- function collectElements ( children : VNodeArrayChildren ) : Node [ ] {
820
+ function collectNodes ( children : VNodeArrayChildren ) : Node [ ] {
736
821
const nodes : Node [ ] = [ ]
737
822
for ( const child of children ) {
738
823
if ( isArray ( child ) ) {
739
- nodes . push ( ...collectElements ( child ) )
824
+ nodes . push ( ...collectNodes ( child ) )
740
825
} else if ( isVNode ( child ) ) {
741
826
if ( child . type === Fragment ) {
742
- nodes . push ( ...collectFragmentElements ( child ) )
827
+ nodes . push ( ...collectFragmentNodes ( child ) )
743
828
} else if ( child . el ) {
744
829
nodes . push ( child . el as Node )
745
830
}
746
831
}
747
832
}
748
833
return nodes
749
834
}
835
+
836
+ function isComment ( node : Node , data : string ) : node is Comment {
837
+ return node . nodeType === 8 && ( node as Comment ) . data === data
838
+ }
0 commit comments