@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.
Files changed (159) hide show
  1. package/README.md +242 -49
  2. package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
  3. package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
  4. package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
  5. package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
  6. package/dist/JsonTree-6RYAOPSS.mjs +4 -0
  7. package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
  8. package/dist/JsonTree-7OH6CIHT.cjs +10 -0
  9. package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
  10. package/dist/MapContainer-GXQLP5WY.mjs +214 -0
  11. package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
  12. package/dist/MapContainer-RYG4HPH4.cjs +221 -0
  13. package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
  14. package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
  15. package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
  16. package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
  17. package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
  18. package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
  19. package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
  20. package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
  21. package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
  22. package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
  23. package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
  24. package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
  25. package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
  26. package/dist/chunk-2DSR7V2L.mjs +561 -0
  27. package/dist/chunk-2DSR7V2L.mjs.map +1 -0
  28. package/dist/chunk-47T5ECYV.cjs +1357 -0
  29. package/dist/chunk-47T5ECYV.cjs.map +1 -0
  30. package/dist/chunk-5QT3QYFZ.cjs +189 -0
  31. package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
  32. package/dist/chunk-7IIRYG4S.mjs +1057 -0
  33. package/dist/chunk-7IIRYG4S.mjs.map +1 -0
  34. package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
  35. package/dist/chunk-DI3HUXHK.cjs.map +1 -0
  36. package/dist/chunk-EVGWYASL.cjs +1528 -0
  37. package/dist/chunk-EVGWYASL.cjs.map +1 -0
  38. package/dist/chunk-F2N7P5XU.cjs +30 -0
  39. package/dist/chunk-F2N7P5XU.cjs.map +1 -0
  40. package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
  41. package/dist/chunk-G6PRZP5I.mjs.map +1 -0
  42. package/dist/chunk-JWB2EWQO.mjs +5 -0
  43. package/dist/chunk-JWB2EWQO.mjs.map +1 -0
  44. package/dist/chunk-LTJX2JXE.mjs +338 -0
  45. package/dist/chunk-LTJX2JXE.mjs.map +1 -0
  46. package/dist/chunk-OVNC4KW6.mjs +1494 -0
  47. package/dist/chunk-OVNC4KW6.mjs.map +1 -0
  48. package/dist/chunk-PNZSJN6T.cjs +1086 -0
  49. package/dist/chunk-PNZSJN6T.cjs.map +1 -0
  50. package/dist/chunk-TEFRA7GW.cjs +565 -0
  51. package/dist/chunk-TEFRA7GW.cjs.map +1 -0
  52. package/dist/chunk-UOMPPIED.mjs +1343 -0
  53. package/dist/chunk-UOMPPIED.mjs.map +1 -0
  54. package/dist/chunk-W6YHQI4F.mjs +187 -0
  55. package/dist/chunk-W6YHQI4F.mjs.map +1 -0
  56. package/dist/chunk-XTBRWVIV.cjs +346 -0
  57. package/dist/chunk-XTBRWVIV.cjs.map +1 -0
  58. package/dist/components-C7ZL7OMY.mjs +5 -0
  59. package/dist/components-C7ZL7OMY.mjs.map +1 -0
  60. package/dist/components-CJ2IB65O.cjs +27 -0
  61. package/dist/components-CJ2IB65O.cjs.map +1 -0
  62. package/dist/components-EASJYK45.mjs +6 -0
  63. package/dist/components-EASJYK45.mjs.map +1 -0
  64. package/dist/components-LDRFDV4A.cjs +22 -0
  65. package/dist/components-LDRFDV4A.cjs.map +1 -0
  66. package/dist/components-VZKUTDJK.mjs +5 -0
  67. package/dist/components-VZKUTDJK.mjs.map +1 -0
  68. package/dist/components-Y64GTIMQ.cjs +42 -0
  69. package/dist/components-Y64GTIMQ.cjs.map +1 -0
  70. package/dist/index.cjs +701 -4813
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.d.cts +1274 -1026
  73. package/dist/index.d.ts +1274 -1026
  74. package/dist/index.mjs +358 -4730
  75. package/dist/index.mjs.map +1 -1
  76. package/package.json +27 -4
  77. package/src/components/index.ts +17 -0
  78. package/src/components/lazy-wrapper.tsx +281 -0
  79. package/src/index.ts +92 -7
  80. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
  81. package/src/tools/AudioPlayer/lazy.tsx +85 -0
  82. package/src/tools/Gallery/components/Gallery.tsx +182 -0
  83. package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
  84. package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
  85. package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
  86. package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
  87. package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
  88. package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
  89. package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
  90. package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
  91. package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
  92. package/src/tools/Gallery/components/index.ts +13 -0
  93. package/src/tools/Gallery/hooks/index.ts +23 -0
  94. package/src/tools/Gallery/hooks/useGallery.ts +137 -0
  95. package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
  96. package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
  97. package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
  98. package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
  99. package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
  100. package/src/tools/Gallery/hooks/useZoom.ts +316 -0
  101. package/src/tools/Gallery/index.ts +66 -0
  102. package/src/tools/Gallery/types.ts +183 -0
  103. package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
  104. package/src/tools/Gallery/utils/index.ts +11 -0
  105. package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
  106. package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
  107. package/src/tools/ImageViewer/lazy.tsx +37 -0
  108. package/src/tools/JsonForm/lazy.tsx +43 -0
  109. package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
  110. package/src/tools/JsonTree/lazy.tsx +45 -0
  111. package/src/tools/LottiePlayer/lazy.tsx +57 -0
  112. package/src/tools/Map/components/CustomOverlay.tsx +54 -0
  113. package/src/tools/Map/components/DrawControl.tsx +36 -0
  114. package/src/tools/Map/components/GeocoderControl.tsx +70 -0
  115. package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
  116. package/src/tools/Map/components/MapCluster.tsx +273 -0
  117. package/src/tools/Map/components/MapContainer.tsx +191 -0
  118. package/src/tools/Map/components/MapControls.tsx +44 -0
  119. package/src/tools/Map/components/MapLegend.tsx +161 -0
  120. package/src/tools/Map/components/MapMarker.tsx +102 -0
  121. package/src/tools/Map/components/MapPopup.tsx +46 -0
  122. package/src/tools/Map/components/MapSource.tsx +30 -0
  123. package/src/tools/Map/components/index.ts +20 -0
  124. package/src/tools/Map/context/MapContext.tsx +89 -0
  125. package/src/tools/Map/context/index.ts +2 -0
  126. package/src/tools/Map/hooks/index.ts +9 -0
  127. package/src/tools/Map/hooks/useMap.ts +11 -0
  128. package/src/tools/Map/hooks/useMapControl.ts +99 -0
  129. package/src/tools/Map/hooks/useMapEvents.ts +147 -0
  130. package/src/tools/Map/hooks/useMapLayers.ts +83 -0
  131. package/src/tools/Map/hooks/useMapViewport.ts +62 -0
  132. package/src/tools/Map/hooks/useMarkers.ts +85 -0
  133. package/src/tools/Map/index.ts +116 -0
  134. package/src/tools/Map/layers/cluster.ts +94 -0
  135. package/src/tools/Map/layers/index.ts +15 -0
  136. package/src/tools/Map/layers/line.ts +93 -0
  137. package/src/tools/Map/layers/point.ts +61 -0
  138. package/src/tools/Map/layers/polygon.ts +73 -0
  139. package/src/tools/Map/lazy.tsx +56 -0
  140. package/src/tools/Map/styles/index.ts +15 -0
  141. package/src/tools/Map/types.ts +259 -0
  142. package/src/tools/Map/utils/geo.ts +88 -0
  143. package/src/tools/Map/utils/index.ts +16 -0
  144. package/src/tools/Map/utils/transform.ts +107 -0
  145. package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
  146. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
  147. package/src/tools/Mermaid/lazy.tsx +46 -0
  148. package/src/tools/OpenapiViewer/lazy.tsx +72 -0
  149. package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
  150. package/src/tools/PrettyCode/lazy.tsx +64 -0
  151. package/src/tools/VideoPlayer/lazy.tsx +63 -0
  152. package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
  153. package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
  154. package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
  155. package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
  156. package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
  157. package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
  158. package/dist/chunk-FB5QBSI3.cjs.map +0 -1
  159. package/dist/chunk-L6UHASYQ.mjs.map +0 -1
