diff --git a/index.html b/index.html index 8ff117d..9be3e96 100644 --- a/index.html +++ b/index.html @@ -40,28 +40,6 @@ - - - - Search - - - - - Back - - - - - Sort - - - - - - - - Search diff --git a/script.js b/script.js index 13cce9c..132cd29 100644 --- a/script.js +++ b/script.js @@ -59,68 +59,15 @@ // UX Manager const UX = (() => { let currentBase = null; // { view, el } - const overlayStack = []; // [{ view, el, lastFocus, isHidden, isClosing }] + const overlayStack = []; // [{ view, el, lastFocus }] function ensureViewEl(view) { if (!view || !view.el) throw new Error('Invalid view'); view.el.classList.add('ux-view'); - if (view.kind) view.el.dataset.uxKind = view.kind; return view.el; } - function assertKind(view, expected) { - if (!view) throw new Error('Missing view'); - if (view.kind && view.kind !== expected) { - throw new Error(`Expected view kind "${expected}" but received "${view.kind}"`); - } - } - - async function closeAllOverlays({ restoreFocus = false } = {}) { - while (overlayStack.length) { - const shouldRestore = restoreFocus && overlayStack.length === 1; - await closeTop({ restoreFocus: shouldRestore }); - } - } - - 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) { - assertKind(view, 'base'); - await closeAllOverlays({ restoreFocus: false }); const el = ensureViewEl(view); const prev = currentBase; if (prev) { @@ -133,40 +80,30 @@ } $uxRoot.appendChild(el); currentBase = { view, el }; - syncOverlayVisibility(); await fadeIn(el); if (typeof view.onShow === 'function') view.onShow(); } async function openOverlay(view) { - assertKind(view, 'overlay'); 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({ restoreFocus = true } = {}) { - const top = overlayStack[overlayStack.length - 1]; + async function closeTop() { + const top = overlayStack.pop(); 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(); - overlayStack.pop(); - syncOverlayVisibility(); - if (restoreFocus && lastFocus && typeof lastFocus.focus === 'function') { - lastFocus.focus(); - } + if (overlayStack.length === 0) $uxRoot.removeAttribute('aria-hidden'); + if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus(); } window.addEventListener('keydown', (e) => { @@ -176,7 +113,7 @@ } }); - return { replace, openOverlay, closeTop, closeAllOverlays }; + return { replace, openOverlay, closeTop }; })(); // --- IndexedDB helpers (minimal, no external deps) --- @@ -742,8 +679,35 @@ const $form = el.querySelector('[data-ref="form"]'); const $q = el.querySelector('[data-ref="q"]'); const $results = el.querySelector('[data-ref="results"]'); - const $backBtn = el.querySelector('[data-action="back"]'); - const $sortButtons = el.querySelector('[data-ref="sort-buttons"]'); + + // Back + sort toolbar keeps controls grouped for accessibility. + const $toolbar = document.createElement('div'); + $toolbar.className = 'level mb-4 is-mobile'; + const $toolbarLeft = document.createElement('div'); + $toolbarLeft.className = 'level-left'; + const $toolbarLeftItem = document.createElement('div'); + $toolbarLeftItem.className = 'level-item'; + const $backBtn = document.createElement('button'); + $backBtn.type = 'button'; + $backBtn.className = 'button is-small is-text'; + $backBtn.textContent = 'Back to menu'; + $toolbarLeftItem.appendChild($backBtn); + $toolbarLeft.appendChild($toolbarLeftItem); + const $toolbarRight = document.createElement('div'); + $toolbarRight.className = 'level-right'; + const $toolbarRightItem = document.createElement('div'); + $toolbarRightItem.className = 'level-item'; + const $sortLabel = document.createElement('span'); + $sortLabel.className = 'is-size-7 has-text-grey mr-2'; + $sortLabel.textContent = 'Sort'; + const $sortButtons = document.createElement('div'); + $sortButtons.className = 'buttons has-addons is-small'; + $toolbarRightItem.appendChild($sortLabel); + $toolbarRightItem.appendChild($sortButtons); + $toolbarRight.appendChild($toolbarRightItem); + $toolbar.appendChild($toolbarLeft); + $toolbar.appendChild($toolbarRight); + $form.insertAdjacentElement('afterend', $toolbar); const $status = document.createElement('p'); $status.className = 'has-text-grey'; @@ -789,8 +753,6 @@ { key: 'alpha', label: 'A–Z' }, ]; - if (!$backBtn) throw new Error('Missing back button'); - if (!$sortButtons) throw new Error('Missing sort container'); sortOptions.forEach((opt) => { const btn = document.createElement('button'); btn.type = 'button'; diff --git a/site.css b/site.css index 835fcdb..1830dda 100644 --- a/site.css +++ b/site.css @@ -11,7 +11,6 @@ .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;