Skip to content

Commit ab873d1

Browse files
committed
fix: recognize all custom element prop definitions
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
1 parent 3b38bc2 commit ab873d1

File tree

6 files changed

+71
-5
lines changed

6 files changed

+71
-5
lines changed

.changeset/warm-eyes-protect.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: recognize all custom element prop definitions

documentation/docs/07-misc/04-custom-elements.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ console.log(el.name);
4949
el.name = 'everybody';
5050
```
5151

52+
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.
53+
5254
## Component lifecycle
5355

5456
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.

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,17 +561,19 @@ export function client_component(analysis, options) {
561561

562562
if (analysis.custom_element) {
563563
const ce = analysis.custom_element;
564+
const ce_props = typeof ce === 'boolean' ? {} : ce.props || {};
564565

565566
/** @type {ESTree.Property[]} */
566567
const props_str = [];
567568

568-
for (const [name, binding] of properties) {
569-
const key = binding.prop_alias ?? name;
570-
const prop_def = typeof ce === 'boolean' ? {} : ce.props?.[key] || {};
569+
for (const [name, prop_def] of Object.entries(ce_props)) {
570+
const binding = analysis.instance.scope.get(name);
571+
const key = binding?.prop_alias ?? name;
572+
571573
if (
572574
!prop_def.type &&
573-
binding.initial?.type === 'Literal' &&
574-
typeof binding.initial.value === 'boolean'
575+
binding?.initial?.type === 'Literal' &&
576+
typeof binding?.initial.value === 'boolean'
575577
) {
576578
prop_def.type = 'Boolean';
577579
}
@@ -585,9 +587,17 @@ export function client_component(analysis, options) {
585587
].filter(Boolean)
586588
)
587589
);
590+
588591
props_str.push(b.init(key, value));
589592
}
590593

594+
for (const [name, binding] of properties) {
595+
const key = binding.prop_alias ?? name;
596+
if (ce_props[key]) continue;
597+
598+
props_str.push(b.init(key, b.object([])));
599+
}
600+
591601
const slots_str = b.array([...analysis.slot_names.keys()].map((name) => b.literal(name)));
592602
const accessors_str = b.array(
593603
analysis.exports.map(({ name, alias }) => b.literal(alias ?? name))
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { test } from '../../assert';
2+
const tick = () => Promise.resolve();
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
target.innerHTML = '<custom-element foo-bar="1" bar="2" b-az="3"></custom-element>';
7+
await tick();
8+
9+
/** @type {any} */
10+
const el = target.querySelector('custom-element');
11+
12+
assert.htmlEqual(
13+
el.shadowRoot.innerHTML,
14+
`
15+
<p>1</p>
16+
<p>2</p>
17+
<p>3</p>
18+
`
19+
);
20+
}
21+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<svelte:options
2+
customElement={{
3+
tag: "custom-element",
4+
props: { foo: { attribute: 'foo-bar' } },
5+
}}
6+
/>
7+
8+
<script>
9+
let { bar, 'b-az': baz, ...rest } = $props();
10+
</script>
11+
12+
<p>{rest.foo}</p>
13+
<p>{bar}</p>
14+
<p>{baz}</p>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<svelte:options
2+
customElement={{
3+
tag: "my-widget",
4+
props: { foo: { attribute: 'foo-bar' } },
5+
}}
6+
/>
7+
8+
<script>
9+
let { bar, 'b-az': baz, ...rest } = $props();
10+
</script>
11+
12+
<p>{rest.foo}</p>
13+
<p>{bar}</p>
14+
<p>{baz}</p>

0 commit comments

Comments
 (0)