aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/viewer/js/searchbox.js
blob: c073b65367bdcfb354774289b6975703eb7359df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 });
    }
  }
}