fix: recognize all custom element prop definitions (#14084)

We didn't account for the `$props` rune being writtin in a way that makes some props unknown, and they would only be visible through the `customElement.props` definition. This changes the iteration to account for that and also adds a note to the documentation that you need to list out the properties explicitly.

fixes #13785
pull/14100/head
Simon H 2 months ago committed by GitHub
parent 6a2c28c590
commit 7d11fa8da2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: recognize all custom element prop definitions

@ -49,6 +49,8 @@ console.log(el.name);
el.name = 'everybody';
```
Note that you need to list out all properties explicitly, i.e. doing `let props = $props()` without declaring `props` in the [component options](#Component-options) means that Svelte can't know which props to expose as properties on the DOM element.
## Component lifecycle
Custom elements are created from Svelte components using a wrapper approach. This means the inner Svelte component has no knowledge that it is a custom element. The custom element wrapper takes care of handling its lifecycle appropriately.

@ -561,17 +561,19 @@ export function client_component(analysis, options) {
if (analysis.custom_element) {
const ce = analysis.custom_element;
const ce_props = typeof ce === 'boolean' ? {} : ce.props || {};
/** @type {ESTree.Property[]} */
const props_str = [];
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;
const prop_def = typeof ce === 'boolean' ? {} : ce.props?.[key] || {};
for (const [name, prop_def] of Object.entries(ce_props)) {
const binding = analysis.instance.scope.get(name);
const key = binding?.prop_alias ?? name;
if (
!prop_def.type &&
binding.initial?.type === 'Literal' &&
typeof binding.initial.value === 'boolean'
binding?.initial?.type === 'Literal' &&
typeof binding?.initial.value === 'boolean'
) {
prop_def.type = 'Boolean';
}
@ -585,9 +587,17 @@ export function client_component(analysis, options) {
].filter(Boolean)
)
);
props_str.push(b.init(key, value));
}
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;
if (ce_props[key]) continue;
props_str.push(b.init(key, b.object([])));
}
const slots_str = b.array([...analysis.slot_names.keys()].map((name) => b.literal(name)));
const accessors_str = b.array(
analysis.exports.map(({ name, alias }) => b.literal(alias ?? name))

@ -0,0 +1,21 @@
import { test } from '../../assert';
const tick = () => Promise.resolve();
export default test({
async test({ assert, target }) {
target.innerHTML = '<custom-element foo-bar="1" bar="2" b-az="3"></custom-element>';
await tick();
/** @type {any} */
const el = target.querySelector('custom-element');
assert.htmlEqual(
el.shadowRoot.innerHTML,
`
<p>1</p>
<p>2</p>
<p>3</p>
`
);
}
});

@ -0,0 +1,14 @@
<svelte:options
customElement={{
tag: "custom-element",
props: { foo: { attribute: 'foo-bar' } },
}}
/>
<script>
let { bar, 'b-az': baz, ...rest } = $props();
</script>
<p>{rest.foo}</p>
<p>{bar}</p>
<p>{baz}</p>

@ -0,0 +1,14 @@
<svelte:options
customElement={{
tag: "my-widget",
props: { foo: { attribute: 'foo-bar' } },
}}
/>
<script>
let { bar, 'b-az': baz, ...rest } = $props();
</script>
<p>{rest.foo}</p>
<p>{bar}</p>
<p>{baz}</p>
Loading…
Cancel
Save