shadcn-map 0.1.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.
- package/dist/components/ClusterLayer.svelte +511 -0
- package/dist/components/ClusterLayer.svelte.d.ts +29 -0
- package/dist/components/DetailsPanel.svelte +180 -0
- package/dist/components/DetailsPanel.svelte.d.ts +18 -0
- package/dist/components/Map.svelte +321 -0
- package/dist/components/Map.svelte.d.ts +63 -0
- package/dist/components/Marker.svelte +594 -0
- package/dist/components/Marker.svelte.d.ts +34 -0
- package/dist/components/Popup.svelte +228 -0
- package/dist/components/Popup.svelte.d.ts +18 -0
- package/dist/components/controls/GeolocateControl.svelte +141 -0
- package/dist/components/controls/GeolocateControl.svelte.d.ts +22 -0
- package/dist/components/controls/NavigationControl.svelte +50 -0
- package/dist/components/controls/NavigationControl.svelte.d.ts +12 -0
- package/dist/components/controls/ScaleControl.svelte +49 -0
- package/dist/components/controls/ScaleControl.svelte.d.ts +12 -0
- package/dist/components/controls/index.d.ts +6 -0
- package/dist/components/controls/index.js +3 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.js +7 -0
- package/dist/context.svelte.d.ts +37 -0
- package/dist/context.svelte.js +96 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/styles/colors.d.ts +54 -0
- package/dist/styles/colors.js +53 -0
- package/dist/styles/dark.d.ts +7 -0
- package/dist/styles/dark.js +168 -0
- package/dist/styles/index.d.ts +3 -0
- package/dist/styles/index.js +3 -0
- package/dist/styles/light.d.ts +7 -0
- package/dist/styles/light.js +168 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +34 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<script lang='ts' module>
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
|
|
4
|
+
export interface PopupProps {
|
|
5
|
+
/** Popup position [lng, lat] */
|
|
6
|
+
lngLat: [number, number]
|
|
7
|
+
/** Whether popup is visible */
|
|
8
|
+
open?: boolean
|
|
9
|
+
/** Close callback */
|
|
10
|
+
onclose?: () => void
|
|
11
|
+
/** Additional CSS classes */
|
|
12
|
+
class?: string
|
|
13
|
+
/** Offset from anchor point (number = uniform, array = [x, y], or 'auto' for auto-detected) */
|
|
14
|
+
offset?: number | [number, number] | 'auto'
|
|
15
|
+
/** Children */
|
|
16
|
+
children?: Snippet
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang='ts'>
|
|
21
|
+
import maplibregl from 'maplibre-gl'
|
|
22
|
+
import { onMount } from 'svelte'
|
|
23
|
+
import { getMapContext } from '../context.svelte'
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
lngLat,
|
|
27
|
+
open = true,
|
|
28
|
+
onclose,
|
|
29
|
+
class: className = '',
|
|
30
|
+
offset = 'auto',
|
|
31
|
+
children,
|
|
32
|
+
}: PopupProps = $props()
|
|
33
|
+
|
|
34
|
+
const ctx = getMapContext()
|
|
35
|
+
|
|
36
|
+
let popup: maplibregl.Popup | null = null
|
|
37
|
+
let contentElement: HTMLDivElement
|
|
38
|
+
let programmaticClose = false
|
|
39
|
+
let lastActiveMarkerId: string | null = null
|
|
40
|
+
|
|
41
|
+
const sizeOffsets: Record<'sm' | 'md' | 'lg', [number, number]> = {
|
|
42
|
+
sm: [0, -12],
|
|
43
|
+
md: [0, -16],
|
|
44
|
+
lg: [0, -20],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const markerAtLocation = $derived.by(() => {
|
|
48
|
+
for (const marker of ctx.markers.values()) {
|
|
49
|
+
const lngDiff = Math.abs(marker.lngLat[0] - lngLat[0])
|
|
50
|
+
const latDiff = Math.abs(marker.lngLat[1] - lngLat[1])
|
|
51
|
+
if (lngDiff < 0.000001 && latDiff < 0.000001) {
|
|
52
|
+
return marker
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const detectedMarkerSize = $derived(markerAtLocation?.size ?? 'md')
|
|
59
|
+
|
|
60
|
+
const computedOffset = $derived.by((): number | [number, number] => {
|
|
61
|
+
if (offset !== 'auto') {
|
|
62
|
+
return offset
|
|
63
|
+
}
|
|
64
|
+
return sizeOffsets[detectedMarkerSize]
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
function handleClose() {
|
|
68
|
+
if (!programmaticClose) {
|
|
69
|
+
ctx.setActivePopupMarker(null)
|
|
70
|
+
onclose?.()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function closePopup() {
|
|
75
|
+
if (!popup) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
programmaticClose = true
|
|
79
|
+
popup.remove()
|
|
80
|
+
queueMicrotask(() => {
|
|
81
|
+
programmaticClose = false
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMount(() => {
|
|
86
|
+
const map = ctx.map
|
|
87
|
+
if (!map || !contentElement) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
popup = new maplibregl.Popup({
|
|
92
|
+
closeButton: true,
|
|
93
|
+
closeOnClick: true,
|
|
94
|
+
offset: computedOffset,
|
|
95
|
+
anchor: 'bottom',
|
|
96
|
+
className: 'shadcn-map-popup',
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
popup.setDOMContent(contentElement)
|
|
100
|
+
popup.setLngLat(lngLat)
|
|
101
|
+
popup.on('close', handleClose)
|
|
102
|
+
|
|
103
|
+
if (open) {
|
|
104
|
+
popup.addTo(map)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
// If this popup is being unmounted while open, MapLibre won't necessarily emit a "close"
|
|
109
|
+
// event (we detach the listener before remove). Ensure the marker active state is cleared.
|
|
110
|
+
if (lastActiveMarkerId && ctx.activePopupMarkerId === lastActiveMarkerId) {
|
|
111
|
+
ctx.setActivePopupMarker(null)
|
|
112
|
+
}
|
|
113
|
+
popup?.off('close', handleClose)
|
|
114
|
+
popup?.remove()
|
|
115
|
+
popup = null
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
$effect(() => {
|
|
120
|
+
if (popup) {
|
|
121
|
+
popup.setLngLat(lngLat)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
$effect(() => {
|
|
126
|
+
if (popup) {
|
|
127
|
+
popup.setOffset(computedOffset)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
$effect(() => {
|
|
132
|
+
const map = ctx.map
|
|
133
|
+
if (!popup || !map) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (open) {
|
|
138
|
+
if (!popup.isOpen()) {
|
|
139
|
+
popup.addTo(map)
|
|
140
|
+
}
|
|
141
|
+
if (markerAtLocation) {
|
|
142
|
+
ctx.setActivePopupMarker(markerAtLocation.id)
|
|
143
|
+
lastActiveMarkerId = markerAtLocation.id
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (popup.isOpen()) {
|
|
147
|
+
closePopup()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
$effect(() => {
|
|
152
|
+
if (!open) {
|
|
153
|
+
if (markerAtLocation && ctx.activePopupMarkerId === markerAtLocation.id) {
|
|
154
|
+
ctx.setActivePopupMarker(null)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<div bind:this={contentElement} class='shadcn-popup-content {className}'>
|
|
161
|
+
{#if children}
|
|
162
|
+
{@render children()}
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<style>
|
|
167
|
+
.shadcn-popup-content {
|
|
168
|
+
display: grid;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
:global(.shadcn-map-popup) {
|
|
173
|
+
z-index: 20 !important;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
:global(.shadcn-map-popup .maplibregl-popup-content) {
|
|
177
|
+
background: oklch(var(--card));
|
|
178
|
+
color: oklch(var(--card-foreground));
|
|
179
|
+
border: 1px solid oklch(var(--border));
|
|
180
|
+
border-radius: 10px;
|
|
181
|
+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
|
|
182
|
+
padding: 10px 12px;
|
|
183
|
+
font-size: 13px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
:global(.shadcn-map-popup .maplibregl-popup-close-button) {
|
|
187
|
+
color: oklch(var(--muted-foreground));
|
|
188
|
+
font-size: 16px;
|
|
189
|
+
padding: 2px 6px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
:global(.shadcn-map-popup .maplibregl-popup-close-button:hover) {
|
|
193
|
+
background: oklch(var(--muted));
|
|
194
|
+
color: oklch(var(--foreground));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip) {
|
|
198
|
+
border-top-color: oklch(var(--card));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip) {
|
|
202
|
+
border-bottom-color: oklch(var(--card));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip) {
|
|
206
|
+
border-right-color: oklch(var(--card));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip) {
|
|
210
|
+
border-left-color: oklch(var(--card));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip) {
|
|
214
|
+
border-top-color: oklch(var(--card));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip) {
|
|
218
|
+
border-top-color: oklch(var(--card));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip) {
|
|
222
|
+
border-bottom-color: oklch(var(--card));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
:global(.shadcn-map-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip) {
|
|
226
|
+
border-bottom-color: oklch(var(--card));
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface PopupProps {
|
|
3
|
+
/** Popup position [lng, lat] */
|
|
4
|
+
lngLat: [number, number];
|
|
5
|
+
/** Whether popup is visible */
|
|
6
|
+
open?: boolean;
|
|
7
|
+
/** Close callback */
|
|
8
|
+
onclose?: () => void;
|
|
9
|
+
/** Additional CSS classes */
|
|
10
|
+
class?: string;
|
|
11
|
+
/** Offset from anchor point (number = uniform, array = [x, y], or 'auto' for auto-detected) */
|
|
12
|
+
offset?: number | [number, number] | 'auto';
|
|
13
|
+
/** Children */
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const Popup: import("svelte").Component<PopupProps, {}, "">;
|
|
17
|
+
type Popup = ReturnType<typeof Popup>;
|
|
18
|
+
export default Popup;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang='ts' module>
|
|
2
|
+
import type { ControlPosition } from '../../types'
|
|
3
|
+
|
|
4
|
+
export interface GeolocateControlProps {
|
|
5
|
+
/** Control position on map */
|
|
6
|
+
position?: ControlPosition
|
|
7
|
+
/** Zoom level to fly to when locating */
|
|
8
|
+
zoom?: number
|
|
9
|
+
/** High accuracy geolocation (may be slower / use more battery) */
|
|
10
|
+
enableHighAccuracy?: boolean
|
|
11
|
+
/** Maximum cached position age in ms */
|
|
12
|
+
maximumAge?: number
|
|
13
|
+
/** Timeout in ms */
|
|
14
|
+
timeout?: number
|
|
15
|
+
/** Called with found coordinates */
|
|
16
|
+
onlocate?: (lngLat: [number, number]) => void
|
|
17
|
+
/** Called when geolocation fails */
|
|
18
|
+
onerror?: (error: GeolocationPositionError | Error) => void
|
|
19
|
+
/** Accessible label */
|
|
20
|
+
ariaLabel?: string
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script lang='ts'>
|
|
25
|
+
import type { Map as MapLibreMap } from 'maplibre-gl'
|
|
26
|
+
import { onMount } from 'svelte'
|
|
27
|
+
import { getMapContext } from '../../context.svelte'
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
position = 'bottom-right',
|
|
31
|
+
zoom = 14,
|
|
32
|
+
enableHighAccuracy = true,
|
|
33
|
+
maximumAge = 30_000,
|
|
34
|
+
timeout = 10_000,
|
|
35
|
+
onlocate,
|
|
36
|
+
onerror,
|
|
37
|
+
ariaLabel = 'Go to my location',
|
|
38
|
+
}: GeolocateControlProps = $props()
|
|
39
|
+
|
|
40
|
+
const ctx = getMapContext()
|
|
41
|
+
|
|
42
|
+
type ControlLike = {
|
|
43
|
+
onAdd(map: MapLibreMap): HTMLElement
|
|
44
|
+
onRemove(map: MapLibreMap): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let control: ControlLike | null = null
|
|
48
|
+
let mountedMap: MapLibreMap | null = null
|
|
49
|
+
|
|
50
|
+
function handleClick() {
|
|
51
|
+
if (typeof navigator === 'undefined' || !navigator.geolocation) {
|
|
52
|
+
onerror?.(new Error('Geolocation is not available in this environment.'))
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
navigator.geolocation.getCurrentPosition(
|
|
57
|
+
(pos) => {
|
|
58
|
+
const lngLat: [number, number] = [pos.coords.longitude, pos.coords.latitude]
|
|
59
|
+
ctx.flyTo(lngLat, { zoom })
|
|
60
|
+
onlocate?.(lngLat)
|
|
61
|
+
},
|
|
62
|
+
(err) => {
|
|
63
|
+
onerror?.(err)
|
|
64
|
+
},
|
|
65
|
+
{ enableHighAccuracy, maximumAge, timeout },
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onMount(() => {
|
|
70
|
+
const map = ctx.map
|
|
71
|
+
if (!map) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
control = {
|
|
76
|
+
onAdd() {
|
|
77
|
+
const container = document.createElement('div')
|
|
78
|
+
container.className = 'maplibregl-ctrl maplibregl-ctrl-group shadcn-geolocate'
|
|
79
|
+
|
|
80
|
+
const button = document.createElement('button')
|
|
81
|
+
button.type = 'button'
|
|
82
|
+
button.className = 'maplibregl-ctrl-icon shadcn-geolocate-btn'
|
|
83
|
+
button.setAttribute('aria-label', ariaLabel)
|
|
84
|
+
|
|
85
|
+
// Simple target icon (SVG) so we don't require icon libraries.
|
|
86
|
+
button.innerHTML = `
|
|
87
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
88
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
89
|
+
<circle cx="12" cy="12" r="7"></circle>
|
|
90
|
+
<circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"></circle>
|
|
91
|
+
<path d="M12 2v3"></path>
|
|
92
|
+
<path d="M12 19v3"></path>
|
|
93
|
+
<path d="M2 12h3"></path>
|
|
94
|
+
<path d="M19 12h3"></path>
|
|
95
|
+
</svg>
|
|
96
|
+
`
|
|
97
|
+
|
|
98
|
+
button.addEventListener('click', handleClick)
|
|
99
|
+
container.appendChild(button)
|
|
100
|
+
return container
|
|
101
|
+
},
|
|
102
|
+
onRemove(mapInstance) {
|
|
103
|
+
// MapLibre removes the container element automatically.
|
|
104
|
+
void mapInstance
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mountedMap = map
|
|
109
|
+
map.addControl(control as any, position)
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
if (control && mountedMap) {
|
|
113
|
+
mountedMap.removeControl(control as any)
|
|
114
|
+
}
|
|
115
|
+
control = null
|
|
116
|
+
mountedMap = null
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<style>
|
|
122
|
+
/* Geolocate button base styles */
|
|
123
|
+
:global(.shadcn-map .shadcn-geolocate-btn) {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: center;
|
|
127
|
+
background-image: none !important;
|
|
128
|
+
background-color: transparent !important;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
:global(.shadcn-map .shadcn-geolocate-btn svg) {
|
|
132
|
+
display: block;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Dark mode: override the filter: invert(1) from Map.svelte since we use inline SVG with currentColor.
|
|
136
|
+
* Also explicitly set color so the SVG stroke inherits the light foreground. */
|
|
137
|
+
:global(.dark .shadcn-map .shadcn-geolocate-btn.maplibregl-ctrl-icon) {
|
|
138
|
+
filter: none !important;
|
|
139
|
+
color: var(--map-ui-foreground);
|
|
140
|
+
}
|
|
141
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ControlPosition } from '../../types';
|
|
2
|
+
export interface GeolocateControlProps {
|
|
3
|
+
/** Control position on map */
|
|
4
|
+
position?: ControlPosition;
|
|
5
|
+
/** Zoom level to fly to when locating */
|
|
6
|
+
zoom?: number;
|
|
7
|
+
/** High accuracy geolocation (may be slower / use more battery) */
|
|
8
|
+
enableHighAccuracy?: boolean;
|
|
9
|
+
/** Maximum cached position age in ms */
|
|
10
|
+
maximumAge?: number;
|
|
11
|
+
/** Timeout in ms */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** Called with found coordinates */
|
|
14
|
+
onlocate?: (lngLat: [number, number]) => void;
|
|
15
|
+
/** Called when geolocation fails */
|
|
16
|
+
onerror?: (error: GeolocationPositionError | Error) => void;
|
|
17
|
+
/** Accessible label */
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const GeolocateControl: import("svelte").Component<GeolocateControlProps, {}, "">;
|
|
21
|
+
type GeolocateControl = ReturnType<typeof GeolocateControl>;
|
|
22
|
+
export default GeolocateControl;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang='ts' module>
|
|
2
|
+
import type { ControlPosition } from '../../types'
|
|
3
|
+
|
|
4
|
+
export interface NavigationControlProps {
|
|
5
|
+
/** Control position on map */
|
|
6
|
+
position?: ControlPosition
|
|
7
|
+
/** Show compass */
|
|
8
|
+
showCompass?: boolean
|
|
9
|
+
/** Show zoom buttons */
|
|
10
|
+
showZoom?: boolean
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script lang='ts'>
|
|
15
|
+
import maplibregl from 'maplibre-gl'
|
|
16
|
+
import { onMount } from 'svelte'
|
|
17
|
+
import { getMapContext } from '../../context.svelte'
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
position = 'top-right',
|
|
21
|
+
showCompass = true,
|
|
22
|
+
showZoom = true,
|
|
23
|
+
}: NavigationControlProps = $props()
|
|
24
|
+
|
|
25
|
+
const ctx = getMapContext()
|
|
26
|
+
|
|
27
|
+
let control: maplibregl.NavigationControl | null = null
|
|
28
|
+
|
|
29
|
+
onMount(() => {
|
|
30
|
+
const map = ctx.map
|
|
31
|
+
if (!map)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
control = new maplibregl.NavigationControl({
|
|
35
|
+
showCompass,
|
|
36
|
+
showZoom,
|
|
37
|
+
visualizePitch: true,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
map.addControl(control, position)
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
const currentMap = ctx.map
|
|
44
|
+
if (control && currentMap) {
|
|
45
|
+
currentMap.removeControl(control)
|
|
46
|
+
}
|
|
47
|
+
control = null
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ControlPosition } from '../../types';
|
|
2
|
+
export interface NavigationControlProps {
|
|
3
|
+
/** Control position on map */
|
|
4
|
+
position?: ControlPosition;
|
|
5
|
+
/** Show compass */
|
|
6
|
+
showCompass?: boolean;
|
|
7
|
+
/** Show zoom buttons */
|
|
8
|
+
showZoom?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const NavigationControl: import("svelte").Component<NavigationControlProps, {}, "">;
|
|
11
|
+
type NavigationControl = ReturnType<typeof NavigationControl>;
|
|
12
|
+
export default NavigationControl;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang='ts' module>
|
|
2
|
+
import type { ControlPosition } from '../../types'
|
|
3
|
+
|
|
4
|
+
export interface ScaleControlProps {
|
|
5
|
+
/** Control position on map */
|
|
6
|
+
position?: ControlPosition
|
|
7
|
+
/** Unit of measurement */
|
|
8
|
+
unit?: 'imperial' | 'metric' | 'nautical'
|
|
9
|
+
/** Maximum width in pixels */
|
|
10
|
+
maxWidth?: number
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script lang='ts'>
|
|
15
|
+
import maplibregl from 'maplibre-gl'
|
|
16
|
+
import { onMount } from 'svelte'
|
|
17
|
+
import { getMapContext } from '../../context.svelte'
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
position = 'bottom-left',
|
|
21
|
+
unit = 'metric',
|
|
22
|
+
maxWidth = 100,
|
|
23
|
+
}: ScaleControlProps = $props()
|
|
24
|
+
|
|
25
|
+
const ctx = getMapContext()
|
|
26
|
+
|
|
27
|
+
let control: maplibregl.ScaleControl | null = null
|
|
28
|
+
|
|
29
|
+
onMount(() => {
|
|
30
|
+
const map = ctx.map
|
|
31
|
+
if (!map)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
control = new maplibregl.ScaleControl({
|
|
35
|
+
maxWidth,
|
|
36
|
+
unit,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
map.addControl(control, position)
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
const currentMap = ctx.map
|
|
43
|
+
if (control && currentMap) {
|
|
44
|
+
currentMap.removeControl(control)
|
|
45
|
+
}
|
|
46
|
+
control = null
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ControlPosition } from '../../types';
|
|
2
|
+
export interface ScaleControlProps {
|
|
3
|
+
/** Control position on map */
|
|
4
|
+
position?: ControlPosition;
|
|
5
|
+
/** Unit of measurement */
|
|
6
|
+
unit?: 'imperial' | 'metric' | 'nautical';
|
|
7
|
+
/** Maximum width in pixels */
|
|
8
|
+
maxWidth?: number;
|
|
9
|
+
}
|
|
10
|
+
declare const ScaleControl: import("svelte").Component<ScaleControlProps, {}, "">;
|
|
11
|
+
type ScaleControl = ReturnType<typeof ScaleControl>;
|
|
12
|
+
export default ScaleControl;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as GeolocateControl } from './GeolocateControl.svelte';
|
|
2
|
+
export type { GeolocateControlProps } from './GeolocateControl.svelte';
|
|
3
|
+
export { default as NavigationControl } from './NavigationControl.svelte';
|
|
4
|
+
export type { NavigationControlProps } from './NavigationControl.svelte';
|
|
5
|
+
export { default as ScaleControl } from './ScaleControl.svelte';
|
|
6
|
+
export type { ScaleControlProps } from './ScaleControl.svelte';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as ClusterLayer } from './ClusterLayer.svelte';
|
|
2
|
+
export type { ClusterLayerProps, ClusterPoint } from './ClusterLayer.svelte';
|
|
3
|
+
export { NavigationControl, ScaleControl } from './controls';
|
|
4
|
+
export type { GeolocateControlProps, NavigationControlProps, ScaleControlProps } from './controls';
|
|
5
|
+
export { GeolocateControl } from './controls';
|
|
6
|
+
export { default as DetailsPanel } from './DetailsPanel.svelte';
|
|
7
|
+
export type { DetailsPanelProps } from './DetailsPanel.svelte';
|
|
8
|
+
export { default as Map } from './Map.svelte';
|
|
9
|
+
export type { MapProps } from './Map.svelte';
|
|
10
|
+
export { default as Marker } from './Marker.svelte';
|
|
11
|
+
export type { MarkerProps } from './Marker.svelte';
|
|
12
|
+
export { default as Popup } from './Popup.svelte';
|
|
13
|
+
export type { PopupProps } from './Popup.svelte';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as ClusterLayer } from './ClusterLayer.svelte';
|
|
2
|
+
export { NavigationControl, ScaleControl } from './controls';
|
|
3
|
+
export { GeolocateControl } from './controls';
|
|
4
|
+
export { default as DetailsPanel } from './DetailsPanel.svelte';
|
|
5
|
+
export { default as Map } from './Map.svelte';
|
|
6
|
+
export { default as Marker } from './Marker.svelte';
|
|
7
|
+
export { default as Popup } from './Popup.svelte';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type maplibregl from 'maplibre-gl';
|
|
2
|
+
export interface MarkerRegistration {
|
|
3
|
+
id: string;
|
|
4
|
+
lngLat: [number, number];
|
|
5
|
+
clusterable: boolean;
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
}
|
|
8
|
+
export interface FlyToOptions {
|
|
9
|
+
zoom?: number;
|
|
10
|
+
duration?: number;
|
|
11
|
+
easing?: (t: number) => number;
|
|
12
|
+
}
|
|
13
|
+
export interface LngLatBounds {
|
|
14
|
+
sw: [number, number];
|
|
15
|
+
ne: [number, number];
|
|
16
|
+
}
|
|
17
|
+
export interface MapContextStore {
|
|
18
|
+
readonly map: maplibregl.Map | null;
|
|
19
|
+
readonly loaded: boolean;
|
|
20
|
+
readonly markers: Map<string, MarkerRegistration>;
|
|
21
|
+
readonly clusteredMarkerIds: Set<string>;
|
|
22
|
+
readonly clusteredVersion: number;
|
|
23
|
+
readonly activePopupMarkerId: string | null;
|
|
24
|
+
setMap: (map: maplibregl.Map | null) => void;
|
|
25
|
+
setLoaded: (loaded: boolean) => void;
|
|
26
|
+
registerMarker: (registration: MarkerRegistration) => void;
|
|
27
|
+
updateMarker: (id: string, updates: Partial<MarkerRegistration>) => void;
|
|
28
|
+
unregisterMarker: (id: string) => void;
|
|
29
|
+
setClusteredMarkers: (ids: Set<string>) => void;
|
|
30
|
+
setActivePopupMarker: (id: string | null) => void;
|
|
31
|
+
flyTo: (lngLat: [number, number], options?: FlyToOptions) => void;
|
|
32
|
+
getBounds: () => LngLatBounds | null;
|
|
33
|
+
getCenter: () => [number, number] | null;
|
|
34
|
+
getZoom: () => number | null;
|
|
35
|
+
}
|
|
36
|
+
export declare function createMapContext(): MapContextStore;
|
|
37
|
+
export declare function getMapContext(): MapContextStore;
|