Skip to content

Commit 0aa03df

Browse files
authored
fix: reflect all custom element prop updates back to attribute (#8898)
fixes #8879
1 parent 4b3eb72 commit 0aa03df

File tree

4 files changed

+43
-19
lines changed

4 files changed

+43
-19
lines changed

.changeset/gold-tips-roll.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: reflect all custom element prop updates back to attribute

packages/svelte/src/runtime/internal/Component.js

+25-15
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,31 @@ if (typeof HTMLElement === 'function') {
269269
}
270270
}
271271
});
272+
273+
// Reflect component props as attributes
274+
const reflect_attributes = () => {
275+
this.$$r = true;
276+
for (const key in this.$$p_d) {
277+
this.$$d[key] = this.$$c.$$.ctx[this.$$c.$$.props[key]];
278+
if (this.$$p_d[key].reflect) {
279+
const attribute_value = get_custom_element_value(
280+
key,
281+
this.$$d[key],
282+
this.$$p_d,
283+
'toAttribute'
284+
);
285+
if (attribute_value == null) {
286+
this.removeAttribute(key);
287+
} else {
288+
this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
289+
}
290+
}
291+
}
292+
this.$$r = false;
293+
};
294+
this.$$c.$$.after_update.push(reflect_attributes);
295+
reflect_attributes(); // once initially because after_update is added too late for first render
296+
272297
for (const type in this.$$l) {
273298
for (const listener of this.$$l[type]) {
274299
const unsub = this.$$c.$on(type, listener);
@@ -386,21 +411,6 @@ export function create_custom_element(
386411
value = get_custom_element_value(prop, value, props_definition);
387412
this.$$d[prop] = value;
388413
this.$$c?.$set({ [prop]: value });
389-
if (props_definition[prop].reflect) {
390-
this.$$r = true;
391-
const attribute_value = get_custom_element_value(
392-
prop,
393-
value,
394-
props_definition,
395-
'toAttribute'
396-
);
397-
if (attribute_value == null) {
398-
this.removeAttribute(prop);
399-
} else {
400-
this.setAttribute(props_definition[prop].attribute || prop, attribute_value);
401-
}
402-
this.$$r = false;
403-
}
404414
}
405415
});
406416
});

packages/svelte/test/runtime-browser/custom-elements-samples/reflect-attributes/main.svelte

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
<svelte:options
22
customElement={{
3-
tag: "custom-element",
4-
props: { red: { reflect: true, type: "Boolean" } },
3+
tag: 'custom-element',
4+
props: { red: { reflect: true, type: 'Boolean' } }
55
}}
66
/>
77

88
<script>
9-
import "./my-widget.svelte";
9+
import './my-widget.svelte';
1010
export let red;
1111
red;
1212
</script>
1313

1414
<div>hi</div>
1515
<p>hi</p>
16-
<my-widget red white />
16+
<button on:click={() => (red = false)}>off</button>
17+
<my-widget {red} white />
1718

1819
<style>
1920
:host([red]) div {

packages/svelte/test/runtime-browser/custom-elements-samples/reflect-attributes/test.js

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default async function (target) {
99
const ceRoot = target.querySelector('custom-element').shadowRoot;
1010
const div = ceRoot.querySelector('div');
1111
const p = ceRoot.querySelector('p');
12+
const button = ceRoot.querySelector('button');
1213

1314
assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)');
1415
assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)');
@@ -19,4 +20,11 @@ export default async function (target) {
1920

2021
assert.equal(getComputedStyle(innerDiv).color, 'rgb(255, 0, 0)');
2122
assert.equal(getComputedStyle(innerP).color, 'rgb(255, 255, 255)');
23+
24+
button.click();
25+
await tick();
26+
await tick();
27+
28+
assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)');
29+
assert.equal(getComputedStyle(innerDiv).color, 'rgb(0, 0, 0)');
2230
}

0 commit comments

Comments
 (0)