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
-11
@@ -7,6 +7,7 @@ import spinnerModule from './spinner.js';
|
||||
import * as Modals from './modalManager.js';
|
||||
import { makeWindowDraggable } from './windowDrag.js';
|
||||
import { attachColorPicker } from './colorPicker.js';
|
||||
import { bindMenuDismiss } from './escMenuStack.js';
|
||||
import {
|
||||
WEEKDAYS, MONTHS, MON_SHORT,
|
||||
CAL_PALETTE, CAL_COLORS, _CAL_CUSTOM_GRADIENT, _TYPE_PALETTE,
|
||||
@@ -426,9 +427,10 @@ function _clampDropdown(dropdown, anchorRect) {
|
||||
}
|
||||
|
||||
function _showEventMoreMenu(ev, anchor) {
|
||||
document.querySelectorAll('.cal-event-dropdown').forEach(d => d.remove());
|
||||
document.querySelectorAll('.cal-event-dropdown').forEach(d => { if (typeof d._dismiss === 'function') d._dismiss(); else d.remove(); });
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'cal-event-dropdown';
|
||||
let closeMenu = () => dropdown.remove();
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
dropdown.style.cssText = `position:fixed;z-index:10001;min-width:180px;background:var(--panel,var(--bg));border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);padding:4px;font-size:12px;top:${rect.bottom + 4}px;left:0px;visibility:hidden;`;
|
||||
|
||||
@@ -443,12 +445,12 @@ function _showEventMoreMenu(ev, anchor) {
|
||||
const _editIcon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
|
||||
|
||||
dropdown.appendChild(_item(_editIcon, 'Edit', () => {
|
||||
dropdown.remove();
|
||||
closeMenu();
|
||||
_showEventForm(ev);
|
||||
}));
|
||||
|
||||
dropdown.appendChild(_item(_trashIcon, 'Delete', async () => {
|
||||
dropdown.remove();
|
||||
closeMenu();
|
||||
const name = ev.summary ? `"${ev.summary}"` : 'this event';
|
||||
const ok = await uiModule.styledConfirm(`Delete ${name}?`, { confirmText: 'Delete', danger: true });
|
||||
if (!ok) return;
|
||||
@@ -459,14 +461,7 @@ function _showEventMoreMenu(ev, anchor) {
|
||||
dropdown._anchorRect = rect;
|
||||
_clampDropdown(dropdown, rect);
|
||||
dropdown.style.visibility = '';
|
||||
const close = (ev2) => {
|
||||
if (!dropdown.contains(ev2.target) && ev2.target !== anchor) {
|
||||
dropdown.remove();
|
||||
document.removeEventListener('click', close, true);
|
||||
}
|
||||
};
|
||||
setTimeout(() => document.addEventListener('click', close, true), 10);
|
||||
}
|
||||
closeMenu = bindMenuDismiss(dropdown, () => dropdown.remove(), (ev2) => !dropdown.contains(ev2.target) && ev2.target !== anchor);}
|
||||
|
||||
async function _createEventReminder(ev, dueDate) {
|
||||
// Store the reminder as an absolute UTC instant (with the Z suffix) so the
|
||||
|
||||
Reference in New Issue
Block a user