Skip to content

Commit 77f9145

Browse files
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
1 parent c21f019 commit 77f9145

File tree

8 files changed

+177
-3
lines changed

8 files changed

+177
-3
lines changed

.changeset/sour-jeans-collect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: update value like attributes in a separate template_effect

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,16 @@ function serialize_dynamic_element_attributes(attributes, context, element_id) {
467467
* @param {import('estree').Identifier} node_id
468468
* @param {import('#compiler').Attribute} attribute
469469
* @param {import('../types.js').ComponentContext} context
470+
* @param {boolean} needs_isolation
470471
* @returns {boolean}
471472
*/
472-
function serialize_element_attribute_update_assignment(element, node_id, attribute, context) {
473+
function serialize_element_attribute_update_assignment(
474+
element,
475+
node_id,
476+
attribute,
477+
context,
478+
needs_isolation
479+
) {
473480
const state = context.state;
474481
const name = get_attribute_name(element, attribute, context);
475482
const is_svg = context.state.metadata.namespace === 'svg';
@@ -514,7 +521,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
514521
}
515522

516523
if (attribute.metadata.dynamic) {
517-
if (contains_call_expression) {
524+
if (contains_call_expression || needs_isolation) {
518525
state.init.push(serialize_update(update));
519526
} else {
520527
state.update.push(update);
@@ -2065,7 +2072,24 @@ export const template_visitors = {
20652072
const is =
20662073
is_custom_element && child_metadata.namespace !== 'foreign'
20672074
? serialize_custom_element_attribute_update_assignment(node_id, attribute, context)
2068-
: serialize_element_attribute_update_assignment(node, node_id, attribute, context);
2075+
: serialize_element_attribute_update_assignment(
2076+
node,
2077+
node_id,
2078+
attribute,
2079+
context,
2080+
/**
2081+
* if the input needs input or content reset we also
2082+
* want to isolate the template effect or else every
2083+
* unrelated change will reset the value (and the user could)
2084+
* change the value outside of the reactivity
2085+
*
2086+
* <input value={val} />
2087+
*
2088+
* should only be updated when val changes and not when another
2089+
* unrelated variable changes.
2090+
* */
2091+
needs_content_reset || needs_input_reset
2092+
);
20692093
if (is) is_attributes_reactive = true;
20702094
}
20712095
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, ok } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
mode: ['client'],
6+
7+
async test({ assert, target }) {
8+
/**
9+
* @type {HTMLInputElement | null}
10+
*/
11+
const input = target.querySelector('input[type=text]');
12+
const button = target.querySelector('button');
13+
/**
14+
* @type {HTMLInputElement | null}
15+
*/
16+
const checkbox = target.querySelector('input[type=checkbox]');
17+
const textarea = target.querySelector('textarea');
18+
ok(input);
19+
ok(button);
20+
ok(checkbox);
21+
ok(textarea);
22+
23+
flushSync(() => {
24+
input.value = 'foo';
25+
checkbox.click();
26+
textarea.innerHTML = 'bar';
27+
button.click();
28+
});
29+
30+
assert.equal(input.value, 'foo');
31+
assert.equal(checkbox.checked, true);
32+
assert.equal(textarea.innerHTML, 'bar');
33+
}
34+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let count = 0;
3+
let value = { value: "" };
4+
let checked = { value: false };
5+
</script>
6+
7+
<input type="text" value={value.value} />
8+
9+
<textarea value={value.value}></textarea>
10+
11+
<input type="checkbox" checked={checked.value} />
12+
13+
<button on:click={()=>count++}>{count}</button>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, ok } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
mode: ['client'],
6+
7+
async test({ assert, target }) {
8+
/**
9+
* @type {HTMLInputElement | null}
10+
*/
11+
const input = target.querySelector('input[type=text]');
12+
const button = target.querySelector('button');
13+
/**
14+
* @type {HTMLInputElement | null}
15+
*/
16+
const checkbox = target.querySelector('input[type=checkbox]');
17+
const textarea = target.querySelector('textarea');
18+
ok(input);
19+
ok(button);
20+
ok(checkbox);
21+
ok(textarea);
22+
23+
flushSync(() => {
24+
input.value = 'foo';
25+
checkbox.click();
26+
textarea.innerHTML = 'bar';
27+
button.click();
28+
});
29+
30+
assert.equal(input.value, 'foo');
31+
assert.equal(checkbox.checked, true);
32+
assert.equal(textarea.innerHTML, 'bar');
33+
}
34+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
let count = $state(0);
3+
let value = $state({
4+
value: "",
5+
});
6+
let checked = $state({
7+
checked: false,
8+
});
9+
</script>
10+
11+
<input type="text" {...value} />
12+
13+
<textarea {...value}></textarea>
14+
15+
<input type="checkbox" {...checked} />
16+
17+
<button onclick={()=>count++}>{count}</button>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, ok } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
mode: ['client'],
6+
7+
async test({ assert, target }) {
8+
/**
9+
* @type {HTMLInputElement | null}
10+
*/
11+
const input = target.querySelector('input[type=text]');
12+
const button = target.querySelector('button');
13+
/**
14+
* @type {HTMLInputElement | null}
15+
*/
16+
const checkbox = target.querySelector('input[type=checkbox]');
17+
const textarea = target.querySelector('textarea');
18+
ok(input);
19+
ok(button);
20+
ok(checkbox);
21+
ok(textarea);
22+
23+
flushSync(() => {
24+
input.value = 'foo';
25+
checkbox.click();
26+
textarea.innerHTML = 'bar';
27+
button.click();
28+
});
29+
30+
assert.equal(input.value, 'foo');
31+
assert.equal(checkbox.checked, true);
32+
assert.equal(textarea.innerHTML, 'bar');
33+
}
34+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let count = $state(0);
3+
let value = $state("");
4+
let checked = $state(false);
5+
</script>
6+
7+
<input type="text" {value} />
8+
9+
<textarea {value}></textarea>
10+
11+
<input type="checkbox" {checked} />
12+
13+
<button onclick={()=>count++}>{count}</button>

0 commit comments

Comments
 (0)