mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
fix: make transient dropdown/popup menus close on Escape
The global Escape arbiter in ui.js only sees `.modal` elements, so the many ad-hoc dropdowns and context popups that are built on the fly and appended to <body> ignored Escape entirely: document-library card/chat menus, chat context/stats/overflow popups, cookbook serve & running menus, calendar event menus, and compare pane menus. Add a small DOM-free dismissal registry (static/js/escMenuStack.js). Menus register a dismiss callback while open, and the arbiter closes the most-recently-opened one first, so a menu opened over a modal closes before the modal. bindMenuDismiss() wires the ubiquitous "append-to-body, close on outside click" idiom to both the outside-click listener and the Escape stack in one call, and dismissOrRemove() lets the pre-existing bulk removers (scroll/swipe/ modal-dismiss cleanup, reopen sweeps) tear a menu down through its real teardown instead of orphaning its stack entry. Covers ~14 menus across documentLibrary, chatRenderer, cookbookServe, cookbookRunning, calendar, and compare/panes. Every teardown path — item click, outside click, swipe, toggle, rebuild, bulk cleanup — routes through the registry so no entry is ever stranded. tests/test_esc_menu_stack_js.py pins the registry's LIFO and exactly-one-per-press guarantees (node-driven; skips when node is absent).
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import uiModule from './ui.js';
|
||||
import { _diagnose, _showDiagnosis, _clearDiagnosis } from './cookbook-diagnosis.js';
|
||||
import { registerMenuDismiss } from './escMenuStack.js';
|
||||
|
||||
// Human-friendly badge label for a task's internal status. Avoids surfacing
|
||||
// the word "error" in the sidebar — a server the user stopped or one that
|
||||
@@ -1546,7 +1547,7 @@ export function _renderRunningTab() {
|
||||
el.addEventListener('touchcancel', _lpCancel, { passive: true });
|
||||
menuBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
document.querySelectorAll('.cookbook-task-dropdown').forEach(d => d.remove());
|
||||
document.querySelectorAll('.cookbook-task-dropdown').forEach(d => { if (typeof d._dismiss === 'function') d._dismiss(); else d.remove(); });
|
||||
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'cookbook-task-dropdown';
|
||||
@@ -1696,7 +1697,7 @@ export function _renderRunningTab() {
|
||||
const ic = _MENU_ICONS[item.action] || '';
|
||||
div.innerHTML = `<span style="display:inline-flex;flex-shrink:0;opacity:0.7;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${ic}</svg></span><span>${item.label}</span>`;
|
||||
div.addEventListener('click', () => {
|
||||
dropdown.remove();
|
||||
_cleanup();
|
||||
if (item.custom) { item.custom(); return; }
|
||||
el.querySelector('.cookbook-task-action-' + item.action)?.click();
|
||||
});
|
||||
@@ -1736,17 +1737,21 @@ export function _renderRunningTab() {
|
||||
// fixed position no longer matches the originating ⋮ button, so
|
||||
// it visually drifts. Matches the email kebab behaviour.
|
||||
const scrollClose = () => _cleanup();
|
||||
let _unreg = () => {};
|
||||
const _cleanup = () => {
|
||||
_unreg(); _unreg = () => {};
|
||||
dropdown.remove();
|
||||
document.removeEventListener('click', closeHandler);
|
||||
window.removeEventListener('scroll', scrollClose, true);
|
||||
window.visualViewport?.removeEventListener('scroll', scrollClose);
|
||||
};
|
||||
dropdown._dismiss = _cleanup;
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeHandler);
|
||||
window.addEventListener('scroll', scrollClose, true);
|
||||
window.visualViewport?.addEventListener('scroll', scrollClose);
|
||||
}, 0);
|
||||
_unreg = registerMenuDismiss(_cleanup);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user