@sjvair/map-sdk 0.0.0-stage → 0.1.0-beta.1

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +487 -2
  3. package/dist/core/basemap.svelte.d.ts +38 -0
  4. package/dist/core/basemap.svelte.js +163 -0
  5. package/dist/core/context.d.ts +5 -0
  6. package/dist/core/context.js +13 -0
  7. package/dist/core/create-map.svelte.d.ts +207 -0
  8. package/dist/core/create-map.svelte.js +376 -0
  9. package/dist/core/gl.d.ts +92 -0
  10. package/dist/core/gl.js +1 -0
  11. package/dist/core/interactions.svelte.d.ts +42 -0
  12. package/dist/core/interactions.svelte.js +213 -0
  13. package/dist/core/plugin-host.svelte.d.ts +46 -0
  14. package/dist/core/plugin-host.svelte.js +366 -0
  15. package/dist/core/plugin.d.ts +148 -0
  16. package/dist/core/plugin.js +1 -0
  17. package/dist/core/scoped-context.d.ts +7 -0
  18. package/dist/core/scoped-context.js +34 -0
  19. package/dist/data/format.d.ts +10 -0
  20. package/dist/data/format.js +24 -0
  21. package/dist/data/index.d.ts +9 -0
  22. package/dist/data/index.js +9 -0
  23. package/dist/data/messages/en.json +51 -0
  24. package/dist/data/messages.d.ts +10 -0
  25. package/dist/data/messages.js +17 -0
  26. package/dist/data/metadata.d.ts +22 -0
  27. package/dist/data/metadata.js +110 -0
  28. package/dist/data/refs.d.ts +18 -0
  29. package/dist/data/refs.js +61 -0
  30. package/dist/data/scale-types.d.ts +7 -0
  31. package/dist/data/scale-types.js +1 -0
  32. package/dist/data/scale.d.ts +13 -0
  33. package/dist/data/scale.js +27 -0
  34. package/dist/data/selection.d.ts +7 -0
  35. package/dist/data/selection.js +31 -0
  36. package/dist/data/static-source.d.ts +4 -0
  37. package/dist/data/static-source.js +3 -0
  38. package/dist/data/types.d.ts +69 -0
  39. package/dist/data/types.js +1 -0
  40. package/dist/index.d.ts +56 -0
  41. package/dist/index.js +41 -0
  42. package/dist/plugins/icons.d.ts +21 -0
  43. package/dist/plugins/icons.js +73 -0
  44. package/dist/plugins/points-clusters.d.ts +60 -0
  45. package/dist/plugins/points-clusters.js +93 -0
  46. package/dist/plugins/points.svelte.d.ts +93 -0
  47. package/dist/plugins/points.svelte.js +473 -0
  48. package/dist/plugins/scale-resolution.svelte.d.ts +27 -0
  49. package/dist/plugins/scale-resolution.svelte.js +111 -0
  50. package/dist/plugins/shapes.svelte.d.ts +29 -0
  51. package/dist/plugins/shapes.svelte.js +307 -0
  52. package/dist/plugins/shared.d.ts +6 -0
  53. package/dist/plugins/shared.js +18 -0
  54. package/dist/plugins/style.d.ts +57 -0
  55. package/dist/plugins/style.js +193 -0
  56. package/dist/presets/CollocationTooltip.svelte +132 -0
  57. package/dist/presets/CollocationTooltip.svelte.d.ts +16 -0
  58. package/dist/presets/MonitorClusterTooltip.svelte +149 -0
  59. package/dist/presets/MonitorClusterTooltip.svelte.d.ts +16 -0
  60. package/dist/presets/MonitorReadingBox.svelte +58 -0
  61. package/dist/presets/MonitorReadingBox.svelte.d.ts +12 -0
  62. package/dist/presets/MonitorTooltip.svelte +88 -0
  63. package/dist/presets/MonitorTooltip.svelte.d.ts +20 -0
  64. package/dist/presets/calheatscore.d.ts +24 -0
  65. package/dist/presets/calheatscore.js +22 -0
  66. package/dist/presets/collocation-sites.d.ts +18 -0
  67. package/dist/presets/collocation-sites.js +67 -0
  68. package/dist/presets/fire-groups.d.ts +26 -0
  69. package/dist/presets/fire-groups.js +51 -0
  70. package/dist/presets/forecast-zones.d.ts +25 -0
  71. package/dist/presets/forecast-zones.js +23 -0
  72. package/dist/presets/hms-fire.d.ts +26 -0
  73. package/dist/presets/hms-fire.js +87 -0
  74. package/dist/presets/hms-smoke.d.ts +19 -0
  75. package/dist/presets/hms-smoke.js +20 -0
  76. package/dist/presets/monitors.d.ts +48 -0
  77. package/dist/presets/monitors.js +243 -0
  78. package/dist/presets/reactive-props.svelte.d.ts +4 -0
  79. package/dist/presets/reactive-props.svelte.js +7 -0
  80. package/dist/presets/regions.d.ts +24 -0
  81. package/dist/presets/regions.js +22 -0
  82. package/dist/stores/abort.d.ts +16 -0
  83. package/dist/stores/abort.js +52 -0
  84. package/dist/stores/collocation-store.svelte.d.ts +25 -0
  85. package/dist/stores/collocation-store.svelte.js +72 -0
  86. package/dist/stores/hms-stores.svelte.d.ts +35 -0
  87. package/dist/stores/hms-stores.svelte.js +97 -0
  88. package/dist/stores/index.d.ts +4 -0
  89. package/dist/stores/index.js +4 -0
  90. package/dist/stores/load-once.svelte.d.ts +25 -0
  91. package/dist/stores/load-once.svelte.js +93 -0
  92. package/dist/stores/monitors-store.svelte.d.ts +85 -0
  93. package/dist/stores/monitors-store.svelte.js +340 -0
  94. package/dist/theme.css +34 -0
  95. package/dist/ui/FeatureList.svelte +501 -0
  96. package/dist/ui/FeatureList.svelte.d.ts +14 -0
  97. package/dist/ui/Legend.svelte +172 -0
  98. package/dist/ui/Legend.svelte.d.ts +13 -0
  99. package/dist/ui/LegendBar.svelte +161 -0
  100. package/dist/ui/LegendBar.svelte.d.ts +11 -0
  101. package/dist/ui/MapControls.svelte +135 -0
  102. package/dist/ui/MapControls.svelte.d.ts +11 -0
  103. package/dist/ui/MapTooltip.svelte +158 -0
  104. package/dist/ui/MapTooltip.svelte.d.ts +28 -0
  105. package/dist/ui/SelectionBar.svelte +123 -0
  106. package/dist/ui/SelectionBar.svelte.d.ts +10 -0
  107. package/dist/ui/TooltipItem.svelte +66 -0
  108. package/dist/ui/TooltipItem.svelte.d.ts +11 -0
  109. package/dist/ui/ViewSummary.svelte +97 -0
  110. package/dist/ui/ViewSummary.svelte.d.ts +7 -0
  111. package/dist/ui/controls.css +45 -0
  112. package/dist/ui/data-credits.svelte.d.ts +9 -0
  113. package/dist/ui/data-credits.svelte.js +52 -0
  114. package/dist/ui/feature-list.d.ts +31 -0
  115. package/dist/ui/feature-list.js +80 -0
  116. package/dist/ui/legend-data.d.ts +41 -0
  117. package/dist/ui/legend-data.js +40 -0
  118. package/dist/ui/level-chip.d.ts +11 -0
  119. package/dist/ui/level-chip.js +15 -0
  120. package/dist/ui/value-format.d.ts +7 -0
  121. package/dist/ui/value-format.js +13 -0
  122. package/dist/ui/view-summary.d.ts +11 -0
  123. package/dist/ui/view-summary.js +33 -0
  124. package/dist/view/MapView.svelte +373 -0
  125. package/dist/view/MapView.svelte.d.ts +43 -0
  126. package/dist/view/create-gl-map.d.ts +34 -0
  127. package/dist/view/create-gl-map.js +182 -0
  128. package/dist/view/list-placement.d.ts +21 -0
  129. package/dist/view/list-placement.js +13 -0
  130. package/package.json +103 -5
