Skip to content

Commit 2f685c1

Browse files
adigubaRich-Harris
andauthored
fix: spreading style is not consistent with attribute (#15323)
* style must be set via set_attribute * test * changeset * add empty string and null in test * explanatory comment * this is now redundant, set_attribute takes care of it * drive-by * tweak changeset --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 1efad3f commit 2f685c1

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

.changeset/real-cameras-attack.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: always use `setAttribute` when setting `style`

packages/svelte/src/internal/client/dom/elements/attributes.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ export function set_custom_element_data(node, prop, value) {
218218
// or effect
219219
var previous_reaction = active_reaction;
220220
var previous_effect = active_effect;
221+
221222
// If we're hydrating but the custom element is from Svelte, and it already scaffolded,
222223
// then it might run block logic in hydration mode, which we have to prevent.
223224
let was_hydrating = hydrating;
@@ -227,17 +228,20 @@ export function set_custom_element_data(node, prop, value) {
227228

228229
set_active_reaction(null);
229230
set_active_effect(null);
231+
230232
try {
231233
if (
234+
// `style` should use `set_attribute` rather than the setter
235+
prop !== 'style' &&
232236
// Don't compute setters for custom elements while they aren't registered yet,
233237
// because during their upgrade/instantiation they might add more setters.
234238
// Instead, fall back to a simple "an object, then set as property" heuristic.
235-
setters_cache.has(node.nodeName) ||
239+
(setters_cache.has(node.nodeName) ||
236240
// customElements may not be available in browser extension contexts
237241
!customElements ||
238242
customElements.get(node.tagName.toLowerCase())
239243
? get_setters(node).includes(prop)
240-
: value && typeof value === 'object'
244+
: value && typeof value === 'object')
241245
) {
242246
// @ts-expect-error
243247
node[prop] = value;
@@ -378,8 +382,9 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
378382
// @ts-ignore
379383
element[`__${event_name}`] = undefined;
380384
}
381-
} else if (key === 'style' && value != null) {
382-
element.style.cssText = value + '';
385+
} else if (key === 'style') {
386+
// avoid using the setter
387+
set_attribute(element, key, value);
383388
} else if (key === 'autofocus') {
384389
autofocus(/** @type {HTMLElement} */ (element), Boolean(value));
385390
} else if (!is_custom_element && (key === '__value' || (key === 'value' && value != null))) {
@@ -428,10 +433,6 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
428433
set_attribute(element, name, value, skip_warning);
429434
}
430435
}
431-
if (key === 'style' && '__styles' in element) {
432-
// reset styles to force style: directive to update
433-
element.__styles = {};
434-
}
435436
}
436437

437438
if (is_hydrating_custom_element) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
const style_1 = 'invalid-key:0; margin:4px;;color: green ;color:blue ';
5+
const style_2 = ' other-key : 0 ; padding:2px; COLOR:green; color: blue';
6+
7+
// https://github.com/sveltejs/svelte/issues/15309
8+
export default test({
9+
props: {
10+
style: style_1
11+
},
12+
13+
html: `
14+
<div style="${style_1}"></div>
15+
<div style="${style_1}"></div>
16+
17+
<custom-element style="${style_1}"></custom-element>
18+
<custom-element style="${style_1}"></custom-element>
19+
`,
20+
21+
async test({ assert, target, component }) {
22+
component.style = style_2;
23+
flushSync();
24+
25+
assert.htmlEqual(
26+
target.innerHTML,
27+
`
28+
<div style="${style_2}"></div>
29+
<div style="${style_2}"></div>
30+
31+
<custom-element style="${style_2}"></custom-element>
32+
<custom-element style="${style_2}"></custom-element>
33+
`
34+
);
35+
36+
component.style = '';
37+
flushSync();
38+
39+
assert.htmlEqual(
40+
target.innerHTML,
41+
`
42+
<div style=""></div>
43+
<div style=""></div>
44+
45+
<custom-element style=""></custom-element>
46+
<custom-element style=""></custom-element>
47+
`
48+
);
49+
50+
component.style = null;
51+
flushSync();
52+
53+
assert.htmlEqual(
54+
target.innerHTML,
55+
`
56+
<div></div>
57+
<div></div>
58+
59+
<custom-element></custom-element>
60+
<custom-element></custom-element>
61+
`
62+
);
63+
}
64+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let { style } = $props();
3+
</script>
4+
5+
<div {style}></div>
6+
<div {...{style}}></div>
7+
8+
<custom-element {style}></custom-element>
9+
<custom-element {...{style}}></custom-element>

0 commit comments

Comments
 (0)