pull/18867/merge
Dex 3 days ago committed by GitHub
commit 6bfa239914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: fire delegated event handlers when the element was disabled mid-dispatch

@ -291,7 +291,13 @@ export function handle_event_propagation(event) {
(!(/** @type {any} */ (current_target).disabled) ||
// DOM could've been updated already by the time this is reached, so we check this as well
// -> the target could not have been disabled because it emits the event in the first place
event.target === current_target)
event.target === current_target ||
// the element could have been disabled mid-dispatch by an earlier listener in the
// propagation path (e.g. an `on()` handler on the same element whose effect flushed
// at a microtask checkpoint), in which case the handler should still fire. Svelte
// records the element an event was already observed reaching as `handled_at`, so
// reaching that same element proves it was only disabled after the event arrived
current_target === handled_at)
) {
delegated.call(current_target, event);
}

@ -116,6 +116,10 @@ function normalize_children(node) {
// The following two functions need to be in here; if we had them in test.ts, esbuild would choke on node imports etc
// `__real_click` performs a real (trusted) mouse click at viewport coordinates.
// Pass `real_click: true` in the test config to expose `window.__real_click`
// for dispatching real (trusted) mouse clicks from the test.
/**
* @template Props
* @param {{
@ -154,6 +158,7 @@ function normalize_children(node) {
* InputEvent: typeof InputEvent;
* KeyboardEvent: typeof KeyboardEvent;
* MouseEvent: typeof MouseEvent;
* __real_click: (x: number, y: number) => Promise<void>;
* };
* waitUntil: (fn: any, ms?: number) => Promise<void>;
* }) => void;
@ -161,6 +166,7 @@ function normalize_children(node) {
* immutable?: boolean;
* dev?: boolean;
* warnings?: Warning[];
* real_click?: boolean;
*}} args
*/
export function test(args) {

@ -0,0 +1,26 @@
import { ok, test } from '../../assert';
export default test({
// the test performs a real mouse click so the event is trusted
// (`isTrusted === true`), which cannot be faked from inside the page
real_click: true,
async test({ assert, target, window, waitUntil }) {
const span = target.querySelector('span');
ok(span);
const rect = span.getBoundingClientRect();
// a real user click: the browser flushes effects at the microtask
// checkpoint between listeners, so the `{@attach}` handler's state
// write disables the button mid-dispatch, before the event reaches
// the delegated `onclick` at the root (#18070)
await window.__real_click(rect.x + rect.width / 2, rect.y + rect.height / 2);
await waitUntil(() => target.querySelector('p')?.textContent === 'attach: 1, onclick: 1');
assert.htmlEqual(
target.innerHTML,
`<button disabled=""><span>click me</span></button><p>attach: 1, onclick: 1</p>`
);
}
});

@ -0,0 +1,33 @@
<script>
import { on } from 'svelte/events';
let attach_count = $state(0);
let onclick_count = $state(0);
// throttle, as in https://github.com/sveltejs/svelte/issues/18070
function throttle(node) {
let throttled = $state(false);
const off = on(node, 'click', () => {
attach_count += 1;
throttled = true;
});
$effect(() => {
if (throttled) {
node.disabled = true;
return () => {
node.disabled = false;
};
}
});
return () => off();
}
</script>
<button {@attach throttle} onclick={() => (onclick_count += 1)}>
<span>click me</span>
</button>
<p>attach: {attach_count}, onclick: {onclick_count}</p>

@ -195,6 +195,14 @@ async function run_test(
try {
const page = await browser.newPage();
if (config.real_click) {
// Expose a way to perform a real (trusted) mouse click from inside the page.
// Trusted events (`event.isTrusted === true`) cannot be synthesized from
// inside the page, so tests covering trusted-only behavior need this.
await page.exposeFunction('__real_click', (x: number, y: number) => page.mouse.click(x, y));
}
page.on('console', (message) => {
let method = message.type();
// @ts-ignore

Loading…
Cancel
Save