@@ -0,0 +1,376 @@
1
+ import { untrack } from "svelte";
2
+ import { SvelteMap } from "svelte/reactivity";
3
+ import { createDefaultFormat } from "../data/format.js";
4
+ import { defaultMessages } from "../data/messages.js";
5
+ import { createDefaultMetadata } from "../data/metadata.js";
6
+ import { applyPick, selectionKeys } from "../data/selection.js";
7
+ import { BasemapState } from "./basemap.svelte.js";
8
+ import { InteractionRouter } from "./interactions.svelte.js";
9
+ import { PluginHost } from "./plugin-host.svelte.js";
10
+ /** Monitor-map's start view. */
11
+ const DEFAULT_VIEW = {
12
+ center: [-119.7987626619462, 36.76272050981146],
13
+ zoom: 7,
14
+ bearing: 0,
15
+ pitch: 0
16
+ };
17
+ /** One plugin's selection state. */
18
+ class PluginSelection {
19
+ mode = $state("none");
20
+ selection = $state.raw(null);
21
+ onselectionchange;
22
+ }
23
+ function copyView(view) {
24
+ return { ...view, center: [view.center[0], view.center[1]] };
25
+ }
26
+ function copySelection(sel) {
27
+ return sel && { kind: sel.kind, ids: [...sel.ids] };
28
+ }
29
+ /** 8 hex characters. `crypto.randomUUID` is missing on insecure origins (a dev server on a LAN IP). */
30
+ function randomUid() {
31
+ const bytes = crypto.getRandomValues(new Uint8Array(4));
32
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
33
+ }
34
+ function sameSelection(a, b) {
35
+ if (!a || !b)
36
+ return a === b;
37
+ return (a.kind === b.kind && a.ids.length === b.ids.length && a.ids.every((id, i) => id === b.ids[i]));
38
+ }
39
+ function prefersReducedMotion() {
40
+ return globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
41
+ }
42
+ class MapContextImpl {
43
+ uid = randomUid();
44
+ config;
45
+ metadata;
46
+ messages;
47
+ format;
48
+ basemap;
49
+ #camera = $state.raw(DEFAULT_VIEW);
50
+ #bounds = $state.raw(null);
51
+ #attached = $state(false);
52
+ #ready = $state(false);
53
+ #glState = $state.raw(null);
54
+ #focusedKey = $state(null);
55
+ #focusedPluginId = $state(null);
56
+ #selections = new SvelteMap();
57
+ #featureClickHandlers = new Set();
58
+ #router = new InteractionRouter();
59
+ #host;
60
+ #gl = null;
61
+ #destroyed = false;
62
+ #listInset = 0;
63
+ #listInsetSide = "left";
64
+ constructor(options) {
65
+ const initialView = copyView(options.config.initialView ?? DEFAULT_VIEW);
66
+ this.config = {
67
+ ...options.config,
68
+ initialView,
69
+ cooperativeGestures: options.config.cooperativeGestures ?? false
70
+ };
71
+ this.metadata = options.metadata ?? createDefaultMetadata();
72
+ this.messages = options.messages ?? defaultMessages;
73
+ this.format = options.format ?? createDefaultFormat();
74
+ this.basemap = new BasemapState(options.config.basemaps);
75
+ this.#camera = copyView(initialView);
76
+ this.#host = new PluginHost({
77
+ mapUid: this.uid,
78
+ providers: { metadata: this.metadata, messages: this.messages, format: this.format },
79
+ router: this.#router,
80
+ selectedKeys: (pluginId) => selectionKeys(this.getSelection(pluginId))
81
+ });
82
+ this.#router.onPick((e) => this.#pick(e));
83
+ }
84
+ get camera() {
85
+ return this.#camera;
86
+ }
87
+ get bounds() {
88
+ return this.#bounds;
89
+ }
90
+ get plugins() {
91
+ return this.#host.plugins;
92
+ }
93
+ get attached() {
94
+ return this.#attached;
95
+ }
96
+ get ready() {
97
+ return this.#ready;
98
+ }
99
+ get gl() {
100
+ return this.#glState;
101
+ }
102
+ markReady() {
103
+ if (this.#gl)
104
+ this.#ready = true;
105
+ }
106
+ get focusedKey() {
107
+ return this.#focusedKey;
108
+ }
109
+ get focusedPluginId() {
110
+ return this.#focusedPluginId;
111
+ }
112
+ get hovered() {
113
+ return this.#router.hovered;
114
+ }
115
+ addPlugin(plugin, options = {}) {
116
+ this.#assertAlive();
117
+ const previous = untrack(() => this.#selections.get(plugin.id));
118
+ const state = new PluginSelection();
119
+ // Untracked: addPlugin may be called from inside an effect (e.g. a host component's).
120
+ untrack(() => {
121
+ state.mode = options.selectionMode ?? previous?.mode ?? "none";
122
+ state.selection =
123
+ options.selection !== undefined
124
+ ? copySelection(options.selection)
125
+ : (previous?.selection ?? null);
126
+ });
127
+ state.onselectionchange = options.onselectionchange ?? previous?.onselectionchange;
128
+ // Stored before the host adds it, so a setup that runs at once sees its selection.
129
+ this.#selections.set(plugin.id, state);
130
+ try {
131
+ this.#host.add(plugin, options.before);
132
+ }
133
+ catch (error) {
134
+ if (previous)
135
+ this.#selections.set(plugin.id, previous);
136
+ else
137
+ this.#selections.delete(plugin.id);
138
+ throw error;
139
+ }
140
+ }
141
+ movePlugin(id, options = {}) {
142
+ if (this.#destroyed)
143
+ return;
144
+ this.#host.move(id, options.before);
145
+ }
146
+ removePlugin(id) {
147
+ this.#host.remove(id);
148
+ this.#selections.delete(id);
149
+ }
150
+ setSelection(pluginId, selection) {
151
+ if (this.#destroyed)
152
+ return;
153
+ this.#stateFor(pluginId).selection = copySelection(selection);
154
+ }
155
+ getSelection(pluginId) {
156
+ return this.#selections.get(pluginId)?.selection ?? null;
157
+ }
158
+ getSelectionMode(pluginId) {
159
+ return this.#selections.get(pluginId)?.mode ?? "none";
160
+ }
161
+ pickFromList(pluginId, refKey, additive) {
162
+ if (this.#destroyed)
163
+ return;
164
+ const found = untrack(() => this.#findItem(refKey, pluginId));
165
+ if (!found)
166
+ return;
167
+ this.#emitClick(pluginId, found.item.ref, found.item.lngLat);
168
+ if (found.item.selectable)
169
+ this.#applyPick(pluginId, found.item.ref, additive);
170
+ }
171
+ clearSelection(pluginId) {
172
+ if (this.#destroyed)
173
+ return;
174
+ const state = untrack(() => this.#selections.get(pluginId));
175
+ if (!state || !untrack(() => state.selection))
176
+ return;
177
+ state.selection = null;
178
+ state.onselectionchange?.(null);
179
+ }
180
+ setSelectionMode(pluginId, mode) {
181
+ if (this.#destroyed)
182
+ return;
183
+ this.#stateFor(pluginId).mode = mode;
184
+ }
185
+ on(event, handler) {
186
+ if (this.#destroyed)
187
+ return () => { };
188
+ this.#featureClickHandlers.add(handler);
189
+ return () => {
190
+ this.#featureClickHandlers.delete(handler);
191
+ };
192
+ }
193
+ featuresAt(point) {
194
+ return this.#router.hitsAt([point.x, point.y]);
195
+ }
196
+ featureForElement(element) {
197
+ const row = element.closest("[data-mapsdk-ref]");
198
+ if (!row || row.closest("[data-mapsdk-map]")?.getAttribute("data-mapsdk-map") !== this.uid) {
199
+ return null;
200
+ }
201
+ const refKey = row.getAttribute("data-mapsdk-ref") ?? "";
202
+ const pluginId = row.getAttribute("data-mapsdk-plugin") ?? undefined;
203
+ const found = untrack(() => this.#findItem(refKey, pluginId));
204
+ if (!found)
205
+ return null;
206
+ const { plugin, item } = found;
207
+ return { pluginId: plugin.id, ref: item.ref, refKey: item.refKey, name: item.name };
208
+ }
209
+ focusFeature(refKey, pluginId) {
210
+ if (this.#destroyed)
211
+ return;
212
+ this.#focusedKey = refKey;
213
+ this.#focusedPluginId = refKey === null ? null : (pluginId ?? null);
214
+ const gl = this.#gl;
215
+ if (!gl || refKey === null)
216
+ return;
217
+ const found = untrack(() => this.#findItem(refKey, pluginId));
218
+ if (!found)
219
+ return;
220
+ gl.easeTo({
221
+ center: found.item.lngLat,
222
+ // The middle of the part the panel leaves clear. An offset, not padding: MapLibre keeps
223
+ // padding for later moves (zooms, the reported center), an offset applies to this one.
224
+ ...(this.#listInset > 0 && { offset: [this.#listOffsetX(), 0] }),
225
+ ...(prefersReducedMotion() && { duration: 0 })
226
+ });
227
+ }
228
+ #cameraOptions(target, gl) {
229
+ const zoom = target.zoom ??
230
+ (target.minZoom === undefined ? undefined : Math.max(gl.getZoom(), target.minZoom));
231
+ return {
232
+ center: target.center,
233
+ ...(zoom !== undefined && { zoom }),
234
+ ...(prefersReducedMotion() && { duration: 0 })
235
+ };
236
+ }
237
+ easeTo(target) {
238
+ const gl = this.#destroyed ? null : this.#gl;
239
+ if (gl)
240
+ gl.easeTo(this.#cameraOptions(target, gl));
241
+ }
242
+ flyTo(target) {
243
+ const gl = this.#destroyed ? null : this.#gl;
244
+ if (gl)
245
+ gl.flyTo(this.#cameraOptions(target, gl));
246
+ }
247
+ fitBounds(bounds, options = {}) {
248
+ const gl = this.#destroyed ? null : this.#gl;
249
+ if (!gl)
250
+ return;
251
+ gl.fitBounds([
252
+ [bounds.west, bounds.south],
253
+ [bounds.east, bounds.north]
254
+ ], {
255
+ ...(options.padding !== undefined && { padding: options.padding }),
256
+ ...(options.maxZoom !== undefined && { maxZoom: options.maxZoom }),
257
+ ...(prefersReducedMotion() && { duration: 0 })
258
+ });
259
+ }
260
+ attach(gl) {
261
+ this.#assertAlive();
262
+ if (this.#gl === gl)
263
+ return;
264
+ if (this.#gl)
265
+ this.detach();
266
+ this.#gl = gl;
267
+ this.#glState = gl;
268
+ this.#host.attach(gl);
269
+ this.#router.attach(gl);
270
+ this.basemap.attach(gl, () => this.#host.restyle(), () => {
271
+ this.#ready = false;
272
+ this.#host.suspend();
273
+ });
274
+ this.#attached = true;
275
+ }
276
+ detach() {
277
+ if (!this.#gl)
278
+ return;
279
+ this.#router.detach();
280
+ this.basemap.detach();
281
+ this.#host.detach();
282
+ this.#gl = null;
283
+ this.#glState = null;
284
+ this.#attached = false;
285
+ this.#ready = false;
286
+ }
287
+ themeChanged() {
288
+ if (this.#gl)
289
+ this.#host.restyle();
290
+ }
291
+ /** Half the panel's width, toward the part of the map it leaves clear. */
292
+ #listOffsetX() {
293
+ return ((this.#listInsetSide === "left" ? 1 : -1) * this.#listInset) / 2;
294
+ }
295
+ setListInset(width, side = "left") {
296
+ this.#listInset = Math.max(0, width);
297
+ this.#listInsetSide = side;
298
+ }
299
+ updateView(camera, bounds) {
300
+ this.#camera = copyView(camera);
301
+ this.#bounds = { ...bounds };
302
+ }
303
+ destroy() {
304
+ if (this.#destroyed)
305
+ return;
306
+ this.detach();
307
+ for (const plugin of untrack(() => this.plugins))
308
+ this.#host.remove(plugin.id);
309
+ this.#selections.clear();
310
+ this.#featureClickHandlers.clear();
311
+ this.#focusedKey = null;
312
+ this.#focusedPluginId = null;
313
+ this.#destroyed = true;
314
+ }
315
+ /** A click on a feature: report it, then apply it to the top hit's plugin's selection. */
316
+ #pick(e) {
317
+ if (this.#destroyed)
318
+ return;
319
+ const [{ pluginId, ref }] = e.hits;
320
+ this.#emitClick(pluginId, ref, e.lngLat);
321
+ this.#applyPick(pluginId, ref, e.additive);
322
+ }
323
+ #emitClick(pluginId, ref, lngLat) {
324
+ for (const handler of [...this.#featureClickHandlers]) {
325
+ try {
326
+ handler({ pluginId, ref, lngLat });
327
+ }
328
+ catch (error) {
329
+ console.error("map-sdk: a featureclick handler failed", error);
330
+ }
331
+ }
332
+ }
333
+ /** The one place a user's pick (map click or list key) changes a plugin's selection. */
334
+ #applyPick(pluginId, ref, additive) {
335
+ // A featureclick handler may have removed the plugin.
336
+ const state = untrack(() => this.#selections.get(pluginId));
337
+ if (!state)
338
+ return;
339
+ const current = untrack(() => state.selection);
340
+ const next = applyPick(current, ref, untrack(() => state.mode), { additive });
341
+ if (sameSelection(current, next))
342
+ return;
343
+ state.selection = next;
344
+ state.onselectionchange?.(copySelection(next));
345
+ }
346
+ /**
347
+ * A listed item and its plugin: in `pluginId` when given (`null` if that plugin doesn't list
348
+ * it), else the first plugin listing `refKey`. Reads `features()`, so call it untracked.
349
+ */
350
+ #findItem(refKey, pluginId) {
351
+ for (const plugin of this.plugins) {
352
+ if (pluginId !== undefined && plugin.id !== pluginId)
353
+ continue;
354
+ const item = plugin.features?.().find((f) => f.refKey === refKey);
355
+ if (item)
356
+ return { plugin, item };
357
+ }
358
+ return null;
359
+ }
360
+ #stateFor(pluginId) {
361
+ let state = untrack(() => this.#selections.get(pluginId));
362
+ if (!state) {
363
+ state = new PluginSelection();
364
+ this.#selections.set(pluginId, state);
365
+ }
366
+ return state;
367
+ }
368
+ #assertAlive() {
369
+ if (this.#destroyed)
370
+ throw new Error("map destroyed");
371
+ }
372
+ }
373
+ /** Creates a map's state and services. `MapView` attaches a GL map to it. */
374
+ export function createMap(options) {
375
+ return new MapContextImpl(options);
376
+ }
@@ -0,0 +1,92 @@
1
+ import type { CustomLayerInterface, LayerSpecification, SourceSpecification } from "@maptiler/sdk";
2
+ import type { Geometry } from "geojson";
3
+ /**
4
+ * The subset of MapLibre's `Map` the core and plugins use. Types only, so nothing in core/
5
+ * pulls the map engine at runtime, and tests can pass an in-memory fake.
6
+ */
7
+ export interface GlMapLike {
8
+ addSource(id: string, source: SourceSpecification): void;
9
+ removeSource(id: string): void;
10
+ getSource(id: string): unknown;
11
+ addLayer(layer: LayerSpecification | CustomLayerInterface, beforeId?: string): void;
12
+ /** Moves a layer below `beforeId`, or to the top without it. */
13
+ moveLayer(id: string, beforeId?: string): void;
14
+ removeLayer(id: string): void;
15
+ getLayer(id: string): unknown;
16
+ addImage(id: string, image: HTMLImageElement | ImageData, options?: {
17
+ pixelRatio?: number;
18
+ }): void;
19
+ hasImage(id: string): boolean;
20
+ removeImage(id: string): void;
21
+ setFeatureState(target: {
22
+ source: string;
23
+ id: string | number;
24
+ }, state: Record<string, unknown>): void;
25
+ setLayoutProperty(layerId: string, name: string, value: unknown): void;
26
+ removeFeatureState(target: {
27
+ source: string;
28
+ id?: string | number;
29
+ }, key?: string): void;
30
+ queryRenderedFeatures(point: [number, number] | [[number, number], [number, number]], options?: {
31
+ layers?: string[];
32
+ }): Array<{
33
+ layer: {
34
+ id: string;
35
+ };
36
+ source: string;
37
+ id?: string | number;
38
+ properties: Record<string, unknown>;
39
+ geometry: Geometry;
40
+ }>;
41
+ isStyleLoaded(): boolean | void;
42
+ setStyle(style: unknown, options?: {
43
+ diff?: boolean;
44
+ }): void;
45
+ on(type: string, listener: (e: any) => void): void;
46
+ on(type: string, layerId: string, listener: (e: any) => void): void;
47
+ off(type: string, listener: (e: any) => void): void;
48
+ off(type: string, layerId: string, listener: (e: any) => void): void;
49
+ getBounds(): {
50
+ getWest(): number;
51
+ getSouth(): number;
52
+ getEast(): number;
53
+ getNorth(): number;
54
+ };
55
+ getCenter(): {
56
+ lng: number;
57
+ lat: number;
58
+ };
59
+ getZoom(): number;
60
+ getBearing(): number;
61
+ getPitch(): number;
62
+ easeTo(options: {
63
+ center?: [number, number];
64
+ zoom?: number;
65
+ duration?: number;
66
+ /** Where `center` ends up, in pixels from the container's center (positive x is right). */
67
+ offset?: [number, number];
68
+ }): void;
69
+ flyTo(options: {
70
+ center?: [number, number];
71
+ zoom?: number;
72
+ duration?: number;
73
+ }): void;
74
+ fitBounds(bounds: [[number, number], [number, number]], options?: {
75
+ padding?: number;
76
+ maxZoom?: number;
77
+ duration?: number;
78
+ }): void;
79
+ jumpTo(options: {
80
+ center?: [number, number];
81
+ zoom?: number;
82
+ bearing?: number;
83
+ pitch?: number;
84
+ }): void;
85
+ /** Canvas pixel for a position. */
86
+ project(lngLat: [number, number]): {
87
+ x: number;
88
+ y: number;
89
+ };
90
+ getCanvas(): HTMLCanvasElement;
91
+ remove(): void;
92
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import type { GlMapLike } from "./gl.js";
2
+ import type { BlockingClick, ClickEvent, FeatureHit } from "./plugin.js";
3
+ export interface Hovered {
4
+ hit: FeatureHit;
5
+ lngLat: [number, number];
6
+ }
7
+ /**
8
+ * Routes click and hover events on plugin layers to feature hits. One per map.
9
+ *
10
+ * Listens on the map itself (`click`, `mousemove`, `mouseout`) and queries only the registered
11
+ * layers, so registrations can come and go without re-binding listeners. Layers nobody registered
12
+ * are never hit. A blocking layer (e.g. a cluster bubble, which zooms instead) is never hit either,
13
+ * but when it is topmost at a point nothing beneath it is hit or hovered.
14
+ */
15
+ export declare class InteractionRouter {
16
+ #private;
17
+ /** The top hit under the pointer and where it is. Reactive, for the tooltip. */
18
+ get hovered(): Hovered | null;
19
+ /** Records the layers whose features `toHit` turns into hits. Returns an unregister function. */
20
+ register(pluginId: string, layerIds: string[], toHit: (props: Record<string, unknown>) => FeatureHit | null, priority?: number): () => void;
21
+ /**
22
+ * Records layers that hide what is beneath them: when one is topmost at a point, clicks pick
23
+ * nothing (calling `onClick` with that feature instead) and the hover is `toHover`'s hit for
24
+ * it (or nothing). `toHover` must have no side effects: lookups call it too. Returns an
25
+ * unregister function.
26
+ */
27
+ registerBlocking(pluginId: string, layerIds: string[], toHover?: (props: Record<string, unknown>) => FeatureHit | null, onClick?: (feature: BlockingClick) => void, priority?: number): () => void;
28
+ /** Listens to `gl` from now on. */
29
+ attach(gl: GlMapLike): void;
30
+ /** Stops listening and clears `hovered`. */
31
+ detach(): void;
32
+ /**
33
+ * Hits under a canvas point, topmost first, de-duplicated by `refKey`. `[]` when detached or
34
+ * when a blocking layer is topmost.
35
+ */
36
+ hitsAt(point: [number, number]): FeatureHit[];
37
+ /**
38
+ * Called for every click with at least one hit. `additive` is true for a shift, ⌘, or Ctrl
39
+ * click, or a touch tap; selection rules decide what it means for the plugin's mode.
40
+ */
41
+ onPick(handler: (e: ClickEvent) => void): () => void;
42
+ }