diff --git a/.changeset/smart-turkeys-tell.md b/.changeset/smart-turkeys-tell.md new file mode 100644 index 0000000000..b4598ca14b --- /dev/null +++ b/.changeset/smart-turkeys-tell.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: ensure select value is updated upon select element removal 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 5e6241f8cd..8c0f535cd1 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -91,6 +91,28 @@ export function bind_select_value(select, get_value, update) { select.__value = value; mounting = false; }); + + // If one of the options gets removed from the DOM, the value might have changed + effect(() => { + var observer = new MutationObserver(() => { + // @ts-ignore + var value = select.__value; + select_option(select, value, mounting); + /** @type {HTMLOptionElement | null} */ + var selected_option = select.querySelector(':checked'); + if (selected_option === null || get_option_value(selected_option) !== value) { + update(''); + } + }); + + observer.observe(select, { + childList: true + }); + + return () => { + observer.disconnect(); + }; + }); } /** diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/_config.js new file mode 100644 index 0000000000..7c73cec9a2 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/_config.js @@ -0,0 +1,34 @@ +import { ok, test } from '../../test'; + +// test select binding behavior when a selected option is removed +export default test({ + skip_if_ssr: 'permanent', + + html: `

selected: a

`, + + async test({ assert, component, target }) { + const select = target.querySelector('select'); + ok(select); + const options = target.querySelectorAll('option'); + + // first option should be selected by default since no value was bound + assert.equal(component.selected, 'a'); + assert.equal(select.value, 'a'); + assert.ok(options[0].selected); + + // remove the selected item, so the bound value no longer matches anything + component.items = ['b', 'c']; + + // There's a MutationObserver + await Promise.resolve(); + + // now no option should be selected + assert.equal(select.value, ''); + assert.equal(select.selectedIndex, -1); + + assert.htmlEqual( + target.innerHTML, + `

selected:

` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/main.svelte new file mode 100644 index 0000000000..9cc18fa83b --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-select-unmatched-3/main.svelte @@ -0,0 +1,12 @@ + + +

selected: {selected}

+ +