@@ -30,6 +30,7 @@ import {
30
30
createVNode ,
31
31
defineComponent ,
32
32
getCurrentInstance ,
33
+ isTemplateNode ,
33
34
isVNode ,
34
35
nextTick ,
35
36
unref ,
@@ -627,19 +628,44 @@ export class VueElement
627
628
* Only called when shadowRoot is false
628
629
*/
629
630
private _parseSlots ( remove : boolean = true ) {
630
- const slots : VueElement [ '_slots' ] = ( this . _slots = { } )
631
+ if ( ! this . _slotNames ) this . _slotNames = new Set ( )
632
+ else this . _slotNames . clear ( )
633
+ this . _slots = { }
634
+
631
635
let n = this . firstChild
632
636
while ( n ) {
637
+ const next = n . nextSibling
638
+ if ( isTemplateNode ( n ) ) {
639
+ this . processTemplateChildren ( n , remove )
640
+ this . removeChild ( n )
641
+ } else {
642
+ const slotName =
643
+ ( n . nodeType === 1 && ( n as Element ) . getAttribute ( 'slot' ) ) || 'default'
644
+ this . addToSlot ( slotName , n , remove )
645
+ }
646
+
647
+ n = next
648
+ }
649
+ }
650
+
651
+ private processTemplateChildren ( template : Node , remove : boolean ) {
652
+ let n = template . firstChild
653
+ while ( n ) {
654
+ const next = n . nextSibling
633
655
const slotName =
634
656
( n . nodeType === 1 && ( n as Element ) . getAttribute ( 'slot' ) ) || 'default'
635
- ; ( slots [ slotName ] || ( slots [ slotName ] = [ ] ) ) . push ( n )
636
- ; ( this . _slotNames || ( this . _slotNames = new Set ( ) ) ) . add ( slotName )
637
- const next = n . nextSibling
638
- if ( remove ) this . removeChild ( n )
657
+ this . addToSlot ( slotName , n , remove )
658
+ if ( remove ) template . removeChild ( n )
639
659
n = next
640
660
}
641
661
}
642
662
663
+ private addToSlot ( slotName : string , node : Node , remove : boolean ) {
664
+ ; ( this . _slots ! [ slotName ] || ( this . _slots ! [ slotName ] = [ ] ) ) . push ( node )
665
+ this . _slotNames ! . add ( slotName )
666
+ if ( remove ) this . removeChild ( node )
667
+ }
668
+
643
669
/**
644
670
* Only called when shadowRoot is false
645
671
*/
@@ -664,7 +690,12 @@ export class VueElement
664
690
parent . insertBefore ( anchor , o )
665
691
666
692
if ( content ) {
693
+ const parentNode = content [ 0 ] . parentNode
667
694
insertSlottedContent ( content , scopeId , parent , anchor )
695
+ // remove empty template container
696
+ if ( parentNode && isTemplateNode ( parentNode ) ) {
697
+ this . removeChild ( parentNode )
698
+ }
668
699
} else if ( this . _slotFallbacks ) {
669
700
const nodes = this . _slotFallbacks [ slotName ]
670
701
if ( nodes ) {
@@ -676,29 +707,32 @@ export class VueElement
676
707
parent . removeChild ( o )
677
708
}
678
709
679
- // ensure default slot content is rendered if provided
680
- if ( ! processedSlots . has ( 'default' ) ) {
681
- let content = this . _slots ! [ 'default' ]
682
- if ( content ) {
710
+ // create template for unprocessed slots and insert their content
711
+ // this prevents errors during full diff when anchors are not in the DOM tree
712
+ for ( const slotName of this . _slotNames ! ) {
713
+ if ( processedSlots . has ( slotName ) ) continue
714
+
715
+ const content = this . _slots ! [ slotName ]
716
+ if ( content && ! content [ 0 ] . isConnected ) {
683
717
let anchor
684
- // if the default slot is not the first one, insert it behind the previous slot
685
718
if ( this . _slotAnchors ) {
686
719
const slotNames = Array . from ( this . _slotNames ! )
687
- const defaultSlotIndex = slotNames . indexOf ( 'default' )
688
- if ( defaultSlotIndex > 0 ) {
720
+ const slotIndex = slotNames . indexOf ( slotName )
721
+ if ( slotIndex > 0 ) {
689
722
const prevSlotAnchor = this . _slotAnchors . get (
690
- slotNames [ defaultSlotIndex - 1 ] ,
723
+ slotNames [ slotIndex - 1 ] ,
691
724
)
692
725
if ( prevSlotAnchor ) anchor = prevSlotAnchor . nextSibling
693
726
}
694
727
}
695
728
696
- insertSlottedContent (
697
- content ,
698
- scopeId ,
699
- this . _root ,
700
- anchor || this . firstChild ,
701
- )
729
+ const container = document . createElement ( 'template' )
730
+ container . setAttribute ( 'name' , slotName )
731
+ for ( const n of content ) {
732
+ ; ( n as any ) . $parentNode = container
733
+ container . insertBefore ( n , null )
734
+ }
735
+ this . insertBefore ( container , anchor || null )
702
736
}
703
737
}
704
738
}
@@ -720,18 +754,27 @@ export class VueElement
720
754
Object . entries ( this . _slots ! ) . forEach ( ( [ _ , nodes ] ) => {
721
755
const nodeIndex = nodes . indexOf ( prevNode )
722
756
if ( nodeIndex > - 1 ) {
757
+ const oldNode = nodes [ nodeIndex ]
758
+ const parentNode = ( ( newNode as any ) . $parentNode = (
759
+ oldNode as any
760
+ ) . $parentNode )
723
761
nodes [ nodeIndex ] = newNode
762
+
763
+ if ( oldNode . isConnected ) {
764
+ parentNode . insertBefore ( newNode , oldNode )
765
+ parentNode . removeChild ( oldNode )
766
+ }
724
767
}
725
768
} )
726
769
}
727
770
}
728
771
729
772
// switch between fallback and provided content
730
773
if ( this . _slotFallbacks ) {
731
- const oldSlotNames = Object . keys ( this . _slots ! )
774
+ const oldSlotNames = Array . from ( this . _slotNames ! )
732
775
// re-parse slots
733
776
this . _parseSlots ( false )
734
- const newSlotNames = Object . keys ( this . _slots ! )
777
+ const newSlotNames = Array . from ( this . _slotNames ! )
735
778
const allSlotNames = new Set ( [ ...oldSlotNames , ...newSlotNames ] )
736
779
allSlotNames . forEach ( name => {
737
780
const fallbackNodes = this . _slotFallbacks ! [ name ]
@@ -744,11 +787,21 @@ export class VueElement
744
787
)
745
788
}
746
789
747
- // remove fallback nodes for added slots
790
+ // remove fallback nodes and render provided nodes for added slots
748
791
if ( ! oldSlotNames . includes ( name ) ) {
749
792
fallbackNodes . forEach ( fallbackNode =>
750
793
this . removeChild ( fallbackNode ) ,
751
794
)
795
+
796
+ const content = this . _slots ! [ name ]
797
+ if ( content ) {
798
+ insertSlottedContent (
799
+ content ,
800
+ this . _instance ! . type . __scopeId ,
801
+ this . _root ,
802
+ this . _slotAnchors ! . get ( name ) || null ,
803
+ )
804
+ }
752
805
}
753
806
}
754
807
} )
0 commit comments