fix: implement ref counting for mount with same target (#17695)

### Before submitting the PR, please make sure you do the following

- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [ ] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).

### Tests and linting

- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`

Fixes #17694

Not really sure how to add a failing test here.

The code was generated by Codex 5.3, but I cleaned it up and manually
reviewed it myself. Not sure if this is the best approach to solving the
issue, though. It does add some overhead with the extra maps.

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
pull/17693/head
Abdelrahman 1 day ago committed by GitHub
parent 168702d546
commit 0565dca1f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: preserve delegated event handlers as long as one or more root components are using them

@ -149,8 +149,8 @@ export function hydrate(component, options) {
}
}
/** @type {Map<string, number>} */
const document_listeners = new Map();
/** @type {Map<EventTarget, Map<string, number>>} */
const listeners = new Map();
/**
* @template {Record<string, any>} Exports
@ -177,17 +177,25 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
// 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.
target.addEventListener(event_name, handle_event_propagation, { passive });
//
// The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals).
for (const node of [target, document]) {
var counts = listeners.get(node);
if (counts === undefined) {
counts = new Map();
listeners.set(node, counts);
}
var n = document_listeners.get(event_name);
var count = counts.get(event_name);
if (n === 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, handle_event_propagation, { passive });
document_listeners.set(event_name, 1);
} else {
document_listeners.set(event_name, n + 1);
if (count === undefined) {
node.addEventListener(event_name, handle_event_propagation, { passive });
counts.set(event_name, 1);
} else {
counts.set(event_name, count + 1);
}
}
}
};
@ -245,15 +253,20 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
return () => {
for (var event_name of registered_events) {
target.removeEventListener(event_name, handle_event_propagation);
var n = /** @type {number} */ (document_listeners.get(event_name));
if (--n === 0) {
document.removeEventListener(event_name, handle_event_propagation);
document_listeners.delete(event_name);
} else {
document_listeners.set(event_name, n);
for (const node of [target, document]) {
var counts = /** @type {Map<string, number>} */ (listeners.get(node));
var count = /** @type {number} */ (counts.get(event_name));
if (--count == 0) {
node.removeEventListener(event_name, handle_event_propagation);
counts.delete(event_name);
if (counts.size === 0) {
listeners.delete(node);
}
} else {
counts.set(event_name, count);
}
}
}

Loading…
Cancel
Save