pull/18495/merge
Paolo Ricciuti 3 days ago committed by GitHub
commit 6acd11e08c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent `selectedcontent` mutation from changing the selected option

@ -55,7 +55,13 @@ export function select_option(select, value, mounting = false) {
* @param {HTMLSelectElement} select
*/
export function init_select(select) {
var observer = new MutationObserver(() => {
var observer = new MutationObserver((entries) => {
// Mutations related to `<selectedcontent>` can never affect the option list.
// Reacting to them could revert a user-initiated selection change, because the
// records are delivered as soon as any listener returns (e.g. a delegated `input`
// handler), which can happen before the `change` handler has updated `__value`
if (entries.every(is_selectedcontent_mutation)) return;
// @ts-ignore
select_option(select, select.__value);
// Deliberately don't update the potential binding value,
@ -162,3 +168,22 @@ function get_option_value(option) {
return option.value;
}
}
/**
* Returns `true` if the mutation stems from the browser mirroring the selected
* option's content into `<selectedcontent>`, or from us replacing the
* `<selectedcontent>` element with a clone of itself
* @param {MutationRecord} entry
*/
function is_selectedcontent_mutation(entry) {
if (/** @type {Element} */ (entry.target).closest('selectedcontent') !== null) {
return true;
}
if (entry.type === 'childList') {
var nodes = [...entry.addedNodes, ...entry.removedNodes];
return nodes.length > 0 && nodes.every((node) => node.nodeName === 'SELECTEDCONTENT');
}
return false;
}

@ -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…
Cancel
Save