@swr-data-lab/components 1.11.2 → 1.12.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 (66) hide show
  1. package/.storybook/blocks/Mermaid.jsx +9 -0
  2. package/.storybook/main.ts +23 -17
  3. package/.storybook/preview.ts +42 -13
  4. package/package.json +72 -68
  5. package/src/DesignTokens/Tokens.ts +15 -12
  6. package/src/Select/Select.stories.svelte +3 -3
  7. package/src/Switcher/Switcher.svelte +1 -0
  8. package/src/index.js +12 -0
  9. package/src/maplibre/AttributionControl/AttributionControl.stories.svelte +29 -0
  10. package/src/maplibre/AttributionControl/AttributionControl.svelte +45 -0
  11. package/src/maplibre/AttributionControl/index.js +2 -0
  12. package/src/maplibre/GeocoderControl/GeocoderAPIs.ts +49 -0
  13. package/src/maplibre/GeocoderControl/GeocoderControl.stories.svelte +78 -0
  14. package/src/maplibre/GeocoderControl/GeocoderControl.svelte +207 -0
  15. package/src/maplibre/GeocoderControl/index.js +2 -0
  16. package/src/maplibre/Map/FallbackStyle.ts +18 -0
  17. package/src/maplibre/Map/Map.stories.svelte +118 -0
  18. package/src/maplibre/Map/Map.svelte +283 -0
  19. package/src/maplibre/Map/index.js +2 -0
  20. package/src/maplibre/MapControl/MapControl.mdx +12 -0
  21. package/src/maplibre/MapControl/MapControl.stories.svelte +56 -0
  22. package/src/maplibre/MapControl/MapControl.svelte +41 -0
  23. package/src/maplibre/MapControl/index.js +2 -0
  24. package/src/maplibre/MapStyle/SWRDataLabLight.mdx +86 -0
  25. package/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte +41 -0
  26. package/src/maplibre/MapStyle/SWRDataLabLight.ts +72 -0
  27. package/src/maplibre/MapStyle/components/Admin.ts +173 -0
  28. package/src/maplibre/MapStyle/components/Buildings.ts +23 -0
  29. package/src/maplibre/MapStyle/components/Landuse.ts +499 -0
  30. package/src/maplibre/MapStyle/components/Natural.ts +1 -0
  31. package/src/maplibre/MapStyle/components/PlaceLabels.ts +199 -0
  32. package/src/maplibre/MapStyle/components/Roads.ts +2345 -0
  33. package/src/maplibre/MapStyle/components/Transit.ts +507 -0
  34. package/src/maplibre/MapStyle/components/Walking.ts +1538 -0
  35. package/src/maplibre/MapStyle/index.js +2 -0
  36. package/src/maplibre/MapStyle/tokens.ts +21 -0
  37. package/src/maplibre/Maplibre.mdx +91 -0
  38. package/src/maplibre/NavigationControl/NavigationControl.stories.svelte +39 -0
  39. package/src/maplibre/NavigationControl/NavigationControl.svelte +36 -0
  40. package/src/maplibre/NavigationControl/index.js +2 -0
  41. package/src/maplibre/ScaleControl/ScaleControl.stories.svelte +71 -0
  42. package/src/maplibre/ScaleControl/ScaleControl.svelte +25 -0
  43. package/src/maplibre/ScaleControl/index.js +2 -0
  44. package/src/maplibre/Source/MapSource.stories.svelte +9 -0
  45. package/src/maplibre/Source/MapSource.svelte +61 -0
  46. package/src/maplibre/Source/index.js +2 -0
  47. package/src/maplibre/Source/source.ts +89 -0
  48. package/src/maplibre/Tooltip/Tooltip.stories.svelte +192 -0
  49. package/src/maplibre/Tooltip/Tooltip.svelte +175 -0
  50. package/src/maplibre/Tooltip/index.js +2 -0
  51. package/src/maplibre/VectorLayer/VectorLayer.stories.svelte +65 -0
  52. package/src/maplibre/VectorLayer/VectorLayer.svelte +142 -0
  53. package/src/maplibre/VectorLayer/index.js +2 -0
  54. package/src/maplibre/VectorTileSource/VectorTileSource.mdx +19 -0
  55. package/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte +46 -0
  56. package/src/maplibre/VectorTileSource/VectorTileSource.svelte +24 -0
  57. package/src/maplibre/VectorTileSource/index.js +2 -0
  58. package/src/maplibre/WithLinkLocation/WithLinkLocation.mdx +11 -0
  59. package/src/maplibre/WithLinkLocation/WithLinkLocation.stories.svelte +29 -0
  60. package/src/maplibre/WithLinkLocation/WithLinkLocation.svelte +83 -0
  61. package/src/maplibre/WithLinkLocation/index.js +2 -0
  62. package/src/maplibre/context.svelte.ts +89 -0
  63. package/src/maplibre/types.ts +12 -0
  64. package/src/maplibre/utils.ts +52 -0
  65. package/tsconfig.json +1 -0
  66. package/src/events/clickOutside.js +0 -23
