@djangocfg/ui-tools 2.1.110 → 2.1.112
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/README.md +242 -49
- package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
- package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
- package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
- package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
- package/dist/JsonTree-6RYAOPSS.mjs +4 -0
- package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
- package/dist/JsonTree-7OH6CIHT.cjs +10 -0
- package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
- package/dist/MapContainer-GXQLP5WY.mjs +214 -0
- package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
- package/dist/MapContainer-RYG4HPH4.cjs +221 -0
- package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
- package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
- package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
- package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
- package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
- package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
- package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
- package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
- package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
- package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
- package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
- package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
- package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
- package/dist/chunk-2DSR7V2L.mjs +561 -0
- package/dist/chunk-2DSR7V2L.mjs.map +1 -0
- package/dist/chunk-47T5ECYV.cjs +1357 -0
- package/dist/chunk-47T5ECYV.cjs.map +1 -0
- package/dist/chunk-5QT3QYFZ.cjs +189 -0
- package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
- package/dist/chunk-7IIRYG4S.mjs +1057 -0
- package/dist/chunk-7IIRYG4S.mjs.map +1 -0
- package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
- package/dist/chunk-DI3HUXHK.cjs.map +1 -0
- package/dist/chunk-EVGWYASL.cjs +1528 -0
- package/dist/chunk-EVGWYASL.cjs.map +1 -0
- package/dist/chunk-F2N7P5XU.cjs +30 -0
- package/dist/chunk-F2N7P5XU.cjs.map +1 -0
- package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
- package/dist/chunk-G6PRZP5I.mjs.map +1 -0
- package/dist/chunk-JWB2EWQO.mjs +5 -0
- package/dist/chunk-JWB2EWQO.mjs.map +1 -0
- package/dist/chunk-LTJX2JXE.mjs +338 -0
- package/dist/chunk-LTJX2JXE.mjs.map +1 -0
- package/dist/chunk-OVNC4KW6.mjs +1494 -0
- package/dist/chunk-OVNC4KW6.mjs.map +1 -0
- package/dist/chunk-PNZSJN6T.cjs +1086 -0
- package/dist/chunk-PNZSJN6T.cjs.map +1 -0
- package/dist/chunk-TEFRA7GW.cjs +565 -0
- package/dist/chunk-TEFRA7GW.cjs.map +1 -0
- package/dist/chunk-UOMPPIED.mjs +1343 -0
- package/dist/chunk-UOMPPIED.mjs.map +1 -0
- package/dist/chunk-W6YHQI4F.mjs +187 -0
- package/dist/chunk-W6YHQI4F.mjs.map +1 -0
- package/dist/chunk-XTBRWVIV.cjs +346 -0
- package/dist/chunk-XTBRWVIV.cjs.map +1 -0
- package/dist/components-C7ZL7OMY.mjs +5 -0
- package/dist/components-C7ZL7OMY.mjs.map +1 -0
- package/dist/components-CJ2IB65O.cjs +27 -0
- package/dist/components-CJ2IB65O.cjs.map +1 -0
- package/dist/components-EASJYK45.mjs +6 -0
- package/dist/components-EASJYK45.mjs.map +1 -0
- package/dist/components-LDRFDV4A.cjs +22 -0
- package/dist/components-LDRFDV4A.cjs.map +1 -0
- package/dist/components-VZKUTDJK.mjs +5 -0
- package/dist/components-VZKUTDJK.mjs.map +1 -0
- package/dist/components-Y64GTIMQ.cjs +42 -0
- package/dist/components-Y64GTIMQ.cjs.map +1 -0
- package/dist/index.cjs +701 -4813
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1274 -1026
- package/dist/index.d.ts +1274 -1026
- package/dist/index.mjs +358 -4730
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -4
- package/src/components/index.ts +17 -0
- package/src/components/lazy-wrapper.tsx +281 -0
- package/src/index.ts +92 -7
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
- package/src/tools/AudioPlayer/lazy.tsx +85 -0
- package/src/tools/Gallery/components/Gallery.tsx +182 -0
- package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
- package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
- package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
- package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
- package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
- package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
- package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
- package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
- package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
- package/src/tools/Gallery/components/index.ts +13 -0
- package/src/tools/Gallery/hooks/index.ts +23 -0
- package/src/tools/Gallery/hooks/useGallery.ts +137 -0
- package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
- package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
- package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
- package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
- package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
- package/src/tools/Gallery/hooks/useZoom.ts +316 -0
- package/src/tools/Gallery/index.ts +66 -0
- package/src/tools/Gallery/types.ts +183 -0
- package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
- package/src/tools/Gallery/utils/index.ts +11 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
- package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
- package/src/tools/ImageViewer/lazy.tsx +37 -0
- package/src/tools/JsonForm/lazy.tsx +43 -0
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
- package/src/tools/JsonTree/lazy.tsx +45 -0
- package/src/tools/LottiePlayer/lazy.tsx +57 -0
- package/src/tools/Map/components/CustomOverlay.tsx +54 -0
- package/src/tools/Map/components/DrawControl.tsx +36 -0
- package/src/tools/Map/components/GeocoderControl.tsx +70 -0
- package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
- package/src/tools/Map/components/MapCluster.tsx +273 -0
- package/src/tools/Map/components/MapContainer.tsx +191 -0
- package/src/tools/Map/components/MapControls.tsx +44 -0
- package/src/tools/Map/components/MapLegend.tsx +161 -0
- package/src/tools/Map/components/MapMarker.tsx +102 -0
- package/src/tools/Map/components/MapPopup.tsx +46 -0
- package/src/tools/Map/components/MapSource.tsx +30 -0
- package/src/tools/Map/components/index.ts +20 -0
- package/src/tools/Map/context/MapContext.tsx +89 -0
- package/src/tools/Map/context/index.ts +2 -0
- package/src/tools/Map/hooks/index.ts +9 -0
- package/src/tools/Map/hooks/useMap.ts +11 -0
- package/src/tools/Map/hooks/useMapControl.ts +99 -0
- package/src/tools/Map/hooks/useMapEvents.ts +147 -0
- package/src/tools/Map/hooks/useMapLayers.ts +83 -0
- package/src/tools/Map/hooks/useMapViewport.ts +62 -0
- package/src/tools/Map/hooks/useMarkers.ts +85 -0
- package/src/tools/Map/index.ts +116 -0
- package/src/tools/Map/layers/cluster.ts +94 -0
- package/src/tools/Map/layers/index.ts +15 -0
- package/src/tools/Map/layers/line.ts +93 -0
- package/src/tools/Map/layers/point.ts +61 -0
- package/src/tools/Map/layers/polygon.ts +73 -0
- package/src/tools/Map/lazy.tsx +56 -0
- package/src/tools/Map/styles/index.ts +15 -0
- package/src/tools/Map/types.ts +259 -0
- package/src/tools/Map/utils/geo.ts +88 -0
- package/src/tools/Map/utils/index.ts +16 -0
- package/src/tools/Map/utils/transform.ts +107 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
- package/src/tools/Mermaid/lazy.tsx +46 -0
- package/src/tools/OpenapiViewer/lazy.tsx +72 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
- package/src/tools/PrettyCode/lazy.tsx +64 -0
- package/src/tools/VideoPlayer/lazy.tsx +63 -0
- package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
- package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
- package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
- package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
- package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
- package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
- package/dist/chunk-FB5QBSI3.cjs.map +0 -1
- package/dist/chunk-L6UHASYQ.mjs.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useCallback } from 'react'
|
|
4
|
+
import { useMapContext } from '../context'
|
|
5
|
+
import type { MapEventHandlers, MapMouseEvent, MapViewport } from '../types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for subscribing to map events
|
|
9
|
+
* Automatically cleans up event listeners on unmount
|
|
10
|
+
*/
|
|
11
|
+
export function useMapEvents(handlers: MapEventHandlers) {
|
|
12
|
+
const { mapRef, setViewport, setHoveredFeature, isLoaded } = useMapContext()
|
|
13
|
+
|
|
14
|
+
const handleClick = useCallback(
|
|
15
|
+
(event: maplibregl.MapMouseEvent & { features?: GeoJSON.Feature[] }) => {
|
|
16
|
+
if (!handlers.onClick) return
|
|
17
|
+
|
|
18
|
+
const mapEvent: MapMouseEvent = {
|
|
19
|
+
lngLat: event.lngLat,
|
|
20
|
+
point: event.point,
|
|
21
|
+
features: event.features,
|
|
22
|
+
originalEvent: event.originalEvent,
|
|
23
|
+
}
|
|
24
|
+
handlers.onClick(mapEvent)
|
|
25
|
+
},
|
|
26
|
+
[handlers]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const handleMouseMove = useCallback(
|
|
30
|
+
(event: maplibregl.MapMouseEvent & { features?: GeoJSON.Feature[] }) => {
|
|
31
|
+
if (!handlers.onHover) return
|
|
32
|
+
|
|
33
|
+
const feature = event.features?.[0] ?? null
|
|
34
|
+
setHoveredFeature(feature)
|
|
35
|
+
|
|
36
|
+
const mapEvent: MapMouseEvent = {
|
|
37
|
+
lngLat: event.lngLat,
|
|
38
|
+
point: event.point,
|
|
39
|
+
features: event.features,
|
|
40
|
+
originalEvent: event.originalEvent,
|
|
41
|
+
}
|
|
42
|
+
handlers.onHover(mapEvent)
|
|
43
|
+
},
|
|
44
|
+
[handlers, setHoveredFeature]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const handleMoveStart = useCallback(() => {
|
|
48
|
+
handlers.onMoveStart?.()
|
|
49
|
+
}, [handlers])
|
|
50
|
+
|
|
51
|
+
const handleMoveEnd = useCallback(() => {
|
|
52
|
+
const map = mapRef.current
|
|
53
|
+
if (!map) return
|
|
54
|
+
|
|
55
|
+
const center = map.getCenter()
|
|
56
|
+
const zoom = map.getZoom()
|
|
57
|
+
const bearing = map.getBearing()
|
|
58
|
+
const pitch = map.getPitch()
|
|
59
|
+
|
|
60
|
+
const newViewport: MapViewport = {
|
|
61
|
+
longitude: center.lng,
|
|
62
|
+
latitude: center.lat,
|
|
63
|
+
zoom,
|
|
64
|
+
bearing,
|
|
65
|
+
pitch,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setViewport(newViewport)
|
|
69
|
+
handlers.onMoveEnd?.(newViewport)
|
|
70
|
+
}, [mapRef, setViewport, handlers])
|
|
71
|
+
|
|
72
|
+
const handleZoomStart = useCallback(() => {
|
|
73
|
+
handlers.onZoomStart?.()
|
|
74
|
+
}, [handlers])
|
|
75
|
+
|
|
76
|
+
const handleZoomEnd = useCallback(() => {
|
|
77
|
+
const zoom = mapRef.current?.getZoom()
|
|
78
|
+
if (zoom !== undefined) {
|
|
79
|
+
handlers.onZoomEnd?.(zoom)
|
|
80
|
+
}
|
|
81
|
+
}, [mapRef, handlers])
|
|
82
|
+
|
|
83
|
+
const handleLoad = useCallback(() => {
|
|
84
|
+
handlers.onLoad?.()
|
|
85
|
+
}, [handlers])
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const map = mapRef.current?.getMap()
|
|
89
|
+
if (!map || !isLoaded) return
|
|
90
|
+
|
|
91
|
+
if (handlers.onClick) {
|
|
92
|
+
map.on('click', handleClick)
|
|
93
|
+
}
|
|
94
|
+
if (handlers.onHover) {
|
|
95
|
+
map.on('mousemove', handleMouseMove)
|
|
96
|
+
}
|
|
97
|
+
if (handlers.onMoveStart) {
|
|
98
|
+
map.on('movestart', handleMoveStart)
|
|
99
|
+
}
|
|
100
|
+
if (handlers.onMoveEnd) {
|
|
101
|
+
map.on('moveend', handleMoveEnd)
|
|
102
|
+
}
|
|
103
|
+
if (handlers.onZoomStart) {
|
|
104
|
+
map.on('zoomstart', handleZoomStart)
|
|
105
|
+
}
|
|
106
|
+
if (handlers.onZoomEnd) {
|
|
107
|
+
map.on('zoomend', handleZoomEnd)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
if (handlers.onClick) {
|
|
112
|
+
map.off('click', handleClick)
|
|
113
|
+
}
|
|
114
|
+
if (handlers.onHover) {
|
|
115
|
+
map.off('mousemove', handleMouseMove)
|
|
116
|
+
}
|
|
117
|
+
if (handlers.onMoveStart) {
|
|
118
|
+
map.off('movestart', handleMoveStart)
|
|
119
|
+
}
|
|
120
|
+
if (handlers.onMoveEnd) {
|
|
121
|
+
map.off('moveend', handleMoveEnd)
|
|
122
|
+
}
|
|
123
|
+
if (handlers.onZoomStart) {
|
|
124
|
+
map.off('zoomstart', handleZoomStart)
|
|
125
|
+
}
|
|
126
|
+
if (handlers.onZoomEnd) {
|
|
127
|
+
map.off('zoomend', handleZoomEnd)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}, [
|
|
131
|
+
mapRef,
|
|
132
|
+
isLoaded,
|
|
133
|
+
handlers,
|
|
134
|
+
handleClick,
|
|
135
|
+
handleMouseMove,
|
|
136
|
+
handleMoveStart,
|
|
137
|
+
handleMoveEnd,
|
|
138
|
+
handleZoomStart,
|
|
139
|
+
handleZoomEnd,
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (isLoaded && handlers.onLoad) {
|
|
144
|
+
handleLoad()
|
|
145
|
+
}
|
|
146
|
+
}, [isLoaded, handlers.onLoad, handleLoad])
|
|
147
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react'
|
|
4
|
+
import type { LayerProps } from 'react-map-gl/maplibre'
|
|
5
|
+
import type { FilterSpecification } from 'maplibre-gl'
|
|
6
|
+
import { useMapContext } from '../context'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for dynamic layer management
|
|
10
|
+
*/
|
|
11
|
+
export function useMapLayers() {
|
|
12
|
+
const { mapRef, isLoaded } = useMapContext()
|
|
13
|
+
|
|
14
|
+
const addLayer = useCallback(
|
|
15
|
+
(layer: LayerProps, beforeId?: string) => {
|
|
16
|
+
const map = mapRef.current?.getMap()
|
|
17
|
+
if (!map || !isLoaded) return
|
|
18
|
+
|
|
19
|
+
if (layer.id && map.getLayer(layer.id)) {
|
|
20
|
+
map.removeLayer(layer.id)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
map.addLayer(layer as Parameters<typeof map.addLayer>[0], beforeId)
|
|
24
|
+
},
|
|
25
|
+
[mapRef, isLoaded]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const removeLayer = useCallback(
|
|
29
|
+
(id: string) => {
|
|
30
|
+
const map = mapRef.current?.getMap()
|
|
31
|
+
if (!map || !isLoaded) return
|
|
32
|
+
|
|
33
|
+
if (map.getLayer(id)) {
|
|
34
|
+
map.removeLayer(id)
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[mapRef, isLoaded]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const setLayerVisibility = useCallback(
|
|
41
|
+
(id: string, visible: boolean) => {
|
|
42
|
+
const map = mapRef.current?.getMap()
|
|
43
|
+
if (!map || !isLoaded) return
|
|
44
|
+
|
|
45
|
+
if (map.getLayer(id)) {
|
|
46
|
+
map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none')
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[mapRef, isLoaded]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const setLayerFilter = useCallback(
|
|
53
|
+
(id: string, filter: FilterSpecification | null) => {
|
|
54
|
+
const map = mapRef.current?.getMap()
|
|
55
|
+
if (!map || !isLoaded) return
|
|
56
|
+
|
|
57
|
+
if (map.getLayer(id)) {
|
|
58
|
+
map.setFilter(id, filter)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[mapRef, isLoaded]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const setLayerPaint = useCallback(
|
|
65
|
+
(id: string, property: string, value: unknown) => {
|
|
66
|
+
const map = mapRef.current?.getMap()
|
|
67
|
+
if (!map || !isLoaded) return
|
|
68
|
+
|
|
69
|
+
if (map.getLayer(id)) {
|
|
70
|
+
map.setPaintProperty(id, property, value)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
[mapRef, isLoaded]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
addLayer,
|
|
78
|
+
removeLayer,
|
|
79
|
+
setLayerVisibility,
|
|
80
|
+
setLayerFilter,
|
|
81
|
+
setLayerPaint,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo } from 'react'
|
|
4
|
+
import { useMapContext } from '../context'
|
|
5
|
+
import type { MapViewport } from '../types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for viewport state and animations
|
|
9
|
+
*/
|
|
10
|
+
export function useMapViewport() {
|
|
11
|
+
const { mapRef, viewport, setViewport } = useMapContext()
|
|
12
|
+
|
|
13
|
+
const animateTo = useCallback(
|
|
14
|
+
(newViewport: Partial<MapViewport>, duration = 1000) => {
|
|
15
|
+
const map = mapRef.current
|
|
16
|
+
if (!map) {
|
|
17
|
+
setViewport(newViewport)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
map.easeTo({
|
|
22
|
+
center: newViewport.longitude !== undefined && newViewport.latitude !== undefined
|
|
23
|
+
? [newViewport.longitude, newViewport.latitude]
|
|
24
|
+
: undefined,
|
|
25
|
+
zoom: newViewport.zoom,
|
|
26
|
+
bearing: newViewport.bearing,
|
|
27
|
+
pitch: newViewport.pitch,
|
|
28
|
+
duration,
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
[mapRef, setViewport]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const center = useMemo<[number, number]>(
|
|
35
|
+
() => [viewport.longitude, viewport.latitude],
|
|
36
|
+
[viewport.longitude, viewport.latitude]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const bounds = useMemo(() => {
|
|
40
|
+
const map = mapRef.current
|
|
41
|
+
if (!map) return null
|
|
42
|
+
|
|
43
|
+
const b = map.getBounds()
|
|
44
|
+
if (!b) return null
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
north: b.getNorth(),
|
|
48
|
+
south: b.getSouth(),
|
|
49
|
+
east: b.getEast(),
|
|
50
|
+
west: b.getWest(),
|
|
51
|
+
}
|
|
52
|
+
}, [mapRef, viewport])
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
viewport,
|
|
56
|
+
setViewport,
|
|
57
|
+
animateTo,
|
|
58
|
+
zoom: viewport.zoom,
|
|
59
|
+
center,
|
|
60
|
+
bounds,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react'
|
|
4
|
+
import { useMapContext } from '../context'
|
|
5
|
+
import { useMapControl } from './useMapControl'
|
|
6
|
+
import type { MarkerActions, MarkerData } from '../types'
|
|
7
|
+
|
|
8
|
+
let markerId = 0
|
|
9
|
+
const generateMarkerId = () => `marker-${++markerId}`
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook for marker management
|
|
13
|
+
* Provides methods to add, remove, update markers
|
|
14
|
+
*/
|
|
15
|
+
export function useMarkers(): MarkerActions {
|
|
16
|
+
const { markers, setMarkers } = useMapContext()
|
|
17
|
+
const { fitBounds } = useMapControl()
|
|
18
|
+
|
|
19
|
+
const addMarker = useCallback(
|
|
20
|
+
(marker: Omit<MarkerData, 'id'>): string => {
|
|
21
|
+
const id = generateMarkerId()
|
|
22
|
+
setMarkers((prev) => [...prev, { ...marker, id }])
|
|
23
|
+
return id
|
|
24
|
+
},
|
|
25
|
+
[setMarkers]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const removeMarker = useCallback(
|
|
29
|
+
(id: string) => {
|
|
30
|
+
setMarkers((prev) => prev.filter((m) => m.id !== id))
|
|
31
|
+
},
|
|
32
|
+
[setMarkers]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const updateMarker = useCallback(
|
|
36
|
+
(id: string, updates: Partial<Omit<MarkerData, 'id'>>) => {
|
|
37
|
+
setMarkers((prev) =>
|
|
38
|
+
prev.map((m) => (m.id === id ? { ...m, ...updates } : m))
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
[setMarkers]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const clearMarkers = useCallback(() => {
|
|
45
|
+
setMarkers([])
|
|
46
|
+
}, [setMarkers])
|
|
47
|
+
|
|
48
|
+
const fitToMarkers = useCallback(
|
|
49
|
+
(padding = 50) => {
|
|
50
|
+
if (markers.length === 0) return
|
|
51
|
+
|
|
52
|
+
if (markers.length === 1) {
|
|
53
|
+
const marker = markers[0]
|
|
54
|
+
fitBounds(
|
|
55
|
+
[
|
|
56
|
+
[marker.longitude - 0.01, marker.latitude - 0.01],
|
|
57
|
+
[marker.longitude + 0.01, marker.latitude + 0.01],
|
|
58
|
+
],
|
|
59
|
+
{ padding }
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const lngs = markers.map((m) => m.longitude)
|
|
65
|
+
const lats = markers.map((m) => m.latitude)
|
|
66
|
+
|
|
67
|
+
const bounds: [[number, number], [number, number]] = [
|
|
68
|
+
[Math.min(...lngs), Math.min(...lats)],
|
|
69
|
+
[Math.max(...lngs), Math.max(...lats)],
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
fitBounds(bounds, { padding })
|
|
73
|
+
},
|
|
74
|
+
[markers, fitBounds]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
markers,
|
|
79
|
+
addMarker,
|
|
80
|
+
removeMarker,
|
|
81
|
+
updateMarker,
|
|
82
|
+
clearMarkers,
|
|
83
|
+
fitToMarkers,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Types (single source of truth)
|
|
2
|
+
export type {
|
|
3
|
+
// Marker
|
|
4
|
+
MarkerData,
|
|
5
|
+
// Viewport
|
|
6
|
+
MapViewport,
|
|
7
|
+
// Context
|
|
8
|
+
MapContextValue,
|
|
9
|
+
// Hook return types
|
|
10
|
+
MapControlActions,
|
|
11
|
+
MarkerActions,
|
|
12
|
+
MapEventHandlers,
|
|
13
|
+
MapMouseEvent,
|
|
14
|
+
// Style
|
|
15
|
+
MapStyleKey,
|
|
16
|
+
// Layer specs (re-exported from react-map-gl)
|
|
17
|
+
LayerProps,
|
|
18
|
+
FillLayerSpecification,
|
|
19
|
+
CircleLayerSpecification,
|
|
20
|
+
LineLayerSpecification,
|
|
21
|
+
SymbolLayerSpecification,
|
|
22
|
+
FilterSpecification,
|
|
23
|
+
ControlPosition,
|
|
24
|
+
MarkerProps,
|
|
25
|
+
// Layer options
|
|
26
|
+
ClusterLayerOptions,
|
|
27
|
+
PointLayerOptions,
|
|
28
|
+
PolygonLayerOptions,
|
|
29
|
+
LineLayerOptions,
|
|
30
|
+
RouteLayerOptions,
|
|
31
|
+
// Control props
|
|
32
|
+
DrawControlProps,
|
|
33
|
+
GeocoderControlProps,
|
|
34
|
+
CustomOverlayProps,
|
|
35
|
+
// Legend
|
|
36
|
+
LegendItem,
|
|
37
|
+
MapLegendProps,
|
|
38
|
+
// Layer switcher
|
|
39
|
+
LayerConfig,
|
|
40
|
+
LayerSwitcherProps,
|
|
41
|
+
} from './types'
|
|
42
|
+
|
|
43
|
+
// Context
|
|
44
|
+
export { MapProvider, useMapContext, MapContext } from './context'
|
|
45
|
+
export type { MapProviderProps } from './context'
|
|
46
|
+
|
|
47
|
+
// Hooks
|
|
48
|
+
export {
|
|
49
|
+
useMap,
|
|
50
|
+
useMapControl,
|
|
51
|
+
useMarkers,
|
|
52
|
+
useMapEvents,
|
|
53
|
+
useMapViewport,
|
|
54
|
+
useMapLayers,
|
|
55
|
+
useControl,
|
|
56
|
+
} from './hooks'
|
|
57
|
+
|
|
58
|
+
// Components
|
|
59
|
+
export {
|
|
60
|
+
MapContainer,
|
|
61
|
+
MapView,
|
|
62
|
+
MapControls,
|
|
63
|
+
MapMarker,
|
|
64
|
+
MapPopup,
|
|
65
|
+
MapCluster,
|
|
66
|
+
MapSource,
|
|
67
|
+
MapLayer,
|
|
68
|
+
CustomOverlay,
|
|
69
|
+
MapLegend,
|
|
70
|
+
LayerSwitcher,
|
|
71
|
+
} from './components'
|
|
72
|
+
export type {
|
|
73
|
+
MapContainerProps,
|
|
74
|
+
MapControlsProps,
|
|
75
|
+
MapMarkerProps,
|
|
76
|
+
MapPopupProps,
|
|
77
|
+
MapClusterProps,
|
|
78
|
+
MapSourceProps,
|
|
79
|
+
MapLayerProps,
|
|
80
|
+
} from './components'
|
|
81
|
+
|
|
82
|
+
// Layers
|
|
83
|
+
export {
|
|
84
|
+
createClusterLayers,
|
|
85
|
+
createPointLayer,
|
|
86
|
+
createHeatmapLayer,
|
|
87
|
+
createPolygonLayer,
|
|
88
|
+
createPolygonOutlineLayer,
|
|
89
|
+
createHighlightLayer,
|
|
90
|
+
createLineLayer,
|
|
91
|
+
createRouteLayers,
|
|
92
|
+
createDashedLineLayer,
|
|
93
|
+
createAnimatedLineLayer,
|
|
94
|
+
} from './layers'
|
|
95
|
+
|
|
96
|
+
// Styles
|
|
97
|
+
export { MAP_STYLES, getMapStyle } from './styles'
|
|
98
|
+
|
|
99
|
+
// Utils
|
|
100
|
+
export {
|
|
101
|
+
calculateBounds,
|
|
102
|
+
calculateCenter,
|
|
103
|
+
calculateDistance,
|
|
104
|
+
isPointInBounds,
|
|
105
|
+
expandBounds,
|
|
106
|
+
toGeoJSON,
|
|
107
|
+
fromGeoJSON,
|
|
108
|
+
createPoint,
|
|
109
|
+
createPolygon,
|
|
110
|
+
createLineString,
|
|
111
|
+
createFeatureCollection,
|
|
112
|
+
} from './utils'
|
|
113
|
+
|
|
114
|
+
// Re-export useful types from react-map-gl
|
|
115
|
+
export type { ViewState, MapRef } from 'react-map-gl/maplibre'
|
|
116
|
+
export { Source, Layer, Marker, Popup } from 'react-map-gl/maplibre'
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { LayerProps } from 'react-map-gl/maplibre'
|
|
2
|
+
import type { ClusterLayerOptions } from '../types'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_COLORS: [string, string, string] = ['#51bbd6', '#f1f075', '#f28cb1']
|
|
5
|
+
const DEFAULT_RADII: [number, number, number] = [20, 30, 40]
|
|
6
|
+
const DEFAULT_THRESHOLDS: [number, number] = [100, 750]
|
|
7
|
+
const DEFAULT_HOVER_COLOR = '#3b82f6' // blue-500
|
|
8
|
+
|
|
9
|
+
export function createClusterLayers(options: ClusterLayerOptions): {
|
|
10
|
+
cluster: LayerProps
|
|
11
|
+
clusterCount: LayerProps
|
|
12
|
+
unclusteredPoint: LayerProps
|
|
13
|
+
} {
|
|
14
|
+
const {
|
|
15
|
+
sourceId,
|
|
16
|
+
colors = DEFAULT_COLORS,
|
|
17
|
+
radii = DEFAULT_RADII,
|
|
18
|
+
thresholds = DEFAULT_THRESHOLDS,
|
|
19
|
+
hoverColor = DEFAULT_HOVER_COLOR,
|
|
20
|
+
} = options
|
|
21
|
+
|
|
22
|
+
const cluster: LayerProps = {
|
|
23
|
+
id: `${sourceId}-clusters`,
|
|
24
|
+
type: 'circle',
|
|
25
|
+
source: sourceId,
|
|
26
|
+
filter: ['has', 'point_count'],
|
|
27
|
+
paint: {
|
|
28
|
+
'circle-color': [
|
|
29
|
+
'case',
|
|
30
|
+
['boolean', ['feature-state', 'hover'], false],
|
|
31
|
+
hoverColor,
|
|
32
|
+
[
|
|
33
|
+
'step',
|
|
34
|
+
['get', 'point_count'],
|
|
35
|
+
colors[0],
|
|
36
|
+
thresholds[0],
|
|
37
|
+
colors[1],
|
|
38
|
+
thresholds[1],
|
|
39
|
+
colors[2],
|
|
40
|
+
],
|
|
41
|
+
],
|
|
42
|
+
'circle-radius': [
|
|
43
|
+
'step',
|
|
44
|
+
['get', 'point_count'],
|
|
45
|
+
radii[0],
|
|
46
|
+
thresholds[0],
|
|
47
|
+
radii[1],
|
|
48
|
+
thresholds[1],
|
|
49
|
+
radii[2],
|
|
50
|
+
],
|
|
51
|
+
'circle-stroke-width': 2,
|
|
52
|
+
'circle-stroke-color': '#fff',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const clusterCount: LayerProps = {
|
|
57
|
+
id: `${sourceId}-cluster-count`,
|
|
58
|
+
type: 'symbol',
|
|
59
|
+
source: sourceId,
|
|
60
|
+
filter: ['has', 'point_count'],
|
|
61
|
+
layout: {
|
|
62
|
+
'text-field': ['get', 'point_count_abbreviated'],
|
|
63
|
+
'text-size': 12,
|
|
64
|
+
},
|
|
65
|
+
paint: {
|
|
66
|
+
'text-color': '#000',
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const unclusteredPoint: LayerProps = {
|
|
71
|
+
id: `${sourceId}-unclustered-point`,
|
|
72
|
+
type: 'circle',
|
|
73
|
+
source: sourceId,
|
|
74
|
+
filter: ['!', ['has', 'point_count']],
|
|
75
|
+
paint: {
|
|
76
|
+
'circle-color': [
|
|
77
|
+
'case',
|
|
78
|
+
['boolean', ['feature-state', 'hover'], false],
|
|
79
|
+
hoverColor,
|
|
80
|
+
colors[0],
|
|
81
|
+
],
|
|
82
|
+
'circle-radius': [
|
|
83
|
+
'case',
|
|
84
|
+
['boolean', ['feature-state', 'hover'], false],
|
|
85
|
+
10,
|
|
86
|
+
8,
|
|
87
|
+
],
|
|
88
|
+
'circle-stroke-width': 2,
|
|
89
|
+
'circle-stroke-color': '#fff',
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { cluster, clusterCount, unclusteredPoint }
|
|
94
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createClusterLayers } from './cluster'
|
|
2
|
+
export { createPointLayer, createHeatmapLayer } from './point'
|
|
3
|
+
export {
|
|
4
|
+
createPolygonLayer,
|
|
5
|
+
createPolygonOutlineLayer,
|
|
6
|
+
createHighlightLayer,
|
|
7
|
+
} from './polygon'
|
|
8
|
+
export {
|
|
9
|
+
createLineLayer,
|
|
10
|
+
createRouteLayers,
|
|
11
|
+
createDashedLineLayer,
|
|
12
|
+
createAnimatedLineLayer,
|
|
13
|
+
} from './line'
|
|
14
|
+
|
|
15
|
+
// Types are exported from ../types.ts
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { LayerProps, LineLayerSpecification } from 'react-map-gl/maplibre'
|
|
2
|
+
import type { LineLayerOptions, RouteLayerOptions } from '../types'
|
|
3
|
+
|
|
4
|
+
export function createLineLayer({
|
|
5
|
+
id,
|
|
6
|
+
sourceId,
|
|
7
|
+
color = '#3b82f6',
|
|
8
|
+
width = 3,
|
|
9
|
+
opacity = 1,
|
|
10
|
+
dashArray,
|
|
11
|
+
lineCap = 'round',
|
|
12
|
+
lineJoin = 'round',
|
|
13
|
+
blur = 0,
|
|
14
|
+
minZoom,
|
|
15
|
+
maxZoom,
|
|
16
|
+
}: LineLayerOptions): LayerProps {
|
|
17
|
+
const paint: LineLayerSpecification['paint'] = {
|
|
18
|
+
'line-color': color,
|
|
19
|
+
'line-width': width,
|
|
20
|
+
'line-opacity': opacity,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (dashArray) {
|
|
24
|
+
paint['line-dasharray'] = dashArray
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (blur > 0) {
|
|
28
|
+
paint['line-blur'] = blur
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const layer: LayerProps = {
|
|
32
|
+
id,
|
|
33
|
+
type: 'line',
|
|
34
|
+
source: sourceId,
|
|
35
|
+
layout: {
|
|
36
|
+
'line-cap': lineCap,
|
|
37
|
+
'line-join': lineJoin,
|
|
38
|
+
},
|
|
39
|
+
paint,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (minZoom !== undefined) layer.minzoom = minZoom
|
|
43
|
+
if (maxZoom !== undefined) layer.maxzoom = maxZoom
|
|
44
|
+
|
|
45
|
+
return layer
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createRouteLayers({
|
|
49
|
+
sourceId,
|
|
50
|
+
color = '#3b82f6',
|
|
51
|
+
width = 4,
|
|
52
|
+
...rest
|
|
53
|
+
}: RouteLayerOptions): {
|
|
54
|
+
outline: LayerProps
|
|
55
|
+
route: LayerProps
|
|
56
|
+
} {
|
|
57
|
+
return {
|
|
58
|
+
outline: createLineLayer({
|
|
59
|
+
id: `${sourceId}-outline`,
|
|
60
|
+
sourceId,
|
|
61
|
+
color: '#ffffff',
|
|
62
|
+
width: width + 2,
|
|
63
|
+
lineCap: 'round',
|
|
64
|
+
lineJoin: 'round',
|
|
65
|
+
...rest,
|
|
66
|
+
}),
|
|
67
|
+
route: createLineLayer({
|
|
68
|
+
id: `${sourceId}-route`,
|
|
69
|
+
sourceId,
|
|
70
|
+
color,
|
|
71
|
+
width,
|
|
72
|
+
lineCap: 'round',
|
|
73
|
+
lineJoin: 'round',
|
|
74
|
+
...rest,
|
|
75
|
+
}),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createDashedLineLayer(
|
|
80
|
+
options: Omit<LineLayerOptions, 'dashArray'>
|
|
81
|
+
): LayerProps {
|
|
82
|
+
return createLineLayer({
|
|
83
|
+
...options,
|
|
84
|
+
dashArray: [2, 2],
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createAnimatedLineLayer(options: LineLayerOptions): LayerProps {
|
|
89
|
+
return createLineLayer({
|
|
90
|
+
...options,
|
|
91
|
+
dashArray: [0, 4, 3],
|
|
92
|
+
})
|
|
93
|
+
}
|