From f59dced33aa6808034440d85e303ea886bf49948 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 4 Oct 2026 15:25:45 +0200 Subject: [PATCH] Pithos: Cancel closes dialogs again, and Escape works after a menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cancel did nothing in the confirm dialogs (delete, switch account) and in Add another account: the dialog helper read a Cancel result of false as "keep the dialog open", a meaning meant only for actions like Copy link. A fixed result now always closes; only an action that returns false keeps the dialog. Picking an item from a ⋯ menu also left that menu's Escape listener behind, so Escape no longer closed any dialog opened from it. --- ui/app.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ui/app.js b/ui/app.js index 00bbf0e..55f37f3 100644 --- a/ui/app.js +++ b/ui/app.js @@ -91,12 +91,15 @@ function modal(title, body, buttons = []) { const onKey = (e) => { if (e.key === 'Escape') close(undefined); }; const foot = h('div', { class: 'foot' }, buttons.map((b) => h('button', { class: `btn ${b.kind || ''}`, type: b.submit ? 'submit' : 'button', - onclick: b.submit ? null : async () => { const v = b.value ? await b.value() : b.result; if (v !== false) close(v); }, + // A fixed result (Cancel: false/null) always closes; only a value() + // action that returns false keeps the dialog open (e.g. Copy link). + onclick: b.submit ? null : async () => { if (!b.value) return close(b.result); const v = await b.value(); if (v !== false) close(v); }, }, b.label))); const form = h('form', { class: 'modal', onsubmit: async (e) => { e.preventDefault(); const b = buttons.find((x) => x.submit); - const v = b.value ? await b.value() : b.result; + if (!b.value) return close(b.result); + const v = await b.value(); if (v !== false) close(v); } }, h('h2', {}, title), body, foot); const root = h('div', { class: 'modal-backdrop', onmousedown: (e) => { if (e.target === root) close(undefined); } }, form); @@ -1162,16 +1165,19 @@ function itemMenu(items) { btn.addEventListener('click', (e) => { e.stopPropagation(); document.querySelectorAll('.menu').forEach((m) => m.remove()); - const menu = h('div', { class: 'menu', role: 'menu' }, items.filter(Boolean).map(([label, run, kind]) => - h('button', { type: 'button', role: 'menuitem', class: kind || '', onclick: (ev) => { ev.stopPropagation(); menu.remove(); run(); } }, label))); + // Picking an item must also drop the listeners, or a stale Escape handler + // swallows Escape for every dialog opened afterwards. + let menu; + function done() { menu?.remove(); document.removeEventListener('mousedown', away, true); document.removeEventListener('keydown', esc, true); } + menu = h('div', { class: 'menu', role: 'menu' }, items.filter(Boolean).map(([label, run, kind]) => + h('button', { type: 'button', role: 'menuitem', class: kind || '', onclick: (ev) => { ev.stopPropagation(); done(); run(); } }, label))); document.body.append(menu); const r = btn.getBoundingClientRect(); const w = menu.offsetWidth, ht = menu.offsetHeight; menu.style.left = Math.max(8, Math.min(r.right - w, innerWidth - w - 8)) + 'px'; menu.style.top = (r.bottom + 4 + ht > innerHeight - 8 ? Math.max(8, r.top - ht - 4) : r.bottom + 4) + 'px'; - const done = () => { menu.remove(); document.removeEventListener('mousedown', away, true); document.removeEventListener('keydown', esc, true); }; - const away = (ev) => { if (!menu.contains(ev.target)) done(); }; - const esc = (ev) => { if (ev.key === 'Escape') { ev.stopPropagation(); done(); btn.focus(); } }; + function away(ev) { if (!menu.contains(ev.target)) done(); } + function esc(ev) { if (ev.key === 'Escape') { ev.stopPropagation(); done(); btn.focus(); } } setTimeout(() => { document.addEventListener('mousedown', away, true); document.addEventListener('keydown', esc, true); }, 0); menu.querySelector('button')?.focus(); });