Pithos: Cancel closes dialogs again, and Escape works after a menu

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.
This commit is contained in:
Local Dev 2026-10-04 15:25:45 +02:00
parent 068de477df
commit f59dced33a

View file

@ -91,12 +91,15 @@ function modal(title, body, buttons = []) {
const onKey = (e) => { if (e.key === 'Escape') close(undefined); }; const onKey = (e) => { if (e.key === 'Escape') close(undefined); };
const foot = h('div', { class: 'foot' }, buttons.map((b) => h('button', { const foot = h('div', { class: 'foot' }, buttons.map((b) => h('button', {
class: `btn ${b.kind || ''}`, type: b.submit ? 'submit' : '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))); }, b.label)));
const form = h('form', { class: 'modal', onsubmit: async (e) => { const form = h('form', { class: 'modal', onsubmit: async (e) => {
e.preventDefault(); e.preventDefault();
const b = buttons.find((x) => x.submit); 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); if (v !== false) close(v);
} }, h('h2', {}, title), body, foot); } }, h('h2', {}, title), body, foot);
const root = h('div', { class: 'modal-backdrop', onmousedown: (e) => { if (e.target === root) close(undefined); } }, form); 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) => { btn.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
document.querySelectorAll('.menu').forEach((m) => m.remove()); document.querySelectorAll('.menu').forEach((m) => m.remove());
const menu = h('div', { class: 'menu', role: 'menu' }, items.filter(Boolean).map(([label, run, kind]) => // Picking an item must also drop the listeners, or a stale Escape handler
h('button', { type: 'button', role: 'menuitem', class: kind || '', onclick: (ev) => { ev.stopPropagation(); menu.remove(); run(); } }, label))); // 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); document.body.append(menu);
const r = btn.getBoundingClientRect(); const r = btn.getBoundingClientRect();
const w = menu.offsetWidth, ht = menu.offsetHeight; const w = menu.offsetWidth, ht = menu.offsetHeight;
menu.style.left = Math.max(8, Math.min(r.right - w, innerWidth - w - 8)) + 'px'; 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'; 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); }; function away(ev) { if (!menu.contains(ev.target)) done(); }
const away = (ev) => { if (!menu.contains(ev.target)) done(); }; function esc(ev) { if (ev.key === 'Escape') { ev.stopPropagation(); done(); btn.focus(); } }
const esc = (ev) => { if (ev.key === 'Escape') { ev.stopPropagation(); done(); btn.focus(); } };
setTimeout(() => { document.addEventListener('mousedown', away, true); document.addEventListener('keydown', esc, true); }, 0); setTimeout(() => { document.addEventListener('mousedown', away, true); document.addEventListener('keydown', esc, true); }, 0);
menu.querySelector('button')?.focus(); menu.querySelector('button')?.focus();
}); });