Synchronize UX stack handling and search header
This commit is contained in:
parent
0450ec9e1c
commit
fc85729374
2 changed files with 49 additions and 32 deletions
22
index.html
22
index.html
|
@ -40,6 +40,28 @@
|
||||||
<template id="tpl-search">
|
<template id="tpl-search">
|
||||||
<div class="ux-view fade">
|
<div class="ux-view fade">
|
||||||
<div class="box">
|
<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">
|
<form class="mb-4" role="search" aria-label="Track search" data-ref="form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="q">Search</label>
|
<label class="label" for="q">Search</label>
|
||||||
|
|
59
script.js
59
script.js
|
@ -64,9 +64,24 @@
|
||||||
function ensureViewEl(view) {
|
function ensureViewEl(view) {
|
||||||
if (!view || !view.el) throw new Error('Invalid view');
|
if (!view || !view.el) throw new Error('Invalid view');
|
||||||
view.el.classList.add('ux-view');
|
view.el.classList.add('ux-view');
|
||||||
|
if (view.kind) view.el.dataset.uxKind = view.kind;
|
||||||
return view.el;
|
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() {
|
function syncOverlayVisibility() {
|
||||||
const topIndex = overlayStack.length - 1;
|
const topIndex = overlayStack.length - 1;
|
||||||
overlayStack.forEach((entry, idx) => {
|
overlayStack.forEach((entry, idx) => {
|
||||||
|
@ -104,6 +119,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function replace(view) {
|
async function replace(view) {
|
||||||
|
assertKind(view, 'base');
|
||||||
|
await closeAllOverlays({ restoreFocus: false });
|
||||||
const el = ensureViewEl(view);
|
const el = ensureViewEl(view);
|
||||||
const prev = currentBase;
|
const prev = currentBase;
|
||||||
if (prev) {
|
if (prev) {
|
||||||
|
@ -122,6 +139,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openOverlay(view) {
|
async function openOverlay(view) {
|
||||||
|
assertKind(view, 'overlay');
|
||||||
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;
|
||||||
|
@ -134,7 +152,7 @@
|
||||||
if (typeof view.onShow === 'function') view.onShow();
|
if (typeof view.onShow === 'function') view.onShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeTop() {
|
async function closeTop({ restoreFocus = true } = {}) {
|
||||||
const top = overlayStack[overlayStack.length - 1];
|
const top = overlayStack[overlayStack.length - 1];
|
||||||
if (!top) return;
|
if (!top) return;
|
||||||
if (top.isClosing) return;
|
if (top.isClosing) return;
|
||||||
|
@ -146,7 +164,9 @@
|
||||||
if (typeof view.destroy === 'function') view.destroy();
|
if (typeof view.destroy === 'function') view.destroy();
|
||||||
overlayStack.pop();
|
overlayStack.pop();
|
||||||
syncOverlayVisibility();
|
syncOverlayVisibility();
|
||||||
if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus();
|
if (restoreFocus && lastFocus && typeof lastFocus.focus === 'function') {
|
||||||
|
lastFocus.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener('keydown', (e) => {
|
||||||
|
@ -156,7 +176,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { replace, openOverlay, closeTop };
|
return { replace, openOverlay, closeTop, closeAllOverlays };
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// --- IndexedDB helpers (minimal, no external deps) ---
|
// --- IndexedDB helpers (minimal, no external deps) ---
|
||||||
|
@ -722,35 +742,8 @@
|
||||||
const $form = el.querySelector('[data-ref="form"]');
|
const $form = el.querySelector('[data-ref="form"]');
|
||||||
const $q = el.querySelector('[data-ref="q"]');
|
const $q = el.querySelector('[data-ref="q"]');
|
||||||
const $results = el.querySelector('[data-ref="results"]');
|
const $results = el.querySelector('[data-ref="results"]');
|
||||||
|
const $backBtn = el.querySelector('[data-action="back"]');
|
||||||
// Back + sort toolbar keeps controls grouped for accessibility.
|
const $sortButtons = el.querySelector('[data-ref="sort-buttons"]');
|
||||||
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');
|
const $status = document.createElement('p');
|
||||||
$status.className = 'has-text-grey';
|
$status.className = 'has-text-grey';
|
||||||
|
@ -796,6 +789,8 @@
|
||||||
{ key: 'alpha', label: 'A–Z' },
|
{ key: 'alpha', label: 'A–Z' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (!$backBtn) throw new Error('Missing back button');
|
||||||
|
if (!$sortButtons) throw new Error('Missing sort container');
|
||||||
sortOptions.forEach((opt) => {
|
sortOptions.forEach((opt) => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue