diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 2df6d3e68d..dc66a9c071 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -584,7 +584,16 @@ export function attribute_effect( var select = /** @type {HTMLSelectElement} */ (element); effect(() => { - select_option(select, /** @type {Record} */ (prev).value, true); + var attrs = /** @type {Record} */ (prev); + + // `defaultValue` is meaningless as a property here, so `set_attributes` cannot + // apply it. Mark the default option once the options exist, as we do for the + // non-spread case. + if ('defaultValue' in attrs) { + set_default_select_value(select, attrs.defaultValue); + } + + select_option(select, attrs.value, true); init_select(select); }); } diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 93a9a365a1..96f66695c2 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -332,14 +332,16 @@ export class Renderer { * @returns {void} */ select(attrs, fn, css_hash, classes, styles, flags, is_rich) { - // the attribute name is lowercased by the compiler, as with any HTML attribute - const { value, defaultvalue, ...select_attrs } = attrs; + // the compiler lowercases attribute names written in the template, but a spread + // passes the keys through as the user wrote them, so accept both spellings + const { value, defaultValue, defaultvalue, ...select_attrs } = attrs; + const default_value = defaultValue === undefined ? defaultvalue : defaultValue; this.push(``); this.child((renderer) => { // ``); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/_config.js new file mode 100644 index 0000000000..a0d8ba5b46 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/_config.js @@ -0,0 +1,56 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target }) { + /** + * @param {NodeListOf} options + * @param {any[]} selected + */ + function check_options(options, selected) { + for (let i = 0; i < options.length; i++) { + assert.equal(options[i].selected, selected[i], `option ${i}`); + } + } + + /** + * @param {HTMLOptionElement} option + */ + function select_option(option) { + option.selected = true; + option.dispatchEvent(new Event('change', { bubbles: true })); + } + + const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]')); + const [test1, test2] = target.querySelectorAll('select'); + const [test1_span] = target.querySelectorAll('span'); + + // a spread carrying defaultValue selects the matching option + { + const options = test1.querySelectorAll('option'); + check_options(options, [false, true, false]); + assert.htmlEqual(test1_span.innerHTML, 'b'); + } + + // value in the same spread still wins over defaultValue + { + const options = test2.querySelectorAll('option'); + check_options(options, [false, false, true]); + } + + // changing the selection and resetting goes back to defaultValue + select_option(test1.querySelectorAll('option')[2]); + select_option(test2.querySelectorAll('option')[0]); + flushSync(); + + assert.htmlEqual(test1_span.innerHTML, 'c'); + + reset.click(); + await Promise.resolve(); + flushSync(); + + check_options(test1.querySelectorAll('option'), [false, true, false]); + check_options(test2.querySelectorAll('option'), [false, true, false]); + assert.htmlEqual(test1_span.innerHTML, 'b'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/main.svelte new file mode 100644 index 0000000000..287db5e9a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-spread/main.svelte @@ -0,0 +1,26 @@ + + +
+ + + + + + + +
+ +

{selected}

diff --git a/packages/svelte/tests/server-side-rendering/samples/select-default-value/_expected.html b/packages/svelte/tests/server-side-rendering/samples/select-default-value/_expected.html new file mode 100644 index 0000000000..c2ccbafd2a --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/select-default-value/_expected.html @@ -0,0 +1,12 @@ + + + diff --git a/packages/svelte/tests/server-side-rendering/samples/select-default-value/main.svelte b/packages/svelte/tests/server-side-rendering/samples/select-default-value/main.svelte new file mode 100644 index 0000000000..ca3fc36a35 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/select-default-value/main.svelte @@ -0,0 +1,18 @@ + + + + + + +