fix: allow `let:` directives on slot elements (#10391)

fixes #10382

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/10399/head
Daniel Imfeld 11 months ago committed by GitHub
parent b6fcc149b8
commit 97d3ec2f89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow `let:` directives on slot elements

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

@ -494,6 +494,7 @@ const validation = {
parent === undefined || parent === undefined ||
(parent.type !== 'Component' && (parent.type !== 'Component' &&
parent.type !== 'RegularElement' && parent.type !== 'RegularElement' &&
parent.type !== 'SlotElement' &&
parent.type !== 'SvelteElement' && parent.type !== 'SvelteElement' &&
parent.type !== 'SvelteComponent' && parent.type !== 'SvelteComponent' &&
parent.type !== 'SvelteSelf' && parent.type !== 'SvelteSelf' &&
@ -609,7 +610,7 @@ const validation = {
error(attribute, 'invalid-slot-name', true); error(attribute, 'invalid-slot-name', true);
} }
} }
} else if (attribute.type !== 'SpreadAttribute') { } else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') {
error(attribute, 'invalid-slot-element-attribute'); error(attribute, 'invalid-slot-element-attribute');
} }
} }

@ -2916,6 +2916,9 @@ export const template_visitors = {
/** @type {import('estree').Expression[]} */ /** @type {import('estree').Expression[]} */
const spreads = []; const spreads = [];
/** @type {import('estree').ExpressionStatement[]} */
const lets = [];
let is_default = true; let is_default = true;
/** @type {import('estree').Expression} */ /** @type {import('estree').Expression} */
@ -2931,16 +2934,21 @@ export const template_visitors = {
if (attribute.name === 'name') { if (attribute.name === 'name') {
name = value; name = value;
is_default = false; is_default = false;
} else { } else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) { if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)])); props.push(b.get(attribute.name, [b.return(value)]));
} else { } else {
props.push(b.init(attribute.name, value)); props.push(b.init(attribute.name, value));
} }
} }
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
} }
} }
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression = const props_expression =
spreads.length === 0 spreads.length === 0
? b.object(props) ? b.object(props)

@ -1576,6 +1576,9 @@ const template_visitors = {
/** @type {import('estree').Expression[]} */ /** @type {import('estree').Expression[]} */
const spreads = []; const spreads = [];
/** @type {import('estree').ExpressionStatement[]} */
const lets = [];
/** @type {import('estree').Expression} */ /** @type {import('estree').Expression} */
let expression = b.member_id('$$props.children'); let expression = b.member_id('$$props.children');
@ -1586,16 +1589,21 @@ const template_visitors = {
const value = serialize_attribute_value(attribute.value, context); const value = serialize_attribute_value(attribute.value, context);
if (attribute.name === 'name') { if (attribute.name === 'name') {
expression = b.member(b.member_id('$$props.$$slots'), value, true, true); expression = b.member(b.member_id('$$props.$$slots'), value, true, true);
} else { } else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) { if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)])); props.push(b.get(attribute.name, [b.return(value)]));
} else { } else {
props.push(b.init(attribute.name, value)); props.push(b.init(attribute.name, value));
} }
} }
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
} }
} }
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression = const props_expression =
spreads.length === 0 spreads.length === 0
? b.object(props) ? b.object(props)

@ -357,6 +357,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
}, },
SvelteFragment, SvelteFragment,
SlotElement: SvelteFragment,
SvelteElement: SvelteFragment, SvelteElement: SvelteFragment,
RegularElement: SvelteFragment, RegularElement: SvelteFragment,

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: `<div><div slot="x">5</div></div>`
});

@ -0,0 +1,9 @@
<script>
import Outer from './outer.svelte';
</script>
<Outer>
<div slot="x" let:foo>
{foo}
</div>
</Outer>

@ -0,0 +1,9 @@
<script>
import Inner from './inner.svelte';
</script>
<Inner>
<slot name="x" slot="x" let:foo {foo}>
{foo}
</slot>
</Inner>
Loading…
Cancel
Save