diff --git a/.changeset/cool-clocks-film.md b/.changeset/cool-clocks-film.md new file mode 100644 index 000000000000..a38b2e19d497 --- /dev/null +++ b/.changeset/cool-clocks-film.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: treat `undefined` and `null` the same for the initial input value diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 2229c1a36135..0276069eee49 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -60,13 +60,19 @@ export function remove_input_defaults(input) { export function set_value(element, value) { // @ts-expect-error var attributes = (element.__attributes ??= {}); + if ( - attributes.value === (attributes.value = value) || + attributes.value === + (attributes.value = + // treat null and undefined the same for the initial value + value ?? undefined) || // @ts-expect-error // `progress` elements always need their value set when its `0` (element.value === value && (value !== 0 || element.nodeName !== 'PROGRESS')) - ) + ) { return; + } + // @ts-expect-error element.value = value; } @@ -79,7 +85,15 @@ export function set_checked(element, checked) { // @ts-expect-error var attributes = (element.__attributes ??= {}); - if (attributes.checked === (attributes.checked = checked)) return; + if ( + attributes.checked === + (attributes.checked = + // treat null and undefined the same for the initial value + checked ?? undefined) + ) { + return; + } + // @ts-expect-error element.checked = checked; } 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 3ae8b223bea1..5ef72aaa8ec2 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,8 +37,9 @@ export default test({ const after_reset = []; const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]')); - const [test1, test2, test3, test4, test5, test12] = target.querySelectorAll('div'); - const [test6, test7, test8, test9] = target.querySelectorAll('select'); + const [test1, test2, test3, test4, test5, test6, test7, test14] = + target.querySelectorAll('div'); + const [test8, test9, test10, test11] = target.querySelectorAll('select'); const [ test1_span, test2_span, @@ -48,7 +49,9 @@ export default test({ test6_span, test7_span, test8_span, - test9_span + test9_span, + test10_span, + test11_span ] = target.querySelectorAll('span'); { @@ -74,8 +77,8 @@ export default test({ { /** @type {NodeListOf} */ const inputs = test2.querySelectorAll('input, textarea'); - check_inputs(inputs, 'value', 'y'); - assert.htmlEqual(test2_span.innerHTML, 'y y y y'); + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test2_span.innerHTML, 'x x x x'); for (const input of inputs) { set_input(input, 'value', 'foo'); @@ -85,125 +88,164 @@ export default test({ assert.htmlEqual(test2_span.innerHTML, 'foo foo foo foo'); after_reset.push(() => { + console.log('-------------'); check_inputs(inputs, 'value', 'x'); assert.htmlEqual(test2_span.innerHTML, 'x x x x'); }); } + { + /** @type {NodeListOf} */ + const inputs = test3.querySelectorAll('input, textarea'); + check_inputs(inputs, 'value', 'y'); + assert.htmlEqual(test3_span.innerHTML, 'y y y y'); + + for (const input of inputs) { + set_input(input, 'value', 'foo'); + } + flushSync(); + check_inputs(inputs, 'value', 'foo'); + assert.htmlEqual(test3_span.innerHTML, 'foo foo foo foo'); + + after_reset.push(() => { + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test3_span.innerHTML, 'x x x x'); + }); + } + { /** @type {NodeListOf} */ - const inputs = test3.querySelectorAll('input'); + const inputs = test4.querySelectorAll('input'); check_inputs(inputs, 'checked', true); - assert.htmlEqual(test3_span.innerHTML, 'true true'); + assert.htmlEqual(test4_span.innerHTML, 'true true'); for (const input of inputs) { set_input(input, 'checked', false); } flushSync(); check_inputs(inputs, 'checked', false); - assert.htmlEqual(test3_span.innerHTML, 'false false'); + assert.htmlEqual(test4_span.innerHTML, 'false false'); after_reset.push(() => { check_inputs(inputs, 'checked', true); - assert.htmlEqual(test3_span.innerHTML, 'true true'); + assert.htmlEqual(test4_span.innerHTML, 'true true'); }); } { /** @type {NodeListOf} */ - const inputs = test4.querySelectorAll('input'); + const inputs = test5.querySelectorAll('input'); + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test5_span.innerHTML, 'true true'); + + for (const input of inputs) { + set_input(input, 'checked', false); + } + flushSync(); check_inputs(inputs, 'checked', false); - assert.htmlEqual(test4_span.innerHTML, 'false false'); + assert.htmlEqual(test5_span.innerHTML, 'false false'); after_reset.push(() => { check_inputs(inputs, 'checked', true); - assert.htmlEqual(test4_span.innerHTML, 'true true'); + assert.htmlEqual(test5_span.innerHTML, 'true true'); }); } { /** @type {NodeListOf} */ - const inputs = test5.querySelectorAll('input'); + const inputs = test6.querySelectorAll('input'); + check_inputs(inputs, 'checked', false); + assert.htmlEqual(test6_span.innerHTML, 'false false'); + + after_reset.push(() => { + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test6_span.innerHTML, 'true true'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test7.querySelectorAll('input'); check_inputs(inputs, 'checked', true); - assert.htmlEqual(test5_span.innerHTML, 'true'); + assert.htmlEqual(test7_span.innerHTML, 'true'); after_reset.push(() => { check_inputs(inputs, 'checked', false); - assert.htmlEqual(test5_span.innerHTML, 'false'); + assert.htmlEqual(test7_span.innerHTML, 'false'); }); } { /** @type {NodeListOf} */ - const options = test6.querySelectorAll('option'); + const options = test8.querySelectorAll('option'); check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test6_span.innerHTML, 'b'); + assert.htmlEqual(test8_span.innerHTML, 'b'); select_option(options[2]); flushSync(); check_inputs(options, 'selected', [false, false, true]); - assert.htmlEqual(test6_span.innerHTML, 'c'); + assert.htmlEqual(test8_span.innerHTML, 'c'); after_reset.push(() => { check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test6_span.innerHTML, 'b'); + assert.htmlEqual(test8_span.innerHTML, 'b'); }); } { /** @type {NodeListOf} */ - const options = test7.querySelectorAll('option'); + const options = test9.querySelectorAll('option'); check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test7_span.innerHTML, 'b'); + assert.htmlEqual(test9_span.innerHTML, 'b'); select_option(options[2]); flushSync(); check_inputs(options, 'selected', [false, false, true]); - assert.htmlEqual(test7_span.innerHTML, 'c'); + assert.htmlEqual(test9_span.innerHTML, 'c'); after_reset.push(() => { check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test7_span.innerHTML, 'b'); + assert.htmlEqual(test9_span.innerHTML, 'b'); }); } { /** @type {NodeListOf} */ - const options = test8.querySelectorAll('option'); + const options = test10.querySelectorAll('option'); check_inputs(options, 'selected', [false, false, true]); - assert.htmlEqual(test8_span.innerHTML, 'c'); + assert.htmlEqual(test10_span.innerHTML, 'c'); select_option(options[0]); flushSync(); check_inputs(options, 'selected', [true, false, false]); - assert.htmlEqual(test8_span.innerHTML, 'a'); + assert.htmlEqual(test10_span.innerHTML, 'a'); after_reset.push(() => { check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test8_span.innerHTML, 'b'); + assert.htmlEqual(test10_span.innerHTML, 'b'); }); } { /** @type {NodeListOf} */ - const options = test9.querySelectorAll('option'); + const options = test11.querySelectorAll('option'); check_inputs(options, 'selected', [false, false, true]); - assert.htmlEqual(test9_span.innerHTML, 'c'); + assert.htmlEqual(test11_span.innerHTML, 'c'); select_option(options[0]); flushSync(); check_inputs(options, 'selected', [true, false, false]); - assert.htmlEqual(test9_span.innerHTML, 'a'); + assert.htmlEqual(test11_span.innerHTML, 'a'); after_reset.push(() => { check_inputs(options, 'selected', [false, true, false]); - assert.htmlEqual(test9_span.innerHTML, 'b'); + assert.htmlEqual(test11_span.innerHTML, 'b'); }); } { /** @type {NodeListOf} */ - const inputs = test12.querySelectorAll('input, textarea'); + const inputs = test14.querySelectorAll('input, textarea'); assert.equal(inputs[0].value, 'x'); assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, true); assert.equal(inputs[2].value, 'x'); 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 35d495300b70..4d6a1a00b83a 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 @@ -7,25 +7,37 @@ let value6 = $state(); let value7 = $state(); let value8 = $state(); - let value9 = $state('y'); - let value10 = $state('y'); - let value11 = $state('y'); - let value12 = $state('y'); - let value13 = $state('y'); - let value14 = $state('y'); - let value15 = $state('y'); - let value16 = $state('y'); + let value9 = $state(null); + let value10 = $state(null); + let value11 = $state(null); + let value12 = $state(null); + let value13 = $state(null); + let value14 = $state(null); + let value15 = $state(null); + let value16 = $state(null); + let value17 = $state('y'); + let value18 = $state('y'); + let value19 = $state('y'); + let value20 = $state('y'); + let value21 = $state('y'); + let value22 = $state('y'); + let value23 = $state('y'); + let value24 = $state('y'); let checked1 = $state(); let checked2 = $state(); let checked3 = $state(); let checked4 = $state(); - let checked5 = $state(false); - let checked6 = $state(false); - let checked7 = $state(false); - let checked8 = $state(false); - let checked9 = $state(true); - let checked10 = $state(true); + let checked5 = $state(null); + let checked6 = $state(null); + let checked7 = $state(null); + let checked8 = $state(null); + let checked9 = $state(false); + let checked10 = $state(false); + let checked11 = $state(false); + let checked12 = $state(false); + let checked13 = $state(true); + let checked14 = $state(true); let selected1 = $state(); @@ -53,7 +65,7 @@ - +
@@ -65,27 +77,47 @@
+ +
+ + + + + + + + +
+

Input checked

-
+
- -
+ +
+ +
+ + + + +
+ -
- - +
+ +
@@ -138,7 +170,7 @@

Static values

-
+
@@ -151,13 +183,15 @@ Bound values: {value1} {value3} {value6} {value8} {value9} {value12} {value14} {value16} - {checked2} {checked4} - {checked6} {checked8} - {checked10} - {selected1} - {selected2} - {selected3} - {selected4} - {selected5} - {selected6} + {value17} {value20} {value22} {value24} + {checked2} {checked4} + {checked6} {checked8} + {checked10} {checked12} + {checked14} + {selected1} + {selected2} + {selected3} + {selected4} + {selected5} + {selected6}