diff --git a/.changeset/eighty-hornets-breathe.md b/.changeset/eighty-hornets-breathe.md new file mode 100644 index 0000000000..f4e3cb0ad7 --- /dev/null +++ b/.changeset/eighty-hornets-breathe.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply class/style directives after attributes diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 9be4351563..d4624c9d3a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -19,7 +19,7 @@ import { import * as b from '../../../../utils/builders.js'; import { is_custom_element_node } from '../../../nodes.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; -import { build_getter, can_inline_variable } from '../utils.js'; +import { build_getter, can_inline_variable, create_derived } from '../utils.js'; import { get_attribute_name, build_attribute_value, @@ -534,6 +534,14 @@ function build_element_attribute_update_assignment(element, node_id, attribute, let update; if (name === 'class') { + if (attribute.metadata.expression.has_state && has_call) { + // ensure we're not creating a separate template effect for this so that + // potential class directives are added to the same effect and therefore always apply + const id = b.id(state.scope.generate('class_derived')); + state.init.push(b.const(id, create_derived(state, b.thunk(value)))); + value = b.call('$.get', id); + has_call = false; + } update = b.stmt( b.call( is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class', @@ -548,6 +556,14 @@ function build_element_attribute_update_assignment(element, node_id, attribute, } else if (is_dom_property(name)) { update = b.stmt(b.assignment('=', b.member(node_id, name), value)); } else { + if (name === 'style' && attribute.metadata.expression.has_state && has_call) { + // ensure we're not creating a separate template effect for this so that + // potential style directives are added to the same effect and therefore always apply + const id = b.id(state.scope.generate('style_derived')); + state.init.push(b.const(id, create_derived(state, b.thunk(value)))); + value = b.call('$.get', id); + has_call = false; + } const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute'; update = b.stmt( b.call( diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/_config.js new file mode 100644 index 0000000000..ec50fc7253 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/_config.js @@ -0,0 +1,37 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ target, logs, assert }) { + const [div, div2] = target.querySelectorAll('div'); + const button = target.querySelector('button'); + + assert.deepEqual(logs, [ + 'updated class attribute', + 'updated class directive', + 'updated style attribute', + 'updated style directive' + ]); + + assert.ok(div.classList.contains('dark')); + assert.ok(div.classList.contains('small')); + + assert.equal(div2.getAttribute('style'), 'background: green; color: green;'); + + flushSync(() => button?.click()); + + assert.deepEqual(logs, [ + 'updated class attribute', + 'updated class directive', + 'updated style attribute', + 'updated style directive', + 'updated class attribute', + 'updated style attribute' + ]); + + assert.ok(div.classList.contains('dark')); + assert.ok(div.classList.contains('big')); + + assert.equal(div2.getAttribute('style'), 'background: red; color: green;'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/main.svelte new file mode 100644 index 0000000000..698016b96a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-attribute-and-attribute-directive-2/main.svelte @@ -0,0 +1,23 @@ + + +
+ + \ No newline at end of file