Files
KantSites/public/assets/page-picker.js
T
kantarci 495b26896e
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s
Fassung 1.7.0: verlässliche Installation, übersichtliche Verwaltung und bedienbare Bewegung
2026-09-14 10:49:54 +02:00

68 lines
4.4 KiB
JavaScript

export function setupPicker(picker, onSelect = () => {}) {
if (picker.dataset.ready) return;
picker.dataset.ready = 'true';
const input = picker.querySelector('[role="combobox"]');
const value = picker.querySelector('.picker-value');
const results = picker.querySelector('[role="listbox"]');
const status = picker.querySelector('.picker-status');
let controller, timer, current = -1, items = [];
const close = () => {
results.hidden = true; input.setAttribute('aria-expanded', 'false');
input.removeAttribute('aria-activedescendant'); current = -1;
};
const select = item => {
value.value = item?.id ?? '';
input.value = item ? `${item.title} (/${item.path === 'home' ? '' : item.path})` : '';
input.setCustomValidity(''); status.textContent = item ? 'Ausgewählt.' : '';
close(); value.dispatchEvent(new Event('change', { bubbles: true })); onSelect(item);
};
const activate = index => {
current = Math.max(0, Math.min(items.length - 1, index));
[...results.children].forEach((node, i) => node.setAttribute('aria-selected', String(i === current)));
const option = results.children[current];
if (option) { input.setAttribute('aria-activedescendant', option.id); option.scrollIntoView({ block: 'nearest' }); }
};
const search = async query => {
controller?.abort(); controller = new AbortController();
const signal = controller.signal;
const url = new URL(picker.dataset.source, location.href);
url.searchParams.set('q', query); url.searchParams.set('type', picker.dataset.type || '');
url.searchParams.set('parents', picker.dataset.parents || '0');
status.textContent = 'Wird gesucht …';
try {
const response = await fetch(url, { signal, credentials: 'same-origin' });
if (!response.ok || response.redirected) throw new Error();
const data = await response.json();
if (signal.aborted) return;
items = data.items; current = -1; results.replaceChildren();
items.forEach((item, index) => {
const option = document.createElement('button'); option.type = 'button'; option.tabIndex = -1;
option.id = `${input.id}-option-${index}`; option.setAttribute('role', 'option'); option.setAttribute('aria-selected', 'false');
const title = document.createElement('strong'); title.textContent = item.title;
const detail = document.createElement('span'); detail.textContent = `/${item.path === 'home' ? '' : item.path} · ${item.type === 'post' ? 'Beitrag' : 'Seite'}${item.published ? '' : ' · Entwurf'}`;
option.append(title, detail); option.addEventListener('mousedown', event => event.preventDefault());
option.addEventListener('click', () => { select(item); input.focus({ preventScroll: true }); close(); }); results.append(option);
});
results.hidden = !items.length; input.setAttribute('aria-expanded', String(Boolean(items.length)));
status.textContent = items.length ? `${items.length} Treffer${items.length === 20 ? ' · Suche eingrenzen für weitere Ergebnisse' : ''}.` : 'Keine passenden Inhalte gefunden.';
} catch (error) {
if (error.name !== 'AbortError') { close(); status.textContent = 'Inhalte konnten nicht geladen werden. Bitte erneut suchen oder neu anmelden.'; }
}
};
input.addEventListener('focus', () => search(''));
input.addEventListener('input', () => {
value.value = ''; input.setCustomValidity(input.value && !picker.closest('.menu-add') ? 'Bitte einen Suchtreffer auswählen oder die Suche leeren.' : '');
value.dispatchEvent(new Event('change', { bubbles: true }));
controller?.abort(); clearTimeout(timer); timer = setTimeout(() => search(input.value), 180);
});
input.addEventListener('keydown', event => {
if (event.key === 'Escape') { event.preventDefault(); controller?.abort(); clearTimeout(timer); close(); }
if (['ArrowDown', 'ArrowUp'].includes(event.key) && !results.hidden) { event.preventDefault(); activate(current + (event.key === 'ArrowDown' ? 1 : -1)); }
if (event.key === 'Enter' && current >= 0 && !results.hidden) { event.preventDefault(); select(items[current]); }
});
picker.addEventListener('focusout', event => { if (!picker.contains(event.relatedTarget)) { controller?.abort(); clearTimeout(timer); close(); } });
picker.querySelector('.picker-clear').addEventListener('click', () => { controller?.abort(); clearTimeout(timer); select(null); input.focus(); });
}
document.querySelectorAll('.content-picker').forEach(picker => setupPicker(picker));