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 401891b41b..adc9ec72dd 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 @@ -520,20 +520,28 @@ export function RegularElement(node, context) { // deferred from the attribute loop above, so that the options it selects from // have been created and had their values assigned if (!has_spread && name === 'select') { - for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { - if (get_attribute_name(node, attribute) === 'defaultValue') { - const { value, has_state } = build_attribute_value(attribute.value, context, (v, m) => - context.state.memoizer.add(v, m) - ); + const default_value = /** @type {AST.Attribute[]} */ (attributes).find( + (attribute) => get_attribute_name(node, attribute) === 'defaultValue' + ); - const update = b.stmt(b.call('$.set_default_select_value', node_id, value)); + if (default_value) { + const { value, has_state } = build_attribute_value(default_value.value, context, (v, m) => + context.state.memoizer.add(v, m) + ); - (has_state ? context.state.update : context.state.init).push(update); - if (!bindings.has('value')) { - context.state.init.push(b.stmt(b.call('$.init_select', node_id))); - } - break; - } + (has_state ? context.state.update : context.state.init).push( + b.stmt(b.call('$.set_default_select_value', node_id, value)) + ); + } + + const value_attribute = lookup.get('value'); + const dynamic_value = + value_attribute !== undefined && + value_attribute.value !== true && + !is_text_attribute(value_attribute); + + if (default_value || dynamic_value || bindings.has('value')) { + context.state.init.push(b.stmt(b.call('$.init_select', node_id))); } } @@ -772,10 +780,6 @@ function build_element_special_value_attribute( } else { state.init.push(build_update(value)); } - - if (is_select_with_value) { - state.init.push(b.stmt(b.call('$.init_select', node_id))); - } } /** diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 7439db56e9..0fe1b8b2d3 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -525,7 +525,7 @@ export function attribute_effect( var select = /** @type {HTMLSelectElement} */ (element); if ('defaultValue' in next) { - set_default_select_value(select, next.defaultValue, false); + set_default_select_value(select, next.defaultValue); } if ('value' in next) { @@ -558,7 +558,7 @@ export function attribute_effect( var attrs = /** @type {Record} */ (prev); if ('defaultValue' in attrs) { - set_default_select_value(select, attrs.defaultValue, true); + set_default_select_value(select, attrs.defaultValue); } select_option(select, attrs.value, true); diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js index fd1ebb92b8..cac7531ed2 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -20,33 +20,54 @@ export function set_selected(option, selected) { } /** - * Sets the options a form reset should restore without changing the current selection. - * The initial call is allowed to establish the current selection when no value exists. + * Sets the options a form reset should restore. The first call selects + * them if nothing has set a value, later calls leave the current selection alone. * @param {HTMLSelectElement} select * @param {any} value - * @param {boolean} [mounting] */ -export function set_default_select_value(select, value, mounting = !('__defaultValue' in select)) { - // The DOM cannot recover unmatched, object or multiple defaults from selected options. - // Keep the requested value so option mutations can reapply it; property presence also - // distinguishes the initial application from later updates when the value is undefined. +export function set_default_select_value(select, value) { + var mounting = !('__defaultValue' in select); + // @ts-expect-error + if (!mounting && select.__defaultValue === value) return; // @ts-expect-error select.__defaultValue = value; - var values = select.multiple ? (value == null ? [] : value) : null; + apply_default_select_value(select, !mounting || '__value' in select); +} - if (select.multiple && !is_array(values)) return; - var selected = !mounting || '__value' in select ? new Set(select.selectedOptions) : null; +/** + * Marks the options matching `__defaultValue` as selected. Without `preserve` + * a newly matching option gets selected, as an inserted ` + + + + + + + + + + + + + + + +

{unmatched} {String(nothing)}

diff --git a/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/_config.js b/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/_config.js new file mode 100644 index 0000000000..411a7868f7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/_config.js @@ -0,0 +1,30 @@ +import { test } from '../../test'; + +let observers = 0; +const MutationObserver = globalThis.MutationObserver; + +export default test({ + before_test() { + observers = 0; + globalThis.MutationObserver = class extends MutationObserver { + /** @param {MutationCallback} callback */ + constructor(callback) { + super(callback); + observers++; + } + }; + }, + + after_test() { + globalThis.MutationObserver = MutationObserver; + }, + + test({ assert, target }) { + const selects = target.querySelectorAll('select'); + for (const select of selects) { + assert.equal(select.selectedIndex, 0); + assert.equal(select.options[1].defaultSelected, true); + } + assert.equal(observers, selects.length); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/main.svelte b/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/main.svelte new file mode 100644 index 0000000000..7754b3c6fd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-default-value-single-observer/main.svelte @@ -0,0 +1,21 @@ + + + + + + +