Refine overlay stacking behavior
This commit is contained in:
parent
8b2bc9bb1c
commit
0450ec9e1c
2 changed files with 49 additions and 5 deletions
53
script.js
53
script.js
|
@ -59,7 +59,7 @@
|
||||||
// UX Manager
|
// UX Manager
|
||||||
const UX = (() => {
|
const UX = (() => {
|
||||||
let currentBase = null; // { view, el }
|
let currentBase = null; // { view, el }
|
||||||
const overlayStack = []; // [{ view, el, lastFocus }]
|
const overlayStack = []; // [{ view, el, lastFocus, isHidden, isClosing }]
|
||||||
|
|
||||||
function ensureViewEl(view) {
|
function ensureViewEl(view) {
|
||||||
if (!view || !view.el) throw new Error('Invalid view');
|
if (!view || !view.el) throw new Error('Invalid view');
|
||||||
|
@ -67,6 +67,42 @@
|
||||||
return view.el;
|
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) {
|
async function replace(view) {
|
||||||
const el = ensureViewEl(view);
|
const el = ensureViewEl(view);
|
||||||
const prev = currentBase;
|
const prev = currentBase;
|
||||||
|
@ -80,6 +116,7 @@
|
||||||
}
|
}
|
||||||
$uxRoot.appendChild(el);
|
$uxRoot.appendChild(el);
|
||||||
currentBase = { view, el };
|
currentBase = { view, el };
|
||||||
|
syncOverlayVisibility();
|
||||||
await fadeIn(el);
|
await fadeIn(el);
|
||||||
if (typeof view.onShow === 'function') view.onShow();
|
if (typeof view.onShow === 'function') view.onShow();
|
||||||
}
|
}
|
||||||
|
@ -88,21 +125,27 @@
|
||||||
const el = ensureViewEl(view);
|
const el = ensureViewEl(view);
|
||||||
el.classList.add('ux-view--overlay');
|
el.classList.add('ux-view--overlay');
|
||||||
const lastFocus = document.activeElement;
|
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);
|
$uxOverlays.appendChild(el);
|
||||||
|
syncOverlayVisibility();
|
||||||
await fadeIn(el);
|
await fadeIn(el);
|
||||||
overlayStack.push({ view, el, lastFocus });
|
|
||||||
$uxRoot.setAttribute('aria-hidden', 'true');
|
|
||||||
if (typeof view.onShow === 'function') view.onShow();
|
if (typeof view.onShow === 'function') view.onShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeTop() {
|
async function closeTop() {
|
||||||
const top = overlayStack.pop();
|
const top = overlayStack[overlayStack.length - 1];
|
||||||
if (!top) return;
|
if (!top) return;
|
||||||
|
if (top.isClosing) return;
|
||||||
|
top.isClosing = true;
|
||||||
const { view, el, lastFocus } = top;
|
const { view, el, lastFocus } = top;
|
||||||
|
if (view && typeof view.onHide === 'function') view.onHide();
|
||||||
await fadeOut(el);
|
await fadeOut(el);
|
||||||
if (el.parentNode) el.parentNode.removeChild(el);
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
if (typeof view.destroy === 'function') view.destroy();
|
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();
|
if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
site.css
1
site.css
|
@ -11,6 +11,7 @@
|
||||||
.ux-overlays { position: absolute; inset: 0; pointer-events: none; }
|
.ux-overlays { position: absolute; inset: 0; pointer-events: none; }
|
||||||
.ux-view { width: 100%; }
|
.ux-view { width: 100%; }
|
||||||
.ux-view--overlay { position: absolute; inset: 0; z-index: 10; pointer-events: auto; }
|
.ux-view--overlay { position: absolute; inset: 0; z-index: 10; pointer-events: auto; }
|
||||||
|
.ux-view[hidden] { display: none !important; }
|
||||||
|
|
||||||
.is-sr-only {
|
.is-sr-only {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue