Synchronize UX stack handling and search header

This commit is contained in:
Jordan Wages 2025-09-18 18:55:40 -05:00
commit fc85729374
2 changed files with 49 additions and 32 deletions

View file

@ -40,6 +40,28 @@
<template id="tpl-search">
<div class="ux-view fade">
<div class="box">
<header class="level mb-4">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4 mb-0">Search</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-text" type="button" data-action="back">Back</button>
</div>
<div class="level-item">
<div class="field is-grouped is-grouped-right is-align-items-center">
<div class="control">
<span class="is-size-7 has-text-grey mr-2">Sort</span>
</div>
<div class="control">
<div class="buttons has-addons is-small" role="group" aria-label="Sort results" data-ref="sort-buttons"></div>
</div>
</div>
</div>
</div>
</header>
<form class="mb-4" role="search" aria-label="Track search" data-ref="form">
<div class="field">
<label class="label" for="q">Search</label>

View file

@ -64,9 +64,24 @@
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) => {
@ -104,6 +119,8 @@
}
async function replace(view) {
assertKind(view, 'base');
await closeAllOverlays({ restoreFocus: false });
const el = ensureViewEl(view);
const prev = currentBase;
if (prev) {
@ -122,6 +139,7 @@
}
async function openOverlay(view) {
assertKind(view, 'overlay');
const el = ensureViewEl(view);
el.classList.add('ux-view--overlay');
const lastFocus = document.activeElement;
@ -134,7 +152,7 @@
if (typeof view.onShow === 'function') view.onShow();
}
async function closeTop() {
async function closeTop({ restoreFocus = true } = {}) {
const top = overlayStack[overlayStack.length - 1];
if (!top) return;
if (top.isClosing) return;
@ -146,7 +164,9 @@
if (typeof view.destroy === 'function') view.destroy();
overlayStack.pop();
syncOverlayVisibility();
if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus();
if (restoreFocus && lastFocus && typeof lastFocus.focus === 'function') {
lastFocus.focus();
}
}
window.addEventListener('keydown', (e) => {
@ -156,7 +176,7 @@
}
});
return { replace, openOverlay, closeTop };
return { replace, openOverlay, closeTop, closeAllOverlays };
})();
// --- IndexedDB helpers (minimal, no external deps) ---
@ -722,35 +742,8 @@
const $form = el.querySelector('[data-ref="form"]');
const $q = el.querySelector('[data-ref="q"]');
const $results = el.querySelector('[data-ref="results"]');
// 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 $backBtn = el.querySelector('[data-action="back"]');
const $sortButtons = el.querySelector('[data-ref="sort-buttons"]');
const $status = document.createElement('p');
$status.className = 'has-text-grey';
@ -796,6 +789,8 @@
{ key: 'alpha', label: 'AZ' },
];
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';