mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-07 07:22:21 +02:00
Squash Odysseus development history
This commit is contained in:
+48
-24
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user