Skip to content

Commit

Permalink
fix: class:directive not working with $$restProps (#15389)
Browse files Browse the repository at this point in the history
* add spread to test

* fix #15386

* do no set cssHash on non-scoped element

* changeset
  • Loading branch information
adiguba authored Feb 26, 2025
1 parent da98c89 commit cf56973
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 19 deletions.
6 changes: 6 additions & 0 deletions .changeset/lemon-cougars-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'svelte': patch
---

fix: class:directive not working with $$restProps #15386
fix: spread add an useless cssHash on non-scoped element
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ export function build_set_attributes(
element_id,
is_dynamic ? attributes_id : b.literal(null),
b.object(values),
context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash),
element.metadata.scoped &&
context.state.analysis.css.hash !== '' &&
b.literal(context.state.analysis.css.hash),
preserve_attribute_case,
is_custom_element,
is_ignored(element, 'hydration_attribute_changed') && b.true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,10 @@ function build_element_spread_attributes(
})
);

const css_hash = context.state.analysis.css.hash
? b.literal(context.state.analysis.css.hash)
: b.null;
const css_hash =
element.metadata.scoped && context.state.analysis.css.hash
? b.literal(context.state.analysis.css.hash)
: b.null;

const args = [object, css_hash, classes, styles, flags ? b.literal(flags) : undefined];
context.state.template.push(b.call('$.spread_attributes', ...args));
Expand Down
13 changes: 9 additions & 4 deletions packages/svelte/src/internal/client/dom/elements/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,16 @@ export function set_attributes(
continue;
}

if (key === 'class') {
var is_html = element.namespaceURI === 'http://www.w3.org/1999/xhtml';
set_class(element, is_html, value, css_hash, prev?.[CLASS], next[CLASS]);
current[key] = value;
current[CLASS] = next[CLASS];
continue;
}

var prev_value = current[key];
if (value === prev_value && key !== 'class') continue;
if (value === prev_value) continue;

current[key] = value;

Expand Down Expand Up @@ -377,9 +385,6 @@ export function set_attributes(
// @ts-ignore
element[`__${event_name}`] = undefined;
}
} else if (key === 'class') {
var is_html = element.namespaceURI === 'http://www.w3.org/1999/xhtml';
set_class(element, is_html, value, css_hash, prev?.[CLASS], next[CLASS]);
} else if (key === 'style' && value != null) {
element.style.cssText = value + '';
} else if (key === 'autofocus') {
Expand Down
8 changes: 3 additions & 5 deletions packages/svelte/src/internal/client/dom/elements/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { hydrating } from '../hydration.js';
* @param {boolean | number} is_html
* @param {string | null} value
* @param {string} [hash]
* @param {Record<string, boolean>} [prev_classes]
* @param {Record<string, boolean>} [next_classes]
* @param {Record<string, any>} [prev_classes]
* @param {Record<string, any>} [next_classes]
* @returns {Record<string, boolean> | undefined}
*/
export function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
Expand All @@ -34,12 +34,10 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
// @ts-expect-error need to add __className to patched prototype
dom.__className = value;
} else if (next_classes) {
prev_classes ??= {};

for (var key in next_classes) {
var is_present = !!next_classes[key];

if (is_present !== !!prev_classes[key]) {
if (prev_classes == null || is_present !== !!prev_classes[key]) {
dom.classList.toggle(key, is_present);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { flushSync } from 'svelte';
import { flushSync, tick } from 'svelte';
import { test } from '../../test';

// This test counts mutations on hydration
Expand All @@ -16,7 +16,12 @@ export default test({

html: `
<main id="main" class="browser">
<div class="custom svelte-1cjqok6 foo bar"></div>
<div class="custom svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 foo bar"></span>
<b class="custom foo bar"></b>
<i class="foo bar"></i>
<div class="custom svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 foo bar"></span>
<b class="custom foo bar"></b>
<i class="foo bar"></i>
Expand All @@ -25,19 +30,97 @@ export default test({

ssrHtml: `
<main id="main">
<div class="custom svelte-1cjqok6 foo bar"></div>
<div class="custom svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 foo bar"></span>
<b class="custom foo bar"></b>
<i class="foo bar"></i>
<div class="custom svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 foo bar"></span>
<b class="custom foo bar"></b>
<i class="foo bar"></i>
</main>
`,

async test({ assert, component, instance }) {
async test({ target, assert, component, instance }) {
flushSync();
tick();
assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']);

component.foo = false;
flushSync();
assert.deepEqual(instance.get_and_clear_mutations(), ['DIV', 'SPAN', 'B', 'I']);
tick();
assert.deepEqual(
instance.get_and_clear_mutations(),
['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'],
'first mutation'
);

assert.htmlEqual(
target.innerHTML,
`
<main id="main" class="browser">
<div class="custom svelte-1cjqok6 bar" title="a title"></div>
<span class="svelte-1cjqok6 bar"></span>
<b class="custom bar"></b>
<i class="bar"></i>
<div class="custom svelte-1cjqok6 bar" title="a title"></div>
<span class="svelte-1cjqok6 bar"></span>
<b class="custom bar"></b>
<i class="bar"></i>
</main>
`
);

component.foo = true;
flushSync();
assert.deepEqual(
instance.get_and_clear_mutations(),
['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'],
'second mutation'
);

assert.htmlEqual(
target.innerHTML,
`
<main id="main" class="browser">
<div class="custom svelte-1cjqok6 bar foo" title="a title"></div>
<span class="svelte-1cjqok6 bar foo"></span>
<b class="custom bar foo"></b>
<i class="bar foo"></i>
<div class="custom svelte-1cjqok6 bar foo" title="a title"></div>
<span class="svelte-1cjqok6 bar foo"></span>
<b class="custom bar foo"></b>
<i class="bar foo"></i>
</main>
`
);

component.classname = 'another';
flushSync();
assert.deepEqual(
instance.get_and_clear_mutations(),
['DIV', 'B', 'DIV', 'B'],
'class mutation'
);

assert.htmlEqual(
target.innerHTML,
`
<main id="main" class="browser">
<div class="another svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 bar foo"></span>
<b class="another foo bar"></b>
<i class="bar foo"></i>
<div class="another svelte-1cjqok6 foo bar" title="a title"></div>
<span class="svelte-1cjqok6 bar foo"></span>
<b class="another foo bar"></b>
<i class="bar foo"></i>
</main>
`
);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@
</script>

<main id="main" class:browser>
<div class={classname} class:foo class:bar></div>
<div class={classname} title="a title" class:foo class:bar></div>
<span class:foo class:bar></span>
<b class={classname} class:foo class:bar></b>
<i class:foo class:bar></i>

<div {...{class:classname, title:"a title"}} class:foo class:bar></div>
<span {...{}} class:foo class:bar></span>
<b {...{class:classname}} class:foo class:bar></b>
<i {...{}} class:foo class:bar></i>
</main>

<style>
Expand Down

0 comments on commit cf56973

Please sign in to comment.