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"]);