diff --git a/script.js b/script.js index 132cd29..51b30c0 100644 --- a/script.js +++ b/script.js @@ -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(); } diff --git a/site.css b/site.css index 1830dda..835fcdb 100644 --- a/site.css +++ b/site.css @@ -11,6 +11,7 @@ .ux-overlays { position: absolute; inset: 0; pointer-events: none; } .ux-view { width: 100%; } .ux-view--overlay { position: absolute; inset: 0; z-index: 10; pointer-events: auto; } +.ux-view[hidden] { display: none !important; } .is-sr-only { position: absolute;