fix: prevent spread attribute from overriding class directive (#13763)

pull/13788/head
Paolo Ricciuti 2 months ago committed by GitHub
parent de609ec34c
commit b6a67e85b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent spread attribute from overriding class directive

@ -219,7 +219,8 @@ export function RegularElement(node, context) {
node_id,
attributes_id,
(node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true,
node.name.includes('-') && b.true
node.name.includes('-') && b.true,
context.state
);
// If value binding exists, that one takes care of calling $.init_select

@ -102,7 +102,8 @@ export function SvelteElement(node, context) {
element_id,
attributes_id,
b.binary('!==', b.member(element_id, 'namespaceURI'), b.id('$.NAMESPACE_SVG')),
b.call(b.member(b.member(element_id, 'nodeName'), 'includes'), b.literal('-'))
b.call(b.member(b.member(element_id, 'nodeName'), 'includes'), b.literal('-')),
context.state
);
}

@ -1,6 +1,6 @@
/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
import { normalize_attribute } from '../../../../../../utils.js';
import { is_ignored } from '../../../../../state.js';
import { get_attribute_expression, is_event_attribute } from '../../../../../utils/ast.js';
@ -16,6 +16,7 @@ import { build_template_literal, build_update } from './utils.js';
* @param {Identifier} attributes_id
* @param {false | Expression} preserve_attribute_case
* @param {false | Expression} is_custom_element
* @param {ComponentClientTransformState} state
*/
export function build_set_attributes(
attributes,
@ -24,9 +25,9 @@ export function build_set_attributes(
element_id,
attributes_id,
preserve_attribute_case,
is_custom_element
is_custom_element,
state
) {
let needs_isolation = false;
let has_state = false;
/** @type {ObjectExpression['properties']} */
@ -50,12 +51,17 @@ export function build_set_attributes(
has_state ||= attribute.metadata.expression.has_state;
} else {
values.push(b.spread(/** @type {Expression} */ (context.visit(attribute))));
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
has_state = true;
needs_isolation ||= attribute.metadata.expression.has_call;
let value = /** @type {Expression} */ (context.visit(attribute));
if (attribute.metadata.expression.has_call) {
const id = b.id(state.scope.generate('spread_with_call'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
}
values.push(b.spread(value));
}
}
@ -72,14 +78,7 @@ export function build_set_attributes(
if (has_state) {
context.state.init.push(b.let(attributes_id));
const update = b.stmt(b.assignment('=', attributes_id, call));
if (needs_isolation) {
context.state.init.push(build_update(update));
return false;
}
context.state.update.push(update);
return true;
}

@ -0,0 +1,25 @@
import { flushSync } from 'svelte';
import { test, ok } from '../../test';
export default test({
test({ target, logs, assert }) {
const input = target.querySelector('input');
ok(input);
assert.deepEqual(logs, ['get_rest']);
assert.ok(input.classList.contains('dark'));
assert.equal(input.dataset.rest, 'true');
flushSync(() => {
input.focus();
});
assert.ok(input.classList.contains('dark'));
assert.ok(input.classList.contains('focused'));
assert.equal(input.dataset.rest, 'true');
assert.deepEqual(logs, ['get_rest']);
}
});

@ -0,0 +1,19 @@
<script>
let focused = $state(false)
function get_rest() {
console.log("get_rest");
return {
"data-rest": "true"
}
}
</script>
<input
onfocus={() => focused = true}
onblur={() => focused = false}
class:dark={true}
class={`${focused ? 'focused' : ''}`}
{...get_rest()}
>
Loading…
Cancel
Save