fix: update value like attributes in a separate template_effect (#11720)

* fix: update value like attributes in a separate template_effect

* chore: remove unnecessary commented code

* chore: add test for spread values
pull/11726/head
Paolo Ricciuti 8 months ago committed by GitHub
parent c21f019a4b
commit 77f91459b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: update value like attributes in a separate template_effect

@ -467,9 +467,16 @@ function serialize_dynamic_element_attributes(attributes, context, element_id) {
* @param {import('estree').Identifier} node_id * @param {import('estree').Identifier} node_id
* @param {import('#compiler').Attribute} attribute * @param {import('#compiler').Attribute} attribute
* @param {import('../types.js').ComponentContext} context * @param {import('../types.js').ComponentContext} context
* @param {boolean} needs_isolation
* @returns {boolean} * @returns {boolean}
*/ */
function serialize_element_attribute_update_assignment(element, node_id, attribute, context) { function serialize_element_attribute_update_assignment(
element,
node_id,
attribute,
context,
needs_isolation
) {
const state = context.state; const state = context.state;
const name = get_attribute_name(element, attribute, context); const name = get_attribute_name(element, attribute, context);
const is_svg = context.state.metadata.namespace === 'svg'; const is_svg = context.state.metadata.namespace === 'svg';
@ -514,7 +521,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
} }
if (attribute.metadata.dynamic) { if (attribute.metadata.dynamic) {
if (contains_call_expression) { if (contains_call_expression || needs_isolation) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else { } else {
state.update.push(update); state.update.push(update);
@ -2065,7 +2072,24 @@ export const template_visitors = {
const is = const is =
is_custom_element && child_metadata.namespace !== 'foreign' is_custom_element && child_metadata.namespace !== 'foreign'
? serialize_custom_element_attribute_update_assignment(node_id, attribute, context) ? serialize_custom_element_attribute_update_assignment(node_id, attribute, context)
: serialize_element_attribute_update_assignment(node, node_id, attribute, context); : serialize_element_attribute_update_assignment(
node,
node_id,
attribute,
context,
/**
* if the input needs input or content reset we also
* want to isolate the template effect or else every
* unrelated change will reset the value (and the user could)
* change the value outside of the reactivity
*
* <input value={val} />
*
* should only be updated when val changes and not when another
* unrelated variable changes.
* */
needs_content_reset || needs_input_reset
);
if (is) is_attributes_reactive = true; if (is) is_attributes_reactive = true;
} }
} }

@ -0,0 +1,34 @@
import { test, ok } from '../../test';
import { flushSync } from 'svelte';
export default test({
mode: ['client'],
async test({ assert, target }) {
/**
* @type {HTMLInputElement | null}
*/
const input = target.querySelector('input[type=text]');
const button = target.querySelector('button');
/**
* @type {HTMLInputElement | null}
*/
const checkbox = target.querySelector('input[type=checkbox]');
const textarea = target.querySelector('textarea');
ok(input);
ok(button);
ok(checkbox);
ok(textarea);
flushSync(() => {
input.value = 'foo';
checkbox.click();
textarea.innerHTML = 'bar';
button.click();
});
assert.equal(input.value, 'foo');
assert.equal(checkbox.checked, true);
assert.equal(textarea.innerHTML, 'bar');
}
});

@ -0,0 +1,13 @@
<script>
let count = 0;
let value = { value: "" };
let checked = { value: false };
</script>
<input type="text" value={value.value} />
<textarea value={value.value}></textarea>
<input type="checkbox" checked={checked.value} />
<button on:click={()=>count++}>{count}</button>

@ -0,0 +1,34 @@
import { test, ok } from '../../test';
import { flushSync } from 'svelte';
export default test({
mode: ['client'],
async test({ assert, target }) {
/**
* @type {HTMLInputElement | null}
*/
const input = target.querySelector('input[type=text]');
const button = target.querySelector('button');
/**
* @type {HTMLInputElement | null}
*/
const checkbox = target.querySelector('input[type=checkbox]');
const textarea = target.querySelector('textarea');
ok(input);
ok(button);
ok(checkbox);
ok(textarea);
flushSync(() => {
input.value = 'foo';
checkbox.click();
textarea.innerHTML = 'bar';
button.click();
});
assert.equal(input.value, 'foo');
assert.equal(checkbox.checked, true);
assert.equal(textarea.innerHTML, 'bar');
}
});

@ -0,0 +1,17 @@
<script>
let count = $state(0);
let value = $state({
value: "",
});
let checked = $state({
checked: false,
});
</script>
<input type="text" {...value} />
<textarea {...value}></textarea>
<input type="checkbox" {...checked} />
<button onclick={()=>count++}>{count}</button>

@ -0,0 +1,34 @@
import { test, ok } from '../../test';
import { flushSync } from 'svelte';
export default test({
mode: ['client'],
async test({ assert, target }) {
/**
* @type {HTMLInputElement | null}
*/
const input = target.querySelector('input[type=text]');
const button = target.querySelector('button');
/**
* @type {HTMLInputElement | null}
*/
const checkbox = target.querySelector('input[type=checkbox]');
const textarea = target.querySelector('textarea');
ok(input);
ok(button);
ok(checkbox);
ok(textarea);
flushSync(() => {
input.value = 'foo';
checkbox.click();
textarea.innerHTML = 'bar';
button.click();
});
assert.equal(input.value, 'foo');
assert.equal(checkbox.checked, true);
assert.equal(textarea.innerHTML, 'bar');
}
});

@ -0,0 +1,13 @@
<script>
let count = $state(0);
let value = $state("");
let checked = $state(false);
</script>
<input type="text" {value} />
<textarea {value}></textarea>
<input type="checkbox" {checked} />
<button onclick={()=>count++}>{count}</button>
Loading…
Cancel
Save