From e9a1764f425912fae651995c467925914dfd4fba Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 31 Jul 2026 21:33:33 +0200 Subject: [PATCH] fix: themed-dialog click-to-dismiss fires on any shadow-internal click, not just the backdrop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hive-dialog's dismiss-on-backdrop-click handler checked e.target === this (the host). Shadow DOM event retargeting sets e.target to the host for ANY click that originated inside the shadow tree once it reaches a listener attached on the host itself, not just clicks that actually hit the host's own rendering — so the check was true for every click inside .box that no other element's listener consumed first (title, message, a bare checkbox row with no button to intercept it), immediately closing the whole dialog. Reported by mara: clicking a checkbox in a confirmation dialog (e.g. the restart dialog) dismissed the dialog instead of toggling the box. Switched to e.composedPath()[0] === this, the true original target unaffected by retargeting — true only for a genuine backdrop click. --- frontend/packages/shared/src/modal.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/packages/shared/src/modal.js b/frontend/packages/shared/src/modal.js index a0bb2471..ef0a7a52 100644 --- a/frontend/packages/shared/src/modal.js +++ b/frontend/packages/shared/src/modal.js @@ -105,7 +105,19 @@ class HiveDialog extends HTMLElement { root.append(box); this.addEventListener('click', (e) => { - if (dismissable && e.target === this) done(null); + // `e.target` is retargeted to `this` (the host) for ANY click that + // originated inside the shadow tree, once it bubbles out to a + // listener attached on the host itself — per spec, retargeting + // applies to every ancestor outside the shadow tree, and the host + // element's own light-DOM-side listeners count as outside. So + // `e.target === this` was true for every click inside `.box` too + // (title, message, a bare checkbox row with no button to consume + // it first), not just genuine backdrop clicks — dismissing the + // whole dialog on, say, a checkbox click. `composedPath()[0]` is + // the true original target, unaffected by retargeting: it's `this` + // only when nothing inside the shadow tree was actually under the + // cursor, i.e. a real backdrop click. + if (dismissable && e.composedPath()[0] === this) done(null); }); document.addEventListener('keydown', onKey, true);