Skip to content

Commit

Permalink
fix: recognize all custom element prop definitions (#14084)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dummdidumm authored Nov 1, 2024
1 parent 6a2c28c commit 7d11fa8
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-eyes-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: recognize all custom element prop definitions
2 changes: 2 additions & 0 deletions documentation/docs/07-misc/04-custom-elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
`
);
}
});
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit 7d11fa8

Please sign in to comment.