Skip to content

Commit 8c2f603

Browse files
trueadmdummdidumm
andauthored
fix: improve element class attribute behaviour (#10856)
* fix: improve element class attribute behaviour * Update packages/svelte/src/internal/client/dom/elements/class.js Co-authored-by: Simon H <[email protected]> --------- Co-authored-by: Simon H <[email protected]>
1 parent 86c57f9 commit 8c2f603

File tree

6 files changed

+66
-11
lines changed

6 files changed

+66
-11
lines changed

.changeset/selfish-spies-help.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: improve element class attribute behaviour

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ function serialize_dynamic_element_attributes(attributes, context, element_id) {
473473
function serialize_element_attribute_update_assignment(element, node_id, attribute, context) {
474474
const state = context.state;
475475
const name = get_attribute_name(element, attribute, context);
476+
const is_svg = context.state.metadata.namespace === 'svg';
476477
let [contains_call_expression, value] = serialize_attribute_value(attribute.value, context);
477478

478479
// The foreign namespace doesn't have any special handling, everything goes through the attr function
@@ -507,13 +508,19 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
507508
if (name === 'class') {
508509
if (singular) {
509510
return {
510-
singular: b.stmt(b.call('$.class_name_effect', node_id, b.thunk(singular))),
511-
grouped: b.stmt(b.call('$.class_name', node_id, singular)),
511+
singular: b.stmt(
512+
b.call(
513+
is_svg ? '$.svg_class_name_effect' : '$.class_name_effect',
514+
node_id,
515+
b.thunk(singular)
516+
)
517+
),
518+
grouped: b.stmt(b.call(is_svg ? '$.svg_class_name' : '$.class_name', node_id, singular)),
512519
skip_condition: true
513520
};
514521
}
515522
return {
516-
grouped: b.stmt(b.call('$.class_name', node_id, value)),
523+
grouped: b.stmt(b.call(is_svg ? '$.svg_class_name' : '$.class_name', node_id, value)),
517524
skip_condition: true
518525
};
519526
} else if (!DOMProperties.includes(name)) {

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

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { set_class_name } from '../operations.js';
33
import { render_effect } from '../../reactivity/effects.js';
44

55
/**
6-
* @param {Element} dom
6+
* @param {HTMLElement} dom
77
* @param {() => string} value
88
* @returns {void}
99
*/
@@ -14,7 +14,47 @@ export function class_name_effect(dom, value) {
1414
}
1515

1616
/**
17-
* @param {Element} dom
17+
* @param {SVGElement} dom
18+
* @param {() => string} value
19+
* @returns {void}
20+
*/
21+
export function svg_class_name_effect(dom, value) {
22+
render_effect(() => {
23+
svg_class_name(dom, value());
24+
});
25+
}
26+
27+
/**
28+
* @param {SVGElement} dom
29+
* @param {string} value
30+
* @returns {void}
31+
*/
32+
export function svg_class_name(dom, value) {
33+
// @ts-expect-error need to add __className to patched prototype
34+
var prev_class_name = dom.__className;
35+
var next_class_name = to_class(value);
36+
37+
if (hydrating && dom.getAttribute('class') === next_class_name) {
38+
// In case of hydration don't reset the class as it's already correct.
39+
// @ts-expect-error need to add __className to patched prototype
40+
dom.__className = next_class_name;
41+
} else if (
42+
prev_class_name !== next_class_name ||
43+
(hydrating && dom.getAttribute('class') !== next_class_name)
44+
) {
45+
if (next_class_name === '') {
46+
dom.removeAttribute('class');
47+
} else {
48+
dom.setAttribute('class', next_class_name);
49+
}
50+
51+
// @ts-expect-error need to add __className to patched prototype
52+
dom.__className = next_class_name;
53+
}
54+
}
55+
56+
/**
57+
* @param {HTMLElement} dom
1858
* @param {string} value
1959
* @returns {void}
2060
*/
@@ -31,7 +71,10 @@ export function class_name(dom, value) {
3171
prev_class_name !== next_class_name ||
3272
(hydrating && dom.className !== next_class_name)
3373
) {
34-
if (next_class_name === '') {
74+
// Removing the attribute when the value is only an empty string causes
75+
// peformance issues vs simply making the className an empty string. So
76+
// we should only remove the class if the the value is nullish.
77+
if (value == null) {
3578
dom.removeAttribute('class');
3679
} else {
3780
set_class_name(dom, next_class_name);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!--ssr:0--><div class="foo"></div><!--ssr:0-->
1+
<!--ssr:0--><div id="foo"></div><!--ssr:0-->
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
export let className;
2+
export let id;
33
</script>
44

5-
<div class={className}></div>
5+
<div id={id}></div>

packages/svelte/tests/runtime-legacy/samples/component-binding-infinite-loop/C.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<span
1515
on:click="{toggle}"
16-
class="{isCurrentlySelected ? 'selected' : ''}"
16+
class="{isCurrentlySelected ? 'selected' : null}"
1717
>
1818
<slot></slot>
19-
</span>
19+
</span>

0 commit comments

Comments
 (0)