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