diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index dc66a9c071..083dfcbd4d 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -1,7 +1,7 @@ /** @import { Blocker, Effect } from '#client' */ import { DEV } from 'esm-env'; import { hydrating, set_hydrating } from '../hydration.js'; -import { get_descriptors, get_prototype_of } from '../../../shared/utils.js'; +import { get_descriptors, get_prototype_of, is_array } from '../../../shared/utils.js'; import { create_event, delegate, delegated, event, event_symbol } from './events.js'; import { add_form_reset_listener, autofocus } from './misc.js'; import * as w from '../../warnings.js'; @@ -179,6 +179,27 @@ export function set_default_select_value(select, value) { var has_explicit_value = '__value' in select; var selected_index = select.selectedIndex; + if (select.multiple) { + // a `multiple` select takes a list of values, so an option is part of the + // default selection when it is a member of that list — mirroring how + // `select_option` applies the current value + if (!is_array(value)) return; + + var selected = new Set([...select.selectedOptions]); + + for (var multi_option of select.options) { + set_selected(multi_option, value.includes(get_option_value(multi_option))); + } + + if (has_explicit_value) { + for (var option_to_restore of select.options) { + option_to_restore.selected = selected.has(option_to_restore); + } + } + + return; + } + for (var option of select.options) { set_selected(option, is(get_option_value(option), value)); } diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/_config.js new file mode 100644 index 0000000000..c220aa0d8a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/_config.js @@ -0,0 +1,55 @@ +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 {HTMLSelectElement} select + * @param {number[]} indexes + */ + function select_options(select, indexes) { + const options = select.querySelectorAll('option'); + for (let i = 0; i < options.length; i++) { + options[i].selected = indexes.includes(i); + } + select.dispatchEvent(new Event('change', { bubbles: true })); + } + + const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]')); + const [test1, test2] = target.querySelectorAll('select'); + const span = /** @type {HTMLSpanElement} */ (target.querySelector('span')); + + // every option named by defaultValue is selected, not just none of them + check_options(test1.querySelectorAll('option'), [true, false, true]); + + // an explicit value still wins over defaultValue + check_options(test2.querySelectorAll('option'), [false, false, true]); + assert.htmlEqual(span.innerHTML, 'c'); + + // change both selections, then reset the form + select_options(test1, [1]); + select_options(test2, [0, 1]); + flushSync(); + + assert.htmlEqual(span.innerHTML, 'a,b'); + + reset.click(); + await Promise.resolve(); + flushSync(); + + // reset restores the default selection in both cases + check_options(test1.querySelectorAll('option'), [true, false, true]); + check_options(test2.querySelectorAll('option'), [true, false, true]); + assert.htmlEqual(span.innerHTML, 'a,c'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/main.svelte new file mode 100644 index 0000000000..c214e3ba10 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-select-multiple/main.svelte @@ -0,0 +1,27 @@ + + +
+ + + + + + + +
+ +

+ {selected.join(',')} +