shadcn-map 0.1.0 → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/components/ClusterLayer.svelte +37 -8
- package/dist/components/Map.svelte +90 -29
- package/dist/components/Marker.svelte +13 -5
- package/dist/components/controls/GeolocateControl.svelte +1 -1
- package/dist/context.svelte.d.ts +3 -0
- package/dist/context.svelte.js +8 -2
- package/package.json +3 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hegyi Áron Ferenc
|
|
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,75 @@
|
|
|
1
|
+
# shadcn-map
|
|
2
|
+
|
|
3
|
+
A minimal and fast map library for Svelte 5 with [shadcn](https://shadcn-svelte.com/) design.
|
|
4
|
+
Uses [MapLibre GL](https://maplibre.org/) and [Protomaps](https://protomaps.com/) for vector tiles.
|
|
5
|
+
|
|
6
|
+
Recommended to use with [UnoCSS](https://unocss.dev/), and [unocss-preset-shadcn](https://github.com/Rettend/unocss-preset-shadcn/).
|
|
7
|
+
|
|
8
|
+
## install
|
|
9
|
+
|
|
10
|
+
```cmd
|
|
11
|
+
bun i shadcn-map
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Peer dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun i svelte@^5.0.0 bits-ui mode-watcher
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Map
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
import { Map, Marker, NavigationControl } from 'shadcn-map';
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="h-600px w-full rounded-lg border overflow-hidden">
|
|
30
|
+
<!-- Map data for development -->
|
|
31
|
+
<Map
|
|
32
|
+
center={[-74.006, 40.7128]}
|
|
33
|
+
zoom={12}
|
|
34
|
+
tiles="https://r2-public.protomaps.com/protomaps-sample-datasets/protomaps-basemap-opensource-20230408.pmtiles"
|
|
35
|
+
>
|
|
36
|
+
<NavigationControl position="top-right" />
|
|
37
|
+
|
|
38
|
+
<Marker
|
|
39
|
+
lngLat={[-74.006, 40.7128]}
|
|
40
|
+
label="New York"
|
|
41
|
+
color="primary"
|
|
42
|
+
/>
|
|
43
|
+
</Map>
|
|
44
|
+
</div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Components
|
|
48
|
+
|
|
49
|
+
- **Map**:
|
|
50
|
+
- **`<Map>`**: The core container. Renders MapLibre GL.
|
|
51
|
+
- **`<Marker>`**: Styled pins with shadcn theme colors, icons and badges.
|
|
52
|
+
- **`<ClusterLayer>`**: Auto-clustering for markers.
|
|
53
|
+
- **UI**:
|
|
54
|
+
- **`<Popup>`**: In-place info popup.
|
|
55
|
+
- **`<DetailsPanel>`**: Full-height sidebar or bottom drawer.
|
|
56
|
+
- **Controls**:
|
|
57
|
+
- **`<NavigationControl>`**: Zoom and compass controls.
|
|
58
|
+
- **`<ScaleControl>`**: Distance scale bar.
|
|
59
|
+
- **`<GeolocateControl>`**: Show user location.
|
|
60
|
+
|
|
61
|
+
## Styles
|
|
62
|
+
|
|
63
|
+
The map comes with two minimal styles:
|
|
64
|
+
|
|
65
|
+
- **Dark**: Dark zinc colors, subtle roads, dark blue water.
|
|
66
|
+
- **Light**: Clean white/zinc colors, minimal distractions.
|
|
67
|
+
|
|
68
|
+
Labels:
|
|
69
|
+
|
|
70
|
+
- **Minimal**: City names, roads, water.
|
|
71
|
+
- **Roads**: Show road names too.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -66,15 +66,8 @@
|
|
|
66
66
|
let supercluster: Supercluster<{ id: string | number }, Supercluster.AnyProps> | null = null
|
|
67
67
|
let lastSuperclusterPointsKey = ''
|
|
68
68
|
|
|
69
|
-
function getIsDarkMode(): boolean {
|
|
70
|
-
if (typeof document === 'undefined') {
|
|
71
|
-
return true
|
|
72
|
-
}
|
|
73
|
-
return document.documentElement.classList.contains('dark')
|
|
74
|
-
}
|
|
75
|
-
|
|
76
69
|
function getThemeColors() {
|
|
77
|
-
const isDark =
|
|
70
|
+
const isDark = ctx.resolvedMode === 'dark'
|
|
78
71
|
return isDark
|
|
79
72
|
? {
|
|
80
73
|
clusterLow: '#3f3f46',
|
|
@@ -508,4 +501,40 @@
|
|
|
508
501
|
updateSourceData()
|
|
509
502
|
scheduleUnclusteredUpdate()
|
|
510
503
|
})
|
|
504
|
+
|
|
505
|
+
// Update cluster colors when resolvedMode changes
|
|
506
|
+
$effect(() => {
|
|
507
|
+
// Access resolvedMode to track as dependency
|
|
508
|
+
void ctx.resolvedMode
|
|
509
|
+
if (!map || !map.isStyleLoaded()) {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const colors = getThemeColors()
|
|
514
|
+
|
|
515
|
+
// Update cluster layer colors
|
|
516
|
+
if (map.getLayer(clusterLayerId)) {
|
|
517
|
+
map.setPaintProperty(clusterLayerId, 'circle-color', [
|
|
518
|
+
'step',
|
|
519
|
+
['get', 'point_count'],
|
|
520
|
+
colors.clusterLow,
|
|
521
|
+
10,
|
|
522
|
+
colors.clusterMid,
|
|
523
|
+
50,
|
|
524
|
+
colors.clusterHigh,
|
|
525
|
+
])
|
|
526
|
+
map.setPaintProperty(clusterLayerId, 'circle-stroke-color', colors.clusterStroke)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Update cluster count text color
|
|
530
|
+
if (map.getLayer(clusterCountId)) {
|
|
531
|
+
map.setPaintProperty(clusterCountId, 'text-color', colors.clusterText)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Update unclustered point colors
|
|
535
|
+
if (showUnclustered && map.getLayer(unclusteredLayerId)) {
|
|
536
|
+
map.setPaintProperty(unclusteredLayerId, 'circle-color', colors.point)
|
|
537
|
+
map.setPaintProperty(unclusteredLayerId, 'circle-stroke-color', colors.pointStroke)
|
|
538
|
+
}
|
|
539
|
+
})
|
|
511
540
|
</script>
|
|
@@ -96,17 +96,34 @@
|
|
|
96
96
|
return document.documentElement.classList.contains('dark')
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Track document dark mode for auto style
|
|
100
|
+
let documentIsDark = $state(getIsDarkMode())
|
|
101
|
+
|
|
102
|
+
// Resolved mode: for explicit 'dark'/'light' use that, for 'auto' follow document, for custom objects default to 'dark'
|
|
103
|
+
const resolvedMode = $derived.by((): 'dark' | 'light' => {
|
|
104
|
+
if (typeof style === 'object') {
|
|
105
|
+
return 'dark' // Custom style objects default to dark for UI
|
|
106
|
+
}
|
|
107
|
+
if (style === 'auto') {
|
|
108
|
+
return documentIsDark ? 'dark' : 'light'
|
|
109
|
+
}
|
|
110
|
+
return style // 'dark' or 'light'
|
|
111
|
+
})
|
|
112
|
+
|
|
99
113
|
function getStyle() {
|
|
100
114
|
if (typeof style === 'object') {
|
|
101
115
|
return style
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return mode === 'dark' ? createDarkStyle(tiles, { labels }) : createLightStyle(tiles, { labels })
|
|
118
|
+
return resolvedMode === 'dark' ? createDarkStyle(tiles, { labels }) : createLightStyle(tiles, { labels })
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
const ctx = createMapContext()
|
|
122
|
+
|
|
123
|
+
// Keep context in sync with resolved mode
|
|
124
|
+
$effect(() => {
|
|
125
|
+
ctx.setResolvedMode(resolvedMode)
|
|
126
|
+
})
|
|
110
127
|
const autoClusterPoints = $derived(
|
|
111
128
|
autoCluster
|
|
112
129
|
? Array.from(ctx.markers.values())
|
|
@@ -183,17 +200,22 @@
|
|
|
183
200
|
onzoomCallback?.(mapInstance.getZoom())
|
|
184
201
|
})
|
|
185
202
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
// Only observe document dark class changes when style='auto'
|
|
204
|
+
let observer: MutationObserver | null = null
|
|
205
|
+
if (style === 'auto') {
|
|
206
|
+
observer = new MutationObserver(() => {
|
|
207
|
+
documentIsDark = getIsDarkMode()
|
|
208
|
+
mapInstance.setStyle(getStyle())
|
|
209
|
+
})
|
|
189
210
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
observer.observe(document.documentElement, {
|
|
212
|
+
attributes: true,
|
|
213
|
+
attributeFilter: ['class'],
|
|
214
|
+
})
|
|
215
|
+
}
|
|
194
216
|
|
|
195
217
|
return () => {
|
|
196
|
-
observer
|
|
218
|
+
observer?.disconnect()
|
|
197
219
|
maplibregl.removeProtocol('pmtiles')
|
|
198
220
|
ctx.setMap(null)
|
|
199
221
|
ctx.setLoaded(false)
|
|
@@ -202,7 +224,7 @@
|
|
|
202
224
|
})
|
|
203
225
|
</script>
|
|
204
226
|
|
|
205
|
-
<div bind:this={container} class='shadcn-map {className}'>
|
|
227
|
+
<div bind:this={container} class='shadcn-map {className}' data-map-mode={resolvedMode}>
|
|
206
228
|
{#if loaded && children}
|
|
207
229
|
{#if autoCluster}
|
|
208
230
|
<ClusterLayer
|
|
@@ -267,55 +289,94 @@
|
|
|
267
289
|
box-shadow: none;
|
|
268
290
|
}
|
|
269
291
|
|
|
270
|
-
/* Dark mode theme variables */
|
|
271
|
-
|
|
272
|
-
--map-ui-surface:
|
|
273
|
-
--map-ui-foreground:
|
|
274
|
-
--map-ui-border:
|
|
292
|
+
/* Dark mode theme variables - hardcoded dark colors that don't depend on mode switcher */
|
|
293
|
+
.shadcn-map[data-map-mode='dark'] {
|
|
294
|
+
--map-ui-surface: #27272a;
|
|
295
|
+
--map-ui-foreground: #f4f4f5;
|
|
296
|
+
--map-ui-border: #3f3f46;
|
|
275
297
|
}
|
|
276
298
|
|
|
277
|
-
/*
|
|
278
|
-
|
|
299
|
+
/* Light mode theme variables - hardcoded light colors */
|
|
300
|
+
.shadcn-map[data-map-mode='light'] {
|
|
301
|
+
--map-ui-surface: #ffffff;
|
|
302
|
+
--map-ui-foreground: #18181b;
|
|
303
|
+
--map-ui-border: #e4e4e7;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Navigation control group - dark mode */
|
|
307
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-group) {
|
|
279
308
|
background-color: var(--map-ui-surface);
|
|
280
309
|
border: 1px solid var(--map-ui-border);
|
|
281
310
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
|
|
282
311
|
}
|
|
283
312
|
|
|
284
|
-
|
|
313
|
+
/* Navigation control group - light mode */
|
|
314
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-group) {
|
|
315
|
+
background-color: var(--map-ui-surface);
|
|
316
|
+
border: 1px solid var(--map-ui-border);
|
|
317
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-group button) {
|
|
321
|
+
background-color: var(--map-ui-surface);
|
|
322
|
+
color: var(--map-ui-foreground);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-group button) {
|
|
285
326
|
background-color: var(--map-ui-surface);
|
|
286
327
|
color: var(--map-ui-foreground);
|
|
287
328
|
}
|
|
288
329
|
|
|
289
|
-
|
|
330
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-group button + button) {
|
|
290
331
|
border-top-color: var(--map-ui-border);
|
|
291
332
|
}
|
|
292
333
|
|
|
293
|
-
|
|
334
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-group button + button) {
|
|
335
|
+
border-top-color: var(--map-ui-border);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-icon) {
|
|
294
339
|
filter: invert(1);
|
|
295
340
|
}
|
|
296
341
|
|
|
297
|
-
|
|
342
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-compass .maplibregl-ctrl-icon) {
|
|
298
343
|
filter: invert(1) brightness(1.8) contrast(1.3) drop-shadow(0 0 1px rgba(0, 0, 0, 0.6));
|
|
299
344
|
}
|
|
300
345
|
|
|
301
|
-
/* Scale control dark mode */
|
|
302
|
-
|
|
346
|
+
/* Scale control - dark mode */
|
|
347
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-scale) {
|
|
303
348
|
color: var(--map-ui-foreground);
|
|
304
349
|
border-color: var(--map-ui-foreground);
|
|
305
350
|
}
|
|
306
351
|
|
|
307
|
-
/*
|
|
308
|
-
|
|
352
|
+
/* Scale control - light mode */
|
|
353
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-scale) {
|
|
309
354
|
color: var(--map-ui-foreground);
|
|
355
|
+
border-color: var(--map-ui-foreground);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Attribution text panel - dark mode */
|
|
359
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-attrib) {
|
|
360
|
+
color: var(--map-ui-foreground);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* Attribution text panel - light mode */
|
|
364
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-attrib) {
|
|
365
|
+
color: var(--map-ui-foreground);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-attrib.maplibregl-compact-show) {
|
|
369
|
+
background-color: var(--map-ui-surface);
|
|
370
|
+
border-radius: 4px;
|
|
310
371
|
}
|
|
311
372
|
|
|
312
|
-
|
|
373
|
+
.shadcn-map[data-map-mode='light'] :global(.maplibregl-ctrl-attrib.maplibregl-compact-show) {
|
|
313
374
|
background-color: var(--map-ui-surface);
|
|
314
375
|
border-radius: 4px;
|
|
315
376
|
}
|
|
316
377
|
|
|
317
378
|
/* Attribution button - invert icon in dark mode */
|
|
318
|
-
|
|
379
|
+
.shadcn-map[data-map-mode='dark'] :global(.maplibregl-ctrl-attrib-button) {
|
|
319
380
|
filter: invert(1);
|
|
320
381
|
}
|
|
321
382
|
</style>
|
|
@@ -302,6 +302,7 @@
|
|
|
302
302
|
class:popup-open={anyPopupOpen}
|
|
303
303
|
class:marker-active={isActive}
|
|
304
304
|
data-color-mode={isThemeColor ? 'theme' : 'class'}
|
|
305
|
+
data-map-mode={ctx.resolvedMode}
|
|
305
306
|
style:--marker-color={isThemeColor ? markerColorVar : 'currentColor'}
|
|
306
307
|
style:--marker-text={isThemeColor ? markerTextVar : undefined}
|
|
307
308
|
style:--marker-width='{sizeConfig.width}px'
|
|
@@ -395,14 +396,21 @@
|
|
|
395
396
|
.shadcn-marker[data-color-mode='theme'] .marker-inner {
|
|
396
397
|
background: oklch(var(--marker-color));
|
|
397
398
|
color: oklch(var(--marker-text));
|
|
398
|
-
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.shadcn-marker[data-color-mode='theme'][data-map-mode='dark'] .marker-inner {
|
|
402
|
+
border-color: #3f3f46;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.shadcn-marker[data-color-mode='theme'][data-map-mode='light'] .marker-inner {
|
|
406
|
+
border-color: #e4e4e7;
|
|
399
407
|
}
|
|
400
408
|
|
|
401
409
|
.shadcn-marker[data-color-mode='class'] .marker-inner {
|
|
402
410
|
border-color: rgba(0, 0, 0, 0.25);
|
|
403
411
|
}
|
|
404
412
|
|
|
405
|
-
|
|
413
|
+
.shadcn-marker[data-color-mode='class'][data-map-mode='dark'] .marker-inner {
|
|
406
414
|
border-color: rgba(255, 255, 255, 0.25);
|
|
407
415
|
}
|
|
408
416
|
|
|
@@ -528,7 +536,7 @@
|
|
|
528
536
|
justify-content: center;
|
|
529
537
|
}
|
|
530
538
|
|
|
531
|
-
|
|
539
|
+
.shadcn-marker[data-map-mode='dark'] .marker-badge {
|
|
532
540
|
border-color: rgba(0, 0, 0, 0.3);
|
|
533
541
|
}
|
|
534
542
|
|
|
@@ -581,13 +589,13 @@
|
|
|
581
589
|
0 6px 20px rgba(0, 0, 0, 0.35);
|
|
582
590
|
}
|
|
583
591
|
|
|
584
|
-
|
|
592
|
+
.shadcn-marker[data-map-mode='dark'] .fallback-ring {
|
|
585
593
|
box-shadow:
|
|
586
594
|
0 0 0 4px rgba(50, 50, 50, 0.9),
|
|
587
595
|
0 6px 20px rgba(0, 0, 0, 0.35);
|
|
588
596
|
}
|
|
589
597
|
|
|
590
|
-
|
|
598
|
+
.shadcn-marker[data-map-mode='dark'].has-label::after {
|
|
591
599
|
background: hsl(240 5.9% 90%);
|
|
592
600
|
color: hsl(240 10% 3.9%);
|
|
593
601
|
}
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
/* Dark mode: override the filter: invert(1) from Map.svelte since we use inline SVG with currentColor.
|
|
136
136
|
* Also explicitly set color so the SVG stroke inherits the light foreground. */
|
|
137
|
-
:global(.
|
|
137
|
+
:global(.shadcn-map[data-map-mode='dark'] .shadcn-geolocate-btn.maplibregl-ctrl-icon) {
|
|
138
138
|
filter: none !important;
|
|
139
139
|
color: var(--map-ui-foreground);
|
|
140
140
|
}
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface MapContextStore {
|
|
|
21
21
|
readonly clusteredMarkerIds: Set<string>;
|
|
22
22
|
readonly clusteredVersion: number;
|
|
23
23
|
readonly activePopupMarkerId: string | null;
|
|
24
|
+
/** The resolved theme mode for the map ('dark' or 'light') */
|
|
25
|
+
readonly resolvedMode: 'dark' | 'light';
|
|
24
26
|
setMap: (map: maplibregl.Map | null) => void;
|
|
25
27
|
setLoaded: (loaded: boolean) => void;
|
|
26
28
|
registerMarker: (registration: MarkerRegistration) => void;
|
|
@@ -28,6 +30,7 @@ export interface MapContextStore {
|
|
|
28
30
|
unregisterMarker: (id: string) => void;
|
|
29
31
|
setClusteredMarkers: (ids: Set<string>) => void;
|
|
30
32
|
setActivePopupMarker: (id: string | null) => void;
|
|
33
|
+
setResolvedMode: (mode: 'dark' | 'light') => void;
|
|
31
34
|
flyTo: (lngLat: [number, number], options?: FlyToOptions) => void;
|
|
32
35
|
getBounds: () => LngLatBounds | null;
|
|
33
36
|
getCenter: () => [number, number] | null;
|
package/dist/context.svelte.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getContext, setContext } from 'svelte';
|
|
1
|
+
import { getContext, setContext, untrack } from 'svelte';
|
|
2
2
|
const MAP_CONTEXT_KEY = Symbol('shadcn-map-context');
|
|
3
3
|
export function createMapContext() {
|
|
4
4
|
let map = $state(null);
|
|
@@ -7,6 +7,7 @@ export function createMapContext() {
|
|
|
7
7
|
let clusteredMarkerIds = $state(new Set());
|
|
8
8
|
let clusteredVersion = $state(0);
|
|
9
9
|
let activePopupMarkerId = $state(null);
|
|
10
|
+
let resolvedMode = $state('dark');
|
|
10
11
|
const store = {
|
|
11
12
|
get map() { return map; },
|
|
12
13
|
get loaded() { return loaded; },
|
|
@@ -14,8 +15,10 @@ export function createMapContext() {
|
|
|
14
15
|
get clusteredMarkerIds() { return clusteredMarkerIds; },
|
|
15
16
|
get clusteredVersion() { return clusteredVersion; },
|
|
16
17
|
get activePopupMarkerId() { return activePopupMarkerId; },
|
|
18
|
+
get resolvedMode() { return resolvedMode; },
|
|
17
19
|
setMap: (m) => { map = m; },
|
|
18
20
|
setLoaded: (l) => { loaded = l; },
|
|
21
|
+
setResolvedMode: (m) => { resolvedMode = m; },
|
|
19
22
|
registerMarker: (registration) => {
|
|
20
23
|
const next = new Map(markers);
|
|
21
24
|
next.set(registration.id, registration);
|
|
@@ -44,7 +47,10 @@ export function createMapContext() {
|
|
|
44
47
|
},
|
|
45
48
|
setClusteredMarkers: (ids) => {
|
|
46
49
|
clusteredMarkerIds = new Set(ids);
|
|
47
|
-
clusteredVersion
|
|
50
|
+
// IMPORTANT: avoid tracking `clusteredVersion` reads in callers (e.g. $effect)
|
|
51
|
+
// otherwise an effect that calls `setClusteredMarkers` can accidentally
|
|
52
|
+
// become dependent on `clusteredVersion` and re-run forever.
|
|
53
|
+
clusteredVersion = untrack(() => clusteredVersion) + 1;
|
|
48
54
|
},
|
|
49
55
|
setActivePopupMarker: (id) => {
|
|
50
56
|
activePopupMarkerId = id;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shadcn-map",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "A minimal, fast, dark map library for Svelte 5 built on MapLibre GL and shadcn-svelte aesthetics.",
|
|
6
6
|
"author": "Hegyi Áron Ferenc",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"files": [
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"README.md",
|
|
40
42
|
"dist"
|
|
41
43
|
],
|
|
42
44
|
"scripts": {
|