fix: apply `<select multiple defaultValue>` by membership

`set_default_select_value` compared every option against the whole value,
so a `multiple` select never matched and its default selection was lost.
Mirror `select_option` instead: branch on `select.multiple` and test
membership, leaving a non-array value alone.

Restoring `selectedIndex` only preserves a single selection, so the
multiple branch snapshots `selectedOptions` and restores each option,
keeping the current selection untouched.
pull/18591/head
Socialpranker 1 day ago
parent 22c07e69bc
commit 72eef03b23

@ -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));
}

@ -0,0 +1,55 @@
import { test } from '../../test';
import { flushSync } from 'svelte';
export default test({
async test({ assert, target }) {
/**
* @param {NodeListOf<any>} 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');
}
});

@ -0,0 +1,27 @@
<script>
let selected = $state(['c']);
let defaultValue = $state(['a', 'c']);
</script>
<form>
<!-- defaultValue=[a, c], no value: the default selection takes effect -->
<select multiple {defaultValue}>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<!-- defaultValue=[a, c], value=[c]: the explicit value wins -->
<select multiple {defaultValue} bind:value={selected}>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<input type="reset" value="Reset" />
</form>
<p>
<span class="test-1">{selected.join(',')}</span>
</p>
Loading…
Cancel
Save