diff --git a/.changeset/blue-fans-greet.md b/.changeset/blue-fans-greet.md new file mode 100644 index 000000000000..747f534d2242 --- /dev/null +++ b/.changeset/blue-fans-greet.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle static form values in combination with default values diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 56f7b6d6f0cd..3c0be589c363 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -291,7 +291,7 @@ export function RegularElement(node, context) { const is = is_custom_element ? build_custom_element_attribute_update_assignment(node_id, attribute, context) - : build_element_attribute_update_assignment(node, node_id, attribute, context); + : build_element_attribute_update_assignment(node, node_id, attribute, attributes, context); if (is) is_attributes_reactive = true; } } @@ -519,10 +519,17 @@ function setup_select_synchronization(value_binding, context) { * @param {AST.RegularElement} element * @param {Identifier} node_id * @param {AST.Attribute} attribute + * @param {Array} attributes * @param {ComponentContext} context * @returns {boolean} */ -function build_element_attribute_update_assignment(element, node_id, attribute, context) { +function build_element_attribute_update_assignment( + element, + node_id, + attribute, + attributes, + context +) { const state = context.state; const name = get_attribute_name(element, attribute); const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg'; @@ -565,6 +572,26 @@ function build_element_attribute_update_assignment(element, node_id, attribute, update = b.stmt(b.call('$.set_checked', node_id, value)); } else if (name === 'selected') { update = b.stmt(b.call('$.set_selected', node_id, value)); + } else if ( + // If we would just set the defaultValue property, it would override the value property, + // because it is set in the template which implicitly means it's also setting the default value, + // and if one updates the default value while the input is pristine it will also update the + // current value, which is not what we want, which is why we need to do some extra work. + name === 'defaultValue' && + (attributes.some( + (attr) => attr.type === 'Attribute' && attr.name === 'value' && is_text_attribute(attr) + ) || + (element.name === 'textarea' && element.fragment.nodes.length > 0)) + ) { + update = b.stmt(b.call('$.set_default_value', node_id, value)); + } else if ( + // See defaultValue comment + name === 'defaultChecked' && + attributes.some( + (attr) => attr.type === 'Attribute' && attr.name === 'checked' && attr.value === true + ) + ) { + update = b.stmt(b.call('$.set_default_checked', node_id, value)); } else if (is_dom_property(name)) { update = b.stmt(b.assignment('=', b.member(node_id, name), value)); } else { diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index b4dd92a9abf2..2229c1a36135 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -103,6 +103,28 @@ export function set_selected(element, selected) { } } +/** + * Applies the default checked property without influencing the current checked property. + * @param {HTMLInputElement} element + * @param {boolean} checked + */ +export function set_default_checked(element, checked) { + const existing_value = element.checked; + element.defaultChecked = checked; + element.checked = existing_value; +} + +/** + * Applies the default value property without influencing the current value property. + * @param {HTMLInputElement | HTMLTextAreaElement} element + * @param {string} value + */ +export function set_default_value(element, value) { + const existing_value = element.value; + element.defaultValue = value; + element.value = existing_value; +} + /** * @param {Element} element * @param {string} attribute diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index d514dd3de19c..b706e52a5378 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -35,7 +35,9 @@ export { handle_lazy_img, set_value, set_checked, - set_selected + set_selected, + set_default_checked, + set_default_value } from './dom/elements/attributes.js'; export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js'; export { apply, event, delegate, replay_events } from './dom/elements/events.js'; diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js index 7c31b9982519..3ae8b223bea1 100644 --- a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js @@ -37,7 +37,7 @@ export default test({ const after_reset = []; const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]')); - const [test1, test2, test3, test4, test5] = target.querySelectorAll('div'); + const [test1, test2, test3, test4, test5, test12] = target.querySelectorAll('div'); const [test6, test7, test8, test9] = target.querySelectorAll('select'); const [ test1_span, @@ -201,6 +201,20 @@ export default test({ }); } + { + /** @type {NodeListOf} */ + const inputs = test12.querySelectorAll('input, textarea'); + assert.equal(inputs[0].value, 'x'); + assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, true); + assert.equal(inputs[2].value, 'x'); + + after_reset.push(() => { + assert.equal(inputs[0].value, 'y'); + assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, false); + assert.equal(inputs[2].value, 'y'); + }); + } + reset.click(); await Promise.resolve(); flushSync(); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-default-value/main.svelte index d2b864e7ec37..35d495300b70 100644 --- a/packages/svelte/tests/runtime-runes/samples/form-default-value/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value/main.svelte @@ -137,6 +137,13 @@ +

Static values

+
+ + + +
+