diff --git a/.changeset/dull-socks-provide.md b/.changeset/dull-socks-provide.md
new file mode 100644
index 000000000..32b2cb6fa
--- /dev/null
+++ b/.changeset/dull-socks-provide.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/compiler': patch
+---
+
+Fixes an issue where a slotted element in an expression would cause subsequent ones to be incorrectly printed
diff --git a/internal/printer/print-to-js.go b/internal/printer/print-to-js.go
index 2d6b7bba9..c37cf2061 100644
--- a/internal/printer/print-to-js.go
+++ b/internal/printer/print-to-js.go
@@ -432,7 +432,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
p.printAttributesToObject(n)
} else if isSlot {
if len(n.Attr) == 0 {
- p.print(`"default"`)
+ p.print(DEFAULT_SLOT_PROP)
} else {
slotted := false
for _, a := range n.Attr {
@@ -454,7 +454,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
}
}
if !slotted {
- p.print(`"default"`)
+ p.print(DEFAULT_SLOT_PROP)
}
}
p.print(`]`)
@@ -559,7 +559,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
switch true {
case n.CustomElement:
p.print(`,({`)
- p.print(fmt.Sprintf(`"%s": () => `, "default"))
+ p.print(fmt.Sprintf(`%s: () => `, DEFAULT_SLOT_PROP))
p.printTemplateLiteralOpen()
for c := n.FirstChild; c != nil; c = c.NextSibling {
render1(p, c, RenderOptions{
@@ -638,6 +638,8 @@ func render1(p *printer, n *Node, opts RenderOptions) {
}
}
+const DEFAULT_SLOT_PROP = `"default"`
+
// Section 12.1.2, "Elements", gives this list of void elements. Void elements
// are those that can't have any contents.
// nolint
@@ -662,12 +664,14 @@ var voidElements = map[string]bool{
func handleSlots(p *printer, n *Node, opts RenderOptions, depth int) {
p.print(`,`)
slottedChildren := make(map[string][]*Node)
- hasAnyDynamicSlots := false
+ hasAnyNestedDynamicSlot := false
nestedSlotChildren := make([]*NestedSlotChild, 0)
- numberOfNestedSlots := 0
+
+ // the highest number of nested slots in an expression
+ maxNestedSlotsCount := 0
for c := n.FirstChild; c != nil; c = c.NextSibling {
- slotProp := `"default"`
+ slotProp := DEFAULT_SLOT_PROP
for _, a := range c.Attr {
if a.Key == "slot" {
if a.Type == QuotedAttribute {
@@ -686,82 +690,79 @@ func handleSlots(p *printer, n *Node, opts RenderOptions, depth int) {
}
}
if c.Expression {
- nestedSlotsCount := 0
- var firstNestedSlotProp string
+ // Only slot ElementNodes (except expressions containing only comments) or non-empty TextNodes!
+ // CommentNode, JSX comments and others should not be slotted
+ if expressionOnlyHasComment(c) {
+ continue
+ }
+ nestedSlotsInExprCount := 0
+ hasAnyDynamicSlotsInExpr := false
+ var slotProp = DEFAULT_SLOT_PROP
for c1 := c.FirstChild; c1 != nil; c1 = c1.NextSibling {
- var slotProp = ""
for _, a := range c1.Attr {
if a.Key == "slot" {
if a.Type == QuotedAttribute {
slotProp = fmt.Sprintf(`"%s"`, escapeDoubleQuote(a.Val))
} else if a.Type == ExpressionAttribute {
slotProp = fmt.Sprintf(`[%s]`, a.Val)
- hasAnyDynamicSlots = true
+ hasAnyNestedDynamicSlot, hasAnyDynamicSlotsInExpr = true, true
} else if a.Type == TemplateLiteralAttribute {
slotProp = fmt.Sprintf(`[%s%s%s]`, BACKTICK, a.Val, BACKTICK)
- hasAnyDynamicSlots = true
+ hasAnyNestedDynamicSlot, hasAnyDynamicSlotsInExpr = true, true
} else {
panic(`unknown slot attribute type`)
}
}
- if firstNestedSlotProp == "" && slotProp != "" {
- firstNestedSlotProp = slotProp
- }
}
- if firstNestedSlotProp != "" {
- nestedSlotsCount++
+ if c1.Type == ElementNode {
+ nestedSlotsInExprCount++
}
}
- if nestedSlotsCount == 1 && !hasAnyDynamicSlots {
- slottedChildren[firstNestedSlotProp] = append(slottedChildren[firstNestedSlotProp], c)
+ if nestedSlotsInExprCount == 1 && !hasAnyDynamicSlotsInExpr {
+ slottedChildren[slotProp] = append(slottedChildren[slotProp], c)
continue
- } else if nestedSlotsCount > 1 || hasAnyDynamicSlots {
+ } else if nestedSlotsInExprCount > 1 || hasAnyDynamicSlotsInExpr {
+ if nestedSlotsInExprCount > maxNestedSlotsCount {
+ maxNestedSlotsCount = nestedSlotsInExprCount
+ }
child_loop:
for c1 := c.FirstChild; c1 != nil; c1 = c1.NextSibling {
foundNamedSlot := false
+ isFirstInGroup := c1 == c.FirstChild
for _, a := range c1.Attr {
if a.Key == "slot" {
var nestedSlotProp string
var nestedSlotEntry *NestedSlotChild
if a.Type == QuotedAttribute {
nestedSlotProp = fmt.Sprintf(`"%s"`, escapeDoubleQuote(a.Val))
- hasAnyDynamicSlots = true
} else if a.Type == ExpressionAttribute {
nestedSlotProp = fmt.Sprintf(`[%s]`, a.Val)
- hasAnyDynamicSlots = true
+ hasAnyNestedDynamicSlot = true
} else if a.Type == TemplateLiteralAttribute {
- hasAnyDynamicSlots = true
+ hasAnyNestedDynamicSlot = true
nestedSlotProp = fmt.Sprintf(`[%s%s%s]`, BACKTICK, a.Val, BACKTICK)
} else {
panic(`unknown slot attribute type`)
}
foundNamedSlot = true
- isFirstInGroup := c1 == c.FirstChild
nestedSlotEntry = &NestedSlotChild{nestedSlotProp, []*Node{c1}, isFirstInGroup}
nestedSlotChildren = append(nestedSlotChildren, nestedSlotEntry)
continue child_loop
}
}
- isFirstInGroup := c1 == c.FirstChild
if !foundNamedSlot && c1.Type == ElementNode {
- pseudoSlotEntry := &NestedSlotChild{`"default"`, []*Node{c1}, isFirstInGroup}
+ pseudoSlotEntry := &NestedSlotChild{DEFAULT_SLOT_PROP, []*Node{c1}, isFirstInGroup}
nestedSlotChildren = append(nestedSlotChildren, pseudoSlotEntry)
} else {
nestedSlotEntry := &NestedSlotChild{`"@@NON_ELEMENT_ENTRY"`, []*Node{c1}, isFirstInGroup}
nestedSlotChildren = append(nestedSlotChildren, nestedSlotEntry)
}
- numberOfNestedSlots++
}
continue
}
}
- // Only slot ElementNodes (except expressions containing only comments) or non-empty TextNodes!
- // CommentNode, JSX comments and others should not be slotted
- if expressionOnlyHasComment(c) {
- continue
- }
if c.Type == ElementNode || c.Type == TextNode && !emptyTextNodeWithoutSiblings(c) {
slottedChildren[slotProp] = append(slottedChildren[slotProp], c)
}
@@ -772,7 +773,12 @@ func handleSlots(p *printer, n *Node, opts RenderOptions, depth int) {
slottedKeys = append(slottedKeys, k)
}
sort.Strings(slottedKeys)
- if numberOfNestedSlots > 0 || hasAnyDynamicSlots {
+
+ // if any slotted expression contains more than one nested slot (e.g.