@loidolt/theme-maps 0.7.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/components/BasemapSwitcher.svelte +53 -0
  4. package/dist/components/BasemapSwitcher.svelte.d.ts +20 -0
  5. package/dist/components/CircleLayer.svelte +22 -0
  6. package/dist/components/CircleLayer.svelte.d.ts +4 -0
  7. package/dist/components/CoordinateDisplay.svelte +68 -0
  8. package/dist/components/CoordinateDisplay.svelte.d.ts +13 -0
  9. package/dist/components/DeckOverlay.svelte +58 -0
  10. package/dist/components/DeckOverlay.svelte.d.ts +18 -0
  11. package/dist/components/FillExtrusionLayer.svelte +17 -0
  12. package/dist/components/FillExtrusionLayer.svelte.d.ts +4 -0
  13. package/dist/components/FillLayer.svelte +21 -0
  14. package/dist/components/FillLayer.svelte.d.ts +4 -0
  15. package/dist/components/HeatmapLayer.svelte +36 -0
  16. package/dist/components/HeatmapLayer.svelte.d.ts +4 -0
  17. package/dist/components/LayerManager.svelte +234 -0
  18. package/dist/components/LayerManager.svelte.d.ts +24 -0
  19. package/dist/components/LineLayer.svelte +18 -0
  20. package/dist/components/LineLayer.svelte.d.ts +4 -0
  21. package/dist/components/MapControl.svelte +47 -0
  22. package/dist/components/MapControl.svelte.d.ts +13 -0
  23. package/dist/components/MapFeatureList.svelte +67 -0
  24. package/dist/components/MapFeatureList.svelte.d.ts +20 -0
  25. package/dist/components/MapLegend.svelte +68 -0
  26. package/dist/components/MapLegend.svelte.d.ts +19 -0
  27. package/dist/components/MapLegendGroup.svelte +72 -0
  28. package/dist/components/MapLegendGroup.svelte.d.ts +19 -0
  29. package/dist/components/MapMarker.svelte +130 -0
  30. package/dist/components/MapMarker.svelte.d.ts +32 -0
  31. package/dist/components/MapPopup.svelte +123 -0
  32. package/dist/components/MapPopup.svelte.d.ts +21 -0
  33. package/dist/components/MapSource.svelte +109 -0
  34. package/dist/components/MapSource.svelte.d.ts +31 -0
  35. package/dist/components/MapView.svelte +454 -0
  36. package/dist/components/MapView.svelte.d.ts +76 -0
  37. package/dist/components/RasterLayer.svelte +14 -0
  38. package/dist/components/RasterLayer.svelte.d.ts +4 -0
  39. package/dist/components/SymbolLayer.svelte +22 -0
  40. package/dist/components/SymbolLayer.svelte.d.ts +4 -0
  41. package/dist/core/accessibility.d.ts +24 -0
  42. package/dist/core/accessibility.js +33 -0
  43. package/dist/core/bounds.d.ts +18 -0
  44. package/dist/core/bounds.js +91 -0
  45. package/dist/core/cluster.d.ts +35 -0
  46. package/dist/core/cluster.js +44 -0
  47. package/dist/core/data.d.ts +33 -0
  48. package/dist/core/data.js +157 -0
  49. package/dist/core/equality.d.ts +7 -0
  50. package/dist/core/equality.js +33 -0
  51. package/dist/core/format.d.ts +20 -0
  52. package/dist/core/format.js +47 -0
  53. package/dist/core/index.d.ts +16 -0
  54. package/dist/core/index.js +18 -0
  55. package/dist/core/lod.d.ts +27 -0
  56. package/dist/core/lod.js +49 -0
  57. package/dist/core/route.d.ts +58 -0
  58. package/dist/core/route.js +182 -0
  59. package/dist/core/simplify.d.ts +9 -0
  60. package/dist/core/simplify.js +102 -0
  61. package/dist/core/spatial.d.ts +21 -0
  62. package/dist/core/spatial.js +88 -0
  63. package/dist/core/style.d.ts +43 -0
  64. package/dist/core/style.js +205 -0
  65. package/dist/core/types.d.ts +110 -0
  66. package/dist/core/types.js +5 -0
  67. package/dist/core/workers/client.d.ts +26 -0
  68. package/dist/core/workers/client.js +88 -0
  69. package/dist/core/workers/handler.d.ts +6 -0
  70. package/dist/core/workers/handler.js +53 -0
  71. package/dist/core/workers/protocol.d.ts +32 -0
  72. package/dist/core/workers/protocol.js +1 -0
  73. package/dist/core/workers/spatial.worker.d.ts +1 -0
  74. package/dist/core/workers/spatial.worker.js +12 -0
  75. package/dist/deck.d.ts +18 -0
  76. package/dist/deck.js +27 -0
  77. package/dist/index.d.ts +27 -0
  78. package/dist/index.js +23 -0
  79. package/dist/internal/context.d.ts +39 -0
  80. package/dist/internal/context.js +15 -0
  81. package/dist/internal/safely.d.ts +5 -0
  82. package/dist/internal/safely.js +12 -0
  83. package/dist/internal/use-layer.svelte.d.ts +45 -0
  84. package/dist/internal/use-layer.svelte.js +179 -0
  85. package/dist/maplibre.d.ts +19 -0
  86. package/dist/maplibre.js +47 -0
  87. package/dist/styles.css +10 -0
  88. package/package.json +76 -0
