/* Zoeken in de bovenbalk: live resultaten (search_live) onder het veld; Enter opent de volledige zoekpagina. * Pijltjes lopen door de resultaten, Escape sluit, "/" springt naar het zoekveld. */ (() => { const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]); document.querySelectorAll('form.tsearch').forEach((form) => { const input = form.querySelector('input[name=q]'); const box = form.querySelector('.tsearch-results'); let timer = null, ctrl = null, last = ''; const items = () => [...box.querySelectorAll('a')]; const close = () => { box.classList.remove('open'); input.setAttribute('aria-expanded', 'false'); }; const open = () => { if (box.innerHTML) { box.classList.add('open'); input.setAttribute('aria-expanded', 'true'); } }; const render = (data) => { box.innerHTML = data.groups.length ? data.groups.map((g) => `
${esc(g.label)}
` + g.items.map((i) => `${esc(i.title)}${i.sub ? `${esc(i.sub)}` : ''}`).join('')).join('') + `Alle resultaten voor “${esc(data.q)}”` : `
Niets gevonden voor “${esc(data.q)}”.
`; open(); }; input.addEventListener('input', () => { clearTimeout(timer); const v = input.value.trim(); if (v.length < 2) { box.innerHTML = ''; close(); return; } timer = setTimeout(() => { if (v === last && box.innerHTML) { open(); return; } if (ctrl) ctrl.abort(); ctrl = new AbortController(); fetch(`${form.dataset.live}?q=${encodeURIComponent(v)}`, { signal: ctrl.signal, headers: { Accept: 'application/json' } }) .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (d && input.value.trim() === v) { last = v; render(d); } }) .catch(() => {}); }, 180); }); form.addEventListener('keydown', (e) => { const list = items(), at = list.indexOf(document.activeElement); if (e.key === 'ArrowDown' && list.length) { e.preventDefault(); open(); list[Math.min(at + 1, list.length - 1)].focus(); } else if (e.key === 'ArrowUp' && list.length) { e.preventDefault(); (at <= 0 ? input : list[at - 1]).focus(); } else if (e.key === 'Escape') { close(); input.focus(); } }); input.addEventListener('focus', () => { if (input.value.trim().length >= 2) open(); }); document.addEventListener('click', (e) => { if (!form.contains(e.target)) close(); }); // mobiel: het veld krijgt de focus zodra het uitklapmenu opengaat const toggle = form.closest('.dropdown')?.querySelector('[data-bs-toggle="dropdown"]'); if (toggle) toggle.addEventListener('shown.bs.dropdown', () => input.focus()); }); document.addEventListener('keydown', (e) => { if (e.key !== '/' || e.ctrlKey || e.metaKey || e.altKey || e.target.closest('input, textarea, select, [contenteditable]')) return; const input = [...document.querySelectorAll('form.tsearch input[name=q]')].find((i) => i.offsetParent); if (input) { e.preventDefault(); input.focus(); input.select(); } }); })();