fix: apply class/style directives after attributes (#13535)

fixes #13270 by resetting the has_call boolean to false to style/class attributes, which means we're not creating a separate template effect for the attribute, instead they're added to the common template effect in which style/class directives are also added to later
pull/13540/head
Paolo Ricciuti 3 months ago committed by GitHub
parent 2b0741fa11
commit 8148e63ced
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: apply class/style directives after attributes

@ -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(

@ -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;');
}
});

@ -0,0 +1,23 @@
<script>
let value = $state(0);
function dark(){
console.log('updated class directive');
return true;
}
function get_class(){
console.log('updated class attribute');
return value % 2 ? 'big' : 'small';
}
function color(){
console.log('updated style directive');
return "green";
}
function get_style(){
console.log('updated style attribute');
return value % 2 ? 'background: red' : 'background: green';
}
</script>
<div class:dark={dark()} class={get_class()}></div>
<div style:color={color()} style={get_style()}></div>
<button onclick={()=> value++}>switch</button>
Loading…
Cancel
Save