fix: prevent crash on async custom element attributes (#18661)

attributes setter logic wasn't async-aware

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/18668/head
Gautier Ben Aïm 3 weeks ago committed by GitHub
parent 248a2e1db4
commit 9414eaa41b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow custom elements to receive async values as props

@ -664,14 +664,25 @@ function build_element_attribute_update(element, node_id, name, value, attribute
* @param {ComponentContext} context
*/
function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
const { value, has_state } = build_attribute_value(attribute.value, context);
const memoizer = new Memoizer();
const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
memoizer.add(value, metadata)
);
// don't lowercase name, as we set the element's property, which might be case sensitive
const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value);
// this is different from other updates — it doesn't get grouped,
// because set_custom_element_data may not be idempotent
const update = has_state ? b.call('$.template_effect', b.thunk(call)) : call;
const update = has_state
? b.call(
'$.template_effect',
b.arrow(memoizer.apply(), call),
memoizer.sync_values(),
memoizer.async_values(),
memoizer.blockers()
)
: call;
context.state.init.push(b.stmt(update));
}

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ target, assert }) {
await tick();
const [element] = target.querySelectorAll('async-custom-element');
assert.htmlEqual(element.innerHTML, `Hello foobar!`);
}
});

@ -0,0 +1,41 @@
<script>
class MyCustomElement extends HTMLElement {
constructor() {
super();
this._foo = null;
this._bar = null;
}
/**
* @param {string} foo
*/
set foo(foo) {
this._foo = foo;
this.render();
}
/**
* @param {string} foo
*/
set bar(bar) {
this._bar = bar;
this.render();
}
connectedCallback() {
this.render();
}
render() {
this.innerHTML = "Hello " + this._foo + this._bar + "!";
}
}
if (!customElements.get('async-custom-element')) {
customElements.define("async-custom-element", MyCustomElement);
}
const foo = $derived(await "foo");
</script>
<async-custom-element {foo} bar={await 'bar'}></async-custom-element>

@ -25,7 +25,7 @@ export default function Main($$anchor) {
var svg_1 = $.sibling(div_1, 2);
var custom_element_1 = $.sibling(svg_1, 2);
$.template_effect(() => $.set_custom_element_data(custom_element_1, 'fooBar', y()));
$.template_effect(($0) => $.set_custom_element_data(custom_element_1, 'fooBar', $0), [() => y()]);
$.template_effect(
($0, $1) => {

Loading…
Cancel
Save