Skip to content

Commit c564c77

Browse files
authored
fix: ensure select value is updated upon select element removal (#10846)
* fix: ensure select value is updated upon select element removal * lint * lol
1 parent 682f4a6 commit c564c77

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

.changeset/smart-turkeys-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: ensure select value is updated upon select element removal

packages/svelte/src/internal/client/dom/elements/bindings/select.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,28 @@ export function bind_select_value(select, get_value, update) {
9191
select.__value = value;
9292
mounting = false;
9393
});
94+
95+
// If one of the options gets removed from the DOM, the value might have changed
96+
effect(() => {
97+
var observer = new MutationObserver(() => {
98+
// @ts-ignore
99+
var value = select.__value;
100+
select_option(select, value, mounting);
101+
/** @type {HTMLOptionElement | null} */
102+
var selected_option = select.querySelector(':checked');
103+
if (selected_option === null || get_option_value(selected_option) !== value) {
104+
update('');
105+
}
106+
});
107+
108+
observer.observe(select, {
109+
childList: true
110+
});
111+
112+
return () => {
113+
observer.disconnect();
114+
};
115+
});
94116
}
95117

96118
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ok, test } from '../../test';
2+
3+
// test select binding behavior when a selected option is removed
4+
export default test({
5+
skip_if_ssr: 'permanent',
6+
7+
html: `<p>selected: a</p><select><option value="a">a</option><option value="b">b</option><option value="c">c</option></select>`,
8+
9+
async test({ assert, component, target }) {
10+
const select = target.querySelector('select');
11+
ok(select);
12+
const options = target.querySelectorAll('option');
13+
14+
// first option should be selected by default since no value was bound
15+
assert.equal(component.selected, 'a');
16+
assert.equal(select.value, 'a');
17+
assert.ok(options[0].selected);
18+
19+
// remove the selected item, so the bound value no longer matches anything
20+
component.items = ['b', 'c'];
21+
22+
// There's a MutationObserver
23+
await Promise.resolve();
24+
25+
// now no option should be selected
26+
assert.equal(select.value, '');
27+
assert.equal(select.selectedIndex, -1);
28+
29+
assert.htmlEqual(
30+
target.innerHTML,
31+
`<p>selected:</p><select><option value="b">b</option><option value="c">c</option></select>`
32+
);
33+
}
34+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
export let selected;
3+
export let items = ['a', 'b', 'c'];
4+
</script>
5+
6+
<p>selected: {selected}</p>
7+
8+
<select bind:value={selected}>
9+
{#each items as letter}
10+
<option>{letter}</option>
11+
{/each}
12+
</select>

0 commit comments

Comments
 (0)