Skip to content

Commit 399e464

Browse files
trueadmRich-Harris
andauthored
fix: ensure input value is correctly set during hydration (#12083)
* fix: ensure input value is correctly set during hydration * fix: ensure input value is correctly set during hydration * address feedback * fix typos * early return, var * fix test * Update packages/svelte/src/internal/client/dom/elements/attributes.js Co-authored-by: Rich Harris <[email protected]> * update changeset * tweak names --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 696a4b3 commit 399e464

File tree

8 files changed

+64
-26
lines changed

8 files changed

+64
-26
lines changed

.changeset/moody-toys-relax.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: preserve current input values when removing defaults

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2030,7 +2030,7 @@ export const template_visitors = {
20302030
}
20312031

20322032
if (needs_input_reset && node.name === 'input') {
2033-
context.state.init.push(b.stmt(b.call('$.remove_input_attr_defaults', context.state.node)));
2033+
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
20342034
}
20352035

20362036
if (needs_content_reset && node.name === 'textarea') {

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,40 @@ import { queue_idle_task, queue_micro_task } from '../task.js';
1616
/**
1717
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
1818
* to remove it upon hydration to avoid a bug when someone resets the form value.
19-
* @param {HTMLInputElement} dom
19+
* @param {HTMLInputElement} input
2020
* @returns {void}
2121
*/
22-
export function remove_input_attr_defaults(dom) {
23-
if (hydrating) {
24-
let already_removed = false;
25-
// We try and remove the default attributes later, rather than sync during hydration.
26-
// Doing it sync during hydration has a negative impact on performance, but deferring the
27-
// work in an idle task alleviates this greatly. If a form reset event comes in before
28-
// the idle callback, then we ensure the input defaults are cleared just before.
29-
const remove_defaults = () => {
30-
if (already_removed) return;
31-
already_removed = true;
32-
const value = dom.getAttribute('value');
33-
set_attribute(dom, 'value', null);
34-
set_attribute(dom, 'checked', null);
35-
if (value) dom.value = value;
36-
};
37-
// @ts-expect-error
38-
dom.__on_r = remove_defaults;
39-
queue_idle_task(remove_defaults);
40-
add_form_reset_listener();
41-
}
22+
export function remove_input_defaults(input) {
23+
if (!hydrating) return;
24+
25+
var already_removed = false;
26+
27+
// We try and remove the default attributes later, rather than sync during hydration.
28+
// Doing it sync during hydration has a negative impact on performance, but deferring the
29+
// work in an idle task alleviates this greatly. If a form reset event comes in before
30+
// the idle callback, then we ensure the input defaults are cleared just before.
31+
var remove_defaults = () => {
32+
if (already_removed) return;
33+
already_removed = true;
34+
35+
// Remove the attributes but preserve the values
36+
if (input.hasAttribute('value')) {
37+
var value = input.value;
38+
set_attribute(input, 'value', null);
39+
input.value = value;
40+
}
41+
42+
if (input.hasAttribute('checked')) {
43+
var checked = input.checked;
44+
set_attribute(input, 'checked', null);
45+
input.checked = checked;
46+
}
47+
};
48+
49+
// @ts-expect-error
50+
input.__on_r = remove_defaults;
51+
queue_idle_task(remove_defaults);
52+
add_form_reset_listener();
4253
}
4354

4455
/**

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export { element } from './dom/blocks/svelte-element.js';
2121
export { head } from './dom/blocks/svelte-head.js';
2222
export { action } from './dom/elements/actions.js';
2323
export {
24-
remove_input_attr_defaults,
24+
remove_input_defaults,
2525
set_attribute,
2626
set_attributes,
2727
set_custom_element_data,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
server_props: {
5+
name: 'server'
6+
},
7+
8+
props: {
9+
name: 'browser'
10+
},
11+
12+
test(assert, target) {
13+
const input = target.querySelector('input');
14+
assert.equal(input?.value, 'browser');
15+
}
16+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!--[--><input type="text"><!--]-->
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
const { name } = $props();
3+
</script>
4+
5+
<input type="text" value={name} />

packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export default function State_proxy_literal($$anchor) {
1616
var fragment = root();
1717
var input = $.first_child(fragment);
1818

19-
$.remove_input_attr_defaults(input);
19+
$.remove_input_defaults(input);
2020

2121
var input_1 = $.sibling($.sibling(input, true));
2222

23-
$.remove_input_attr_defaults(input_1);
23+
$.remove_input_defaults(input_1);
2424

2525
var button = $.sibling($.sibling(input_1, true));
2626

@@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) {
3030
$.append($$anchor, fragment);
3131
}
3232

33-
$.delegate(["click"]);
33+
$.delegate(["click"]);

0 commit comments

Comments
 (0)