Squash Odysseus development history

This commit is contained in:
pewdiepie-archdaemon
2026-09-11 06:04:19 +00:00
parent c9dd68d890
commit 84aa9a91de
871 changed files with 265870 additions and 27854 deletions
+48 -24
View File
@@ -1,27 +1,20 @@
// static/js/escMenuStack.js
//
// Dismissal registry for transient, ad-hoc overlays — dropdown menus and
// context popups that are built on the fly and appended to <body>, living
// OUTSIDE the .modal system. The global Escape arbiter in ui.js can find
// modals but not these, so each menu registers a dismiss callback here while
// it is open and unregisters when it closes.
// Dismissal registry for transient UI layers — dropdown menus, context
// popups, and expanded cards that live outside the .modal system. The global
// Escape arbiter in ui.js dismisses the most recently active layer first.
//
// The stack is LIFO: dismissTopMenu() closes the most-recently-opened menu
// first, so a dropdown opened on top of a modal closes before the modal does.
// The stack is LIFO: dismissTopEscapeLayer() closes the most-recently-opened
// active layer first, so a dropdown or expanded card closes before its modal.
// Deliberately DOM-free so it can be unit-tested under plain node (see
// tests/test_esc_menu_stack_js.py).
const _stack = [];
/**
* Register a menu's dismiss callback. Returns an unregister function that the
* menu MUST call from its own teardown (outside-click close, item click, etc.)
* so the stack never holds a stale entry. Calling the returned function more
* than once, or after the menu was already dismissed via Escape, is safe.
*/
export function registerMenuDismiss(dismissFn) {
/** Register any Escape-dismissable layer. */
export function registerEscapeLayer(dismissFn, isActive) {
if (typeof dismissFn !== 'function') return () => {};
const entry = { dismissFn };
const entry = { dismissFn, isActive };
_stack.push(entry);
return () => {
const i = _stack.indexOf(entry);
@@ -30,17 +23,28 @@ export function registerMenuDismiss(dismissFn) {
}
/**
* Dismiss the most-recently-registered menu, if any. Returns true when a menu
* was dismissed (so the caller can swallow the Escape key), false when nothing
* was open. The entry is popped BEFORE its callback runs, so even if a
* dismissFn forgets to unregister or throws, a single Escape closes exactly
* one menu and the stack never gets stuck.
* Backwards-compatible menu name. Menus and cards share the same stack.
*/
export function registerMenuDismiss(dismissFn) {
return registerEscapeLayer(dismissFn);
}
/** Dismiss the most-recently-registered active layer, if any. */
export function dismissTopEscapeLayer() {
while (_stack.length) {
const entry = _stack.pop();
let active = true;
try { active = typeof entry.isActive !== 'function' || entry.isActive() !== false; } catch { active = false; }
if (!active) continue;
try { entry.dismissFn(); } catch {}
return true;
}
return false;
}
/** Backwards-compatible arbiter name used by existing callers. */
export function dismissTopMenu() {
const entry = _stack.pop();
if (!entry) return false;
try { entry.dismissFn(); } catch {}
return true;
return dismissTopEscapeLayer();
}
/** Test/debug helper: number of currently-registered menus. */
@@ -100,3 +104,23 @@ export function bindMenuDismiss(el, onClose, isOutside) {
el._dismiss = close;
return close;
}
// Expanded library cards are Escape layers too. The active predicate means a
// card that was removed or collapsed without its teardown being reached is
// skipped instead of consuming a future Escape press.
export function bindExpandedCardDismiss(card, onDismiss, expandedClass = 'doclib-card-expanded') {
if (!card || typeof onDismiss !== 'function') return () => {};
unbindExpandedCardDismiss(card);
const unreg = registerEscapeLayer(onDismiss, () => (
card.isConnected !== false && card.classList?.contains(expandedClass)
));
card._escapeLayerUnregister = unreg;
return unreg;
}
export function unbindExpandedCardDismiss(card) {
if (typeof card?._escapeLayerUnregister === 'function') {
card._escapeLayerUnregister();
delete card._escapeLayerUnregister;
}
}