@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Loidolt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @loidolt/theme-maps
2
+
3
+ Accessible, themed [MapLibre](https://maplibre.org) maps for Svelte 5, painted from the Loidolt
4
+ tokens.
5
+
6
+ - **A basemap in the loidolt palette.** Land, water, parks, buildings, roads and names drawn from
7
+ any OpenMapTiles-schema vector tiles — [OpenFreeMap](https://openfreemap.org)'s free, keyless
8
+ tiles by default — in the `--loidolt-map-*` colours. A theme switch repaints it in place, and
9
+ data layers follow with it. `basemap="blank"` draws land only, with no network at all.
10
+ - **Keyboard and screen reader first.** The canvas is named and described (including how to move
11
+ it); arrow keys pan and + and − zoom; where a keyboard move lands is announced. Markers are real
12
+ buttons with names; popups take focus and give it back; `MapFeatureList` lists every place as a
13
+ list you can tab through.
14
+ - **Server-rendered safely.** MapLibre loads in the browser only. Without WebGL the map says so
15
+ instead of leaving a blank box.
16
+ - **Only what changed.** Layers diff their paint, layout and filters and send MapLibre only the
17
+ difference; GeoJSON updates replace data in place.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ npm install @loidolt/theme-maps maplibre-gl
23
+ ```
24
+
25
+ `maplibre-gl` (5 or 6) is a peer dependency. Import the map stylesheet once, next to the theme's:
26
+
27
+ ```css
28
+ @import '@loidolt/theme-svelte/styles.css';
29
+ @import '@loidolt/theme-maps/styles.css';
30
+ ```
31
+
32
+ **MapLibre 6 and bundlers.** MapLibre 6 ships its web worker as a separate file and finds it at
33
+ runtime in a way bundlers cannot follow. Without it, raster layers draw but GeoJSON and vector
34
+ tiles never do. With Vite, have it bundle the worker and say where it went, once at start-up:
35
+
36
+ ```ts
37
+ import { setMapLibreWorkerUrl } from '@loidolt/theme-maps';
38
+ import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
39
+
40
+ setMapLibreWorkerUrl(workerUrl);
41
+ ```
42
+
43
+ and set `worker: { format: 'es' }` in `vite.config`. MapLibre 5 inlines its worker and needs
44
+ neither.
45
+
46
+ The stylesheet layers MapLibre's own CSS under the loidolt components (see _Cascade layers_ in the main
47
+ README), so the loidolt control, popup and attribution styles win without `!important`.
48
+
49
+ ```svelte
50
+ <script lang="ts">
51
+ import { CircleLayer, MapMarker, MapSource, MapView } from '@loidolt/theme-maps';
52
+ </script>
53
+
54
+ <MapView label="Delivery stops" center={[-122.68, 45.52]} zoom={12}>
55
+ <MapSource id="stops" data={stopsGeoJSON}>
56
+ <CircleLayer id="stop-points" />
57
+ </MapSource>
58
+ <MapMarker lngLat={[-122.66, 45.51]} label="Workshop">
59
+ {#snippet popup()}<strong>Workshop</strong>
60
+ <p>Open 8 to 5</p>{/snippet}
61
+ </MapMarker>
62
+ </MapView>
63
+ ```
64
+
65
+ ## Components
66
+
67
+ | Component | What it is |
68
+ | -------------------- | ------------------------------------------------------------------------- |
69
+ | `MapView` | The map: basemap, view state (bindable), controls, keyboard announcements |
70
+ | `MapSource` | GeoJSON or any MapLibre source, with optional clustering |
71
+ | `FillLayer` | Filled polygons |
72
+ | `LineLayer` | Lines |
73
+ | `CircleLayer` | Points as circles |
74
+ | `SymbolLayer` | Text and icons |
75
+ | `FillExtrusionLayer` | Polygons in 3D |
76
+ | `HeatmapLayer` | Point density on the sequential ramp |
77
+ | `RasterLayer` | Raster tiles or a georeferenced image |
78
+ | `MapMarker` | The loidolt pin as a named button, with an optional popup |
79
+ | `MapPopup` | A non-modal panel pointing at a spot |
80
+ | `MapControl` | Any content as a map control |
81
+ | `MapLegend` | Category or gradient key, on the map or beside it |
82
+ | `MapLegendGroup` | Several collapsible legends in one control |
83
+ | `CoordinateDisplay` | Coordinate under the pointer (or at the centre) and zoom |
84
+ | `BasemapSwitcher` | Switch between the loidolt basemap, the plain one, and your own styles |
85
+ | `MapFeatureList` | Every place as a list that flies the map there — the non-visual route |
86
+ | `LayerManager` | Show, fade and reorder layers; keyboard reordering, announced |
87
+ | `DeckOverlay` | deck.gl layers on the map (optional peer) |
88
+
89
+ Layers take `id`, `source` (or the enclosing `MapSource`), `sourceLayer`, `beforeId`, `filter`,
90
+ `minZoom`/`maxZoom`, `visible`, `layout`, `label`, `onClick`, `onHover` and `paint`. `paint` is
91
+ merged over themed defaults; pass a function of the colours to follow light and dark:
92
+
93
+ ```svelte
94
+ <FillLayer id="zones" paint={(colors) => ({ 'fill-color': colors.categorical[2] })} />
95
+ ```
96
+
97
+ ## Tiles and attribution
98
+
99
+ The default basemap uses OpenFreeMap. It needs no key and no account, but it is a free service
100
+ with no service-level agreement, and its attribution must stay visible (the attribution control
101
+ shows it). Labels use the fonts its glyph server has — Noto Sans — not the page's.
102
+
103
+ To use other tiles, pass any OpenMapTiles-schema source as a TileJSON URL or `{z}/{x}/{y}`
104
+ template, with its attribution:
105
+
106
+ ```svelte
107
+ <MapView label="…" tiles="https://tiles.example.com/planet.json" attribution="© Example" />
108
+ ```
109
+
110
+ `mapStyle` takes a complete style URL or document instead; it is used as is and not recoloured.
111
+
112
+ ## Styles and helpers without a component
113
+
114
+ `@loidolt/theme-maps/core` is plain TypeScript with no Svelte and no MapLibre import:
115
+
116
+ - `createBasemapStyle`, `createBlankStyle`, `basemapPaint` — the styles `MapView` uses, for a
117
+ map you create yourself.
118
+ - `boundsOf`, `boundsCenter`, `padBounds`, `boundsContain`, `boundsIntersect`.
119
+ - `validateGeoJSON` (catches `[lat, lng]` order), `loadGeoJSON`, `pointsToFeatureCollection`,
120
+ `featureAnchor`.
121
+ - `formatCoordinate` (latitude first, with hemispheres; decimal or DMS), `formatDistance`,
122
+ `formatArea`.
123
+ - `clusterColor`, `clusterRadius`, `expandCluster`.
124
+ - `formatMoveAnnouncement`, `describeZoom`, `MAP_KEYS`.
125
+
126
+ ## Routes and directions
127
+
128
+ Straight-line routing with no service and no key: `haversine` and `pathLength` (metres),
129
+ `distanceMatrix`, and `optimizeRoute(origin, stops)` — nearest neighbour, then 2-opt — which
130
+ returns the visiting order, each leg and the total. Hand the order to a phone's maps app with
131
+ `googleDirectionsUrl` or `appleDirectionsUrl`; `googleDirectionsLegUrls` splits a long route into
132
+ links that each fit Google's waypoint limit, every leg starting where the last one ended.
133
+
134
+ ## Large datasets
135
+
136
+ - `createSpatialIndex()` — an R-tree over features: `search(bounds)` for what is in view,
137
+ `nearest(point, count)`.
138
+ - `simplifyLine`, `simplifyFeatures`, `convexHull`, and `createLevelOfDetail(collection)`, which
139
+ simplifies to what a pixel can show at each zoom and caches per level; `outlineOf(features)`
140
+ collapses a group into one shape for far zooms.
141
+ - `createSpatialWorker()` runs the index and simplification in a web worker, falling back to the
142
+ main thread (same answers) where no worker can start. It finds its worker next to itself; if a
143
+ bundler's dependency pre-bundling loses it, import it through the bundler and pass it in:
144
+
145
+ ```ts
146
+ import SpatialWorker from '@loidolt/theme-maps/spatial.worker?worker';
147
+ const spatial = createSpatialWorker({ workerFactory: () => new SpatialWorker() });
148
+ ```
149
+
150
+ ## deck.gl
151
+
152
+ `DeckOverlay` draws deck.gl layers on the map, on a canvas of their own that follows the camera.
153
+ `interleaved` draws them in among MapLibre's layers instead, sharing its depth — but it reaches
154
+ into MapLibre's renderer, so it needs a deck.gl release that supports your MapLibre major
155
+ (deck.gl 9.4 does not yet support MapLibre 6's). deck.gl is an optional peer; register it once
156
+ and build your layers as usual:
157
+
158
+ ```ts
159
+ import { setDeckLoader } from '@loidolt/theme-maps';
160
+ setDeckLoader(() => import('@deck.gl/mapbox'));
161
+ ```
162
+
163
+ ```svelte
164
+ <DeckOverlay layers={[new ScatterplotLayer({ id: 'jobs', data, getPosition, getRadius })]} />
165
+ ```
166
+
167
+ ## Loading MapLibre yourself
168
+
169
+ Maps import `maplibre-gl` on first use. To use a global build, or a stand-in under test:
170
+
171
+ ```ts
172
+ import { setMapLibreLoader } from '@loidolt/theme-maps';
173
+ setMapLibreLoader(() => window.maplibregl);
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import { ToggleGroup, cx } from '@loidolt/theme-svelte';
3
+ import type { StyleSpecification } from '../core/types.js';
4
+ import { requireMapContext } from '../internal/context.js';
5
+ import MapControl from './MapControl.svelte';
6
+
7
+ type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
8
+
9
+ interface Basemap {
10
+ id: string;
11
+ label: string;
12
+ /** A style of your own. Leave it out for the built-in `default` and `blank`. */
13
+ style?: string | StyleSpecification;
14
+ }
15
+
16
+ interface Props {
17
+ /** The choices, in order. Defaults to the loidolt basemap and the plain one. */
18
+ basemaps?: Basemap[];
19
+ /** Names the group of choices. */
20
+ label?: string;
21
+ position?: Corner;
22
+ onChange?: (id: string) => void;
23
+ class?: string;
24
+ }
25
+
26
+ let {
27
+ basemaps = [
28
+ { id: 'default', label: 'Map' },
29
+ { id: 'blank', label: 'Plain' },
30
+ ],
31
+ label = 'Basemap',
32
+ position = 'top-left',
33
+ onChange,
34
+ class: className,
35
+ }: Props = $props();
36
+
37
+ const context = requireMapContext('BasemapSwitcher');
38
+ </script>
39
+
40
+ <MapControl {position} class={cx('ldt-basemap-switcher', className)}>
41
+ <ToggleGroup
42
+ {label}
43
+ size="sm"
44
+ value={context.basemap}
45
+ options={basemaps.map((basemap) => ({ value: basemap.id, label: basemap.label }))}
46
+ onValueChange={(next) => {
47
+ const choice = basemaps.find((basemap) => basemap.id === next);
48
+ if (!choice) return;
49
+ context.setBasemap(choice.id, choice.style);
50
+ onChange?.(choice.id);
51
+ }}
52
+ />
53
+ </MapControl>
@@ -0,0 +1,20 @@
1
+ import type { StyleSpecification } from '../core/types.js';
2
+ type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
+ interface Basemap {
4
+ id: string;
5
+ label: string;
6
+ /** A style of your own. Leave it out for the built-in `default` and `blank`. */
7
+ style?: string | StyleSpecification;
8
+ }
9
+ interface Props {
10
+ /** The choices, in order. Defaults to the loidolt basemap and the plain one. */
11
+ basemaps?: Basemap[];
12
+ /** Names the group of choices. */
13
+ label?: string;
14
+ position?: Corner;
15
+ onChange?: (id: string) => void;
16
+ class?: string;
17
+ }
18
+ declare const BasemapSwitcher: import("svelte").Component<Props, {}, "">;
19
+ type BasemapSwitcher = ReturnType<typeof BasemapSwitcher>;
20
+ export default BasemapSwitcher;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { MapColors } from '@loidolt/theme-tokens';
3
+ import { useLayer, type LayerProps } from '../internal/use-layer.svelte.js';
4
+
5
+ /* Points as circles. Defaults to accent dots with a surface-coloured ring. */
6
+ type Props = LayerProps;
7
+
8
+ let props: Props = $props();
9
+
10
+ useLayer(
11
+ 'circle',
12
+ () => props,
13
+ (colors: MapColors) => ({
14
+ paint: {
15
+ 'circle-color': colors.accent,
16
+ 'circle-radius': 6,
17
+ 'circle-stroke-color': colors.surface,
18
+ 'circle-stroke-width': 1.5,
19
+ },
20
+ })
21
+ );
22
+ </script>
@@ -0,0 +1,4 @@
1
+ import { type LayerProps } from '../internal/use-layer.svelte.js';
2
+ declare const CircleLayer: import("svelte").Component<LayerProps, {}, "">;
3
+ type CircleLayer = ReturnType<typeof CircleLayer>;
4
+ export default CircleLayer;
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { cx } from '@loidolt/theme-svelte';
3
+ import { formatCoordinate } from '../core/format.js';
4
+ import type { LngLat } from '../core/types.js';
5
+ import { requireMapContext } from '../internal/context.js';
6
+ import MapControl from './MapControl.svelte';
7
+
8
+ type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
9
+
10
+ interface Props {
11
+ position?: Corner;
12
+ /** `decimal` degrees, or degrees, minutes and seconds. */
13
+ format?: 'decimal' | 'dms';
14
+ precision?: number;
15
+ showZoom?: boolean;
16
+ zoomLabel?: string;
17
+ class?: string;
18
+ }
19
+
20
+ let {
21
+ position = 'bottom-left',
22
+ format = 'decimal',
23
+ precision,
24
+ showZoom = true,
25
+ zoomLabel = 'Zoom',
26
+ class: className,
27
+ }: Props = $props();
28
+
29
+ const context = requireMapContext('CoordinateDisplay');
30
+ let coordinate = $state<LngLat | null>(null);
31
+ let zoom = $state(0);
32
+
33
+ // Under the pointer while it is over the map; the centre otherwise, so keyboard users see it too.
34
+ $effect(() => {
35
+ const target = context.map;
36
+ if (!target) return;
37
+ let pointer = false;
38
+ const centre = () => {
39
+ const { lng, lat } = target.getCenter();
40
+ zoom = target.getZoom();
41
+ if (!pointer) coordinate = [lng, lat];
42
+ };
43
+ const move = (event: { lngLat: { lng: number; lat: number } }) => {
44
+ pointer = true;
45
+ coordinate = [event.lngLat.lng, event.lngLat.lat];
46
+ };
47
+ const leave = () => {
48
+ pointer = false;
49
+ centre();
50
+ };
51
+ centre();
52
+ target.on('move', centre);
53
+ target.on('mousemove', move);
54
+ target.on('mouseout', leave);
55
+ return () => {
56
+ target.off('move', centre);
57
+ target.off('mousemove', move);
58
+ target.off('mouseout', leave);
59
+ };
60
+ });
61
+ </script>
62
+
63
+ <MapControl {position}>
64
+ <p class={cx('ldt-map-coordinates', className)}>
65
+ {#if coordinate}<span>{formatCoordinate(coordinate, { format, precision })}</span>{/if}
66
+ {#if showZoom}<span>{zoomLabel} {zoom.toFixed(1)}</span>{/if}
67
+ </p>
68
+ </MapControl>
@@ -0,0 +1,13 @@
1
+ type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
2
+ interface Props {
3
+ position?: Corner;
4
+ /** `decimal` degrees, or degrees, minutes and seconds. */
5
+ format?: 'decimal' | 'dms';
6
+ precision?: number;
7
+ showZoom?: boolean;
8
+ zoomLabel?: string;
9
+ class?: string;
10
+ }
11
+ declare const CoordinateDisplay: import("svelte").Component<Props, {}, "">;
12
+ type CoordinateDisplay = ReturnType<typeof CoordinateDisplay>;
13
+ export default CoordinateDisplay;
@@ -0,0 +1,58 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import type { IControl } from 'maplibre-gl';
4
+ import { loadDeck, type DeckOverlayInstance } from '../deck.js';
5
+ import { requireMapContext } from '../internal/context.js';
6
+ import { safely } from '../internal/safely.js';
7
+
8
+ interface Props {
9
+ /** deck.gl layers, built by you (`new ScatterplotLayer({…})`). Replace the array to update. */
10
+ layers: unknown[];
11
+ /**
12
+ * Draw deck.gl layers in among the map's own, sharing its depth, rather than on a canvas
13
+ * above it. Off by default: interleaving reaches into MapLibre's renderer, so it needs a
14
+ * deck.gl release that supports your MapLibre major (deck.gl 9.4 does not yet support
15
+ * MapLibre 6's).
16
+ */
17
+ interleaved?: boolean;
18
+ /** Anything else `MapboxOverlay` takes — `getTooltip`, `pickingRadius`, `effects`… */
19
+ overlayProps?: Record<string, unknown>;
20
+ /** deck.gl could not be loaded: nothing is drawn. Register it with `setDeckLoader`. */
21
+ onUnavailable?: () => void;
22
+ }
23
+
24
+ let { layers, interleaved = false, overlayProps = {}, onUnavailable }: Props = $props();
25
+
26
+ const context = requireMapContext('DeckOverlay');
27
+ let overlay = $state.raw<DeckOverlayInstance | null>(null);
28
+
29
+ $effect(() => {
30
+ const map = context.map;
31
+ if (!map) return;
32
+ let disposed = false;
33
+ let created: DeckOverlayInstance | null = null;
34
+ void loadDeck().then((deck) => {
35
+ if (disposed) return;
36
+ if (!deck) {
37
+ untrack(() => onUnavailable?.());
38
+ return;
39
+ }
40
+ created = new deck.MapboxOverlay(untrack(() => ({ ...overlayProps, interleaved, layers })));
41
+ map.addControl(created as unknown as IControl);
42
+ overlay = created;
43
+ });
44
+ return () => {
45
+ disposed = true;
46
+ overlay = null;
47
+ if (created) {
48
+ const done = created;
49
+ safely(() => map.removeControl(done as unknown as IControl));
50
+ done.finalize?.();
51
+ }
52
+ };
53
+ });
54
+
55
+ $effect(() => {
56
+ overlay?.setProps({ ...overlayProps, layers });
57
+ });
58
+ </script>
@@ -0,0 +1,18 @@
1
+ interface Props {
2
+ /** deck.gl layers, built by you (`new ScatterplotLayer({…})`). Replace the array to update. */
3
+ layers: unknown[];
4
+ /**
5
+ * Draw deck.gl layers in among the map's own, sharing its depth, rather than on a canvas
6
+ * above it. Off by default: interleaving reaches into MapLibre's renderer, so it needs a
7
+ * deck.gl release that supports your MapLibre major (deck.gl 9.4 does not yet support
8
+ * MapLibre 6's).
9
+ */
10
+ interleaved?: boolean;
11
+ /** Anything else `MapboxOverlay` takes — `getTooltip`, `pickingRadius`, `effects`… */
12
+ overlayProps?: Record<string, unknown>;
13
+ /** deck.gl could not be loaded: nothing is drawn. Register it with `setDeckLoader`. */
14
+ onUnavailable?: () => void;
15
+ }
16
+ declare const DeckOverlay: import("svelte").Component<Props, {}, "">;
17
+ type DeckOverlay = ReturnType<typeof DeckOverlay>;
18
+ export default DeckOverlay;
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import type { MapColors } from '@loidolt/theme-tokens';
3
+ import { useLayer, type LayerProps } from '../internal/use-layer.svelte.js';
4
+
5
+ /* Polygons raised into 3D by a height property. */
6
+ type Props = LayerProps;
7
+
8
+ let props: Props = $props();
9
+
10
+ useLayer(
11
+ 'fill-extrusion',
12
+ () => props,
13
+ (colors: MapColors) => ({
14
+ paint: { 'fill-extrusion-color': colors.categorical[1], 'fill-extrusion-opacity': 0.8 },
15
+ })
16
+ );
17
+ </script>
@@ -0,0 +1,4 @@
1
+ import { type LayerProps } from '../internal/use-layer.svelte.js';
2
+ declare const FillExtrusionLayer: import("svelte").Component<LayerProps, {}, "">;
3
+ type FillExtrusionLayer = ReturnType<typeof FillExtrusionLayer>;
4
+ export default FillExtrusionLayer;
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import type { MapColors } from '@loidolt/theme-tokens';
3
+ import { useLayer, type LayerProps } from '../internal/use-layer.svelte.js';
4
+
5
+ /* Shapes filled from polygon data. Defaults to the first series colour, lightly filled. */
6
+ type Props = LayerProps;
7
+
8
+ let props: Props = $props();
9
+
10
+ useLayer(
11
+ 'fill',
12
+ () => props,
13
+ (colors: MapColors) => ({
14
+ paint: {
15
+ 'fill-color': colors.categorical[0],
16
+ 'fill-opacity': 0.28,
17
+ 'fill-outline-color': colors.categorical[0],
18
+ },
19
+ })
20
+ );
21
+ </script>
@@ -0,0 +1,4 @@
1
+ import { type LayerProps } from '../internal/use-layer.svelte.js';
2
+ declare const FillLayer: import("svelte").Component<LayerProps, {}, "">;
3
+ type FillLayer = ReturnType<typeof FillLayer>;
4
+ export default FillLayer;
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type { MapColors } from '@loidolt/theme-tokens';
3
+ import { useLayer, type LayerProps } from '../internal/use-layer.svelte.js';
4
+
5
+ /* Point density as a heatmap, coloured on the sequential ramp. */
6
+ type Props = LayerProps;
7
+
8
+ let props: Props = $props();
9
+
10
+ useLayer(
11
+ 'heatmap',
12
+ () => props,
13
+ (colors: MapColors) => ({
14
+ paint: {
15
+ 'heatmap-color': [
16
+ 'interpolate',
17
+ ['linear'],
18
+ ['heatmap-density'],
19
+ 0,
20
+ `${colors.sequential[0].slice(0, 7)}00`,
21
+ 0.2,
22
+ colors.sequential[0],
23
+ 0.4,
24
+ colors.sequential[1],
25
+ 0.6,
26
+ colors.sequential[2],
27
+ 0.8,
28
+ colors.sequential[3],
29
+ 1,
30
+ colors.sequential[4],
31
+ ],
32
+ 'heatmap-opacity': 0.85,
33
+ },
34
+ })
35
+ );
36
+ </script>
@@ -0,0 +1,4 @@
1
+ import { type LayerProps } from '../internal/use-layer.svelte.js';
2
+ declare const HeatmapLayer: import("svelte").Component<LayerProps, {}, "">;
3
+ type HeatmapLayer = ReturnType<typeof HeatmapLayer>;
4
+ export default HeatmapLayer;