Refine overlay stacking behavior

This commit is contained in:
Jordan Wages 2025-09-18 18:42:52 -05:00
commit 0450ec9e1c
2 changed files with 49 additions and 5 deletions

View file

@ -59,7 +59,7 @@
// UX Manager
const UX = (() => {
let currentBase = null; // { view, el }
const overlayStack = []; // [{ view, el, lastFocus }]
const overlayStack = []; // [{ view, el, lastFocus, isHidden, isClosing }]
function ensureViewEl(view) {
if (!view || !view.el) throw new Error('Invalid view');
@ -67,6 +67,42 @@
return view.el;
}
function syncOverlayVisibility() {
const topIndex = overlayStack.length - 1;
overlayStack.forEach((entry, idx) => {
const { el, view } = entry;
const shouldHide = idx !== topIndex;
if (shouldHide) {
if (!el.hasAttribute('hidden')) el.setAttribute('hidden', '');
el.setAttribute('aria-hidden', 'true');
el.setAttribute('inert', '');
el.inert = true;
if (!entry.isHidden) {
entry.isHidden = true;
if (view && typeof view.onHide === 'function') view.onHide();
}
} else {
if (el.hasAttribute('hidden')) el.removeAttribute('hidden');
el.removeAttribute('aria-hidden');
el.removeAttribute('inert');
el.inert = false;
entry.isHidden = false;
}
});
if (currentBase && currentBase.el) {
if (overlayStack.length > 0) {
currentBase.el.setAttribute('aria-hidden', 'true');
currentBase.el.setAttribute('inert', '');
currentBase.el.inert = true;
} else {
currentBase.el.removeAttribute('aria-hidden');
currentBase.el.removeAttribute('inert');
currentBase.el.inert = false;
}
}
}
async function replace(view) {
const el = ensureViewEl(view);
const prev = currentBase;
@ -80,6 +116,7 @@
}
$uxRoot.appendChild(el);
currentBase = { view, el };
syncOverlayVisibility();
await fadeIn(el);
if (typeof view.onShow === 'function') view.onShow();
}
@ -88,21 +125,27 @@
const el = ensureViewEl(view);
el.classList.add('ux-view--overlay');
const lastFocus = document.activeElement;
const prevEntry = overlayStack.length ? overlayStack[overlayStack.length - 1] : null;
const entry = { view, el, lastFocus, isHidden: false, isClosing: false, prev: prevEntry };
overlayStack.push(entry);
$uxOverlays.appendChild(el);
syncOverlayVisibility();
await fadeIn(el);
overlayStack.push({ view, el, lastFocus });
$uxRoot.setAttribute('aria-hidden', 'true');
if (typeof view.onShow === 'function') view.onShow();
}
async function closeTop() {
const top = overlayStack.pop();
const top = overlayStack[overlayStack.length - 1];
if (!top) return;
if (top.isClosing) return;
top.isClosing = true;
const { view, el, lastFocus } = top;
if (view && typeof view.onHide === 'function') view.onHide();
await fadeOut(el);
if (el.parentNode) el.parentNode.removeChild(el);
if (typeof view.destroy === 'function') view.destroy();
if (overlayStack.length === 0) $uxRoot.removeAttribute('aria-hidden');
overlayStack.pop();
syncOverlayVisibility();
if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus();
}