|
1 | 1 | <script lang="ts">
|
2 |
| - import Control from "../ol/Control.svelte"; |
3 |
| - import geocode from "../search/geocode"; |
4 |
| - import type { GeocodeResult } from "../search/geocode"; |
5 |
| - import { createEventDispatcher, tick } from "svelte"; |
6 |
| - import { getOlContext } from "../ol/Map.svelte"; |
7 |
| - import MapControl from "./MapControl.svelte"; |
8 |
| - import grow from "../transitions/grow"; |
9 |
| - import { debounce } from "../utils/debounce"; |
10 |
| - import Spinner from "./Spinner.svelte"; |
11 |
| - import { filterResults } from "../search/match"; |
| 2 | + import geocode from '../search/geocode' |
| 3 | + import { createEventDispatcher, tick } from 'svelte' |
| 4 | + import { getOlContext } from '../ol/Map.svelte' |
| 5 | + import MapControl from './MapControl.svelte' |
| 6 | + import grow from '../transitions/grow' |
| 7 | + import type { Place } from '../search/places' |
12 | 8 |
|
13 |
| - const dispatcher = createEventDispatcher(); |
14 |
| - const { map } = getOlContext(); |
| 9 | + const dispatcher = createEventDispatcher() |
| 10 | + const { map } = getOlContext() |
15 | 11 |
|
16 |
| - let searchBox: HTMLInputElement; |
| 12 | + let searchBox: HTMLInputElement |
17 | 13 | $: {
|
18 | 14 | if (searchBox) {
|
19 |
| - searchBox.focus(); |
| 15 | + searchBox.focus() |
20 | 16 | }
|
21 | 17 | }
|
22 | 18 |
|
23 |
| - let query = ""; |
24 |
| - let debouncedQuery = query; |
| 19 | + let query = '' |
25 | 20 |
|
26 |
| - let open = false; |
27 |
| - let results: GeocodeResult[] = []; |
28 |
| - $: filteredResults = filterResults(results, query); |
| 21 | + let open = false |
| 22 | + let results: Place[] = [] |
29 | 23 |
|
30 |
| - let shouldClose = false; |
| 24 | + let shouldClose = false |
31 | 25 | const close = () => {
|
32 |
| - shouldClose = true; |
| 26 | + shouldClose = true |
33 | 27 | setTimeout(() => {
|
34 |
| - if (!shouldClose) return; |
35 |
| - open = false; |
36 |
| - }); |
37 |
| - }; |
| 28 | + if (!shouldClose) return |
| 29 | + open = false |
| 30 | + }) |
| 31 | + } |
38 | 32 |
|
39 |
| - const debouncedOnlineSearch = debounce(async () => { |
| 33 | + const updateResults = async (query: string) => { |
40 | 34 | try {
|
41 |
| - results = await geocode(query); |
| 35 | + let currentQuery = query |
| 36 | + const queryResults = await geocode(currentQuery) |
| 37 | + if (query === currentQuery) results = queryResults |
42 | 38 | } catch {}
|
43 |
| - searching = false; |
44 |
| - debouncedQuery = query; |
45 |
| - }, 0); |
| 39 | + } |
46 | 40 |
|
47 |
| - $: searching = debouncedQuery !== query; |
48 | 41 | $: {
|
49 |
| - if (debouncedQuery !== query) debouncedOnlineSearch(); |
| 42 | + updateResults(query) |
50 | 43 | }
|
51 | 44 |
|
52 |
| - const selectResult = (result: GeocodeResult) => { |
53 |
| - dispatcher("change", { result, map }); |
54 |
| - open = false; |
55 |
| - }; |
| 45 | + const selectResult = (result: Place) => { |
| 46 | + dispatcher('change', { result, map }) |
| 47 | + open = false |
| 48 | + } |
56 | 49 | </script>
|
57 | 50 |
|
58 | 51 | <MapControl>
|
| 52 | + <!-- svelte-ignore a11y-no-noninteractive-tabindex --> |
59 | 53 | <div
|
60 | 54 | tabindex="0"
|
61 | 55 | on:focusin={(e) => {
|
62 |
| - shouldClose = false; |
63 |
| - open = true; |
| 56 | + shouldClose = false |
| 57 | + open = true |
64 | 58 | }}
|
65 |
| - on:focusout={close}> |
| 59 | + on:focusout={close} |
| 60 | + > |
66 | 61 | <div
|
67 |
| - class="transition-colors divide-purple-200 flex rounded items-start border ${open && 'focus-within:border-primary focus-within:ring-2 focus-within:ring-primary'}"> |
68 |
| - {#if !searching} |
69 |
| - <button |
70 |
| - tabindex="-1" |
71 |
| - class={`transition-colors duration-200 map-button flex-shrink-0 ${open && 'bg-primary focus:bg-primary-hover hover:bg-primary-hover'}`}> |
72 |
| - <span class="-mx-2">🔎</span> |
73 |
| - </button> |
74 |
| - {:else} |
75 |
| - <div class="w-11 bg-primary rounded"> |
76 |
| - <Spinner class="text-background" /> |
77 |
| - </div> |
78 |
| - {/if} |
| 62 | + class="transition-colors divide-purple-200 flex rounded items-start border ${open && |
| 63 | + 'focus-within:border-primary focus-within:ring-2 focus-within:ring-primary'}" |
| 64 | + > |
| 65 | + <button |
| 66 | + tabindex="-1" |
| 67 | + class={`transition-colors duration-200 map-button flex-shrink-0 ${ |
| 68 | + open && 'bg-primary focus:bg-primary-hover hover:bg-primary-hover' |
| 69 | + }`} |
| 70 | + > |
| 71 | + <span class="-mx-2">🔎</span> |
| 72 | + </button> |
79 | 73 |
|
80 | 74 | {#if open}
|
81 | 75 | <input
|
82 | 76 | bind:this={searchBox}
|
83 | 77 | transition:grow={{ animateHeight: false }}
|
84 |
| - class={`${!open && 'hidden'} h-full py-2 px-2 rounded-l-none appearance-none rounded focus:outline-none`} |
| 78 | + class={`${ |
| 79 | + !open && 'hidden' |
| 80 | + } h-full py-2 px-2 rounded-l-none appearance-none rounded focus:outline-none`} |
85 | 81 | type="search"
|
86 |
| - bind:value={query} /> |
| 82 | + bind:value={query} |
| 83 | + /> |
87 | 84 | {/if}
|
88 | 85 | </div>
|
89 | 86 | {#if open}
|
90 |
| - <div |
91 |
| - class={`max-h-72 overflow-y-auto ${!!filteredResults.length && 'mt-1'}`}> |
92 |
| - {#each filteredResults.slice(0, 20) as result} |
| 87 | + <div class={`max-h-72 overflow-y-auto ${!!results.length && 'mt-1'}`}> |
| 88 | + {#each results.slice(0, 20) as result} |
93 | 89 | <div
|
94 | 90 | class="opacity-95 hover:bg-background-hover px-2 py-1"
|
95 |
| - on:click={() => selectResult(result)}> |
| 91 | + on:click={() => selectResult(result)} |
| 92 | + > |
96 | 93 | {result.name}
|
97 | 94 | </div>
|
98 | 95 | {/each}
|
|
0 commit comments