Skip to content

Commit a3d3270

Browse files
committed
Partial fix for a component not properly retrieving let: bindings when it fills a named slot
1 parent d23805a commit a3d3270

File tree

7 files changed

+95
-10
lines changed

7 files changed

+95
-10
lines changed

.changeset/swift-feet-juggle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
Fix let: bindings on components filling a named slot

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ function serialize_inline_component(node, component_name, context) {
749749
const props_and_spreads = [];
750750

751751
/** @type {import('estree').ExpressionStatement[]} */
752-
const default_lets = [];
752+
const lets = [];
753753

754754
/** @type {Record<string, import('#compiler').TemplateNode[]>} */
755755
const children = {};
@@ -776,11 +776,10 @@ function serialize_inline_component(node, component_name, context) {
776776
}
777777
}
778778

779+
let is_named_parent_slot = false;
779780
for (const attribute of node.attributes) {
780781
if (attribute.type === 'LetDirective') {
781-
default_lets.push(
782-
/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))
783-
);
782+
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
784783
} else if (attribute.type === 'OnDirective') {
785784
events[attribute.name] ||= [];
786785
let handler = serialize_event_handler(attribute, context);
@@ -811,6 +810,10 @@ function serialize_inline_component(node, component_name, context) {
811810
continue;
812811
}
813812

813+
if (attribute.name === 'slot') {
814+
is_named_parent_slot = true;
815+
}
816+
814817
const [, value] = serialize_attribute_value(attribute.value, context);
815818

816819
if (attribute.metadata.dynamic) {
@@ -863,6 +866,12 @@ function serialize_inline_component(node, component_name, context) {
863866
}
864867
}
865868

869+
if (is_named_parent_slot) {
870+
// This component is filling a named slot in its parent component, so get the relevant
871+
// attributes from the parent slot.
872+
context.state.init.push(...lets);
873+
}
874+
866875
if (Object.keys(events).length > 0) {
867876
const events_expression = b.object(
868877
Object.keys(events).map((name) =>
@@ -918,7 +927,7 @@ function serialize_inline_component(node, component_name, context) {
918927

919928
const slot_fn = b.arrow(
920929
[b.id('$$anchor'), b.id('$$slotProps')],
921-
b.block([...(slot_name === 'default' ? default_lets : []), ...body])
930+
b.block([...(slot_name === 'default' && !is_named_parent_slot ? lets : []), ...body])
922931
);
923932

924933
if (slot_name === 'default') {

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ function serialize_inline_component(node, component_name, context) {
808808
const custom_css_props = [];
809809

810810
/** @type {import('estree').ExpressionStatement[]} */
811-
const default_lets = [];
811+
const lets = [];
812812

813813
/** @type {Record<string, import('#compiler').TemplateNode[]>} */
814814
const children = {};
@@ -826,11 +826,10 @@ function serialize_inline_component(node, component_name, context) {
826826
}
827827
}
828828

829+
let is_named_parent_slot = false;
829830
for (const attribute of node.attributes) {
830831
if (attribute.type === 'LetDirective') {
831-
default_lets.push(
832-
/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))
833-
);
832+
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
834833
} else if (attribute.type === 'SpreadAttribute') {
835834
props_and_spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
836835
} else if (attribute.type === 'Attribute') {
@@ -840,6 +839,10 @@ function serialize_inline_component(node, component_name, context) {
840839
continue;
841840
}
842841

842+
if (attribute.name === 'slot') {
843+
is_named_parent_slot = true;
844+
}
845+
843846
const value = serialize_attribute_value(attribute.value, context, false, true);
844847
push_prop(b.prop('init', b.key(attribute.name), value));
845848
} else if (attribute.type === 'BindDirective') {
@@ -862,6 +865,12 @@ function serialize_inline_component(node, component_name, context) {
862865
}
863866
}
864867

868+
if (is_named_parent_slot) {
869+
// This component is filling a named slot in its parent component, so get the relevant
870+
// attributes from the parent slot.
871+
context.state.init.push(...lets);
872+
}
873+
865874
/** @type {import('estree').Statement[]} */
866875
const snippet_declarations = [];
867876

@@ -907,7 +916,7 @@ function serialize_inline_component(node, component_name, context) {
907916

908917
const slot_fn = b.arrow(
909918
[b.id('$$payload'), b.id('$$slotProps')],
910-
b.block([...(slot_name === 'default' ? default_lets : []), ...body])
919+
b.block([...(slot_name === 'default' && !is_named_parent_slot ? lets : []), ...body])
911920
);
912921

913922
if (slot_name === 'default') {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
export let things;
3+
</script>
4+
5+
<div>
6+
{#each things as thing}
7+
<slot name="foo" {thing}/>
8+
{/each}
9+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let thing;
3+
</script>
4+
<span>{thing}</span>
5+
<slot />
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
get props() {
5+
return { things: [1, 2, 3] };
6+
},
7+
8+
html: `
9+
<div>
10+
<span>1</span>
11+
<div class="inner-slot">1</div>
12+
<span>2</span>
13+
<div class="inner-slot">2</div>
14+
<span>3</span>
15+
<div class="inner-slot">3</div>
16+
</div>`,
17+
18+
test({ assert, component, target }) {
19+
component.things = [1, 2, 3, 4];
20+
assert.htmlEqual(
21+
target.innerHTML,
22+
`
23+
<div>
24+
<span>1</span>
25+
<div class="inner-slot">1</div>
26+
<span>2</span>
27+
<div class="inner-slot">2</div>
28+
<span>3</span>
29+
<div class="inner-slot">3</div>
30+
<span>4</span>
31+
<div class="inner-slot">4</div>
32+
</div>
33+
`
34+
);
35+
}
36+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
import SlotInner from './SlotInner.svelte';
4+
5+
export let things;
6+
</script>
7+
8+
<Nested {things}>
9+
<SlotInner slot="foo" let:thing={data} thing={data}>
10+
<div class="inner-slot">{data}</div>
11+
</SlotInner>
12+
</Nested>

0 commit comments

Comments
 (0)