From 3443c1c65e9f1753e1e656b35d08416c1fa298f2 Mon Sep 17 00:00:00 2001 From: godosa Date: Wed, 7 Oct 2026 00:14:38 +0200 Subject: worldmap-viewer: initial public history --- viewer/js/searchbox.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 viewer/js/searchbox.js (limited to 'viewer/js/searchbox.js') diff --git a/viewer/js/searchbox.js b/viewer/js/searchbox.js new file mode 100644 index 0000000..c073b65 --- /dev/null +++ b/viewer/js/searchbox.js @@ -0,0 +1,59 @@ +// 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 }); + } + } +} -- cgit