diff --git a/.changeset/moody-toys-relax.md b/.changeset/moody-toys-relax.md
new file mode 100644
index 0000000000..236fa089c4
--- /dev/null
+++ b/.changeset/moody-toys-relax.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: preserve current input values when removing defaults
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
index b72b61cf7d..2ddafb24d9 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
@@ -2030,7 +2030,7 @@ export const template_visitors = {
}
if (needs_input_reset && node.name === 'input') {
- context.state.init.push(b.stmt(b.call('$.remove_input_attr_defaults', context.state.node)));
+ context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
}
if (needs_content_reset && node.name === 'textarea') {
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index e6d6e78c1f..d09667dd90 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -16,29 +16,40 @@ import { queue_idle_task, queue_micro_task } from '../task.js';
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
* to remove it upon hydration to avoid a bug when someone resets the form value.
- * @param {HTMLInputElement} dom
+ * @param {HTMLInputElement} input
* @returns {void}
*/
-export function remove_input_attr_defaults(dom) {
- if (hydrating) {
- let already_removed = false;
- // We try and remove the default attributes later, rather than sync during hydration.
- // Doing it sync during hydration has a negative impact on performance, but deferring the
- // work in an idle task alleviates this greatly. If a form reset event comes in before
- // the idle callback, then we ensure the input defaults are cleared just before.
- const remove_defaults = () => {
- if (already_removed) return;
- already_removed = true;
- const value = dom.getAttribute('value');
- set_attribute(dom, 'value', null);
- set_attribute(dom, 'checked', null);
- if (value) dom.value = value;
- };
- // @ts-expect-error
- dom.__on_r = remove_defaults;
- queue_idle_task(remove_defaults);
- add_form_reset_listener();
- }
+export function remove_input_defaults(input) {
+ if (!hydrating) return;
+
+ var already_removed = false;
+
+ // We try and remove the default attributes later, rather than sync during hydration.
+ // Doing it sync during hydration has a negative impact on performance, but deferring the
+ // work in an idle task alleviates this greatly. If a form reset event comes in before
+ // the idle callback, then we ensure the input defaults are cleared just before.
+ var remove_defaults = () => {
+ if (already_removed) return;
+ already_removed = true;
+
+ // Remove the attributes but preserve the values
+ if (input.hasAttribute('value')) {
+ var value = input.value;
+ set_attribute(input, 'value', null);
+ input.value = value;
+ }
+
+ if (input.hasAttribute('checked')) {
+ var checked = input.checked;
+ set_attribute(input, 'checked', null);
+ input.checked = checked;
+ }
+ };
+
+ // @ts-expect-error
+ input.__on_r = remove_defaults;
+ queue_idle_task(remove_defaults);
+ add_form_reset_listener();
}
/**
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index 148c64bb69..36df01ce18 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -21,7 +21,7 @@ export { element } from './dom/blocks/svelte-element.js';
export { head } from './dom/blocks/svelte-head.js';
export { action } from './dom/elements/actions.js';
export {
- remove_input_attr_defaults,
+ remove_input_defaults,
set_attribute,
set_attributes,
set_custom_element_data,
diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/_config.js b/packages/svelte/tests/hydration/samples/input-value-changed/_config.js
new file mode 100644
index 0000000000..f8c0dea072
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/input-value-changed/_config.js
@@ -0,0 +1,16 @@
+import { test } from '../../test';
+
+export default test({
+ server_props: {
+ name: 'server'
+ },
+
+ props: {
+ name: 'browser'
+ },
+
+ test(assert, target) {
+ const input = target.querySelector('input');
+ assert.equal(input?.value, 'browser');
+ }
+});
diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html b/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html
new file mode 100644
index 0000000000..c5384e717e
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html
@@ -0,0 +1 @@
+
diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte b/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte
new file mode 100644
index 0000000000..c07692dc02
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js
index 455f227e09..7cb2415bf5 100644
--- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js
@@ -16,11 +16,11 @@ export default function State_proxy_literal($$anchor) {
var fragment = root();
var input = $.first_child(fragment);
- $.remove_input_attr_defaults(input);
+ $.remove_input_defaults(input);
var input_1 = $.sibling($.sibling(input, true));
- $.remove_input_attr_defaults(input_1);
+ $.remove_input_defaults(input_1);
var button = $.sibling($.sibling(input_1, true));
@@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) {
$.append($$anchor, fragment);
}
-$.delegate(["click"]);
\ No newline at end of file
+$.delegate(["click"]);