fix: catch delegated events from elements moved outside the container (#10060)

fixes #9777
pull/10052/head
Simon H 1 year ago committed by GitHub
parent 8a8505928e
commit 15d6308d60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle delegated events of elements moved outside the container

@ -1280,6 +1280,10 @@ function handle_event_propagation(root_element, event) {
const handled_at = event.__root;
if (handled_at) {
const at_idx = path.indexOf(handled_at);
if (at_idx !== -1 && root_element === document) {
// This is the fallback document listener but the event was already handled -> ignore
return;
}
if (at_idx < path.indexOf(root_element)) {
path_idx = at_idx;
}
@ -2778,6 +2782,7 @@ export function mount(component, options) {
set_current_hydration_fragment(previous_hydration_fragment);
}
const bound_event_listener = handle_event_propagation.bind(null, container);
const bound_document_event_listener = handle_event_propagation.bind(null, document);
/** @param {Array<string>} events */
const event_handle = (events) => {
@ -2785,6 +2790,9 @@ export function mount(component, options) {
const event_name = events[i];
if (!registered_events.has(event_name)) {
registered_events.add(event_name);
// Add the event listener to both the container and the document.
// The container listener ensures we catch events from within in case
// the outer content stops propagation of the event.
container.addEventListener(
event_name,
bound_event_listener,
@ -2794,6 +2802,17 @@ export function mount(component, options) {
}
: undefined
);
// The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals).
document.addEventListener(
event_name,
bound_document_event_listener,
PassiveDelegatedEvents.includes(event_name)
? {
passive: true
}
: undefined
);
}
}
};

@ -0,0 +1,23 @@
import { test } from '../../test';
// Tests that event delegation still works when the element with the event listener is moved outside the container
export default test({
async test({ assert, target }) {
const btn1 = target.parentElement?.querySelector('button');
const btn2 = target.querySelector('button');
btn1?.click();
await Promise.resolve();
assert.htmlEqual(
target.parentElement?.innerHTML ?? '',
'<main><div><button>clicks: 1</button></div></main><button>clicks: 1</button>'
);
btn2?.click();
await Promise.resolve();
assert.htmlEqual(
target.parentElement?.innerHTML ?? '',
'<main><div><button>clicks: 2</button></div></main><button>clicks: 2</button>'
);
}
});

@ -0,0 +1,12 @@
<script>
let count = $state(0);
let el;
$effect(() => {
document.getElementsByTagName('body')[0].appendChild(el);
})
</script>
<div>
<button bind:this={el} onclick={() => count++}>clicks: {count}</button>
<button onclick={() => count++}>clicks: {count}</button>
</div>
Loading…
Cancel
Save