interop for passing slots but rendering them with render tags

snippet-work-cont
Simon Holthausen 1 year ago
parent 46dcfca36f
commit 9be0fdd753

@ -1786,6 +1786,10 @@ export const template_visitors = {
const raw_args = unwrap_optional(node.expression).arguments; const raw_args = unwrap_optional(node.expression).arguments;
const is_reactive = const is_reactive =
callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal'; callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal';
const needs_backwards_compat =
callee.type === 'Identifier' &&
raw_args.length < 2 &&
context.state.scope.get(callee.name)?.kind === 'prop';
/** @type {import('estree').Expression[]} */ /** @type {import('estree').Expression[]} */
const args = [context.state.node]; const args = [context.state.node];
@ -1798,7 +1802,19 @@ export const template_visitors = {
snippet_function = b.call('$.validate_snippet', snippet_function); snippet_function = b.call('$.validate_snippet', snippet_function);
} }
if (is_reactive) { if (needs_backwards_compat) {
context.state.init.push(
b.stmt(
b.call(
'$.render_snippet_or_slot',
b.thunk(snippet_function),
b.id('$$props'),
b.literal(callee.name),
...args
)
)
);
} else if (is_reactive) {
context.state.init.push(b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args))); context.state.init.push(b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args)));
} else { } else {
context.state.init.push( context.state.init.push(

@ -1303,6 +1303,10 @@ const template_visitors = {
const callee = unwrap_optional(node.expression).callee; const callee = unwrap_optional(node.expression).callee;
const raw_args = unwrap_optional(node.expression).arguments; const raw_args = unwrap_optional(node.expression).arguments;
const needs_backwards_compat =
callee.type === 'Identifier' &&
raw_args.length < 2 &&
context.state.scope.get(callee.name)?.kind === 'prop';
const expression = /** @type {import('estree').Expression} */ (context.visit(callee)); const expression = /** @type {import('estree').Expression} */ (context.visit(callee));
const snippet_function = state.options.dev const snippet_function = state.options.dev
@ -1313,6 +1317,22 @@ const template_visitors = {
return /** @type {import('estree').Expression} */ (context.visit(arg)); return /** @type {import('estree').Expression} */ (context.visit(arg));
}); });
if (needs_backwards_compat) {
state.template.push(
t_statement(
b.stmt(
b.call(
'$.render_snippet_or_slot',
snippet_function,
b.id('$$props'),
b.literal(callee.name),
b.id('$$payload'),
...snippet_args
)
)
)
);
} else {
state.template.push( state.template.push(
t_statement( t_statement(
b.stmt( b.stmt(
@ -1324,6 +1344,7 @@ const template_visitors = {
) )
) )
); );
}
state.template.push(block_close); state.template.push(block_close);
}, },

@ -57,3 +57,34 @@ export function wrap_snippet(fn) {
} }
); );
} }
/**
* Remove this once slots are gone
* @param {any} snippet_fn
* @param {Record<string, any>} $$props
* @param {string} name
* @param {Element} node
* @param {any} slot_props
*/
export function render_snippet_or_slot(snippet_fn, $$props, name, node, slot_props) {
if ($$props.$$slots) {
const slot = $$props.$$slots[name === 'children' ? 'default' : name];
if (typeof slot === 'function') {
let props = undefined;
if (slot_props) {
props = new Proxy(
{},
{
get(_, key) {
return slot_props()?.[key];
}
}
);
}
slot(node, props);
return;
}
}
snippet(snippet_fn, node, slot_props);
}

@ -15,7 +15,7 @@ export { key_block as key } from './dom/blocks/key.js';
export { css_props } from './dom/blocks/css-props.js'; export { css_props } from './dom/blocks/css-props.js';
export { index, each } from './dom/blocks/each.js'; export { index, each } from './dom/blocks/each.js';
export { html } from './dom/blocks/html.js'; export { html } from './dom/blocks/html.js';
export { snippet, wrap_snippet } from './dom/blocks/snippet.js'; export { snippet, wrap_snippet, render_snippet_or_slot } from './dom/blocks/snippet.js';
export { component } from './dom/blocks/svelte-component.js'; export { component } from './dom/blocks/svelte-component.js';
export { element } from './dom/blocks/svelte-element.js'; export { element } from './dom/blocks/svelte-element.js';
export { head } from './dom/blocks/svelte-head.js'; export { head } from './dom/blocks/svelte-head.js';

@ -563,6 +563,26 @@ export function sanitize_slots(props) {
return sanitized; return sanitized;
} }
/**
* Remove this once slots are gone
* @param {any} snippet_fn
* @param {Record<string, any>} $$props
* @param {string} name
* @param {Payload} payload
* @param {any} slot_props
*/
export function render_snippet_or_slot(snippet_fn, $$props, name, payload, slot_props) {
if ($$props.$$slots) {
const slot = $$props.$$slots[name === 'children' ? 'default' : name];
if (typeof slot === 'function') {
slot(payload, slot_props);
return;
}
}
snippet_fn?.(payload, slot_props);
}
/** /**
* Legacy mode: If the prop has a fallback and is bound in the * Legacy mode: If the prop has a fallback and is bound in the
* parent component, propagate the fallback value upwards. * parent component, propagate the fallback value upwards.

@ -0,0 +1,6 @@
<script>
let { children, named } = $props();
</script>
<p>{@render children()}</p>
{@render named({foo: 'foo'})}

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

@ -0,0 +1,8 @@
<script>
import Child from './Child.svelte';
</script>
<Child>
Default
<p slot="named" let:foo>Named {foo}</p>
</Child>
Loading…
Cancel
Save