fix: don't throw state_unsafe_mutation when the focused element is removed with bind:activeElement (#18921)

Fixes #15742

With `<svelte:document bind:activeElement />`, clicking a button that
removes itself throws `state_unsafe_mutation`. Removing the focused
element makes the browser fire `focusout` while the `{#if}` block effect
is running, and the binding's listener sets the state from inside that
effect.

`bind_active_element` now calls `update` inside
`without_reactive_context`, like event handlers created with
`create_event` and `bind_window_scroll` already do.
pull/18896/merge
911RS 12 hours ago committed by GitHub
parent fe4fb460b1
commit 3f885e3016
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't throw `state_unsafe_mutation` when the focused element is removed while `bind:activeElement` is used

@ -1,4 +1,4 @@
import { listen } from './shared.js';
import { listen, without_reactive_context } from './shared.js';
/**
* @param {(activeElement: Element | null) => void} update
@ -12,6 +12,7 @@ export function bind_active_element(update) {
return;
}
update(document.activeElement);
// Removing the focused element fires `focusout` while an effect is running
without_reactive_context(() => update(document.activeElement));
});
}

@ -0,0 +1,27 @@
import { test, ok } from '../../assert';
export default test({
mode: ['client'],
async test({ assert, target }) {
/** @type {string[]} */
const errors = [];
/** @param {ErrorEvent} event */
const on_error = (event) => {
errors.push(event.message);
event.preventDefault();
};
window.addEventListener('error', on_error);
const button = target.querySelector('button');
ok(button);
button.focus();
button.click();
await new Promise((r) => setTimeout(r, 0));
window.removeEventListener('error', on_error);
assert.equal(errors.length, 0, errors.join('\n'));
assert.equal(target.querySelector('button'), null);
}
});

@ -0,0 +1,10 @@
<script>
let activeElement = $state();
let show = $state(true);
</script>
<svelte:document bind:activeElement />
{#if show}
<button onclick={() => (show = false)}>hide</button>
{/if}
Loading…
Cancel
Save