@@ -0,0 +1,44 @@
1
+ 'use client'
2
+
3
+ import {
4
+ NavigationControl,
5
+ FullscreenControl,
6
+ GeolocateControl,
7
+ ScaleControl,
8
+ } from 'react-map-gl/maplibre'
9
+
10
+ type ControlPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
11
+
12
+ export interface MapControlsProps {
13
+ position?: ControlPosition
14
+ showNavigation?: boolean
15
+ showFullscreen?: boolean
16
+ showGeolocate?: boolean
17
+ showScale?: boolean
18
+ scaleUnit?: 'imperial' | 'metric' | 'nautical'
19
+ }
20
+
21
+ export function MapControls({
22
+ position = 'top-right',
23
+ showNavigation = true,
24
+ showFullscreen = false,
25
+ showGeolocate = false,
26
+ showScale = false,
27
+ scaleUnit = 'metric',
28
+ }: MapControlsProps) {
29
+ return (
30
+ <>
31
+ {showNavigation && (
32
+ <NavigationControl position={position} showCompass showZoom />
33
+ )}
34
+ {showFullscreen && <FullscreenControl position={position} />}
35
+ {showGeolocate && (
36
+ <GeolocateControl
37
+ position={position}
38
+ trackUserLocation
39
+ />
40
+ )}
41
+ {showScale && <ScaleControl unit={scaleUnit} />}
42
+ </>
43
+ )
44
+ }
@@ -0,0 +1,161 @@
1
+ 'use client'
2
+
3
+ import { useState, useMemo, memo } from 'react'
4
+
5
+ import type { MapLegendProps, LegendItem } from '../types'
6
+
7
+ const positionStyles: Record<string, React.CSSProperties> = {
8
+ 'top-left': { top: 10, left: 10 },
9
+ 'top-right': { top: 10, right: 10 },
10
+ 'bottom-left': { bottom: 10, left: 10 },
11
+ 'bottom-right': { bottom: 10, right: 10 },
12
+ }
13
+
14
+ function LegendIcon({
15
+ type,
16
+ color,
17
+ }: {
18
+ type: LegendItem['type']
19
+ color?: string
20
+ }) {
21
+ const fill = color || '#888'
22
+
23
+ switch (type) {
24
+ case 'circle':
25
+ return (
26
+ <svg width="16" height="16" viewBox="0 0 16 16">
27
+ <circle cx="8" cy="8" r="6" fill={fill} />
28
+ </svg>
29
+ )
30
+ case 'line':
31
+ return (
32
+ <svg width="16" height="16" viewBox="0 0 16 16">
33
+ <line
34
+ x1="0"
35
+ y1="8"
36
+ x2="16"
37
+ y2="8"
38
+ stroke={fill}
39
+ strokeWidth="3"
40
+ strokeLinecap="round"
41
+ />
42
+ </svg>
43
+ )
44
+ case 'fill':
45
+ return (
46
+ <svg width="16" height="16" viewBox="0 0 16 16">
47
+ <rect x="1" y="1" width="14" height="14" fill={fill} rx="2" />
48
+ </svg>
49
+ )
50
+ case 'symbol':
51
+ default:
52
+ return (
53
+ <svg width="16" height="16" viewBox="0 0 16 16">
54
+ <path
55
+ d="M8 1l2.5 5 5.5.8-4 3.9.9 5.3L8 13.5l-4.9 2.5.9-5.3-4-3.9 5.5-.8z"
56
+ fill={fill}
57
+ />
58
+ </svg>
59
+ )
60
+ }
61
+ }
62
+
63
+ function MapLegendComponent({
64
+ items,
65
+ position = 'bottom-right',
66
+ title,
67
+ collapsible = false,
68
+ defaultCollapsed = false,
69
+ className = '',
70
+ style,
71
+ onItemClick,
72
+ }: MapLegendProps) {
73
+ const [collapsed, setCollapsed] = useState(defaultCollapsed)
74
+
75
+ const containerStyle = useMemo<React.CSSProperties>(
76
+ () => ({
77
+ position: 'absolute',
78
+ ...positionStyles[position],
79
+ backgroundColor: 'white',
80
+ borderRadius: 8,
81
+ boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
82
+ padding: collapsed ? 8 : 12,
83
+ minWidth: collapsed ? 'auto' : 120,
84
+ zIndex: 1,
85
+ ...style,
86
+ }),
87
+ [position, collapsed, style]
88
+ )
89
+
90
+ const headerStyle = useMemo<React.CSSProperties>(
91
+ () => ({
92
+ display: 'flex',
93
+ alignItems: 'center',
94
+ justifyContent: 'space-between',
95
+ gap: 8,
96
+ cursor: collapsible ? 'pointer' : 'default',
97
+ fontWeight: 600,
98
+ fontSize: 12,
99
+ color: '#333',
100
+ marginBottom: collapsed ? 0 : 8,
101
+ }),
102
+ [collapsible, collapsed]
103
+ )
104
+
105
+ const itemStyle = useMemo<React.CSSProperties>(
106
+ () => ({
107
+ display: 'flex',
108
+ alignItems: 'center',
109
+ gap: 8,
110
+ padding: '4px 0',
111
+ fontSize: 12,
112
+ color: '#666',
113
+ cursor: onItemClick ? 'pointer' : 'default',
114
+ }),
115
+ [onItemClick]
116
+ )
117
+
118
+ const handleHeaderClick = collapsible ? () => setCollapsed(!collapsed) : undefined
119
+
120
+ return (
121
+ <div className={className} style={containerStyle}>
122
+ {(title || collapsible) && (
123
+ <div style={headerStyle} onClick={handleHeaderClick}>
124
+ {title && <span>{title}</span>}
125
+ {collapsible && (
126
+ <svg
127
+ width="12"
128
+ height="12"
129
+ viewBox="0 0 12 12"
130
+ style={{
131
+ transform: collapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
132
+ transition: 'transform 0.2s',
133
+ }}
134
+ >
135
+ <path d="M3 4.5L6 7.5L9 4.5" stroke="#666" fill="none" />
136
+ </svg>
137
+ )}
138
+ </div>
139
+ )}
140
+ {!collapsed && (
141
+ <div>
142
+ {items.map((item) => (
143
+ <div
144
+ key={item.id}
145
+ style={{
146
+ ...itemStyle,
147
+ opacity: item.visible === false ? 0.5 : 1,
148
+ }}
149
+ onClick={onItemClick ? () => onItemClick(item) : undefined}
150
+ >
151
+ {item.icon || <LegendIcon type={item.type} color={item.color} />}
152
+ <span>{item.label}</span>
153
+ </div>
154
+ ))}
155
+ </div>
156
+ )}
157
+ </div>
158
+ )
159
+ }
160
+
161
+ export const MapLegend = memo(MapLegendComponent)
@@ -0,0 +1,102 @@
1
+ 'use client'
2
+
3
+ import { useCallback, memo, type ReactNode } from 'react'
4
+ import { Marker, type MarkerEvent, type MarkerDragEvent } from 'react-map-gl/maplibre'
5
+ import type { LngLat } from 'maplibre-gl'
6
+ import type { MarkerData } from '../types'
7
+
8
+ export interface MapMarkerProps {
9
+ marker: MarkerData
10
+ onClick?: (marker: MarkerData) => void
11
+ children?: ReactNode
12
+ anchor?: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
13
+ draggable?: boolean
14
+ onDragStart?: (marker: MarkerData) => void
15
+ onDrag?: (marker: MarkerData, lngLat: LngLat) => void
16
+ onDragEnd?: (marker: MarkerData, lngLat: LngLat) => void
17
+ color?: string
18
+ size?: number
19
+ }
20
+
21
+ const DefaultPin = memo(function DefaultPin({
22
+ size = 24,
23
+ color = '#ef4444'
24
+ }: {
25
+ size?: number
26
+ color?: string
27
+ }) {
28
+ return (
29
+ <svg
30
+ width={size}
31
+ height={size}
32
+ viewBox="0 0 24 24"
33
+ fill="none"
34
+ style={{ cursor: 'pointer' }}
35
+ >
36
+ <path
37
+ d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"
38
+ fill={color}
39
+ stroke="#fff"
40
+ strokeWidth="1.5"
41
+ />
42
+ <circle cx="12" cy="9" r="2.5" fill="#fff" />
43
+ </svg>
44
+ )
45
+ })
46
+
47
+ export const MapMarker = memo(function MapMarker({
48
+ marker,
49
+ onClick,
50
+ children,
51
+ anchor = 'bottom',
52
+ draggable = false,
53
+ onDragStart,
54
+ onDrag,
55
+ onDragEnd,
56
+ color,
57
+ size,
58
+ }: MapMarkerProps) {
59
+ const handleClick = useCallback(
60
+ (e: MarkerEvent<MouseEvent>) => {
61
+ e.originalEvent.stopPropagation()
62
+ onClick?.(marker)
63
+ },
64
+ [onClick, marker]
65
+ )
66
+
67
+ const handleDragStart = useCallback(
68
+ (e: MarkerDragEvent) => {
69
+ onDragStart?.(marker)
70
+ },
71
+ [onDragStart, marker]
72
+ )
73
+
74
+ const handleDrag = useCallback(
75
+ (e: MarkerDragEvent) => {
76
+ onDrag?.(marker, e.lngLat)
77
+ },
78
+ [onDrag, marker]
79
+ )
80
+
81
+ const handleDragEnd = useCallback(
82
+ (e: MarkerDragEvent) => {
83
+ onDragEnd?.(marker, e.lngLat)
84
+ },
85
+ [onDragEnd, marker]
86
+ )
87
+
88
+ return (
89
+ <Marker
90
+ longitude={marker.longitude}
91
+ latitude={marker.latitude}
92
+ anchor={anchor}
93
+ draggable={draggable}
94
+ onClick={handleClick}
95
+ onDragStart={handleDragStart}
96
+ onDrag={handleDrag}
97
+ onDragEnd={handleDragEnd}
98
+ >
99
+ {children ?? <DefaultPin color={color} size={size} />}
100
+ </Marker>
101
+ )
102
+ })
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+
3
+ import { memo, type ReactNode } from 'react'
4
+ import { Popup } from 'react-map-gl/maplibre'
5
+
6
+ export interface MapPopupProps {
7
+ longitude: number
8
+ latitude: number
9
+ onClose: () => void
10
+ children: ReactNode
11
+ anchor?: 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
12
+ closeOnClick?: boolean
13
+ closeButton?: boolean
14
+ className?: string
15
+ maxWidth?: string
16
+ offset?: number | [number, number]
17
+ }
18
+
19
+ export const MapPopup = memo(function MapPopup({
20
+ longitude,
21
+ latitude,
22
+ onClose,
23
+ children,
24
+ anchor = 'bottom',
25
+ closeOnClick = true,
26
+ closeButton = true,
27
+ className,
28
+ maxWidth = '300px',
29
+ offset = 15,
30
+ }: MapPopupProps) {
31
+ return (
32
+ <Popup
33
+ longitude={longitude}
34
+ latitude={latitude}
35
+ anchor={anchor}
36
+ onClose={onClose}
37
+ closeOnClick={closeOnClick}
38
+ closeButton={closeButton}
39
+ className={className}
40
+ maxWidth={maxWidth}
41
+ offset={offset}
42
+ >
43
+ {children}
44
+ </Popup>
45
+ )
46
+ })
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import { memo, type ReactNode } from 'react'
4
+ import { Source, Layer, type LayerProps } from 'react-map-gl/maplibre'
5
+
6
+ export interface MapSourceProps {
7
+ id: string
8
+ data: GeoJSON.GeoJSON | string
9
+ type?: 'geojson'
10
+ children?: ReactNode
11
+ }
12
+
13
+ export const MapSource = memo(function MapSource({
14
+ id,
15
+ data,
16
+ type = 'geojson',
17
+ children,
18
+ }: MapSourceProps) {
19
+ return (
20
+ <Source id={id} type={type} data={data}>
21
+ {children}
22
+ </Source>
23
+ )
24
+ })
25
+
26
+ export type MapLayerProps = LayerProps
27
+
28
+ export const MapLayer = memo(function MapLayer(props: MapLayerProps) {
29
+ return <Layer {...props} />
30
+ })
@@ -0,0 +1,20 @@
1
+ export { MapContainer, MapView } from './MapContainer'
2
+ export { MapControls } from './MapControls'
3
+ export { MapMarker } from './MapMarker'
4
+ export { MapPopup } from './MapPopup'
5
+ export { MapCluster } from './MapCluster'
6
+ export { MapSource, MapLayer } from './MapSource'
7
+ export { CustomOverlay } from './CustomOverlay'
8
+ export { MapLegend } from './MapLegend'
9
+ export { LayerSwitcher } from './LayerSwitcher'
10
+
11
+ // DrawControl and GeocoderControl require optional dependencies
12
+ // See DrawControl.tsx and GeocoderControl.tsx for implementation examples
13
+
14
+ // Types are exported from ../types.ts
15
+ export type { MapContainerProps } from './MapContainer'
16
+ export type { MapControlsProps } from './MapControls'
17
+ export type { MapMarkerProps } from './MapMarker'
18
+ export type { MapPopupProps } from './MapPopup'
19
+ export type { MapClusterProps } from './MapCluster'
20
+ export type { MapSourceProps, MapLayerProps } from './MapSource'
@@ -0,0 +1,89 @@
1
+ 'use client'
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useRef,
8
+ useMemo,
9
+ useCallback,
10
+ type ReactNode,
11
+ } from 'react'
12
+ import type { MapRef } from 'react-map-gl/maplibre'
13
+ import type { MapContextValue, MapViewport, MarkerData } from '../types'
14
+
15
+ const MapContext = createContext<MapContextValue | null>(null)
16
+
17
+ export interface MapProviderProps {
18
+ children: ReactNode
19
+ initialViewport?: Partial<MapViewport>
20
+ }
21
+
22
+ const DEFAULT_VIEWPORT: MapViewport = {
23
+ longitude: 115.1889,
24
+ latitude: -8.4095,
25
+ zoom: 10,
26
+ bearing: 0,
27
+ pitch: 0,
28
+ }
29
+
30
+ export function MapProvider({ children, initialViewport }: MapProviderProps) {
31
+ const mapRef = useRef<MapRef | null>(null)
32
+ const initialViewportRef = useRef<MapViewport>({
33
+ ...DEFAULT_VIEWPORT,
34
+ ...initialViewport,
35
+ })
36
+ const [viewport, setViewportState] = useState<MapViewport>(initialViewportRef.current)
37
+ const [markers, setMarkers] = useState<MarkerData[]>([])
38
+ const [selectedMarker, setSelectedMarker] = useState<MarkerData | null>(null)
39
+ const [hoveredFeature, setHoveredFeature] = useState<GeoJSON.Feature | null>(null)
40
+ const [isLoaded, setIsLoaded] = useState(false)
41
+
42
+ const setViewport = useCallback((newViewport: Partial<MapViewport>) => {
43
+ setViewportState((prev) => ({ ...prev, ...newViewport }))
44
+ }, [])
45
+
46
+ const resetToInitial = useCallback(() => {
47
+ const map = mapRef.current
48
+ if (map) {
49
+ map.flyTo({
50
+ center: [initialViewportRef.current.longitude, initialViewportRef.current.latitude],
51
+ zoom: initialViewportRef.current.zoom,
52
+ bearing: initialViewportRef.current.bearing ?? 0,
53
+ pitch: initialViewportRef.current.pitch ?? 0,
54
+ duration: 1000,
55
+ })
56
+ }
57
+ }, [])
58
+
59
+ const value = useMemo<MapContextValue>(
60
+ () => ({
61
+ mapRef,
62
+ viewport,
63
+ setViewport,
64
+ initialViewport: initialViewportRef.current,
65
+ resetToInitial,
66
+ markers,
67
+ setMarkers,
68
+ selectedMarker,
69
+ setSelectedMarker,
70
+ hoveredFeature,
71
+ setHoveredFeature,
72
+ isLoaded,
73
+ setIsLoaded,
74
+ }),
75
+ [viewport, setViewport, resetToInitial, markers, selectedMarker, hoveredFeature, isLoaded]
76
+ )
77
+
78
+ return <MapContext.Provider value={value}>{children}</MapContext.Provider>
79
+ }
80
+
81
+ export function useMapContext(): MapContextValue {
82
+ const context = useContext(MapContext)
83
+ if (!context) {
84
+ throw new Error('useMapContext must be used within a MapProvider')
85
+ }
86
+ return context
87
+ }
88
+
89
+ export { MapContext }
@@ -0,0 +1,2 @@
1
+ export { MapProvider, useMapContext, MapContext } from './MapContext'
2
+ export type { MapProviderProps } from './MapContext'
@@ -0,0 +1,9 @@
1
+ export { useMap } from './useMap'
2
+ export { useMapControl } from './useMapControl'
3
+ export { useMarkers } from './useMarkers'
4
+ export { useMapEvents } from './useMapEvents'
5
+ export { useMapViewport } from './useMapViewport'
6
+ export { useMapLayers } from './useMapLayers'
7
+
8
+ // Re-export useControl from react-map-gl for custom control creation
9
+ export { useControl } from 'react-map-gl/maplibre'
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import { useMapContext } from '../context'
4
+
5
+ /**
6
+ * Hook to access the map context
7
+ * Must be used within a MapProvider
8
+ */
9
+ export function useMap() {
10
+ return useMapContext()
11
+ }
@@ -0,0 +1,99 @@
1
+ 'use client'
2
+
3
+ import { useCallback } from 'react'
4
+ import type { LngLatBoundsLike } from 'maplibre-gl'
5
+ import { useMapContext } from '../context'
6
+ import type { MapControlActions, MapViewport } from '../types'
7
+
8
+ /**
9
+ * Hook for imperative map control
10
+ * Provides methods for programmatic map navigation
11
+ */
12
+ export function useMapControl(): MapControlActions {
13
+ const { mapRef } = useMapContext()
14
+
15
+ const flyTo = useCallback(
16
+ (center: [number, number], zoom?: number, options?: { duration?: number }) => {
17
+ mapRef.current?.flyTo({
18
+ center,
19
+ zoom,
20
+ duration: options?.duration ?? 2000,
21
+ })
22
+ },
23
+ [mapRef]
24
+ )
25
+
26
+ const easeTo = useCallback(
27
+ (center: [number, number], zoom?: number, options?: { duration?: number }) => {
28
+ mapRef.current?.easeTo({
29
+ center,
30
+ zoom,
31
+ duration: options?.duration ?? 500,
32
+ })
33
+ },
34
+ [mapRef]
35
+ )
36
+
37
+ const fitBounds = useCallback(
38
+ (bounds: LngLatBoundsLike, options?: { padding?: number; duration?: number }) => {
39
+ mapRef.current?.fitBounds(bounds, {
40
+ padding: options?.padding ?? 50,
41
+ duration: options?.duration ?? 1000,
42
+ })
43
+ },
44
+ [mapRef]
45
+ )
46
+
47
+ const zoomIn = useCallback(() => {
48
+ const currentZoom = mapRef.current?.getZoom() ?? 10
49
+ mapRef.current?.easeTo({ zoom: currentZoom + 1, duration: 300 })
50
+ }, [mapRef])
51
+
52
+ const zoomOut = useCallback(() => {
53
+ const currentZoom = mapRef.current?.getZoom() ?? 10
54
+ mapRef.current?.easeTo({ zoom: Math.max(0, currentZoom - 1), duration: 300 })
55
+ }, [mapRef])
56
+
57
+ const resetView = useCallback(
58
+ (initialViewport: MapViewport) => {
59
+ mapRef.current?.flyTo({
60
+ center: [initialViewport.longitude, initialViewport.latitude],
61
+ zoom: initialViewport.zoom,
62
+ bearing: initialViewport.bearing ?? 0,
63
+ pitch: initialViewport.pitch ?? 0,
64
+ duration: 1000,
65
+ })
66
+ },
67
+ [mapRef]
68
+ )
69
+
70
+ const getCenter = useCallback((): [number, number] | null => {
71
+ const center = mapRef.current?.getCenter()
72
+ return center ? [center.lng, center.lat] : null
73
+ }, [mapRef])
74
+
75
+ const getZoom = useCallback((): number | null => {
76
+ return mapRef.current?.getZoom() ?? null
77
+ }, [mapRef])
78
+
79
+ const getBounds = useCallback((): LngLatBoundsLike | null => {
80
+ const bounds = mapRef.current?.getBounds()
81
+ if (!bounds) return null
82
+ return [
83
+ [bounds.getWest(), bounds.getSouth()],
84
+ [bounds.getEast(), bounds.getNorth()],
85
+ ]
86
+ }, [mapRef])
87
+
88
+ return {
89
+ flyTo,
90
+ easeTo,
91
+ fitBounds,
92
+ zoomIn,
93
+ zoomOut,
94
+ resetView,
95
+ getCenter,
96
+ getZoom,
97
+ getBounds,
98
+ }
99
+ }