@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,207 @@
1
+ <script lang="ts">
2
+ import maplibre from 'maplibre-gl';
3
+ import MaplibreGeocoder, { type MaplibreGeocoderApi } from '@maplibre/maplibre-gl-geocoder';
4
+ import { MaptilerGeocoderAPI } from './GeocoderAPIs';
5
+ import MapControl from '../MapControl/MapControl.svelte';
6
+ import { type GeocodingCountry, type GeocodingLanguage, type GeocodingService } from '../types';
7
+
8
+ interface GeocoderControlProps {
9
+ service: GeocodingService;
10
+ /**
11
+ * API key for selected geocoding `service`
12
+ */
13
+ key: string;
14
+ /**
15
+ * Maximum number of suggestions to display
16
+ */
17
+ limit?: number;
18
+ /**
19
+ * Limit search to one or more countries
20
+ */
21
+ countries?: GeocodingCountry | GeocodingCountry[];
22
+ /**
23
+ * Limit search to one or more languages. The UI is localised to the first language specified if [available](https://github.com/maplibre/maplibre-gl-geocoder/blob/main/lib/localization.ts).
24
+ */
25
+ languages?: GeocodingLanguage | GeocodingLanguage[];
26
+ /**
27
+ * Overwrite the default input placeholder text
28
+ */
29
+ placeholder?: string;
30
+ }
31
+
32
+ const {
33
+ key,
34
+ service = 'maptiler',
35
+ countries = 'de',
36
+ languages = 'en',
37
+ placeholder,
38
+ limit = 3
39
+ }: GeocoderControlProps = $props();
40
+
41
+ const countriesArr = Array.isArray(countries) ? countries : [countries];
42
+ const languagesArr = Array.isArray(languages) ? languages : [languages];
43
+
44
+ // Future: initialise a different GeocoderAPI depending on "service"
45
+ let geocoderAPI: MaplibreGeocoderApi = new MaptilerGeocoderAPI(key);
46
+
47
+ const geocoder = new MaplibreGeocoder(geocoderAPI, {
48
+ maplibregl: maplibre,
49
+ language: languagesArr.join(','),
50
+ countries: countriesArr.join(','),
51
+ showResultsWhileTyping: true,
52
+ showResultMarkers: false,
53
+ placeholder,
54
+ limit
55
+ });
56
+ </script>
57
+
58
+ <MapControl control={geocoder} position="top-left" />
59
+
60
+ <style>
61
+ :global {
62
+ .maplibregl-ctrl-geocoder {
63
+ background-color: #fff;
64
+ position: relative;
65
+ width: 100%;
66
+ z-index: 1;
67
+ font-family: var(--swr-sans);
68
+ font-size: var(--fs-small-1);
69
+ border-radius: var(--br-small);
70
+ border: 1px solid rgba(0, 0, 0, 0.75);
71
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
72
+ }
73
+
74
+ .maplibre-gl-geocoder--error {
75
+ font-size: var(--fs-small-2);
76
+ color: var(--gray-dark-2);
77
+ padding: 0.4em 0.65em;
78
+ }
79
+
80
+ .maplibregl-ctrl-geocoder--input {
81
+ width: 100%;
82
+ height: 100%;
83
+ font-family: inherit;
84
+ font-size: inherit;
85
+ background-color: transparent;
86
+ color: var(--gray-dark-5);
87
+ border: 0;
88
+ height: 2em;
89
+ padding: 0 1.85em;
90
+ padding-top: 0.1em;
91
+ text-overflow: ellipsis;
92
+ white-space: nowrap;
93
+ overflow: hidden;
94
+ }
95
+
96
+ .maplibregl-ctrl-geocoder--input:focus {
97
+ color: rgba(0, 0, 0, 0.75);
98
+ outline: 0;
99
+ }
100
+
101
+ .maplibregl-ctrl-geocoder .maplibregl-ctrl-geocoder--pin-right > * {
102
+ display: none;
103
+ position: absolute;
104
+ z-index: 2;
105
+ right: 0.5em;
106
+ top: 50%;
107
+ transform: translateY(-50%);
108
+ border: 0;
109
+ }
110
+
111
+ /* Suggestions */
112
+ .maplibregl-ctrl-geocoder .suggestions {
113
+ background: white;
114
+ border: 1px solid rgba(0, 0, 0, 0.75);
115
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
116
+ border-radius: var(--br-small);
117
+ left: 0;
118
+ list-style: none;
119
+ position: absolute;
120
+ width: 100%;
121
+ top: calc(100% + 0.5em);
122
+ z-index: 1000;
123
+ overflow: hidden;
124
+ }
125
+
126
+ .maplibregl-ctrl-bottom-left .suggestions,
127
+ .maplibregl-ctrl-bottom-right .suggestions {
128
+ top: auto;
129
+ bottom: 100%;
130
+ }
131
+
132
+ .maplibregl-ctrl-geocoder .suggestions > li > a {
133
+ cursor: default;
134
+ display: block;
135
+ padding: 0.5em 0.75em;
136
+ color: var(--gray-dark-5);
137
+ padding-bottom: 0.5em;
138
+ border-bottom: 1px solid var(--gray-light-3);
139
+ }
140
+ .maplibregl-ctrl-geocoder .suggestions > li:last-child > a {
141
+ border-bottom: 0;
142
+ }
143
+
144
+ .maplibregl-ctrl-geocoder .suggestions > .active > a,
145
+ .maplibregl-ctrl-geocoder .suggestions > li > a:hover {
146
+ background: var(--gray-light-5);
147
+ text-decoration: none;
148
+ cursor: pointer;
149
+ }
150
+ .maplibregl-ctrl-geocoder .suggestions > .active .maplibregl-ctrl-geocoder--result-title,
151
+ .maplibregl-ctrl-geocoder .suggestions > li > a:hover .maplibregl-ctrl-geocoder--result-title {
152
+ text-decoration: underline;
153
+ }
154
+
155
+ .maplibregl-ctrl-geocoder--suggestion {
156
+ display: flex;
157
+ flex-direction: row;
158
+ align-items: center;
159
+ }
160
+
161
+ .maplibregl-ctrl-geocoder--suggestion-info {
162
+ display: flex;
163
+ flex-direction: column;
164
+ }
165
+
166
+ .maplibregl-ctrl-geocoder--suggestion-match {
167
+ font-weight: bold;
168
+ }
169
+
170
+ .maplibregl-ctrl-geocoder--suggestion-title,
171
+ .maplibregl-ctrl-geocoder--suggestion-address,
172
+ .maplibregl-ctrl-geocoder--result-address {
173
+ text-overflow: ellipsis;
174
+ overflow: hidden;
175
+ white-space: nowrap;
176
+ }
177
+
178
+ .maplibregl-ctrl-geocoder--result-icon {
179
+ display: none;
180
+ }
181
+ .maplibregl-ctrl-geocoder--result-title {
182
+ font-weight: 600;
183
+ letter-spacing: 0;
184
+ }
185
+ .maplibregl-ctrl-geocoder--result-address {
186
+ font-size: var(--fs-small-2);
187
+ color: var(--gray-dark-2);
188
+ line-height: 1.3;
189
+ }
190
+
191
+ .maplibregl-ctrl-geocoder--icon {
192
+ display: inline-block;
193
+ position: absolute;
194
+ top: 50%;
195
+ transform: translateY(-50%);
196
+ height: 1em;
197
+ }
198
+
199
+ .maplibregl-ctrl-geocoder--icon-close {
200
+ right: 0;
201
+ }
202
+ .maplibregl-ctrl-geocoder--icon-search {
203
+ left: 0.5em;
204
+ height: 1.5em;
205
+ }
206
+ }
207
+ </style>
@@ -0,0 +1,2 @@
1
+ import GeocoderControl from './GeocoderControl.svelte';
2
+ export default GeocoderControl;
@@ -0,0 +1,18 @@
1
+ import type { StyleSpecification } from 'maplibre-gl';
2
+
3
+ const style: StyleSpecification = {
4
+ version: 8,
5
+ name: 'default',
6
+ sources: {},
7
+ layers: [
8
+ {
9
+ id: 'background',
10
+ type: 'background',
11
+ paint: {
12
+ 'background-color': 'lightgray'
13
+ }
14
+ }
15
+ ]
16
+ };
17
+
18
+ export default style;
@@ -0,0 +1,118 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import { within, expect } from 'storybook/test';
4
+
5
+ import Map from './Map.svelte';
6
+ import ScaleControl from '../ScaleControl/ScaleControl.svelte';
7
+ import NavigationControl from '../NavigationControl/NavigationControl.svelte';
8
+ import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
9
+ import AttributionControl from '../AttributionControl/AttributionControl.svelte';
10
+ import GeocoderControl from '../GeocoderControl/GeocoderControl.svelte';
11
+
12
+ import { SWRDataLabLight } from '../MapStyle';
13
+
14
+ import { eclipse } from '@versatiles/style';
15
+ const alternateStyle = eclipse({
16
+ language: 'de',
17
+ baseUrl: 'https://tiles.versatiles.org',
18
+ glyphs: 'https://static.datenhub.net/maps/fonts/{fontstack}/{range}.pbf'
19
+ });
20
+
21
+ const { Story } = defineMeta({
22
+ title: 'Maplibre/Map',
23
+ component: Map
24
+ });
25
+ </script>
26
+
27
+ <Story
28
+ asChild
29
+ name="Default"
30
+ play={async ({ canvasElement, step }) => {
31
+ const canvas = within(canvasElement);
32
+ const containerEl = canvas.getByTestId('map-container');
33
+
34
+ await step('Map canvas renders', async () => {
35
+ const mapEl = containerEl.querySelector('.maplibregl-canvas');
36
+ expect(mapEl).toBeTruthy();
37
+ });
38
+ await step('Scale control renders', async () => {
39
+ const el = containerEl.querySelector('.maplibregl-ctrl-scale');
40
+ expect(el).toBeTruthy();
41
+ });
42
+ await step('Scale renders expected value', async () => {
43
+ const el = containerEl.querySelector('.maplibregl-ctrl-scale');
44
+ expect(el).toHaveTextContent('100 km');
45
+ });
46
+ await step('Navigation control renders', async () => {
47
+ const zoomInEl = containerEl.querySelector('.maplibregl-ctrl-zoom-in');
48
+ const zoomOutEl = containerEl.querySelector('.maplibregl-ctrl-zoom-out');
49
+ expect(zoomInEl).toBeTruthy();
50
+ expect(zoomInEl).toHaveRole('button');
51
+ expect(zoomOutEl).toBeTruthy();
52
+ expect(zoomOutEl).toHaveRole('button');
53
+ });
54
+
55
+ await step('Attribution control renders', async () => {
56
+ const el = containerEl.querySelector('.maplibregl-ctrl-attrib');
57
+ expect(el).toBeTruthy();
58
+ });
59
+
60
+ await step('Geocoder control renders', async () => {
61
+ const el = containerEl.querySelector('.maplibregl-ctrl-geocoder');
62
+ expect(el).toBeTruthy();
63
+ });
64
+ }}
65
+ >
66
+ <div class="container">
67
+ <DesignTokens>
68
+ <Map style={SWRDataLabLight}>
69
+ <ScaleControl />
70
+ <AttributionControl />
71
+ <NavigationControl showCompass visualizePitch />
72
+ <GeocoderControl languages="de" service="maptiler" key="V32kPHZjMa0Mkn6YvSzA" />
73
+ </Map>
74
+ </DesignTokens>
75
+ </div>
76
+ </Story>
77
+
78
+ <Story asChild name="Globe Projection">
79
+ <div class="container dark">
80
+ <DesignTokens>
81
+ <Map
82
+ style={SWRDataLabLight}
83
+ showDebug
84
+ projection={{ type: 'globe' }}
85
+ pitch={52}
86
+ initialLocation={{ lat: 51.3, lng: 10.2, zoom: 3.5 }}
87
+ >
88
+ <ScaleControl />
89
+ <AttributionControl />
90
+ <NavigationControl showCompass visualizePitch />
91
+ <GeocoderControl languages="de" service="maptiler" key="V32kPHZjMa0Mkn6YvSzA" />
92
+ </Map>
93
+ </DesignTokens>
94
+ </div>
95
+ </Story>
96
+
97
+ <Story asChild name="Alternate Style">
98
+ <div class="container dark">
99
+ <DesignTokens>
100
+ <Map style={alternateStyle}>
101
+ <ScaleControl />
102
+ <AttributionControl />
103
+ <NavigationControl showCompass visualizePitch />
104
+ <GeocoderControl languages="de" service="maptiler" key="V32kPHZjMa0Mkn6YvSzA" />
105
+ </Map>
106
+ </DesignTokens>
107
+ </div>
108
+ </Story>
109
+
110
+ <style>
111
+ .container {
112
+ width: 100%;
113
+ height: 600px;
114
+ }
115
+ .dark {
116
+ color: rgb(230, 230, 230);
117
+ }
118
+ </style>
@@ -0,0 +1,283 @@
1
+ <script lang="ts">
2
+ import maplibre, { type ProjectionSpecification, type StyleSpecification } from 'maplibre-gl';
3
+ import { onMount, onDestroy, type Snippet, getContext, hasContext } from 'svelte';
4
+ import { createMapContext } from '../context.svelte';
5
+ import { type Location } from '../types';
6
+ import FallbackStyle from './FallbackStyle';
7
+
8
+ interface MapProps {
9
+ style?: StyleSpecification | string;
10
+ initialLocation?: Location;
11
+ allowRotation?: boolean;
12
+ allowZoom?: boolean;
13
+ minZoom?: number;
14
+ maxZoom?: number;
15
+ zoom?: number;
16
+ center?: maplibre.LngLat;
17
+ pitch?: number;
18
+ bearing?: number;
19
+ loading?: boolean;
20
+ projection?: ProjectionSpecification;
21
+ showDebug?: boolean;
22
+ options?: any;
23
+ children?: Snippet;
24
+ }
25
+
26
+ let {
27
+ children,
28
+ options,
29
+ style = FallbackStyle,
30
+ minZoom = 0,
31
+ maxZoom = 14.99,
32
+ zoom = $bindable(),
33
+ center = $bindable(),
34
+ pitch = $bindable(0),
35
+ bearing = $bindable(0),
36
+ loading = $bindable(true),
37
+ projection = { type: 'mercator' },
38
+ allowRotation = false,
39
+ allowZoom = true,
40
+ showDebug = false,
41
+ initialLocation = { lat: 51.3, lng: 10.2, zoom: 5 }
42
+ }: MapProps = $props();
43
+
44
+ let container: HTMLElement;
45
+
46
+ const mapContext = createMapContext();
47
+ if (getContext('initialLocation') !== undefined && getContext('initialLocation') !== false) {
48
+ initialLocation = getContext('initialLocation');
49
+ }
50
+
51
+ onMount(() => {
52
+ mapContext.map = new maplibre.Map({
53
+ container,
54
+ style,
55
+ minZoom,
56
+ maxZoom,
57
+ pitch,
58
+ bearing,
59
+ attributionControl: false, // Added via component
60
+ center: [initialLocation.lng, initialLocation.lat],
61
+ zoom: initialLocation.zoom,
62
+ ...options
63
+ });
64
+
65
+ mapContext.map.on('load', () => {
66
+ zoom = mapContext.map?.getZoom();
67
+ center = mapContext.map?.getCenter();
68
+ pitch = mapContext.map?.getPitch();
69
+ bearing = mapContext.map?.getBearing();
70
+ });
71
+
72
+ mapContext.map.on('moveend', () => {
73
+ zoom = mapContext.map?.getZoom();
74
+ center = mapContext.map?.getCenter();
75
+ pitch = mapContext.map?.getPitch();
76
+ bearing = mapContext.map?.getBearing();
77
+ });
78
+ });
79
+
80
+ onDestroy(async () => {
81
+ if (mapContext.map) mapContext.map.remove();
82
+ });
83
+
84
+ $effect(() => {
85
+ if (mapContext.map) mapContext.map.setStyle(style);
86
+ });
87
+ $effect(() => {
88
+ if (mapContext.styleLoaded) {
89
+ mapContext.map?.setProjection(projection);
90
+ }
91
+ });
92
+
93
+ $effect(() => {
94
+ if (allowZoom === false) {
95
+ mapContext.map?.scrollZoom.disable();
96
+ } else {
97
+ mapContext.map?.scrollZoom.enable();
98
+ }
99
+ });
100
+ </script>
101
+
102
+ <div bind:this={container} class="container" data-testid="map-container">
103
+ {#if mapContext.map}
104
+ {#if children}
105
+ {@render children()}
106
+ {/if}
107
+ {/if}
108
+ {#if showDebug}
109
+ <div class="debug">
110
+ {JSON.stringify({ ...center, zoom, allowZoom, allowRotation })}
111
+ </div>
112
+ {/if}
113
+ </div>
114
+
115
+ <style>
116
+ .container {
117
+ width: 100%;
118
+ height: 100%;
119
+ }
120
+
121
+ .debug {
122
+ position: absolute;
123
+ top: 0;
124
+ right: 0;
125
+ background: black;
126
+ color: white;
127
+ z-index: 1000;
128
+ font-family: monospace;
129
+ }
130
+ :global {
131
+ .maplibregl-map {
132
+ overflow: hidden;
133
+ position: relative;
134
+ }
135
+
136
+ .maplibregl-canvas {
137
+ left: 0;
138
+ position: absolute;
139
+ top: 0;
140
+ }
141
+
142
+ .maplibregl-map:fullscreen {
143
+ height: 100%;
144
+ width: 100%;
145
+ }
146
+
147
+ .maplibregl-canvas-container.maplibregl-interactive {
148
+ cursor: grab;
149
+ user-select: none;
150
+ }
151
+
152
+ .maplibregl-canvas-container.maplibregl-interactive.maplibregl-track-pointer {
153
+ cursor: pointer;
154
+ }
155
+
156
+ .maplibregl-canvas-container.maplibregl-interactive:active {
157
+ cursor: grabbing;
158
+ }
159
+
160
+ .maplibregl-canvas-container.maplibregl-touch-zoom-rotate,
161
+ .maplibregl-canvas-container.maplibregl-touch-zoom-rotate .maplibregl-canvas {
162
+ touch-action: pan-x pan-y;
163
+ }
164
+
165
+ .maplibregl-canvas-container.maplibregl-touch-drag-pan,
166
+ .maplibregl-canvas-container.maplibregl-touch-drag-pan .maplibregl-canvas {
167
+ touch-action: pinch-zoom;
168
+ }
169
+
170
+ .maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan,
171
+ .maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan
172
+ .maplibregl-canvas {
173
+ touch-action: none;
174
+ }
175
+ .maplibregl-ctrl-bottom-left,
176
+ .maplibregl-ctrl-bottom-right,
177
+ .maplibregl-ctrl-top-left,
178
+ .maplibregl-ctrl-top-right {
179
+ position: absolute;
180
+ z-index: 2;
181
+ display: flex;
182
+ flex-flow: column;
183
+ gap: 0.75em;
184
+ align-items: flex-start;
185
+ }
186
+
187
+ .maplibregl-ctrl-top-left {
188
+ left: 0.5em;
189
+ top: 0.5em;
190
+ }
191
+
192
+ .maplibregl-ctrl-top-right {
193
+ right: 0;
194
+ top: 0;
195
+ }
196
+
197
+ .maplibregl-ctrl-bottom-left {
198
+ bottom: 0.5em;
199
+ left: 0.5em;
200
+ }
201
+
202
+ .maplibregl-ctrl-bottom-right {
203
+ bottom: 0.5em;
204
+ right: 0.5em;
205
+ }
206
+
207
+ .maplibregl-ctrl {
208
+ pointer-events: auto;
209
+ transform: translate(0);
210
+ }
211
+
212
+ .maplibregl-ctrl-group {
213
+ background: #fff;
214
+ border-radius: var(--br-small);
215
+ border: 1px solid var(--violet-dark-5);
216
+ }
217
+
218
+ .maplibregl-ctrl-group button {
219
+ background-color: transparent;
220
+ border: 0;
221
+ box-sizing: border-box;
222
+ cursor: pointer;
223
+ display: block;
224
+ height: 29px;
225
+ outline: none;
226
+ padding: 0;
227
+ width: 29px;
228
+ }
229
+
230
+ .maplibregl-ctrl-group button + button {
231
+ border-top: 1px solid rgba(0, 0, 0, 0.35);
232
+ }
233
+
234
+ .maplibregl-ctrl button .maplibregl-ctrl-icon {
235
+ background-position: 50%;
236
+ background-repeat: no-repeat;
237
+ display: block;
238
+ height: 100%;
239
+ width: 100%;
240
+ }
241
+
242
+ .maplibregl-ctrl button:disabled {
243
+ cursor: not-allowed;
244
+ }
245
+
246
+ .maplibregl-ctrl button:disabled .maplibregl-ctrl-icon {
247
+ opacity: 0.25;
248
+ }
249
+
250
+ .maplibregl-ctrl button:not(:disabled):hover {
251
+ background-color: rgb(0 0 0/5%);
252
+ }
253
+
254
+ .maplibregl-ctrl-group button:focus:focus-visible {
255
+ box-shadow: 0 0 2px 2px #0096ff;
256
+ }
257
+
258
+ .maplibregl-ctrl-group button:focus:not(:focus-visible) {
259
+ box-shadow: none;
260
+ }
261
+
262
+ .maplibregl-ctrl-group button:focus:first-child {
263
+ border-radius: 4px 4px 0 0;
264
+ }
265
+
266
+ .maplibregl-ctrl-group button:focus:last-child {
267
+ border-radius: 0 0 4px 4px;
268
+ }
269
+
270
+ .maplibregl-ctrl-group button:focus:only-child {
271
+ border-radius: inherit;
272
+ }
273
+ .maplibregl-marker {
274
+ left: 0;
275
+ top: 0;
276
+ position: absolute;
277
+ will-change: transform;
278
+ }
279
+ .maplibregl-marker path {
280
+ fill: var(--violet-dark-5);
281
+ }
282
+ }
283
+ </style>
@@ -0,0 +1,2 @@
1
+ import Map from './Map.svelte';
2
+ export default Map;
@@ -0,0 +1,12 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+ import { Story, Primary, Controls, Stories } from '@storybook/addon-docs/blocks';
3
+ import * as MapControlStories from './MapControl.stories.svelte';
4
+
5
+ <Meta of={MapControlStories} />
6
+
7
+ # MapControl
8
+
9
+ Base component used by all other built-in control components. Also useful for building application-specific controls (see demo below).
10
+
11
+ <Stories />
12
+ <Controls />
@@ -0,0 +1,56 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import MapControl from './MapControl.svelte';
4
+ import { SWRDataLabLight } from '../MapStyle';
5
+ import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
6
+ import Switcher from '../../Switcher/Switcher.svelte';
7
+ import Map from '../Map/Map.svelte';
8
+
9
+ type ThemeMap = {
10
+ [key: string]: StyleSpecification;
11
+ };
12
+
13
+ import { eclipse } from '@versatiles/style';
14
+ import type { StyleSpecification } from 'maplibre-gl';
15
+ const eclipseStyle = eclipse({
16
+ language: 'de',
17
+ baseUrl: 'https://tiles.versatiles.org',
18
+ glyphs: 'https://static.datenhub.net/maps/fonts/{fontstack}/{range}.pbf'
19
+ });
20
+
21
+ const { Story } = defineMeta({
22
+ title: 'Maplibre/Control/MapControl',
23
+ component: MapControl
24
+ });
25
+
26
+ const themes: ThemeMap = {
27
+ 'SWRDL Light': SWRDataLabLight,
28
+ Eclipse: eclipseStyle
29
+ };
30
+
31
+ let currentTheme = $state('SWRDL Light');
32
+ </script>
33
+
34
+ <Story asChild name="Custom HTML Control">
35
+ <DesignTokens>
36
+ <div class="container">
37
+ <Map style={themes[currentTheme]} initialLocation={{ lat: 51, lng: 10, zoom: 20 }}>
38
+ <MapControl position="top-left">
39
+ <Switcher
40
+ options={Object.keys(themes)}
41
+ bind:value={currentTheme}
42
+ label="Select theme"
43
+ size="small"
44
+ />
45
+ </MapControl>
46
+ </Map>
47
+ </div>
48
+ </DesignTokens>
49
+ </Story>
50
+
51
+ <style>
52
+ .container {
53
+ width: 500px;
54
+ height: 300px;
55
+ }
56
+ </style>