Skip to content

Commit

Permalink
fix: catch delegated events from elements moved outside the container (
Browse files Browse the repository at this point in the history
  • Loading branch information
dummdidumm committed Jan 2, 2024
1 parent 8a85059 commit 15d6308
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-bats-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: handle delegated events of elements moved outside the container
19 changes: 19 additions & 0 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -2778,13 +2782,17 @@ 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) => {
for (let i = 0; i < events.length; i++) {
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,
Expand All @@ -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
);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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>'
);
}
});
Original file line number Diff line number Diff line change
@@ -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>

1 comment on commit 15d6308

@vercel
Copy link

@vercel vercel bot commented on 15d6308 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

svelte-5-preview – ./sites/svelte-5-preview

svelte-octane.vercel.app
svelte-5-preview.vercel.app
svelte-5-preview-git-main-svelte.vercel.app
svelte-5-preview-svelte.vercel.app

Please sign in to comment.