@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,179 @@
1
+ import { untrack } from 'svelte';
2
+ import { changedProperties, deepEqual } from '../core/equality.js';
3
+ import { getSourceContext, requireMapContext } from './context.js';
4
+ import { safely } from './safely.js';
5
+ const toEvent = (event) => ({
6
+ feature: event.features[0],
7
+ features: (event.features ?? []),
8
+ lngLat: [event.lngLat.lng, event.lngLat.lat],
9
+ point: [event.point.x, event.point.y],
10
+ });
11
+ /**
12
+ * Adds a layer of `type` to the enclosing map and keeps it in step with its props: added on
13
+ * every style load, diffed on every change (only what changed is sent), recoloured on a theme
14
+ * change, removed on unmount.
15
+ */
16
+ export function useLayer(type, props, defaults = () => ({})) {
17
+ const context = requireMapContext(`${type} layer`);
18
+ const enclosing = getSourceContext();
19
+ let added = $state(false);
20
+ let applied = {};
21
+ const resolve = (current, colors) => {
22
+ const base = defaults(colors);
23
+ const own = typeof current.paint === 'function' ? current.paint(colors) : current.paint;
24
+ return {
25
+ paint: { ...base.paint, ...own },
26
+ layout: {
27
+ ...base.layout,
28
+ ...current.layout,
29
+ visibility: current.visible === false ? 'none' : 'visible',
30
+ },
31
+ };
32
+ };
33
+ // Settled through deriveds: only a real change of id or source re-creates the layer.
34
+ const layerId = $derived(props().id);
35
+ const namedSource = $derived(props().source);
36
+ $effect(() => {
37
+ const target = context.map;
38
+ const [id, named] = [layerId, namedSource];
39
+ const sourceId = named ?? enclosing?.id;
40
+ void context.styleVersion;
41
+ const sourceReady = named ? true : Boolean(enclosing?.ready);
42
+ if (!target || !context.loaded || !sourceReady)
43
+ return;
44
+ if (!sourceId && type !== 'background') {
45
+ throw new Error(`Layer "${id}" has no source: pass \`source\` or place it inside <MapSource>.`);
46
+ }
47
+ let waiting;
48
+ const add = () => {
49
+ if (sourceId && !target.getSource(sourceId))
50
+ return false;
51
+ untrack(() => {
52
+ const current = props();
53
+ const { paint, layout } = resolve(current, context.colors);
54
+ if (!target.getLayer(id)) {
55
+ const before = current.beforeId && target.getLayer(current.beforeId) ? current.beforeId : undefined;
56
+ target.addLayer({
57
+ id,
58
+ type,
59
+ ...(sourceId ? { source: sourceId } : {}),
60
+ ...(current.sourceLayer ? { 'source-layer': current.sourceLayer } : {}),
61
+ ...(current.filter ? { filter: current.filter } : {}),
62
+ ...(current.minZoom !== undefined ? { minzoom: current.minZoom } : {}),
63
+ ...(current.maxZoom !== undefined ? { maxzoom: current.maxZoom } : {}),
64
+ paint,
65
+ layout,
66
+ }, before);
67
+ }
68
+ applied = {
69
+ paint,
70
+ layout,
71
+ filter: current.filter,
72
+ zoom: [current.minZoom ?? 0, current.maxZoom ?? 24],
73
+ beforeId: current.beforeId,
74
+ };
75
+ });
76
+ added = true;
77
+ return true;
78
+ };
79
+ if (!add()) {
80
+ // A source named outside any MapSource may not exist yet; wait for it.
81
+ waiting = () => {
82
+ if (add())
83
+ target.off('sourcedata', waiting);
84
+ };
85
+ target.on('sourcedata', waiting);
86
+ }
87
+ // Registering writes the map's layer list, which this effect must not come to depend on.
88
+ const unregister = untrack(() => context.registerLayer({ id, label: props().label ?? id, type }));
89
+ return () => {
90
+ added = false;
91
+ untrack(unregister);
92
+ safely(() => {
93
+ if (waiting)
94
+ target.off('sourcedata', waiting);
95
+ if (target.getLayer(id))
96
+ target.removeLayer(id);
97
+ });
98
+ };
99
+ });
100
+ // Everything after creation: diffed, so a re-render sends only what changed.
101
+ $effect(() => {
102
+ const current = props();
103
+ const colors = context.colors;
104
+ const target = context.map;
105
+ if (!added || !target)
106
+ return;
107
+ const { id } = current;
108
+ const { paint, layout } = resolve(current, colors);
109
+ for (const [name, value] of changedProperties(applied.paint, paint)) {
110
+ target.setPaintProperty(id, name, (value ?? null));
111
+ }
112
+ for (const [name, value] of changedProperties(applied.layout, layout)) {
113
+ target.setLayoutProperty(id, name, (value ?? null));
114
+ }
115
+ if (!deepEqual(applied.filter, current.filter))
116
+ target.setFilter(id, (current.filter ?? null));
117
+ const zoom = [current.minZoom ?? 0, current.maxZoom ?? 24];
118
+ if (!deepEqual(applied.zoom, zoom))
119
+ target.setLayerZoomRange(id, zoom[0], zoom[1]);
120
+ if (current.beforeId !== applied.beforeId) {
121
+ target.moveLayer(id, current.beforeId && target.getLayer(current.beforeId) ? current.beforeId : undefined);
122
+ }
123
+ applied = { paint, layout, filter: current.filter, zoom, beforeId: current.beforeId };
124
+ });
125
+ // Pointer events. Handlers read the latest props when they fire.
126
+ $effect(() => {
127
+ const target = context.map;
128
+ const id = layerId;
129
+ if (!added || !target)
130
+ return;
131
+ let hovered = null;
132
+ const clearHover = () => {
133
+ if (hovered)
134
+ safely(() => target.setFeatureState(hovered, { hover: false }));
135
+ hovered = null;
136
+ };
137
+ const click = (event) => {
138
+ if (event.features?.length)
139
+ props().onClick?.(toEvent(event));
140
+ };
141
+ const move = (event) => {
142
+ const feature = event.features?.[0];
143
+ if (!feature)
144
+ return;
145
+ const current = props();
146
+ if (current.onClick)
147
+ target.getCanvas().style.cursor = 'pointer';
148
+ if (feature.id !== undefined && feature.id !== hovered?.id) {
149
+ clearHover();
150
+ hovered = {
151
+ source: feature.source,
152
+ ...(feature.sourceLayer ? { sourceLayer: feature.sourceLayer } : {}),
153
+ id: feature.id,
154
+ };
155
+ target.setFeatureState(hovered, { hover: true });
156
+ }
157
+ current.onHover?.(toEvent(event));
158
+ };
159
+ const leave = () => {
160
+ target.getCanvas().style.cursor = '';
161
+ clearHover();
162
+ props().onHover?.(null);
163
+ };
164
+ target.on('click', id, click);
165
+ target.on('mousemove', id, move);
166
+ target.on('mouseleave', id, leave);
167
+ return () => safely(() => {
168
+ clearHover();
169
+ target.off('click', id, click);
170
+ target.off('mousemove', id, move);
171
+ target.off('mouseleave', id, leave);
172
+ });
173
+ });
174
+ return {
175
+ get added() {
176
+ return added;
177
+ },
178
+ };
179
+ }
@@ -0,0 +1,19 @@
1
+ import type * as MapLibre from 'maplibre-gl';
2
+ export type MapLibreModule = typeof MapLibre;
3
+ export type MapLibreLoader = () => Promise<unknown> | unknown;
4
+ /** Replaces how MapLibre is loaded. `null` restores the default dynamic import. */
5
+ export declare function setMapLibreLoader(loader: MapLibreLoader | null): void;
6
+ /**
7
+ * Where MapLibre's web worker is served from. MapLibre 6 ships the worker as its own file and
8
+ * finds it relative to itself at runtime, which bundlers cannot see — so a bundled app has to
9
+ * emit the worker and say where it went. With Vite:
10
+ *
11
+ * import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
12
+ * setMapLibreWorkerUrl(workerUrl);
13
+ *
14
+ * Without it, raster layers still draw but GeoJSON and vector tiles never do. MapLibre 5
15
+ * inlines its worker and needs none of this.
16
+ */
17
+ export declare function setMapLibreWorkerUrl(url: string | null): void;
18
+ /** Loads MapLibre once. Resolves `null` rather than rejecting when it cannot. */
19
+ export declare function loadMapLibre(): Promise<MapLibreModule | null>;
@@ -0,0 +1,47 @@
1
+ /** MapLibre 6 is named exports only; 5 bundles as a default export too. Accept either. */
2
+ const unwrap = (module) => {
3
+ const candidate = module;
4
+ if (typeof candidate?.Map === 'function')
5
+ return candidate;
6
+ if (typeof candidate?.default?.Map === 'function') {
7
+ return candidate.default;
8
+ }
9
+ return null;
10
+ };
11
+ const defaultLoader = () => import('maplibre-gl');
12
+ let registered = null;
13
+ let cached = null;
14
+ let workerUrl = null;
15
+ /** Replaces how MapLibre is loaded. `null` restores the default dynamic import. */
16
+ export function setMapLibreLoader(loader) {
17
+ registered = loader;
18
+ cached = null;
19
+ }
20
+ /**
21
+ * Where MapLibre's web worker is served from. MapLibre 6 ships the worker as its own file and
22
+ * finds it relative to itself at runtime, which bundlers cannot see — so a bundled app has to
23
+ * emit the worker and say where it went. With Vite:
24
+ *
25
+ * import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
26
+ * setMapLibreWorkerUrl(workerUrl);
27
+ *
28
+ * Without it, raster layers still draw but GeoJSON and vector tiles never do. MapLibre 5
29
+ * inlines its worker and needs none of this.
30
+ */
31
+ export function setMapLibreWorkerUrl(url) {
32
+ workerUrl = url;
33
+ cached = null;
34
+ }
35
+ /** Loads MapLibre once. Resolves `null` rather than rejecting when it cannot. */
36
+ export function loadMapLibre() {
37
+ cached ??= Promise.resolve()
38
+ .then(registered ?? defaultLoader)
39
+ .then(unwrap)
40
+ .then((maplibre) => {
41
+ if (maplibre && workerUrl)
42
+ maplibre.setWorkerUrl?.(workerUrl);
43
+ return maplibre;
44
+ })
45
+ .catch(() => null);
46
+ return cached;
47
+ }
@@ -0,0 +1,10 @@
1
+ /*
2
+ * MapLibre's own stylesheet, slotted into the `loidolt.vendor` cascade layer. The layer order
3
+ * is restated first so it holds whichever of this file and `@loidolt/theme-styles` is imported
4
+ * first: the base resets must not reach MapLibre's controls, and the map styles in
5
+ * `@loidolt/theme-styles` must be able to restyle them.
6
+ */
7
+ @layer loidolt.tokens, loidolt.reset, loidolt.base, loidolt.vendor, loidolt.components,
8
+ loidolt.utilities, loidolt.a11y;
9
+
10
+ @import 'maplibre-gl/dist/maplibre-gl.css' layer(loidolt.vendor);
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@loidolt/theme-maps",
3
+ "version": "0.7.0",
4
+ "description": "Accessible, themed MapLibre maps for Loidolt applications",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "svelte": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./core": {
16
+ "types": "./dist/core/index.d.ts",
17
+ "default": "./dist/core/index.js"
18
+ },
19
+ "./styles.css": "./dist/styles.css",
20
+ "./spatial.worker": {
21
+ "types": "./dist/core/workers/spatial.worker.d.ts",
22
+ "default": "./dist/core/workers/spatial.worker.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "sideEffects": [
30
+ "**/*.css"
31
+ ],
32
+ "scripts": {
33
+ "build": "svelte-package -i src/lib -o dist && cp src/styles.css dist/styles.css",
34
+ "prepack": "npm run build",
35
+ "typecheck": "svelte-check --tsconfig ./tsconfig.json",
36
+ "test": "vitest run && vitest run --config vitest.config.ssr.ts",
37
+ "test:coverage": "vitest run --coverage",
38
+ "clean": "rimraf dist"
39
+ },
40
+ "dependencies": {
41
+ "@loidolt/theme-svelte": "^0.7.0",
42
+ "@loidolt/theme-tokens": "^0.7.0",
43
+ "rbush": "^4.0.1"
44
+ },
45
+ "peerDependencies": {
46
+ "maplibre-gl": "^5.0.0 || ^6.0.0",
47
+ "svelte": "^5.33.0"
48
+ },
49
+ "devDependencies": {
50
+ "@sveltejs/package": "^2.5.8",
51
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
52
+ "@testing-library/jest-dom": "^6.8.0",
53
+ "@testing-library/svelte": "^5.2.8",
54
+ "@testing-library/user-event": "^14.6.1",
55
+ "@types/node": "^24.3.0",
56
+ "@types/rbush": "^4.0.0",
57
+ "@vitest/coverage-v8": "^4.1.11",
58
+ "jsdom": "^27.0.0",
59
+ "maplibre-gl": "^6.0.0",
60
+ "rimraf": "^6.0.1",
61
+ "svelte": "^5.56.10",
62
+ "svelte-check": "^4.3.2",
63
+ "typescript": "^6.0.3",
64
+ "vitest": "^4.0.0",
65
+ "vitest-axe": "^0.1.0"
66
+ },
67
+ "publishConfig": {
68
+ "access": "public",
69
+ "provenance": true
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/loidolt/loidolt-theme.git",
74
+ "directory": "packages/maps"
75
+ }
76
+ }