Skip to content

Commit 97d3ec2

Browse files
dimfelddummdidumm
andauthored
fix: allow let: directives on slot elements (#10391)
fixes #10382 --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent b6fcc14 commit 97d3ec2

File tree

10 files changed

+54
-4
lines changed

10 files changed

+54
-4
lines changed

.changeset/friendly-candles-relate.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: allow `let:` directives on slot elements

packages/svelte/src/compiler/errors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,8 @@ const attributes = {
273273

274274
/** @satisfies {Errors} */
275275
const slots = {
276-
'invalid-slot-element-attribute': () => `<slot> can only receive attributes, not directives`,
276+
'invalid-slot-element-attribute': () =>
277+
`<slot> can only receive attributes and (optionally) let directives`,
277278
'invalid-slot-attribute': () => `slot attribute must be a static value`,
278279
/** @param {boolean} is_default */
279280
'invalid-slot-name': (is_default) =>

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ const validation = {
494494
parent === undefined ||
495495
(parent.type !== 'Component' &&
496496
parent.type !== 'RegularElement' &&
497+
parent.type !== 'SlotElement' &&
497498
parent.type !== 'SvelteElement' &&
498499
parent.type !== 'SvelteComponent' &&
499500
parent.type !== 'SvelteSelf' &&
@@ -609,7 +610,7 @@ const validation = {
609610
error(attribute, 'invalid-slot-name', true);
610611
}
611612
}
612-
} else if (attribute.type !== 'SpreadAttribute') {
613+
} else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') {
613614
error(attribute, 'invalid-slot-element-attribute');
614615
}
615616
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2916,6 +2916,9 @@ export const template_visitors = {
29162916
/** @type {import('estree').Expression[]} */
29172917
const spreads = [];
29182918

2919+
/** @type {import('estree').ExpressionStatement[]} */
2920+
const lets = [];
2921+
29192922
let is_default = true;
29202923

29212924
/** @type {import('estree').Expression} */
@@ -2931,16 +2934,21 @@ export const template_visitors = {
29312934
if (attribute.name === 'name') {
29322935
name = value;
29332936
is_default = false;
2934-
} else {
2937+
} else if (attribute.name !== 'slot') {
29352938
if (attribute.metadata.dynamic) {
29362939
props.push(b.get(attribute.name, [b.return(value)]));
29372940
} else {
29382941
props.push(b.init(attribute.name, value));
29392942
}
29402943
}
2944+
} else if (attribute.type === 'LetDirective') {
2945+
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
29412946
}
29422947
}
29432948

2949+
// Let bindings first, they can be used on attributes
2950+
context.state.init.push(...lets);
2951+
29442952
const props_expression =
29452953
spreads.length === 0
29462954
? b.object(props)

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,9 @@ const template_visitors = {
15761576
/** @type {import('estree').Expression[]} */
15771577
const spreads = [];
15781578

1579+
/** @type {import('estree').ExpressionStatement[]} */
1580+
const lets = [];
1581+
15791582
/** @type {import('estree').Expression} */
15801583
let expression = b.member_id('$$props.children');
15811584

@@ -1586,16 +1589,21 @@ const template_visitors = {
15861589
const value = serialize_attribute_value(attribute.value, context);
15871590
if (attribute.name === 'name') {
15881591
expression = b.member(b.member_id('$$props.$$slots'), value, true, true);
1589-
} else {
1592+
} else if (attribute.name !== 'slot') {
15901593
if (attribute.metadata.dynamic) {
15911594
props.push(b.get(attribute.name, [b.return(value)]));
15921595
} else {
15931596
props.push(b.init(attribute.name, value));
15941597
}
15951598
}
1599+
} else if (attribute.type === 'LetDirective') {
1600+
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
15961601
}
15971602
}
15981603

1604+
// Let bindings first, they can be used on attributes
1605+
context.state.init.push(...lets);
1606+
15991607
const props_expression =
16001608
spreads.length === 0
16011609
? b.object(props)

packages/svelte/src/compiler/phases/scope.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
357357
},
358358

359359
SvelteFragment,
360+
SlotElement: SvelteFragment,
360361
SvelteElement: SvelteFragment,
361362
RegularElement: SvelteFragment,
362363

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `<div><div slot="x">5</div></div>`
5+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
<slot name="x" foo={5} />
3+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import Outer from './outer.svelte';
3+
</script>
4+
5+
<Outer>
6+
<div slot="x" let:foo>
7+
{foo}
8+
</div>
9+
</Outer>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import Inner from './inner.svelte';
3+
</script>
4+
5+
<Inner>
6+
<slot name="x" slot="x" let:foo {foo}>
7+
{foo}
8+
</slot>
9+
</Inner>

0 commit comments

Comments
 (0)