@@ -0,0 +1,175 @@
1
+ <script lang="ts">
2
+ import { onDestroy, type Snippet } from 'svelte';
3
+ import { type Listener, type LngLatLike, Popup } from 'maplibre-gl';
4
+ import { getMapContext } from '../context.svelte.ts';
5
+ import { resetPopupEventListener } from '../utils';
6
+
7
+ interface TooltipProps {
8
+ position: LngLatLike | undefined;
9
+ children?: Snippet;
10
+ maxWidth?: number;
11
+ showCloseButton?: boolean;
12
+ /**
13
+ * Close the tooltip if the user clicks anywhere on the map
14
+ */
15
+ closeOnClick?: boolean;
16
+ /**
17
+ * y axis offset (px)
18
+ */
19
+ offset?: number;
20
+ /**
21
+ * Toggle mouse events on the tooltip element. Should be false if the tooltip appears on hover to avoid flickering.
22
+ */
23
+ mouseEvents?: boolean;
24
+ onClose?: Listener | undefined;
25
+ onOpen?: Listener | undefined;
26
+ }
27
+
28
+ let {
29
+ position,
30
+ children,
31
+ offset = 20,
32
+ maxWidth = 360,
33
+ showCloseButton = false,
34
+ closeOnClick = true,
35
+ mouseEvents = false,
36
+ onClose,
37
+ onOpen
38
+ }: TooltipProps = $props();
39
+
40
+ const { map } = $derived(getMapContext());
41
+
42
+ let tooltip = new Popup({
43
+ closeButton: showCloseButton,
44
+ closeOnClick: closeOnClick,
45
+ maxWidth: `${maxWidth}px`,
46
+ offset: offset
47
+ });
48
+
49
+ let el: Node | undefined = $state();
50
+
51
+ $effect(() => {
52
+ if (position && el && map) {
53
+ tooltip.setLngLat(position).setDOMContent(el).addTo(map);
54
+ } else {
55
+ tooltip.remove();
56
+ }
57
+ });
58
+
59
+ // Bind events
60
+ $effect(() => resetPopupEventListener(tooltip, 'open', onOpen));
61
+ $effect(() => resetPopupEventListener(tooltip, 'close', onClose));
62
+
63
+ onDestroy(() => tooltip.remove());
64
+ </script>
65
+
66
+ <div bind:this={el} class="container" class:mouseEvents>
67
+ {@render children?.()}
68
+ </div>
69
+
70
+ <style>
71
+ .container {
72
+ background: white;
73
+ padding: 0.65em;
74
+ border-radius: 2px;
75
+ border: 1px solid rgba(0, 0, 0, 0.75);
76
+ filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1));
77
+ pointer-events: none;
78
+ }
79
+
80
+ .mouseEvents {
81
+ pointer-events: all;
82
+ }
83
+
84
+ :global {
85
+ .maplibregl-popup {
86
+ top: 0;
87
+ left: 0;
88
+ display: flex;
89
+ position: absolute;
90
+ will-change: transform;
91
+ }
92
+ .maplibregl-popup-anchor-top,
93
+ .maplibregl-popup-anchor-top-left,
94
+ .maplibregl-popup-anchor-top-right {
95
+ flex-direction: column;
96
+ }
97
+ .maplibregl-popup-anchor-bottom,
98
+ .maplibregl-popup-anchor-bottom-left,
99
+ .maplibregl-popup-anchor-bottom-right {
100
+ flex-direction: column-reverse;
101
+ }
102
+
103
+ .maplibregl-popup-anchor-left {
104
+ flex-direction: row;
105
+ }
106
+ .maplibregl-popup-anchor-right {
107
+ flex-direction: row-reverse;
108
+ }
109
+ .maplibregl-popup-anchor-bottom .maplibregl-popup-tip {
110
+ transform: translateY(50%) rotate(45deg);
111
+ align-self: center;
112
+ }
113
+ .maplibregl-popup-anchor-top .maplibregl-popup-tip {
114
+ transform: translateY(-50%) rotate(-135deg);
115
+ align-self: center;
116
+ }
117
+
118
+ .maplibregl-popup-anchor-left .maplibregl-popup-tip {
119
+ transform: translateX(-50%) rotate(135deg);
120
+ align-self: center;
121
+ }
122
+ .maplibregl-popup-anchor-top-left .maplibregl-popup-tip {
123
+ transform: translateY(1em) translateX(-50%) rotate(135deg);
124
+ }
125
+ .maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip {
126
+ transform: translateY(-1em) translateX(-50%) rotate(135deg);
127
+ }
128
+ .maplibregl-popup-anchor-right .maplibregl-popup-tip {
129
+ transform: translateX(50%) rotate(-45deg);
130
+ align-self: center;
131
+ }
132
+ .maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
133
+ transform: translateX(-1em) translateY(-50%) rotate(-135deg);
134
+ align-self: flex-end;
135
+ }
136
+ .maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
137
+ transform: translateX(-1em) translateY(50%) rotate(45deg);
138
+ align-self: flex-end;
139
+ }
140
+
141
+ .maplibregl-popup-close-button {
142
+ background-color: transparent;
143
+ border: 0;
144
+ cursor: pointer;
145
+ position: absolute;
146
+ top: 0.45em;
147
+ right: 0.45em;
148
+ display: flex;
149
+ border-radius: var(--br-small);
150
+ justify-content: center;
151
+ align-items: center;
152
+ padding-bottom: 0.1em;
153
+ border: 1px solid var(--violet-dark-5);
154
+ font-size: 1.2rem;
155
+ width: 1em;
156
+ height: 1em;
157
+ z-index: 100;
158
+ }
159
+
160
+ .maplibregl-popup-close-button:hover,
161
+ .maplibregl-popup-close-button:focus {
162
+ background: rgb(245, 245, 245);
163
+ }
164
+
165
+ .maplibregl-popup-tip {
166
+ width: 0.6rem;
167
+ height: 0.6rem;
168
+ background: white;
169
+ position: absolute;
170
+ border-right: 1px solid rgba(0, 0, 0, 0.75);
171
+ border-bottom: 1px solid rgba(0, 0, 0, 0.75);
172
+ z-index: 10;
173
+ }
174
+ }
175
+ </style>
@@ -0,0 +1,2 @@
1
+ import Tooltip from './Tooltip.svelte';
2
+ export default Tooltip;
@@ -0,0 +1,65 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Map from '../Map/Map.svelte';
4
+ import VectorLayer from './VectorLayer.svelte';
5
+ import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
6
+ import AttributionControl from '../AttributionControl/AttributionControl.svelte';
7
+ import VectorTileSource from '../VectorTileSource/VectorTileSource.svelte';
8
+
9
+ import { SWRDataLabLight } from '../MapStyle';
10
+
11
+ const { Story } = defineMeta({
12
+ title: 'Maplibre/Layer/VectorLayer',
13
+ component: VectorLayer
14
+ });
15
+ </script>
16
+
17
+ <Story asChild name="Default">
18
+ <DesignTokens>
19
+ <div class="container">
20
+ <Map showDebug={true} style={SWRDataLabLight}>
21
+ <VectorTileSource
22
+ id="ev-infra-source"
23
+ url={`https://static.datenhub.net/data/p108_e_auto_check/ev_infra_merged.versatiles?tiles/{z}/{x}/{y}`}
24
+ />
25
+ <VectorLayer
26
+ sourceId="ev-infra-source"
27
+ type="fill"
28
+ id="coverage-fill"
29
+ sourceLayer="coverage"
30
+ placeBelow="street-residential"
31
+ paint={{
32
+ 'fill-color': [
33
+ 'step',
34
+ ['get', 'coverage_2025'],
35
+ 'white',
36
+ 1,
37
+ 'lightgray',
38
+ 1.3,
39
+ 'lightgreen'
40
+ ]
41
+ }}
42
+ />
43
+ <VectorLayer
44
+ sourceId="ev-infra-source"
45
+ sourceLayer="coverage"
46
+ id="ev-infra-outline"
47
+ type="line"
48
+ paint={{
49
+ 'line-width': 0.5,
50
+ 'line-color': 'purple',
51
+ 'line-opacity': 1
52
+ }}
53
+ />
54
+ <AttributionControl />
55
+ </Map>
56
+ </div>
57
+ </DesignTokens>
58
+ </Story>
59
+
60
+ <style>
61
+ .container {
62
+ width: 100%;
63
+ height: 600px;
64
+ }
65
+ </style>
@@ -0,0 +1,142 @@
1
+ <script lang="ts">
2
+ import type {
3
+ AddLayerObject,
4
+ FillLayoutProps,
5
+ FillPaintProps,
6
+ LineLayoutProps,
7
+ LinePaintProps,
8
+ MapGeoJSONFeature,
9
+ MapLayerMouseEvent
10
+ } from 'maplibre-gl';
11
+
12
+ import { getMapContext } from '../context.svelte.ts';
13
+ import { onDestroy } from 'svelte';
14
+ import { resetLayerEventListener } from '../utils.ts';
15
+
16
+ interface VectorLayerProps {
17
+ id: string;
18
+ sourceId: string;
19
+ sourceLayer: string;
20
+ type: 'line' | 'fill';
21
+ placeBelow: string;
22
+ visible?: boolean;
23
+ minZoom?: number;
24
+ maxZoom?: number;
25
+ paint?: LinePaintProps | FillPaintProps;
26
+ layout?: LineLayoutProps | FillLayoutProps;
27
+ hovered?: MapGeoJSONFeature | undefined;
28
+ selected?: MapGeoJSONFeature | undefined;
29
+
30
+ onclick: (e: MapLayerMouseEvent) => any;
31
+ onmousemove: (e: MapLayerMouseEvent) => any;
32
+ onmouseleave: (e: MapLayerMouseEvent) => any;
33
+ }
34
+ const {
35
+ id,
36
+ sourceId,
37
+ sourceLayer,
38
+ visible = true,
39
+ placeBelow = 'label-place-capital',
40
+ type,
41
+ paint,
42
+ layout,
43
+ hovered = $bindable(),
44
+ selected = $bindable(),
45
+ minZoom = 0,
46
+ maxZoom = 24,
47
+ onclick,
48
+ onmousemove,
49
+ onmouseleave
50
+ }: VectorLayerProps = $props();
51
+
52
+ const { map, styleLoaded } = $derived(getMapContext());
53
+ let beforeId: string | undefined = $state();
54
+ let prevSelected: string | number | undefined = $state();
55
+ let prevHovered: string | number | undefined = $state();
56
+
57
+ const layerSpec = {
58
+ id,
59
+ type,
60
+ source: sourceId,
61
+ 'source-layer': sourceLayer,
62
+ layout: $state.snapshot(layout) ?? {},
63
+ paint: $state.snapshot(paint) ?? {},
64
+ minzoom: minZoom,
65
+ maxzoom: maxZoom
66
+ } as AddLayerObject;
67
+
68
+ $effect(() => {
69
+ if (map && styleLoaded) {
70
+ const style = map.getStyle();
71
+ beforeId = style.layers.find((l) => {
72
+ return l.id === placeBelow;
73
+ })?.id;
74
+ }
75
+ });
76
+
77
+ $effect(() => {
78
+ if (map && styleLoaded && beforeId) {
79
+ map.addLayer(layerSpec, beforeId);
80
+ }
81
+ });
82
+
83
+ $effect(() => resetLayerEventListener(map, 'click', id, onclick));
84
+ $effect(() => resetLayerEventListener(map, 'mousemove', id, onmousemove));
85
+ $effect(() => resetLayerEventListener(map, 'mouseleave', id, onmouseleave));
86
+
87
+ // Set hovered feature state
88
+ $effect(() => {
89
+ if (styleLoaded) {
90
+ if (hovered) {
91
+ if (prevHovered) {
92
+ map?.setFeatureState(
93
+ { source: sourceId, sourceLayer: sourceLayer, id: prevHovered },
94
+ { hovered: false }
95
+ );
96
+ }
97
+ map?.setFeatureState(
98
+ { source: sourceId, sourceLayer: sourceLayer, id: hovered.id },
99
+ { hovered: true }
100
+ );
101
+ prevHovered = hovered.id;
102
+ } else {
103
+ if (prevHovered) {
104
+ map?.setFeatureState(
105
+ { source: sourceId, sourceLayer: sourceLayer, id: prevHovered },
106
+ { hovered: false }
107
+ );
108
+ }
109
+ }
110
+ }
111
+ });
112
+
113
+ // Set selected feature state
114
+ $effect(() => {
115
+ if (styleLoaded) {
116
+ if (selected) {
117
+ if (prevSelected) {
118
+ map?.setFeatureState(
119
+ { source: sourceId, sourceLayer: sourceLayer, id: prevSelected },
120
+ { selected: false }
121
+ );
122
+ }
123
+ map?.setFeatureState(
124
+ { source: sourceId, sourceLayer: sourceLayer, id: selected.id },
125
+ { selected: true }
126
+ );
127
+ prevSelected = selected.id;
128
+ } else {
129
+ if (prevSelected) {
130
+ map?.setFeatureState(
131
+ { source: sourceId, sourceLayer: sourceLayer, id: prevSelected },
132
+ { selected: false }
133
+ );
134
+ }
135
+ }
136
+ }
137
+ });
138
+
139
+ onDestroy(() => {
140
+ if (map && map.getLayer(id)) map.removeLayer(id);
141
+ });
142
+ </script>
@@ -0,0 +1,2 @@
1
+ import VectorLayer from './VectorLayer.svelte';
2
+ export default VectorLayer;
@@ -0,0 +1,19 @@
1
+ import { Story, Meta, Primary, Controls, Stories } from '@storybook/addon-docs/blocks';
2
+
3
+ import * as VectorTileSourceStories from './VectorTileSource.stories.svelte';
4
+
5
+ <Meta of={VectorTileSourceStories} />
6
+
7
+ # VectorTileSource
8
+
9
+ Loads tiled vector data from a tileserver. Any mablibre-supported tileserver will work, but we'll typically use tiles served by `versatiles-rs` or `versatiles-node-static-proxy`.
10
+
11
+ ```jsx
12
+ <Map>
13
+ <VectorTileSource
14
+ url="https://static.datenhub.net/rent_merged.versatiles?tiles/{z}/{x}/{y}"
15
+ id="chargers"
16
+ />
17
+ <VectorLayer sourceId="chargers" />
18
+ </Map>
19
+ ```
@@ -0,0 +1,46 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
4
+ import VectorTileSource from './VectorTileSource.svelte';
5
+ import VectorLayer from '../VectorLayer/VectorLayer.svelte';
6
+ import Map from '../Map/Map.svelte';
7
+ import { SWRDataLabLight } from '../MapStyle/';
8
+ import AttributionControl from '../AttributionControl';
9
+
10
+ const { Story } = defineMeta({
11
+ title: 'Maplibre/Source/VectorTileSource',
12
+ component: VectorTileSource
13
+ });
14
+ </script>
15
+
16
+ <Story asChild name="Default">
17
+ <DesignTokens>
18
+ <div class="container">
19
+ <Map showDebug={true} style={SWRDataLabLight}>
20
+ <VectorTileSource
21
+ id="ev-infra-source"
22
+ url={`https://static.datenhub.net/data/p108_e_auto_check/ev_infra_merged.versatiles?tiles/{z}/{x}/{y}`}
23
+ />
24
+ <VectorLayer
25
+ sourceId="ev-infra-source"
26
+ sourceLayer="coverage"
27
+ id="ev-infra-outline"
28
+ type="line"
29
+ paint={{
30
+ 'line-width': 0.5,
31
+ 'line-color': 'purple',
32
+ 'line-opacity': 1
33
+ }}
34
+ />
35
+ <AttributionControl />
36
+ </Map>
37
+ </div>
38
+ </DesignTokens>
39
+ </Story>
40
+
41
+ <style>
42
+ .container {
43
+ width: 100%;
44
+ height: 600px;
45
+ }
46
+ </style>
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import { type Snippet } from 'svelte';
3
+ import { type SourceSpecification } from 'maplibre-gl';
4
+ import MapSource from '../Source/MapSource.svelte';
5
+
6
+ interface VectorTileSourceProps {
7
+ id: string;
8
+ url: string;
9
+ minZoom?: number;
10
+ maxZoom?: number;
11
+ children?: Snippet;
12
+ }
13
+
14
+ const { minZoom = 0, maxZoom = 24, id, url }: VectorTileSourceProps = $props();
15
+
16
+ const sourceSpec: SourceSpecification = {
17
+ type: 'vector',
18
+ tiles: [url],
19
+ maxzoom: maxZoom,
20
+ minzoom: minZoom
21
+ };
22
+ </script>
23
+
24
+ <MapSource {id} {sourceSpec} />
@@ -0,0 +1,2 @@
1
+ import VectorTileSource from './VectorTileSource.svelte';
2
+ export default VectorTileSource;
@@ -0,0 +1,11 @@
1
+ import { Story, Meta, Primary, Controls, Stories } from '@storybook/addon-docs/blocks';
2
+
3
+ import * as WithLinkLocationStories from './WithLinkLocation.stories.svelte';
4
+
5
+ <Meta of={WithLinkLocationStories} />
6
+
7
+ # WithLinkLocation
8
+
9
+ <Controls />
10
+
11
+ <Stories />
@@ -0,0 +1,29 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import WithLinkLocation from './WithLinkLocation.svelte';
4
+ import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
5
+ import Map from '../Map/Map.svelte';
6
+ import { SWRDataLabLight } from '../MapStyle';
7
+
8
+ const { Story } = defineMeta({
9
+ title: 'Maplibre/Extras/WithLinkLocation',
10
+ component: WithLinkLocation
11
+ });
12
+ </script>
13
+
14
+ <Story asChild name="Default">
15
+ <DesignTokens>
16
+ <div class="container">
17
+ <WithLinkLocation countries="de" languages="de" service="maptiler" key="V32kPHZjMa0Mkn6YvSzA">
18
+ <Map style={SWRDataLabLight} initialLocation={{ lat: 51, lng: 10, zoom: 20 }}></Map>
19
+ </WithLinkLocation>
20
+ </div>
21
+ </DesignTokens>
22
+ </Story>
23
+
24
+ <style>
25
+ .container {
26
+ width: 500px;
27
+ height: 300px;
28
+ }
29
+ </style>
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import { onMount, setContext, type Snippet } from 'svelte';
3
+ import { MaptilerGeocoderAPI } from '../GeocoderControl/GeocoderAPIs';
4
+ import {
5
+ type GeocodingCountry,
6
+ type GeocodingLanguage,
7
+ type GeocodingService,
8
+ type Location
9
+ } from '../types';
10
+
11
+ import type {
12
+ MaplibreGeocoderApi,
13
+ MaplibreGeocoderApiConfig
14
+ } from '@maplibre/maplibre-gl-geocoder';
15
+
16
+ interface WithLinkLocationProps {
17
+ service?: GeocodingService;
18
+ /**
19
+ * API key for selected geocoding `service`
20
+ */
21
+ key: string;
22
+ /**
23
+ * Limit search to one or more countries
24
+ */
25
+ countries?: GeocodingLanguage | GeocodingCountry[];
26
+ languages?: GeocodingLanguage | GeocodingLanguage[];
27
+ urlParameter?: string;
28
+ children: Snippet;
29
+ }
30
+
31
+ const {
32
+ key,
33
+ service = 'maptiler',
34
+ countries = 'de',
35
+ languages = 'de',
36
+ urlParameter = 'location',
37
+ children
38
+ }: WithLinkLocationProps = $props();
39
+
40
+ const countriesArr = Array.isArray(countries) ? countries : [countries];
41
+ const languagesArr = Array.isArray(languages) ? languages : [languages];
42
+
43
+ let geocoder: MaplibreGeocoderApi;
44
+ if (service === 'maptiler') {
45
+ geocoder = new MaptilerGeocoderAPI(key);
46
+ }
47
+
48
+ let location: Location | boolean | undefined = $state();
49
+
50
+ function bboxToArea(bbox: [number, number, number, number]) {
51
+ return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]);
52
+ }
53
+
54
+ onMount(async () => {
55
+ const params = new URLSearchParams(window.location.search);
56
+ if (params.has(urlParameter)) {
57
+ const config: MaplibreGeocoderApiConfig = {
58
+ countries: countriesArr.join(','),
59
+ language: languagesArr.join(','),
60
+ query: params.get(urlParameter)?.toString(),
61
+ limit: 1
62
+ };
63
+ const res = await geocoder.forwardGeocode(config);
64
+ if (res.features[0].bbox && res.features[0].geometry.type === 'Point') {
65
+ location = {
66
+ lat: res.features[0].geometry.coordinates[1],
67
+ lng: res.features[0].geometry.coordinates[0],
68
+ zoom: 11 - bboxToArea(res.features[0].bbox) * 5.5
69
+ };
70
+ }
71
+ } else {
72
+ location = false;
73
+ }
74
+ });
75
+
76
+ $effect.pre(() => {
77
+ setContext('initialLocation', location);
78
+ });
79
+ </script>
80
+
81
+ {#if location !== undefined}
82
+ {@render children?.()}
83
+ {/if}
@@ -0,0 +1,2 @@
1
+ import WithLinkLocation from './WithLinkLocation.svelte';
2
+ export default WithLinkLocation;
@@ -0,0 +1,89 @@
1
+ import type { Map as MapLibre, Marker, LayerSpecification } from 'maplibre-gl';
2
+ import { getContext, setContext } from 'svelte';
3
+
4
+ const MAP_CONTEXT_KEY = Symbol.for('map-context');
5
+ const SOURCE_CONTEXT_KEY = Symbol.for('source-context');
6
+ const LAYER_CONTEXT_KEY = Symbol.for('layer-context');
7
+ const POPUP_TARGET_KEY = Symbol.for('popup-target');
8
+
9
+ export class Box<T> {
10
+ value = $state() as T;
11
+
12
+ constructor(initialValue: T) {
13
+ this.value = initialValue;
14
+ }
15
+ }
16
+
17
+ export class MapContext {
18
+ _map = $state(null) as MapLibre | null;
19
+ minzoom = $state(0);
20
+ maxzoom = $state(24);
21
+ styleLoaded = $state(false);
22
+ private _listener?: maplibregl.Listener = undefined;
23
+
24
+ get map() {
25
+ return this._map;
26
+ }
27
+
28
+ set map(value: maplibregl.Map | null) {
29
+ // Unbind any old event listeners
30
+ if (this._listener) {
31
+ this._map?.off('styledata', this._listener);
32
+ this._listener = undefined;
33
+ }
34
+ // Set new map instance and bind new event listeners
35
+ this._map = value;
36
+ if (this._map) {
37
+ this._listener = this._onstyledata.bind(this);
38
+ this._map.on('styledata', this._listener);
39
+ }
40
+ }
41
+
42
+ private _onstyledata(e: maplibregl.MapStyleDataEvent) {
43
+ this.styleLoaded = true;
44
+ }
45
+ }
46
+
47
+ export class SourceContext {
48
+ _source = $state();
49
+ loaded = $state(false);
50
+ minzoom = $state(0);
51
+ maxzoom = $state(24);
52
+
53
+ get source() {
54
+ return this._source;
55
+ }
56
+ }
57
+ export class LayerContext {
58
+ layer = $state() as LayerSpecification;
59
+ }
60
+
61
+ export function setPopupTarget(value: Box<Marker | string | undefined>) {
62
+ setContext(POPUP_TARGET_KEY, value);
63
+ }
64
+
65
+ export function getPopupTarget(): Box<Marker | string> | undefined {
66
+ return getContext(POPUP_TARGET_KEY);
67
+ }
68
+
69
+ export function createMapContext(): MapContext {
70
+ return setContext(MAP_CONTEXT_KEY, new MapContext());
71
+ }
72
+ export function getMapContext(): MapContext {
73
+ return getContext(MAP_CONTEXT_KEY);
74
+ }
75
+
76
+ export function createSourceContext(): SourceContext {
77
+ return setContext(SOURCE_CONTEXT_KEY, new SourceContext());
78
+ }
79
+ export function getSourceContext(): SourceContext {
80
+ return getContext(SOURCE_CONTEXT_KEY);
81
+ }
82
+
83
+ export function getLayerContext(): LayerContext {
84
+ return getContext(LAYER_CONTEXT_KEY);
85
+ }
86
+
87
+ export function setLayerContext(value: string) {
88
+ setContext(LAYER_CONTEXT_KEY, value);
89
+ }