mirror of https://github.com/sveltejs/svelte
fix: prevent `selectedcontent` mutation from changing the selected option (#18495)
Basically, in a situation like this
```svelte
<script lang="ts">
let value = $state('A');
</script>
<input oninput={() => {}} />
<select bind:value>
<button><selectedcontent></selectedcontent></button>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<style>
select,::picker(select){
appearance: base-select;
}
</style>
```
what happens is that the `oninput` is delegated and so it registers the
global listener (which means it listen on every `oninput` not just the
one from the input). When the select change, the `input` event is
dispatched first, the listener runs, doesn't find an `__input` handler
and returns. Now before the `change` event is emitted, the
`MutationObserver` in `init_select` is triggered by the browser updating
`selectedcontent` and invokes `select_option` with `select.__value`.
However, since the `change` event has yet to fire, `select.__value`
still has the old value, so we "reselect" that. When the change event
runs, the selected option is effectively the old one and the whole thing
breaks.
I had to add the test in `runtime-browser` because JSDom doesn't support
`selectedcontent`
pull/18685/head
parent
b2a24b0426
commit
06c9b7929a
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: prevent `selectedcontent` mutation from changing the selected option
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { ok, test } from '../../assert';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ target, assert }) {
|
||||||
|
const select = target.querySelector('select');
|
||||||
|
ok(select);
|
||||||
|
|
||||||
|
select.value = 'B';
|
||||||
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
|
||||||
|
// because another element has a delegated `oninput` handler, a global `input`
|
||||||
|
// listener runs and, once it returns, a microtask checkpoint delivers the
|
||||||
|
// mutation records *before* the `change` event — we emulate that checkpoint here
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.equal(select.value, 'B');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let value = $state('A');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input oninput={() => {}} />
|
||||||
|
<select bind:value>
|
||||||
|
<button><selectedcontent></selectedcontent></button>
|
||||||
|
<option>A</option>
|
||||||
|
<option>B</option>
|
||||||
|
<option>C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
select,::picker(select){
|
||||||
|
appearance: base-select;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in new issue