// Client-side city selector: a popup list with search input that supports // approximate cyrillic/latin equivalence. Works the same on mobile and // desktop (native datalist is unreliable on mobile browsers). import { toSearch } from './translit'; let citiesPromise: Promise | null = null; export function fetchCities(): Promise { if (!citiesPromise) { citiesPromise = fetch('/api/cities') .then(r => r.json()) .then(d => (Array.isArray(d) ? (d as string[]) : [])) .catch(() => []); } return citiesPromise; } export { toSearch }; function escapeHtml(s: string): string { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function setupCitySelector(input: HTMLInputElement): void { const wrap = document.createElement('div'); wrap.className = 'city-select-wrap'; input.parentNode!.insertBefore(wrap, input); wrap.appendChild(input); const dd = document.createElement('div'); dd.className = 'city-select-dd'; dd.style.display = 'none'; wrap.appendChild(dd); let cities: string[] = []; function render() { const q = toSearch(input.value); const matches = q ? cities.filter(c => toSearch(c).includes(q)) : cities; dd.innerHTML = matches.slice(0, 60) .map(c => `
${escapeHtml(c)}
`) .join('') || `
Няма резултати
`; dd.querySelectorAll('.city-opt').forEach(el => { el.addEventListener('click', () => { input.value = el.dataset.v!; // fire both so listeners on 'input' (live refresh) and 'change' run input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); dd.style.display = 'none'; }); }); } function show() { dd.style.display = 'block'; render(); } input.addEventListener('focus', show); input.addEventListener('input', show); document.addEventListener('click', (e) => { if (!wrap.contains(e.target as Node)) dd.style.display = 'none'; }); fetchCities().then(cs => { cities = cs; if (dd.style.display === 'block') render(); }); } export function initCitySelectors(root: ParentNode = document): void { root.querySelectorAll('input[data-city-selector]').forEach(input => { if ((input as any).__citySelectorInit) return; (input as any).__citySelectorInit = true; setupCitySelector(input); }); }