// searchbox.js — the search field: suggestions as you type, ↑/↓/Enter/Esc, fly to the choice import { el } from './ui.js'; import { suggest } from './search.js'; export class SearchBox { constructor(input, list, { pins, locate, go }) { Object.assign(this, { input, list, pins, locate, go }); this.items = []; this.sel = -1; input.addEventListener('input', () => this.update()); input.addEventListener('keydown', e => this.key(e)); input.addEventListener('blur', () => setTimeout(() => this.close(), 150)); // let a click on an item land first } update() { this.items = suggest(this.input.value, this.pins()); this.sel = this.items.length ? 0 : -1; this.render(); } render() { this.list.hidden = !this.items.length; this.list.replaceChildren(...this.items.map((it, i) => el('div', { class: `item${i === this.sel ? ' sel' : ''}`, onmousedown: e => { e.preventDefault(); this.choose(i); } }, it.label, it.sub ? el('span', { class: 'sub' }, it.sub) : null))); } key(e) { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (!this.items.length) return; this.sel = (this.sel + (e.key === 'ArrowDown' ? 1 : -1) + this.items.length) % this.items.length; this.render(); } else if (e.key === 'Enter') { e.preventDefault(); if (this.sel >= 0) this.choose(this.sel); } else if (e.key === 'Escape') { this.close(); this.input.blur(); } } close() { this.items = []; this.sel = -1; this.render(); } async choose(i) { const it = this.items[i]; this.close(); try { const at = it.kind === 'cell' ? await this.locate(it.cell) : { lat: it.lat, lon: it.lon }; this.go({ lat: at.lat, lon: at.lon, label: it.label, marker: it.kind !== 'pin' }); } catch (e) { this.go({ error: e.message }); } } }