@@ -0,0 +1,110 @@
1
+ /** `[longitude, latitude]`, in that order, as GeoJSON and MapLibre both use. */
2
+ export type LngLat = [number, number];
3
+ /** `[[west, south], [east, north]]`. */
4
+ export type LngLatBounds = [LngLat, LngLat];
5
+ export type Position = number[];
6
+ export type Geometry = {
7
+ type: 'Point';
8
+ coordinates: Position;
9
+ } | {
10
+ type: 'MultiPoint';
11
+ coordinates: Position[];
12
+ } | {
13
+ type: 'LineString';
14
+ coordinates: Position[];
15
+ } | {
16
+ type: 'MultiLineString';
17
+ coordinates: Position[][];
18
+ } | {
19
+ type: 'Polygon';
20
+ coordinates: Position[][];
21
+ } | {
22
+ type: 'MultiPolygon';
23
+ coordinates: Position[][][];
24
+ } | {
25
+ type: 'GeometryCollection';
26
+ geometries: Geometry[];
27
+ };
28
+ export interface Feature<P = Record<string, unknown>> {
29
+ type: 'Feature';
30
+ id?: string | number;
31
+ geometry: Geometry | null;
32
+ properties: P | null;
33
+ }
34
+ export interface FeatureCollection<P = Record<string, unknown>> {
35
+ type: 'FeatureCollection';
36
+ features: Array<Feature<P>>;
37
+ }
38
+ export type GeoJSON = Geometry | Feature | FeatureCollection;
39
+ /** A MapLibre style document. Structural: any valid style object fits. */
40
+ export interface StyleSpecification {
41
+ version: 8;
42
+ name?: string;
43
+ metadata?: Record<string, unknown>;
44
+ sources: Record<string, Record<string, unknown>>;
45
+ layers: Array<Record<string, unknown> & {
46
+ id: string;
47
+ type: string;
48
+ }>;
49
+ glyphs?: string;
50
+ sprite?: string;
51
+ [key: string]: unknown;
52
+ }
53
+ /** Where the map is looking. */
54
+ export interface MapViewState {
55
+ center: LngLat;
56
+ zoom: number;
57
+ bearing: number;
58
+ pitch: number;
59
+ bounds: LngLatBounds;
60
+ }
61
+ /** A click on the map. */
62
+ export interface MapClickEvent {
63
+ lngLat: LngLat;
64
+ point: [number, number];
65
+ /** Rendered features under the pointer, topmost first. */
66
+ features: unknown[];
67
+ }
68
+ /** A click on, or pointer over, a feature of one layer. */
69
+ export interface LayerFeatureEvent {
70
+ /** The topmost feature of the layer under the pointer. */
71
+ feature: Feature & {
72
+ layer?: {
73
+ id: string;
74
+ };
75
+ source?: string;
76
+ state?: Record<string, unknown>;
77
+ };
78
+ /** Every feature of the layer under the pointer. */
79
+ features: Feature[];
80
+ lngLat: LngLat;
81
+ point: [number, number];
82
+ }
83
+ /** One entry in a map legend. */
84
+ export interface LegendItem {
85
+ label: string;
86
+ color: string;
87
+ /** How the key is drawn: a filled square, a line, or a dot. */
88
+ shape?: 'fill' | 'line' | 'dot';
89
+ }
90
+ /** A continuous scale in a map legend: its colours from least to most, and the end labels. */
91
+ export interface LegendGradient {
92
+ colors: string[];
93
+ min: string;
94
+ max: string;
95
+ }
96
+ /** A place the non-visual feature list offers. */
97
+ export interface MapFeatureItem {
98
+ id: string | number;
99
+ label: string;
100
+ lngLat: LngLat;
101
+ /** A second line, e.g. an address or a status. */
102
+ description?: string;
103
+ }
104
+ /** A layer's row in `LayerManager`: what it shows and sets. */
105
+ export interface LayerState {
106
+ id: string;
107
+ label: string;
108
+ visible: boolean;
109
+ opacity: number;
110
+ }
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Minimal GeoJSON and map types, kept structural so `/core` needs neither MapLibre nor
3
+ * `@types/geojson` to type-check.
4
+ */
5
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { Feature, FeatureCollection, LngLat, LngLatBounds } from '../types.js';
2
+ export interface SpatialWorkerOptions {
3
+ /** Starts the worker. Defaults to the one this package ships, found next to this file. */
4
+ workerFactory?: () => Worker;
5
+ /** Give up on a request after this many milliseconds. */
6
+ timeout?: number;
7
+ }
8
+ export interface SpatialWorker {
9
+ /** `worker` while a worker is answering; `main-thread` where none could start. */
10
+ readonly mode: 'worker' | 'main-thread';
11
+ /** Indexes a collection, replacing what was there. Resolves to the number of features indexed. */
12
+ load(features: FeatureCollection, options?: {
13
+ idProperty?: string;
14
+ }): Promise<number>;
15
+ search(bounds: LngLatBounds, limit?: number): Promise<Feature[]>;
16
+ nearest(point: LngLat, count?: number): Promise<Feature[]>;
17
+ /** The loaded collection simplified for `zoom`. */
18
+ simplify(zoom: number, pixels?: number): Promise<FeatureCollection>;
19
+ terminate(): void;
20
+ }
21
+ /**
22
+ * Spatial indexing and simplification off the main thread, so a map of tens of thousands of
23
+ * features stays smooth while it pans. Falls back to the main thread — with the same answers —
24
+ * where workers are unavailable (the server, tests) or the worker fails to start.
25
+ */
26
+ export declare function createSpatialWorker(options?: SpatialWorkerOptions): SpatialWorker;
@@ -0,0 +1,88 @@
1
+ import { createSpatialHandler } from './handler.js';
2
+ const defaultWorker = () => new Worker(new URL('./spatial.worker.js', import.meta.url), { type: 'module' });
3
+ /**
4
+ * Spatial indexing and simplification off the main thread, so a map of tens of thousands of
5
+ * features stays smooth while it pans. Falls back to the main thread — with the same answers —
6
+ * where workers are unavailable (the server, tests) or the worker fails to start.
7
+ */
8
+ export function createSpatialWorker(options = {}) {
9
+ const { timeout = 30_000 } = options;
10
+ let nextId = 1;
11
+ let worker = null;
12
+ let local = null;
13
+ let lastLoad = null;
14
+ const pending = new Map();
15
+ const settle = (response) => {
16
+ const entry = pending.get(response.id);
17
+ if (!entry)
18
+ return;
19
+ pending.delete(response.id);
20
+ clearTimeout(entry.timer);
21
+ if (response.ok)
22
+ entry.resolve(response.result);
23
+ else
24
+ entry.reject(new Error(response.error));
25
+ };
26
+ /** Moves to the main thread, replaying the last load so the index is not lost. */
27
+ const fallBack = () => {
28
+ worker?.terminate();
29
+ worker = null;
30
+ local ??= createSpatialHandler();
31
+ const queued = [...pending];
32
+ if (lastLoad && !queued.some(([, entry]) => entry.request === lastLoad)) {
33
+ local({ ...lastLoad, id: 0 });
34
+ }
35
+ for (const [id, entry] of queued)
36
+ settle(local({ ...entry.request, id }));
37
+ };
38
+ try {
39
+ if (options.workerFactory || typeof Worker !== 'undefined') {
40
+ worker = (options.workerFactory ?? defaultWorker)();
41
+ worker.addEventListener('message', (event) => settle(event.data));
42
+ worker.addEventListener('error', fallBack);
43
+ }
44
+ }
45
+ catch {
46
+ worker = null;
47
+ }
48
+ if (!worker)
49
+ local = createSpatialHandler();
50
+ const send = (request) => {
51
+ if (request.type === 'load')
52
+ lastLoad = request;
53
+ const id = nextId++;
54
+ if (!worker) {
55
+ const response = local({ ...request, id });
56
+ return response.ok
57
+ ? Promise.resolve(response.result)
58
+ : Promise.reject(new Error(response.error));
59
+ }
60
+ return new Promise((resolve, reject) => {
61
+ const timer = setTimeout(() => {
62
+ pending.delete(id);
63
+ reject(new Error(`Spatial worker: "${request.type}" took longer than ${timeout} ms`));
64
+ }, timeout);
65
+ pending.set(id, { request, resolve: resolve, reject, timer });
66
+ worker.postMessage({ ...request, id });
67
+ });
68
+ };
69
+ return {
70
+ get mode() {
71
+ return worker ? 'worker' : 'main-thread';
72
+ },
73
+ load: (features, { idProperty } = {}) => send({ type: 'load', features, idProperty }),
74
+ search: (bounds, limit) => send({ type: 'search', bounds, limit }),
75
+ nearest: (point, count) => send({ type: 'nearest', point, count }),
76
+ simplify: (zoom, pixels) => send({ type: 'simplify', zoom, pixels }),
77
+ terminate() {
78
+ worker?.terminate();
79
+ worker = null;
80
+ for (const [id, entry] of pending) {
81
+ clearTimeout(entry.timer);
82
+ entry.reject(new Error('Spatial worker terminated'));
83
+ pending.delete(id);
84
+ }
85
+ local ??= createSpatialHandler();
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,6 @@
1
+ import type { SpatialRequest, SpatialResponse } from './protocol.js';
2
+ /**
3
+ * Answers spatial requests. The worker runs this; so does the client itself, on the main thread,
4
+ * wherever a worker cannot start — the answers are the same either way.
5
+ */
6
+ export declare function createSpatialHandler(): (request: SpatialRequest) => SpatialResponse;
@@ -0,0 +1,53 @@
1
+ import { createLevelOfDetail } from '../lod.js';
2
+ import { createSpatialIndex } from '../spatial.js';
3
+ /**
4
+ * Answers spatial requests. The worker runs this; so does the client itself, on the main thread,
5
+ * wherever a worker cannot start — the answers are the same either way.
6
+ */
7
+ export function createSpatialHandler() {
8
+ let index = createSpatialIndex();
9
+ let collection = { type: 'FeatureCollection', features: [] };
10
+ let detail = null;
11
+ return (request) => {
12
+ try {
13
+ switch (request.type) {
14
+ case 'load': {
15
+ const property = request.idProperty;
16
+ index = createSpatialIndex({
17
+ getId: property
18
+ ? (feature, position) => feature.properties?.[property] ?? position
19
+ : undefined,
20
+ });
21
+ index.load(request.features);
22
+ collection = request.features;
23
+ detail = null;
24
+ return { id: request.id, ok: true, result: index.size };
25
+ }
26
+ case 'search':
27
+ return {
28
+ id: request.id,
29
+ ok: true,
30
+ result: index.search(request.bounds, { limit: request.limit }),
31
+ };
32
+ case 'nearest':
33
+ return { id: request.id, ok: true, result: index.nearest(request.point, request.count) };
34
+ case 'simplify':
35
+ detail ??= createLevelOfDetail(collection, { pixels: request.pixels });
36
+ return { id: request.id, ok: true, result: detail.at(request.zoom) };
37
+ default:
38
+ return {
39
+ id: request.id,
40
+ ok: false,
41
+ error: `Unknown request "${request.type}"`,
42
+ };
43
+ }
44
+ }
45
+ catch (error) {
46
+ return {
47
+ id: request.id,
48
+ ok: false,
49
+ error: error instanceof Error ? error.message : String(error),
50
+ };
51
+ }
52
+ };
53
+ }
@@ -0,0 +1,32 @@
1
+ import type { FeatureCollection, LngLat, LngLatBounds } from '../types.js';
2
+ /** What the spatial worker can be asked. */
3
+ export type SpatialRequest = {
4
+ id: number;
5
+ type: 'load';
6
+ features: FeatureCollection;
7
+ idProperty?: string;
8
+ } | {
9
+ id: number;
10
+ type: 'search';
11
+ bounds: LngLatBounds;
12
+ limit?: number;
13
+ } | {
14
+ id: number;
15
+ type: 'nearest';
16
+ point: LngLat;
17
+ count?: number;
18
+ } | {
19
+ id: number;
20
+ type: 'simplify';
21
+ zoom: number;
22
+ pixels?: number;
23
+ };
24
+ export type SpatialResponse = {
25
+ id: number;
26
+ ok: true;
27
+ result: unknown;
28
+ } | {
29
+ id: number;
30
+ ok: false;
31
+ error: string;
32
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ /*
2
+ * The spatial worker. It only relays messages to the same handler the client can run itself —
3
+ * all the logic lives in `handler.ts`, where it can be tested without a worker.
4
+ *
5
+ * Bundlers that pre-bundle dependencies can lose the worker URL the client computes; import
6
+ * this file through the bundler instead (`@loidolt/theme-maps/spatial.worker?worker` with Vite)
7
+ * and pass it as `workerFactory`.
8
+ */
9
+ import { createSpatialHandler } from './handler.js';
10
+ const handle = createSpatialHandler();
11
+ const scope = self;
12
+ scope.onmessage = (event) => scope.postMessage(handle(event.data));
package/dist/deck.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /** The slice of `@deck.gl/mapbox`'s `MapboxOverlay` that `DeckOverlay` uses. */
2
+ export interface DeckOverlayInstance {
3
+ setProps(props: Record<string, unknown>): void;
4
+ onAdd?(map: unknown): HTMLElement;
5
+ onRemove?(map: unknown): void;
6
+ finalize?(): void;
7
+ }
8
+ export interface DeckModule {
9
+ MapboxOverlay: new (props: Record<string, unknown>) => DeckOverlayInstance;
10
+ }
11
+ export type DeckLoader = () => Promise<unknown> | unknown;
12
+ /** Registers how to load `@deck.gl/mapbox`. `null` unregisters. */
13
+ export declare function setDeckLoader(loader: DeckLoader | null): void;
14
+ /**
15
+ * Resolves `@deck.gl/mapbox` through the registered loader, else a global `deck` from a script
16
+ * tag. Resolves `null` rather than rejecting when neither is there.
17
+ */
18
+ export declare function loadDeck(): Promise<DeckModule | null>;
package/dist/deck.js ADDED
@@ -0,0 +1,27 @@
1
+ /*
2
+ * deck.gl, for large-data visualisation over the map. An optional peer this package never
3
+ * imports itself; register it once where you use it:
4
+ *
5
+ * import { setDeckLoader } from '@loidolt/theme-maps';
6
+ * setDeckLoader(() => import('@deck.gl/mapbox'));
7
+ */
8
+ let registered = null;
9
+ let cached = null;
10
+ /** Registers how to load `@deck.gl/mapbox`. `null` unregisters. */
11
+ export function setDeckLoader(loader) {
12
+ registered = loader;
13
+ cached = null;
14
+ }
15
+ /**
16
+ * Resolves `@deck.gl/mapbox` through the registered loader, else a global `deck` from a script
17
+ * tag. Resolves `null` rather than rejecting when neither is there.
18
+ */
19
+ export function loadDeck() {
20
+ cached ??= Promise.resolve()
21
+ .then(registered ?? (() => globalThis.deck))
22
+ .then((module) => typeof module?.MapboxOverlay === 'function'
23
+ ? module
24
+ : null)
25
+ .catch(() => null);
26
+ return cached;
27
+ }
@@ -0,0 +1,27 @@
1
+ export { default as BasemapSwitcher } from './components/BasemapSwitcher.svelte';
2
+ export { default as CircleLayer } from './components/CircleLayer.svelte';
3
+ export { default as CoordinateDisplay } from './components/CoordinateDisplay.svelte';
4
+ export { default as DeckOverlay } from './components/DeckOverlay.svelte';
5
+ export { default as FillExtrusionLayer } from './components/FillExtrusionLayer.svelte';
6
+ export { default as FillLayer } from './components/FillLayer.svelte';
7
+ export { default as HeatmapLayer } from './components/HeatmapLayer.svelte';
8
+ export { default as LayerManager } from './components/LayerManager.svelte';
9
+ export { default as LineLayer } from './components/LineLayer.svelte';
10
+ export { default as MapControl } from './components/MapControl.svelte';
11
+ export { default as MapFeatureList } from './components/MapFeatureList.svelte';
12
+ export { default as MapLegend } from './components/MapLegend.svelte';
13
+ export { default as MapLegendGroup } from './components/MapLegendGroup.svelte';
14
+ export { default as MapMarker } from './components/MapMarker.svelte';
15
+ export { default as MapPopup } from './components/MapPopup.svelte';
16
+ export { default as MapSource } from './components/MapSource.svelte';
17
+ export { default as MapView } from './components/MapView.svelte';
18
+ export { default as RasterLayer } from './components/RasterLayer.svelte';
19
+ export { default as SymbolLayer } from './components/SymbolLayer.svelte';
20
+ export { loadMapLibre, setMapLibreLoader, setMapLibreWorkerUrl } from './maplibre.js';
21
+ export type { MapLibreLoader, MapLibreModule } from './maplibre.js';
22
+ export { loadDeck, setDeckLoader } from './deck.js';
23
+ export type { DeckLoader, DeckModule, DeckOverlayInstance } from './deck.js';
24
+ export { getMapContext } from './internal/context.js';
25
+ export type { LayerEntry, MapContext } from './internal/context.js';
26
+ export type { LayerPaint, LayerProps } from './internal/use-layer.svelte.js';
27
+ export * from './core/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ export { default as BasemapSwitcher } from './components/BasemapSwitcher.svelte';
2
+ export { default as CircleLayer } from './components/CircleLayer.svelte';
3
+ export { default as CoordinateDisplay } from './components/CoordinateDisplay.svelte';
4
+ export { default as DeckOverlay } from './components/DeckOverlay.svelte';
5
+ export { default as FillExtrusionLayer } from './components/FillExtrusionLayer.svelte';
6
+ export { default as FillLayer } from './components/FillLayer.svelte';
7
+ export { default as HeatmapLayer } from './components/HeatmapLayer.svelte';
8
+ export { default as LayerManager } from './components/LayerManager.svelte';
9
+ export { default as LineLayer } from './components/LineLayer.svelte';
10
+ export { default as MapControl } from './components/MapControl.svelte';
11
+ export { default as MapFeatureList } from './components/MapFeatureList.svelte';
12
+ export { default as MapLegend } from './components/MapLegend.svelte';
13
+ export { default as MapLegendGroup } from './components/MapLegendGroup.svelte';
14
+ export { default as MapMarker } from './components/MapMarker.svelte';
15
+ export { default as MapPopup } from './components/MapPopup.svelte';
16
+ export { default as MapSource } from './components/MapSource.svelte';
17
+ export { default as MapView } from './components/MapView.svelte';
18
+ export { default as RasterLayer } from './components/RasterLayer.svelte';
19
+ export { default as SymbolLayer } from './components/SymbolLayer.svelte';
20
+ export { loadMapLibre, setMapLibreLoader, setMapLibreWorkerUrl } from './maplibre.js';
21
+ export { loadDeck, setDeckLoader } from './deck.js';
22
+ export { getMapContext } from './internal/context.js';
23
+ export * from './core/index.js';
@@ -0,0 +1,39 @@
1
+ import type { Map as MapLibreMap } from 'maplibre-gl';
2
+ import type { MapColors } from '@loidolt/theme-tokens';
3
+ import type { StyleSpecification } from '../core/types.js';
4
+ import type { MapLibreModule } from '../maplibre.js';
5
+ /** A layer as the map knows it — what `LayerManager` lists. */
6
+ export interface LayerEntry {
7
+ id: string;
8
+ label: string;
9
+ type: string;
10
+ }
11
+ export interface MapContext {
12
+ readonly map: MapLibreMap | null;
13
+ readonly lib: MapLibreModule | null;
14
+ /** The style has loaded, so sources and layers can be added. */
15
+ readonly loaded: boolean;
16
+ /** Goes up whenever a style (re)loads; everything on the map re-adds itself then. */
17
+ readonly styleVersion: number;
18
+ readonly colors: MapColors;
19
+ /** Goes up whenever `colors` changes. */
20
+ readonly colorVersion: number;
21
+ readonly reducedMotion: boolean;
22
+ /** Which basemap is showing: `default`, `blank`, or the id of one passed to the switcher. */
23
+ readonly basemap: string;
24
+ setBasemap(id: string, style?: string | StyleSpecification): void;
25
+ readonly layers: LayerEntry[];
26
+ registerLayer(entry: LayerEntry): () => void;
27
+ }
28
+ export interface SourceContext {
29
+ readonly id: string;
30
+ /** The source exists on the current style. */
31
+ readonly ready: boolean;
32
+ }
33
+ export declare const setMapContext: (context: MapContext) => MapContext;
34
+ /** The enclosing map, or `null` outside one. */
35
+ export declare const getMapContext: () => MapContext | null;
36
+ /** The enclosing map; throws with a clear message outside one. */
37
+ export declare function requireMapContext(component: string): MapContext;
38
+ export declare const setSourceContext: (context: SourceContext) => SourceContext;
39
+ export declare const getSourceContext: () => SourceContext | null;
@@ -0,0 +1,15 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const MAP = Symbol('loidolt-map');
3
+ const SOURCE = Symbol('loidolt-map-source');
4
+ export const setMapContext = (context) => setContext(MAP, context);
5
+ /** The enclosing map, or `null` outside one. */
6
+ export const getMapContext = () => getContext(MAP) ?? null;
7
+ /** The enclosing map; throws with a clear message outside one. */
8
+ export function requireMapContext(component) {
9
+ const context = getMapContext();
10
+ if (!context)
11
+ throw new Error(`${component} must be placed inside a <MapView>.`);
12
+ return context;
13
+ }
14
+ export const setSourceContext = (context) => setContext(SOURCE, context);
15
+ export const getSourceContext = () => getContext(SOURCE) ?? null;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Runs a teardown step that may find the map already gone. When a `MapView` unmounts, its map
3
+ * can be removed before its children clean up, and MapLibre throws on a removed map.
4
+ */
5
+ export declare function safely(step: () => void): void;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Runs a teardown step that may find the map already gone. When a `MapView` unmounts, its map
3
+ * can be removed before its children clean up, and MapLibre throws on a removed map.
4
+ */
5
+ export function safely(step) {
6
+ try {
7
+ step();
8
+ }
9
+ catch {
10
+ /* the map was removed first; there is nothing left to undo */
11
+ }
12
+ }
@@ -0,0 +1,45 @@
1
+ import type { MapColors } from '@loidolt/theme-tokens';
2
+ import type { LayerFeatureEvent } from '../core/types.js';
3
+ export type LayerPaint = Record<string, unknown>;
4
+ /** What every layer component accepts. */
5
+ export interface LayerProps {
6
+ /** Unique on the map. */
7
+ id: string;
8
+ /** The source to draw from. Defaults to the enclosing `MapSource`. */
9
+ source?: string;
10
+ /** For vector tiles: which layer of the tiles to draw. */
11
+ sourceLayer?: string;
12
+ /** Draw beneath this layer rather than on top of everything. */
13
+ beforeId?: string;
14
+ /** A MapLibre filter expression. */
15
+ filter?: unknown[];
16
+ minZoom?: number;
17
+ maxZoom?: number;
18
+ visible?: boolean;
19
+ /**
20
+ * MapLibre paint properties, merged over this layer type's themed defaults. Pass a function
21
+ * of the theme colours to have them follow light and dark.
22
+ */
23
+ paint?: LayerPaint | ((colors: MapColors) => LayerPaint);
24
+ /** MapLibre layout properties. */
25
+ layout?: Record<string, unknown>;
26
+ /** Names the layer in `LayerManager`. Defaults to `id`. */
27
+ label?: string;
28
+ /** A click on a feature of this layer. */
29
+ onClick?: (event: LayerFeatureEvent) => void;
30
+ /** The pointer entering a feature of this layer, or `null` as it leaves. */
31
+ onHover?: (event: LayerFeatureEvent | null) => void;
32
+ }
33
+ type Defaults = (colors: MapColors) => {
34
+ paint?: LayerPaint;
35
+ layout?: Record<string, unknown>;
36
+ };
37
+ /**
38
+ * Adds a layer of `type` to the enclosing map and keeps it in step with its props: added on
39
+ * every style load, diffed on every change (only what changed is sent), recoloured on a theme
40
+ * change, removed on unmount.
41
+ */
42
+ export declare function useLayer(type: string, props: () => LayerProps, defaults?: Defaults): {
43
+ readonly added: boolean;
44
+ };
45
+ export {};