Skip to content

fix: recognize all custom element prop definitions #14084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>