From 8f1fc0866331f0b402067200a5577cab3747af15 Mon Sep 17 00:00:00 2001 From: abdel-17 Date: Fri, 13 Feb 2026 06:37:07 +0200 Subject: [PATCH 1/5] fix: implement ref counting for mount with same target --- packages/svelte/src/internal/client/render.js | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c09f5fdd0566..8f70fa759cf1 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -152,6 +152,9 @@ export function hydrate(component, options) { /** @type {Map} */ const document_listeners = new Map(); +/** @type {Map>} */ +const target_listeners = new Map(); + /** * @template {Record} Exports * @param {ComponentType> | Component} Component @@ -177,7 +180,21 @@ 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 }); + var target_map = target_listeners.get(target); + + if (target_map === undefined) { + target_map = new Map(); + target_listeners.set(target, target_map); + } + + var target_count = target_map.get(event_name); + + if (target_count === undefined) { + target.addEventListener(event_name, handle_event_propagation, { passive }); + target_map.set(event_name, 1); + } else { + target_map.set(event_name, target_count + 1); + } var n = document_listeners.get(event_name); @@ -245,15 +262,27 @@ 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 target_map = /** @type {Map} */ (target_listeners.get(target)); + var target_count = /** @type {number} */ (target_map.get(event_name)); + + if (--target_count == 0) { + target.removeEventListener(event_name, handle_event_propagation); + target_map.delete(event_name); + + if (target_map.size === 0) { + target_listeners.delete(target); + } + } else { + target_map.set(event_name, target_count - 1); + } - var n = /** @type {number} */ (document_listeners.get(event_name)); + var document_count = /** @type {number} */ (document_listeners.get(event_name)); - if (--n === 0) { + if (--document_count === 0) { document.removeEventListener(event_name, handle_event_propagation); document_listeners.delete(event_name); } else { - document_listeners.set(event_name, n); + document_listeners.set(event_name, document_count); } } From d7cc4afd3084c30b63682d88bd0f3d28a187c29d Mon Sep 17 00:00:00 2001 From: abdel-17 Date: Fri, 13 Feb 2026 06:47:24 +0200 Subject: [PATCH 2/5] chore: add changeset --- .changeset/upset-spiders-study.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/upset-spiders-study.md diff --git a/.changeset/upset-spiders-study.md b/.changeset/upset-spiders-study.md new file mode 100644 index 000000000000..d49b5213a80f --- /dev/null +++ b/.changeset/upset-spiders-study.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: unmounting can break event handling From f953156153a2d8bada94e23691ce9bb186599702 Mon Sep 17 00:00:00 2001 From: abdel-17 Date: Fri, 13 Feb 2026 06:54:53 +0200 Subject: [PATCH 3/5] fix: double decrement --- packages/svelte/src/internal/client/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 8f70fa759cf1..f18bfee9a3ea 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -273,7 +273,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro target_listeners.delete(target); } } else { - target_map.set(event_name, target_count - 1); + target_map.set(event_name, target_count); } var document_count = /** @type {number} */ (document_listeners.get(event_name)); From 368d184ba46cefba5311cb7fa76c9273b9f7735d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 13 Feb 2026 09:39:17 -0500 Subject: [PATCH 4/5] reuse the map for document --- packages/svelte/src/internal/client/render.js | 78 ++++++++----------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index f18bfee9a3ea..0d5bc6cb4928 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -149,11 +149,8 @@ export function hydrate(component, options) { } } -/** @type {Map} */ -const document_listeners = new Map(); - /** @type {Map>} */ -const target_listeners = new Map(); +const listeners = new Map(); /** * @template {Record} Exports @@ -180,31 +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. - var target_map = target_listeners.get(target); - - if (target_map === undefined) { - target_map = new Map(); - target_listeners.set(target, target_map); - } - - var target_count = target_map.get(event_name); - - if (target_count === undefined) { - target.addEventListener(event_name, handle_event_propagation, { passive }); - target_map.set(event_name, 1); - } else { - target_map.set(event_name, target_count + 1); - } + // + // 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); + } } } }; @@ -262,27 +253,20 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro return () => { for (var event_name of registered_events) { - var target_map = /** @type {Map} */ (target_listeners.get(target)); - var target_count = /** @type {number} */ (target_map.get(event_name)); - - if (--target_count == 0) { - target.removeEventListener(event_name, handle_event_propagation); - target_map.delete(event_name); - - if (target_map.size === 0) { - target_listeners.delete(target); + for (const node of [target, document]) { + var counts = /** @type {Map} */ (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); } - } else { - target_map.set(event_name, target_count); - } - - var document_count = /** @type {number} */ (document_listeners.get(event_name)); - - if (--document_count === 0) { - document.removeEventListener(event_name, handle_event_propagation); - document_listeners.delete(event_name); - } else { - document_listeners.set(event_name, document_count); } } From 23cedacbf96571108fec1ff4c6c5028f777c67f9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 13 Feb 2026 11:18:21 -0500 Subject: [PATCH 5/5] Apply suggestion from @Rich-Harris --- .changeset/upset-spiders-study.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/upset-spiders-study.md b/.changeset/upset-spiders-study.md index d49b5213a80f..3f908280381b 100644 --- a/.changeset/upset-spiders-study.md +++ b/.changeset/upset-spiders-study.md @@ -2,4 +2,4 @@ 'svelte': patch --- -fix: unmounting can break event handling +fix: preserve delegated event handlers as long as one or more root components are using them