@versatiles/svelte 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +21 -4
  2. package/dist/components/BBoxMap/AutoComplete.svelte +176 -0
  3. package/dist/components/{AutoComplete.svelte.d.ts → BBoxMap/AutoComplete.svelte.d.ts} +12 -18
  4. package/dist/components/BBoxMap/BBoxMap.d.ts +1 -12
  5. package/dist/components/BBoxMap/BBoxMap.js +1 -200
  6. package/dist/components/BBoxMap/BBoxMap.svelte +43 -127
  7. package/dist/components/BBoxMap/BBoxMap.svelte.d.ts +4 -20
  8. package/dist/components/BasicMap/BasicMap.svelte +34 -37
  9. package/dist/components/BasicMap/BasicMap.svelte.d.ts +9 -24
  10. package/dist/components/LocatorMap/LocatorMap.svelte +75 -0
  11. package/dist/components/LocatorMap/LocatorMap.svelte.d.ts +19 -0
  12. package/dist/components/MapEditor/Editor.svelte +25 -0
  13. package/dist/components/MapEditor/Editor.svelte.d.ts +7 -0
  14. package/dist/components/MapEditor/EditorLine.svelte +27 -0
  15. package/dist/components/MapEditor/EditorLine.svelte.d.ts +7 -0
  16. package/dist/components/MapEditor/EditorMarker.svelte +42 -0
  17. package/dist/components/MapEditor/EditorMarker.svelte.d.ts +7 -0
  18. package/dist/components/MapEditor/MapEditor.svelte +99 -0
  19. package/dist/components/MapEditor/MapEditor.svelte.d.ts +4 -0
  20. package/dist/components/MapEditor/editor.scss +16 -0
  21. package/dist/components/MapEditor/lib/element_abstract.d.ts +20 -0
  22. package/dist/components/MapEditor/lib/element_abstract.js +58 -0
  23. package/dist/components/MapEditor/lib/element_line.d.ts +14 -0
  24. package/dist/components/MapEditor/lib/element_line.js +76 -0
  25. package/dist/components/MapEditor/lib/element_marker.d.ts +20 -0
  26. package/dist/components/MapEditor/lib/element_marker.js +191 -0
  27. package/dist/components/MapEditor/lib/geocoder.d.ts +1 -0
  28. package/dist/components/MapEditor/lib/geocoder.js +8 -0
  29. package/dist/components/MapEditor/lib/geometry_manager.d.ts +18 -0
  30. package/dist/components/MapEditor/lib/geometry_manager.js +117 -0
  31. package/dist/components/MapEditor/lib/map_layer.d.ts +14 -0
  32. package/dist/components/MapEditor/lib/map_layer.js +61 -0
  33. package/dist/components/MapEditor/lib/types.d.ts +112 -0
  34. package/dist/components/MapEditor/lib/types.js +1 -0
  35. package/dist/components/MapEditor/lib/utils.d.ts +2 -0
  36. package/dist/components/MapEditor/lib/utils.js +11 -0
  37. package/dist/index.d.ts +3 -1
  38. package/dist/index.js +3 -1
  39. package/dist/utils/draw/bbox.d.ts +28 -0
  40. package/dist/utils/draw/bbox.js +193 -0
  41. package/dist/utils/location.d.ts +2 -1
  42. package/dist/utils/location.js +19 -10
  43. package/dist/utils/map_style.d.ts +6 -0
  44. package/dist/utils/map_style.js +28 -0
  45. package/dist/utils/sprite_library.d.ts +19 -0
  46. package/dist/utils/sprite_library.js +30 -0
  47. package/package.json +19 -14
  48. package/dist/components/AutoComplete.svelte +0 -197
  49. package/dist/components/BBoxMap/README.md +0 -70
  50. package/dist/components/BBoxMap/data/countries.jsonl +0 -258
  51. package/dist/components/BBoxMap/data/eu.jsonl +0 -1876
  52. package/dist/components/BBoxMap/data/us.jsonl +0 -52
  53. package/dist/components/BBoxMap/data/world.jsonl +0 -7
  54. package/dist/components/BBoxMap/helpers/geojson2bboxes.d.ts +0 -2
  55. package/dist/components/BBoxMap/helpers/geojson2bboxes.js +0 -183
  56. package/dist/components/BBoxMap/helpers/merge_bboxes.d.ts +0 -2
  57. package/dist/components/BBoxMap/helpers/merge_bboxes.js +0 -84
  58. package/dist/components/BBoxMap/helpers/population.raw.br +0 -0
  59. package/dist/utils/style.d.ts +0 -3
  60. package/dist/utils/style.js +0 -21
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
+ [![NPM Version](https://img.shields.io/npm/v/%40versatiles%2Fsvelte)](https://www.npmjs.com/package/@versatiles/svelte)
2
+ [![NPM Downloads](https://img.shields.io/npm/dy/%40versatiles%2Fsvelte)](https://www.npmjs.com/package/@versatiles/svelte)
3
+ [![Code Coverage](https://codecov.io/gh/versatiles-org/node-versatiles-svelte/branch/main/graph/badge.svg?token=DaJDN0E3Ae)](https://codecov.io/gh/versatiles-org/node-versatiles-svelte)
4
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/versatiles-org/node-versatiles-svelte/ci.yml)](https://github.com/versatiles-org/node-versatiles-svelte/actions/workflows/ci.yml)
5
+
1
6
  # Svelte Components for VersaTiles
2
7
 
8
+ Play with them: https://versatiles.org/node-versatiles-svelte/
9
+
3
10
  ## Install
4
11
 
5
12
  ```bash
@@ -16,12 +23,22 @@ npm i @versatiles/svelte
16
23
  </tr>
17
24
  <tr>
18
25
  <th>BasicMap</th>
19
- <td><img src="https://github.com/versatiles-org/node-versatiles-svelte/releases/latest/download/basic-map-light.png"></td>
20
- <td><img src="https://github.com/versatiles-org/node-versatiles-svelte/releases/latest/download/basic-map-dark.png"></td>
26
+ <td><img width="512" src="./screenshots/basic-map-light.png"></td>
27
+ <td><img width="512" src="./screenshots/basic-map-dark.png"></td>
21
28
  </tr>
22
29
  <tr>
23
30
  <th>BBoxMap</th>
24
- <td><img src="https://github.com/versatiles-org/node-versatiles-svelte/releases/latest/download/bbox-map-light.png"></td>
25
- <td><img src="https://github.com/versatiles-org/node-versatiles-svelte/releases/latest/download/bbox-map-dark.png"></td>
31
+ <td><img width="512" src="./screenshots/bbox-map-light.png"></td>
32
+ <td><img width="512" src="./screenshots/bbox-map-dark.png"></td>
33
+ </tr>
34
+ <tr>
35
+ <th>LocatorMap</th>
36
+ <td><img width="512" src="./screenshots/locator-map-light.png"></td>
37
+ <td><img width="512" src="./screenshots/locator-map-dark.png"></td>
38
+ </tr>
39
+ <tr>
40
+ <th>MapEditor</th>
41
+ <td><img width="512" src="./screenshots/map-editor-light.png"></td>
42
+ <td><img width="512" src="./screenshots/map-editor-dark.png"></td>
26
43
  </tr>
27
44
  </table>
@@ -0,0 +1,176 @@
1
+ <!-- AutoComplete.svelte -->
2
+ <script lang="ts" generics="T">import { isDarkMode } from '../../utils/map_style.js';
3
+ /* eslint svelte/no-at-html-tags: off */
4
+ let { change, initialInputText, inputText = $bindable(''), items = [], maxItems = 10, minChar = 3, placeholder = '' } = $props();
5
+ import { onMount } from 'svelte';
6
+ let inputElement; // Reference to DOM element
7
+ let autocompleteElement; // Reference to DOM element
8
+ let isOpen = $state(false);
9
+ let results = $state([]);
10
+ let selectedIndex = $state(0);
11
+ // Escape special characters in search string for use in regex
12
+ const regExpEscape = (s) => s.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
13
+ if (inputText.length >= minChar) {
14
+ const r = filterResults();
15
+ if (r.length > 0) {
16
+ const { key, value } = r[0];
17
+ inputText = key;
18
+ change(JSON.parse(JSON.stringify(value)));
19
+ }
20
+ else {
21
+ inputText = '';
22
+ }
23
+ }
24
+ // Handle input change
25
+ function onChange() {
26
+ if (inputText.length >= minChar) {
27
+ results = filterResults();
28
+ selectedIndex = 0;
29
+ isOpen = true;
30
+ }
31
+ else {
32
+ isOpen = false;
33
+ }
34
+ }
35
+ function onFocus() {
36
+ inputElement.setSelectionRange(0, 1000);
37
+ }
38
+ // Filter results based on search query
39
+ function filterResults() {
40
+ const searchText = inputText.trim();
41
+ const searchTextUpper = searchText.toUpperCase();
42
+ const searchReg = RegExp(regExpEscape(searchText), 'i');
43
+ return items
44
+ .filter((item) => item.key.toUpperCase().includes(searchTextUpper))
45
+ .slice(0, maxItems)
46
+ .map((item) => ({
47
+ ...item,
48
+ _label: item.key.replace(searchReg, '<span>$&</span>')
49
+ }));
50
+ }
51
+ // Handle keyboard navigation
52
+ function onKeyDown(event) {
53
+ switch (event.key) {
54
+ case 'ArrowDown':
55
+ if (selectedIndex < results.length - 1)
56
+ selectedIndex += 1;
57
+ break;
58
+ case 'ArrowUp':
59
+ if (selectedIndex > 0)
60
+ selectedIndex -= 1;
61
+ break;
62
+ case 'Enter':
63
+ event.preventDefault();
64
+ close(selectedIndex);
65
+ break;
66
+ case 'Escape':
67
+ event.preventDefault();
68
+ close();
69
+ break;
70
+ }
71
+ }
72
+ // Close the autocomplete and select an item
73
+ function close(index = -1) {
74
+ isOpen = false;
75
+ if (index >= 0 && results[index]) {
76
+ const { key, value } = results[index];
77
+ inputText = key;
78
+ change(JSON.parse(JSON.stringify(value)));
79
+ }
80
+ }
81
+ onMount(() => {
82
+ const darkMode = isDarkMode(autocompleteElement);
83
+ autocompleteElement.style.setProperty('--bg-color', darkMode ? '#000' : '#fff');
84
+ autocompleteElement.style.setProperty('--fg-color', darkMode ? '#fff' : '#000');
85
+ if (initialInputText) {
86
+ inputText = initialInputText;
87
+ results = filterResults();
88
+ close(0);
89
+ }
90
+ });
91
+ </script>
92
+
93
+ <svelte:window on:click={() => close()} />
94
+
95
+ <div class="autocomplete" bind:this={autocompleteElement}>
96
+ <input
97
+ type="text"
98
+ bind:value={inputText}
99
+ {placeholder}
100
+ autocomplete="off"
101
+ oninput={onChange}
102
+ onkeydown={onKeyDown}
103
+ onfocusin={onFocus}
104
+ onclick={(e) => e.stopPropagation()}
105
+ bind:this={inputElement}
106
+ aria-activedescendant={isOpen ? `result-${selectedIndex}` : undefined}
107
+ aria-autocomplete="list"
108
+ aria-controls="autocomplete-results"
109
+ />
110
+ <div class="autocomplete-results" class:hide-results={!isOpen}>
111
+ {#each results as result, i}
112
+ <button
113
+ onclick={() => close(i)}
114
+ class={i === selectedIndex ? ' is-active' : ''}
115
+ role="option"
116
+ aria-selected={i === selectedIndex}
117
+ >
118
+ {@html result._label}
119
+ </button>
120
+ {/each}
121
+ </div>
122
+ </div>
123
+
124
+ <style>
125
+ .autocomplete {
126
+ position: relative;
127
+ border-radius: 0.5em;
128
+ background: color-mix(in srgb, var(--bg-color) 80%, transparent);
129
+ box-sizing: border-box;
130
+ line-height: normal;
131
+ }
132
+
133
+ input {
134
+ width: 100%;
135
+ display: block;
136
+ box-sizing: border-box;
137
+ padding: 0.3em 0.6em;
138
+ border: none;
139
+ background: none;
140
+ color: var(--fg-color);
141
+ }
142
+
143
+ .autocomplete-results {
144
+ padding: 0;
145
+ margin: 0;
146
+ background: none;
147
+ width: 100%;
148
+ display: block;
149
+ border-radius: 0 0 0.5em 0.5em;
150
+ }
151
+
152
+ .autocomplete-results.hide-results {
153
+ display: none;
154
+ }
155
+
156
+ button {
157
+ padding: 0.2rem 0.5rem;
158
+ cursor: pointer;
159
+ border: none;
160
+ display: block;
161
+ background: transparent;
162
+ font-weight: normal;
163
+ color: color-mix(in srgb, var(--fg-color) 50%, transparent);
164
+ width: 100%;
165
+ text-align: left;
166
+ }
167
+
168
+ button > :global(span) {
169
+ color: var(--fg-color);
170
+ }
171
+
172
+ button.is-active,
173
+ button:hover {
174
+ background-color: color-mix(in srgb, var(--fg-color) 15%, transparent);
175
+ }
176
+ </style>
@@ -1,32 +1,26 @@
1
1
  declare class __sveltets_Render<T> {
2
2
  props(): {
3
- placeholder?: string;
4
- minChar?: number;
5
- maxItems?: number;
6
- items: {
3
+ change: (value: T) => void;
4
+ initialInputText?: string;
5
+ inputText?: string;
6
+ items?: {
7
7
  key: string;
8
8
  value: T;
9
- }[];
10
- setInputText?: ((text: string) => void) | undefined;
11
- };
12
- events(): {
13
- change: CustomEvent<any>;
14
- } & {
15
- [evt: string]: CustomEvent<any>;
9
+ }[] | undefined;
10
+ maxItems?: number;
11
+ minChar?: number;
12
+ placeholder?: string;
16
13
  };
14
+ events(): {};
17
15
  slots(): {};
18
- bindings(): string;
19
- exports(): {
20
- setInputText: (text: string) => void;
21
- };
16
+ bindings(): "inputText";
17
+ exports(): {};
22
18
  }
23
19
  interface $$IsomorphicComponent {
24
20
  new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
25
21
  $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
26
22
  } & ReturnType<__sveltets_Render<T>['exports']>;
27
- <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {
28
- $$events?: ReturnType<__sveltets_Render<T>['events']>;
29
- }): ReturnType<__sveltets_Render<T>['exports']>;
23
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
30
24
  z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
31
25
  }
32
26
  declare const AutoComplete: $$IsomorphicComponent;
@@ -1,15 +1,4 @@
1
- import type { GeoJSON } from 'geojson';
2
- import type { LngLat, Point } from 'maplibre-gl';
3
- export type BBox = [number, number, number, number];
4
- export type BBoxDrag = '0_' | '1_' | '_0' | '_1' | '00' | '01' | '10' | '11' | false;
5
- export declare function getBBoxDrag(point: Point, bboxPixel: BBox): BBoxDrag;
6
- export declare function dragBBox(bbox: BBox, drag: BBoxDrag, lngLat: LngLat): {
7
- bbox: BBox;
8
- drag: BBoxDrag;
9
- };
10
- export declare function getCursor(drag: BBoxDrag): string | false;
11
- export declare function getBBoxGeometry(bbox: BBox): GeoJSON;
12
1
  export declare function loadBBoxes(): Promise<{
13
2
  key: string;
14
- value: BBox;
3
+ value: [number, number, number, number];
15
4
  }[]>;
@@ -1,204 +1,5 @@
1
- export function getBBoxDrag(point, bboxPixel) {
2
- const maxDistance = 5;
3
- const { x, y } = point;
4
- const [x0, y0, x1, y1] = bboxPixel;
5
- // Don't think outside the box
6
- if (x < x0 - maxDistance)
7
- return false;
8
- if (x > x1 + maxDistance)
9
- return false;
10
- if (y < y0 - maxDistance)
11
- return false;
12
- if (y > y1 + maxDistance)
13
- return false;
14
- const drag = [
15
- Math.abs(x0 - x) < maxDistance,
16
- Math.abs(y0 - y) < maxDistance,
17
- Math.abs(x1 - x) < maxDistance,
18
- Math.abs(y1 - y) < maxDistance
19
- ];
20
- if (drag[0] && drag[2]) {
21
- if (Math.abs(x0 - x) < Math.abs(x1 - x)) {
22
- drag[2] = false;
23
- }
24
- else {
25
- drag[0] = false;
26
- }
27
- }
28
- if (drag[1] && drag[3]) {
29
- if (Math.abs(y0 - y) < Math.abs(y1 - y)) {
30
- drag[3] = false;
31
- }
32
- else {
33
- drag[1] = false;
34
- }
35
- }
36
- if (drag[0]) {
37
- if (drag[1])
38
- return '01';
39
- if (drag[3])
40
- return '00';
41
- return '0_';
42
- }
43
- else if (drag[1]) {
44
- if (drag[2])
45
- return '11';
46
- return '_1';
47
- }
48
- else if (drag[2]) {
49
- if (drag[3])
50
- return '10';
51
- return '1_';
52
- }
53
- else if (drag[3]) {
54
- return '_0';
55
- }
56
- else
57
- return false;
58
- }
59
- export function dragBBox(bbox, drag, lngLat) {
60
- const x = Math.round(lngLat.lng * 1e3) / 1e3;
61
- const y = Math.round(lngLat.lat * 1e3) / 1e3;
62
- switch (drag) {
63
- case '_0':
64
- bbox[1] = y;
65
- break;
66
- case '_1':
67
- bbox[3] = y;
68
- break;
69
- case '0_':
70
- bbox[0] = x;
71
- break;
72
- case '00':
73
- bbox[0] = x;
74
- bbox[1] = y;
75
- break;
76
- case '01':
77
- bbox[0] = x;
78
- bbox[3] = y;
79
- break;
80
- case '1_':
81
- bbox[2] = x;
82
- break;
83
- case '10':
84
- bbox[2] = x;
85
- bbox[1] = y;
86
- break;
87
- case '11':
88
- bbox[2] = x;
89
- bbox[3] = y;
90
- break;
91
- }
92
- if (bbox[2] < bbox[0]) {
93
- // flip horizontal
94
- const t = bbox[0];
95
- bbox[0] = bbox[2];
96
- bbox[2] = t;
97
- switch (drag) {
98
- case '0_':
99
- drag = '1_';
100
- break;
101
- case '00':
102
- drag = '10';
103
- break;
104
- case '01':
105
- drag = '11';
106
- break;
107
- case '1_':
108
- drag = '0_';
109
- break;
110
- case '10':
111
- drag = '00';
112
- break;
113
- case '11':
114
- drag = '01';
115
- break;
116
- }
117
- }
118
- if (bbox[3] < bbox[1]) {
119
- // flip vertical
120
- const t = bbox[1];
121
- bbox[1] = bbox[3];
122
- bbox[3] = t;
123
- switch (drag) {
124
- case '_0':
125
- drag = '_1';
126
- break;
127
- case '_1':
128
- drag = '_0';
129
- break;
130
- case '00':
131
- drag = '01';
132
- break;
133
- case '01':
134
- drag = '00';
135
- break;
136
- case '10':
137
- drag = '11';
138
- break;
139
- case '11':
140
- drag = '10';
141
- break;
142
- }
143
- }
144
- return { drag, bbox };
145
- }
146
- export function getCursor(drag) {
147
- switch (drag) {
148
- case '_0':
149
- return 'ns-resize';
150
- case '_1':
151
- return 'ns-resize';
152
- case '0_':
153
- return 'ew-resize';
154
- case '00':
155
- return 'nesw-resize';
156
- case '01':
157
- return 'nwse-resize';
158
- case '1_':
159
- return 'ew-resize';
160
- case '10':
161
- return 'nwse-resize';
162
- case '11':
163
- return 'nesw-resize';
164
- }
165
- return false;
166
- }
167
- export function getBBoxGeometry(bbox) {
168
- return {
169
- type: 'FeatureCollection',
170
- features: [polygon(getRing([-180, -86, 180, 86]), getRing(bbox)), linestring(getRing(bbox))]
171
- };
172
- function polygon(...coordinates) {
173
- return {
174
- type: 'Feature',
175
- geometry: { type: 'Polygon', coordinates },
176
- properties: {}
177
- };
178
- }
179
- function linestring(coordinates) {
180
- return {
181
- type: 'Feature',
182
- geometry: { type: 'LineString', coordinates },
183
- properties: {}
184
- };
185
- }
186
- function getRing(bbox) {
187
- const x0 = Math.min(bbox[0], bbox[2]);
188
- const x1 = Math.max(bbox[0], bbox[2]);
189
- const y0 = Math.min(bbox[1], bbox[3]);
190
- const y1 = Math.max(bbox[1], bbox[3]);
191
- return [
192
- [x0, y0],
193
- [x1, y0],
194
- [x1, y1],
195
- [x0, y1],
196
- [x0, y0]
197
- ];
198
- }
199
- }
200
1
  export async function loadBBoxes() {
201
- const data = await import('./bboxes.json');
2
+ const data = await import('../../components/BBoxMap/bboxes.json');
202
3
  const bboxes = data.default.map((e) => {
203
4
  const key = e[0];
204
5
  const value = e.slice(1, 5);
@@ -1,128 +1,44 @@
1
1
  <!-- BBoxMap.svelte -->
2
- <script lang="ts">
3
- import type { CameraOptions, Point, Map as MaplibreMapType, GeoJSONSource } from 'maplibre-gl';
4
- import type { BBox, BBoxDrag } from './BBoxMap.js';
5
- import { onMount } from 'svelte';
6
- import 'maplibre-gl/dist/maplibre-gl.css';
7
- import { dragBBox, getBBoxDrag, loadBBoxes, getBBoxGeometry, getCursor } from './BBoxMap.js';
8
- import AutoComplete from '../AutoComplete.svelte';
9
- import { getCountry } from '../../utils/location.js';
10
- import BasicMap from '../BasicMap/BasicMap.svelte';
11
- import { isDarkMode } from '../../utils/style.js';
12
-
13
- let bboxes: { key: string; value: BBox }[] | undefined = undefined;
14
- let mapContainer: HTMLDivElement;
15
- let autoComplete: AutoComplete<BBox> | undefined = undefined;
16
- const worldBBox: BBox = [-180, -85, 180, 85];
17
- const startTime = Date.now();
18
- export let selectedBBox: BBox = worldBBox;
19
- let map: MaplibreMapType; // Declare map instance at the top level
20
-
21
- onMount(async () => {
22
- bboxes = await loadBBoxes();
23
- start();
24
- });
25
-
26
- function handleMapReady(event: CustomEvent) {
27
- map = event.detail.map;
28
- map.setPadding({ top: 31 + 5, right: 5, bottom: 5, left: 5 });
29
-
30
- const canvas = map.getCanvasContainer();
31
- const bboxColor = isDarkMode(mapContainer) ? '#FFFFFF' : '#000000';
32
-
33
- map.on('load', () => {
34
- map.addSource('bbox', { type: 'geojson', data: getBBoxGeometry(selectedBBox) });
35
- map.addLayer({
36
- id: 'bbox-line',
37
- type: 'line',
38
- source: 'bbox',
39
- filter: ['==', '$type', 'LineString'],
40
- layout: { 'line-cap': 'round', 'line-join': 'round' },
41
- paint: { 'line-color': bboxColor, 'line-width': 0.5 }
42
- });
43
- map.addLayer({
44
- id: 'bbox-fill',
45
- type: 'fill',
46
- source: 'bbox',
47
- filter: ['==', '$type', 'Polygon'],
48
- paint: { 'fill-color': bboxColor, 'fill-opacity': 0.2 }
49
- });
50
- });
51
-
52
- function getDrag(point: Point): BBoxDrag {
53
- const { x: x0, y: y1 } = map.project([selectedBBox[0], selectedBBox[1]]);
54
- const { x: x1, y: y0 } = map.project([selectedBBox[2], selectedBBox[3]]);
55
- return getBBoxDrag(point, [x0, y0, x1, y1]);
56
- }
57
-
58
- let lastDrag: BBoxDrag = false;
59
- let dragging = false;
60
- map.on('mousemove', (e) => {
61
- if (dragging) {
62
- if (e.originalEvent.buttons % 2) {
63
- const { drag, bbox } = dragBBox(selectedBBox, lastDrag, e.lngLat);
64
- lastDrag = drag;
65
- selectedBBox = bbox;
66
- redrawBBox();
67
- e.preventDefault();
68
- } else {
69
- dragging = false;
70
- }
71
- } else {
72
- const drag = getDrag(e.point);
73
- if (drag !== lastDrag) {
74
- lastDrag = drag;
75
- canvas.style.cursor = getCursor(drag) || 'grab';
76
- }
77
- }
78
- });
79
-
80
- map.on('mousedown', (e) => {
81
- if (e.originalEvent.buttons % 2) {
82
- const drag = getDrag(e.point);
83
- lastDrag = drag;
84
- if (drag) {
85
- dragging = true;
86
- e.preventDefault();
87
- }
88
- }
89
- });
90
-
91
- map.on('mouseup', () => (dragging = false));
92
-
93
- start();
94
- }
95
-
96
- function start() {
97
- if (!bboxes) return;
98
- if (!map) return;
99
- if (!autoComplete) return;
100
- autoComplete.setInputText(getCountry()); // Initial search text
101
- }
102
-
103
- function redrawBBox() {
104
- const bboxSource = map.getSource('bbox') as GeoJSONSource;
105
- bboxSource.setData(getBBoxGeometry(selectedBBox));
106
- }
107
-
108
- function flyTo(bbox: BBox) {
109
- selectedBBox = bbox ?? worldBBox;
110
- if (map) {
111
- if (map.getSource('bbox')) redrawBBox();
112
-
113
- const transform = map.cameraForBounds(selectedBBox) as CameraOptions;
114
- if (transform == null) return;
115
- transform.zoom = transform.zoom ?? 0 - 0.5;
116
- transform.bearing = 0;
117
- transform.pitch = 0;
118
-
119
- if (Date.now() - startTime < 1000) {
120
- map.jumpTo(transform);
121
- } else {
122
- map.flyTo({ ...transform, essential: true, speed: 5 });
123
- }
124
- }
125
- }
2
+ <script lang="ts">import 'maplibre-gl/dist/maplibre-gl.css';
3
+ import AutoComplete from './AutoComplete.svelte';
4
+ import { getCountryName } from '../../utils/location.js';
5
+ import BasicMap from '../BasicMap/BasicMap.svelte';
6
+ import { isDarkMode } from '../../utils/map_style.js';
7
+ import { loadBBoxes } from './BBoxMap.js';
8
+ import { BBoxDrawer } from '../../utils/draw/bbox.js';
9
+ let { selectedBBox = $bindable() } = $props();
10
+ const startTime = Date.now();
11
+ let bbox;
12
+ let map = $state();
13
+ let bboxes = $state();
14
+ let mapContainer;
15
+ function onMapInit(_map) {
16
+ map = _map;
17
+ mapContainer = map.getContainer();
18
+ map.setPadding({ top: 31 + 5, right: 5, bottom: 5, left: 5 });
19
+ map.on('load', async () => {
20
+ bbox = new BBoxDrawer(map, [-180, -86, 180, 86], isDarkMode(mapContainer) ? '#FFFFFF' : '#000000');
21
+ bboxes = await loadBBoxes();
22
+ });
23
+ }
24
+ function setBBox(newBBox) {
25
+ selectedBBox = newBBox;
26
+ if (bbox && map) {
27
+ bbox.setGeometry(newBBox);
28
+ const transform = map.cameraForBounds(bbox.getBounds());
29
+ if (transform == null)
30
+ return;
31
+ transform.zoom = transform.zoom ?? 0 - 0.5;
32
+ transform.bearing = 0;
33
+ transform.pitch = 0;
34
+ if (Date.now() - startTime < 1000) {
35
+ map.jumpTo(transform);
36
+ }
37
+ else {
38
+ map.flyTo({ ...transform, essential: true, speed: 5 });
39
+ }
40
+ }
41
+ }
126
42
  </script>
127
43
 
128
44
  <div class="container">
@@ -131,12 +47,12 @@
131
47
  <AutoComplete
132
48
  items={bboxes}
133
49
  placeholder="Find country, region or city …"
134
- on:change={(e) => flyTo(e.detail)}
135
- bind:this={autoComplete}
50
+ change={setBBox}
51
+ initialInputText={getCountryName() ?? ''}
136
52
  />
137
53
  </div>
138
54
  {/if}
139
- <BasicMap {map} bind:container={mapContainer} on:mapReady={handleMapReady}></BasicMap>
55
+ <BasicMap {onMapInit}></BasicMap>
140
56
  </div>
141
57
 
142
58
  <style>