@elabs-ai/components-maps 4.2.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-2FPB53NG.js +36 -0
  3. package/dist/chunk-2FPB53NG.js.map +1 -0
  4. package/dist/chunk-2VVJRT66.js +37 -0
  5. package/dist/chunk-2VVJRT66.js.map +1 -0
  6. package/dist/chunk-3HXFONRP.js +2 -0
  7. package/dist/chunk-3HXFONRP.js.map +1 -0
  8. package/dist/chunk-3SJN3IYT.js +2 -0
  9. package/dist/chunk-3SJN3IYT.js.map +1 -0
  10. package/dist/chunk-4DANBD5B.js +55 -0
  11. package/dist/chunk-4DANBD5B.js.map +1 -0
  12. package/dist/chunk-5PDVG53B.js +2 -0
  13. package/dist/chunk-5PDVG53B.js.map +1 -0
  14. package/dist/chunk-643R7IJC.js +2 -0
  15. package/dist/chunk-643R7IJC.js.map +1 -0
  16. package/dist/chunk-B3ZDBQ6I.js +164 -0
  17. package/dist/chunk-B3ZDBQ6I.js.map +1 -0
  18. package/dist/chunk-BGCE3RR5.js +35 -0
  19. package/dist/chunk-BGCE3RR5.js.map +1 -0
  20. package/dist/chunk-C4VI37TD.js +485 -0
  21. package/dist/chunk-C4VI37TD.js.map +1 -0
  22. package/dist/chunk-CJJT6AEI.js +179 -0
  23. package/dist/chunk-CJJT6AEI.js.map +1 -0
  24. package/dist/chunk-D2RCPTRO.js +2 -0
  25. package/dist/chunk-D2RCPTRO.js.map +1 -0
  26. package/dist/chunk-DTV7JZVN.js +2 -0
  27. package/dist/chunk-DTV7JZVN.js.map +1 -0
  28. package/dist/chunk-E65Z3MSV.js +80 -0
  29. package/dist/chunk-E65Z3MSV.js.map +1 -0
  30. package/dist/chunk-E7VPNINZ.js +70 -0
  31. package/dist/chunk-E7VPNINZ.js.map +1 -0
  32. package/dist/chunk-FHV5IA6N.js +100 -0
  33. package/dist/chunk-FHV5IA6N.js.map +1 -0
  34. package/dist/chunk-FQ6V7BFZ.js +347 -0
  35. package/dist/chunk-FQ6V7BFZ.js.map +1 -0
  36. package/dist/chunk-G7JDXDJN.js +19 -0
  37. package/dist/chunk-G7JDXDJN.js.map +1 -0
  38. package/dist/chunk-GWULZOMW.js +21 -0
  39. package/dist/chunk-GWULZOMW.js.map +1 -0
  40. package/dist/chunk-GYDTTAVD.js +120 -0
  41. package/dist/chunk-GYDTTAVD.js.map +1 -0
  42. package/dist/chunk-HCOBWIM4.js +48 -0
  43. package/dist/chunk-HCOBWIM4.js.map +1 -0
  44. package/dist/chunk-IAHK5MVT.js +2 -0
  45. package/dist/chunk-IAHK5MVT.js.map +1 -0
  46. package/dist/chunk-JGJ6442I.js +235 -0
  47. package/dist/chunk-JGJ6442I.js.map +1 -0
  48. package/dist/chunk-JYFERXZT.js +18 -0
  49. package/dist/chunk-JYFERXZT.js.map +1 -0
  50. package/dist/chunk-LU2UEKFM.js +53 -0
  51. package/dist/chunk-LU2UEKFM.js.map +1 -0
  52. package/dist/chunk-LVYM6C5C.js +2 -0
  53. package/dist/chunk-LVYM6C5C.js.map +1 -0
  54. package/dist/chunk-MIMDP3EU.js +104 -0
  55. package/dist/chunk-MIMDP3EU.js.map +1 -0
  56. package/dist/chunk-NNBURJHY.js +2 -0
  57. package/dist/chunk-NNBURJHY.js.map +1 -0
  58. package/dist/chunk-NPUC3V37.js +2 -0
  59. package/dist/chunk-NPUC3V37.js.map +1 -0
  60. package/dist/chunk-OEVVNPN3.js +201 -0
  61. package/dist/chunk-OEVVNPN3.js.map +1 -0
  62. package/dist/chunk-PF3R2BXM.js +200 -0
  63. package/dist/chunk-PF3R2BXM.js.map +1 -0
  64. package/dist/chunk-Q2I5YODW.js +2 -0
  65. package/dist/chunk-Q2I5YODW.js.map +1 -0
  66. package/dist/chunk-QDWWP3N7.js +247 -0
  67. package/dist/chunk-QDWWP3N7.js.map +1 -0
  68. package/dist/chunk-QU33KPMP.js +2 -0
  69. package/dist/chunk-QU33KPMP.js.map +1 -0
  70. package/dist/chunk-RK4BUMHS.js +2 -0
  71. package/dist/chunk-RK4BUMHS.js.map +1 -0
  72. package/dist/chunk-RNSLG64F.js +26 -0
  73. package/dist/chunk-RNSLG64F.js.map +1 -0
  74. package/dist/chunk-RV673TB4.js +68 -0
  75. package/dist/chunk-RV673TB4.js.map +1 -0
  76. package/dist/chunk-S7N2WMVT.js +180 -0
  77. package/dist/chunk-S7N2WMVT.js.map +1 -0
  78. package/dist/chunk-T3Z6TCDD.js +2 -0
  79. package/dist/chunk-T3Z6TCDD.js.map +1 -0
  80. package/dist/chunk-T4TJTVVA.js +105 -0
  81. package/dist/chunk-T4TJTVVA.js.map +1 -0
  82. package/dist/chunk-UMOYRCDX.js +103 -0
  83. package/dist/chunk-UMOYRCDX.js.map +1 -0
  84. package/dist/chunk-V2REM6UT.js +185 -0
  85. package/dist/chunk-V2REM6UT.js.map +1 -0
  86. package/dist/chunk-VFBRFGID.js +89 -0
  87. package/dist/chunk-VFBRFGID.js.map +1 -0
  88. package/dist/chunk-W76NLFER.js +103 -0
  89. package/dist/chunk-W76NLFER.js.map +1 -0
  90. package/dist/chunk-WNU4IOGL.js +116 -0
  91. package/dist/chunk-WNU4IOGL.js.map +1 -0
  92. package/dist/chunk-WS4Z7ENS.js +494 -0
  93. package/dist/chunk-WS4Z7ENS.js.map +1 -0
  94. package/dist/chunk-XQT7YLJY.js +497 -0
  95. package/dist/chunk-XQT7YLJY.js.map +1 -0
  96. package/dist/chunk-Y44MEKJV.js +21 -0
  97. package/dist/chunk-Y44MEKJV.js.map +1 -0
  98. package/dist/chunk-Z4EPSOB4.js +53 -0
  99. package/dist/chunk-Z4EPSOB4.js.map +1 -0
  100. package/dist/chunk-Z5OPDBDC.js +2 -0
  101. package/dist/chunk-Z5OPDBDC.js.map +1 -0
  102. package/dist/index.d.ts +1241 -46
  103. package/dist/index.js +189 -1471
  104. package/dist/index.js.map +1 -1
  105. package/dist/lib/arc-math.js +8 -0
  106. package/dist/lib/arc-math.js.map +1 -0
  107. package/dist/lib/map-position.js +11 -0
  108. package/dist/lib/map-position.js.map +1 -0
  109. package/dist/lib/merge-hover-paint.js +10 -0
  110. package/dist/lib/merge-hover-paint.js.map +1 -0
  111. package/dist/lib/plan-crs.js +14 -0
  112. package/dist/lib/plan-crs.js.map +1 -0
  113. package/dist/lib/plan-patterns.js +14 -0
  114. package/dist/lib/plan-patterns.js.map +1 -0
  115. package/dist/lib/plan-regions.js +18 -0
  116. package/dist/lib/plan-regions.js.map +1 -0
  117. package/dist/lib/plan-status.js +14 -0
  118. package/dist/lib/plan-status.js.map +1 -0
  119. package/dist/lib/route-arrow.js +12 -0
  120. package/dist/lib/route-arrow.js.map +1 -0
  121. package/dist/lib/use-map-breakpoint.js +33 -0
  122. package/dist/lib/use-map-breakpoint.js.map +1 -0
  123. package/dist/lib/use-plan-projection.js +9 -0
  124. package/dist/lib/use-plan-projection.js.map +1 -0
  125. package/dist/lib/use-token-color.js +10 -0
  126. package/dist/lib/use-token-color.js.map +1 -0
  127. package/dist/lib/warn-once.js +10 -0
  128. package/dist/lib/warn-once.js.map +1 -0
  129. package/dist/map-annotation/anchor.js +14 -0
  130. package/dist/map-annotation/anchor.js.map +1 -0
  131. package/dist/map-annotation/index.js +21 -0
  132. package/dist/map-annotation/index.js.map +1 -0
  133. package/dist/map-annotation/map-annotation.js +18 -0
  134. package/dist/map-annotation/map-annotation.js.map +1 -0
  135. package/dist/map-arc/index.js +16 -0
  136. package/dist/map-arc/index.js.map +1 -0
  137. package/dist/map-arc/map-arc.js +13 -0
  138. package/dist/map-arc/map-arc.js.map +1 -0
  139. package/dist/map-canvas/index.css +11 -0
  140. package/dist/map-canvas/index.css.map +1 -0
  141. package/dist/map-canvas/index.js +20 -0
  142. package/dist/map-canvas/index.js.map +1 -0
  143. package/dist/map-canvas/map-canvas.css +11 -0
  144. package/dist/map-canvas/map-canvas.css.map +1 -0
  145. package/dist/map-canvas/map-canvas.js +17 -0
  146. package/dist/map-canvas/map-canvas.js.map +1 -0
  147. package/dist/map-canvas/map-context.js +11 -0
  148. package/dist/map-canvas/map-context.js.map +1 -0
  149. package/dist/map-canvas/use-resolved-basemap-theme.js +9 -0
  150. package/dist/map-canvas/use-resolved-basemap-theme.js.map +1 -0
  151. package/dist/map-cluster-layer/index.js +12 -0
  152. package/dist/map-cluster-layer/index.js.map +1 -0
  153. package/dist/map-cluster-layer/map-cluster-layer.js +12 -0
  154. package/dist/map-cluster-layer/map-cluster-layer.js.map +1 -0
  155. package/dist/map-controls/index.js +11 -0
  156. package/dist/map-controls/index.js.map +1 -0
  157. package/dist/map-controls/map-controls.js +11 -0
  158. package/dist/map-controls/map-controls.js.map +1 -0
  159. package/dist/map-geojson/index.js +13 -0
  160. package/dist/map-geojson/index.js.map +1 -0
  161. package/dist/map-geojson/map-geojson.js +13 -0
  162. package/dist/map-geojson/map-geojson.js.map +1 -0
  163. package/dist/map-inset/index.css +11 -0
  164. package/dist/map-inset/index.css.map +1 -0
  165. package/dist/map-inset/index.js +20 -0
  166. package/dist/map-inset/index.js.map +1 -0
  167. package/dist/map-inset/map-inset.css +11 -0
  168. package/dist/map-inset/map-inset.css.map +1 -0
  169. package/dist/map-inset/map-inset.js +20 -0
  170. package/dist/map-inset/map-inset.js.map +1 -0
  171. package/dist/map-legend/index.js +14 -0
  172. package/dist/map-legend/index.js.map +1 -0
  173. package/dist/map-legend/map-corner.js +10 -0
  174. package/dist/map-legend/map-corner.js.map +1 -0
  175. package/dist/map-legend/map-legend.js +11 -0
  176. package/dist/map-legend/map-legend.js.map +1 -0
  177. package/dist/map-marker/index.js +22 -0
  178. package/dist/map-marker/index.js.map +1 -0
  179. package/dist/map-marker/map-marker.js +22 -0
  180. package/dist/map-marker/map-marker.js.map +1 -0
  181. package/dist/map-north-arrow/index.js +11 -0
  182. package/dist/map-north-arrow/index.js.map +1 -0
  183. package/dist/map-north-arrow/map-north-arrow.js +11 -0
  184. package/dist/map-north-arrow/map-north-arrow.js.map +1 -0
  185. package/dist/map-plan/index.js +30 -0
  186. package/dist/map-plan/index.js.map +1 -0
  187. package/dist/map-plan/map-plan-legend.js +10 -0
  188. package/dist/map-plan/map-plan-legend.js.map +1 -0
  189. package/dist/map-plan/map-plan-overlay.js +13 -0
  190. package/dist/map-plan/map-plan-overlay.js.map +1 -0
  191. package/dist/map-plan/map-plan-status.js +9 -0
  192. package/dist/map-plan/map-plan-status.js.map +1 -0
  193. package/dist/map-plan/map-plan-table.js +10 -0
  194. package/dist/map-plan/map-plan-table.js.map +1 -0
  195. package/dist/map-plan/use-plan-selection.js +9 -0
  196. package/dist/map-plan/use-plan-selection.js.map +1 -0
  197. package/dist/map-plan-image/index.js +11 -0
  198. package/dist/map-plan-image/index.js.map +1 -0
  199. package/dist/map-plan-image/map-plan-image.js +11 -0
  200. package/dist/map-plan-image/map-plan-image.js.map +1 -0
  201. package/dist/map-popup/index.js +12 -0
  202. package/dist/map-popup/index.js.map +1 -0
  203. package/dist/map-popup/map-popup.js +12 -0
  204. package/dist/map-popup/map-popup.js.map +1 -0
  205. package/dist/map-route/index.js +12 -0
  206. package/dist/map-route/index.js.map +1 -0
  207. package/dist/map-route/map-route.js +12 -0
  208. package/dist/map-route/map-route.js.map +1 -0
  209. package/dist/map-scale-bar/index.js +18 -0
  210. package/dist/map-scale-bar/index.js.map +1 -0
  211. package/dist/map-scale-bar/map-scale-bar.js +18 -0
  212. package/dist/map-scale-bar/map-scale-bar.js.map +1 -0
  213. package/package.json +5 -5
  214. package/src/index.ts +83 -0
  215. package/src/lib/arc-math.test.ts +19 -0
  216. package/src/lib/arc-math.ts +6 -1
  217. package/src/lib/map-position.ts +77 -0
  218. package/src/lib/merge-hover-paint.ts +44 -13
  219. package/src/lib/plan-crs.parity.test.ts +39 -0
  220. package/src/lib/plan-crs.test.ts +248 -0
  221. package/src/lib/plan-crs.ts +252 -0
  222. package/src/lib/plan-patterns.test.ts +255 -0
  223. package/src/lib/plan-patterns.ts +160 -0
  224. package/src/lib/plan-regions.test.ts +186 -0
  225. package/src/lib/plan-regions.ts +209 -0
  226. package/src/lib/plan-status.test.ts +87 -0
  227. package/src/lib/plan-status.ts +147 -0
  228. package/src/lib/route-arrow.test.ts +88 -0
  229. package/src/lib/route-arrow.ts +59 -0
  230. package/src/lib/use-map-breakpoint.test.ts +83 -0
  231. package/src/lib/use-map-breakpoint.ts +238 -0
  232. package/src/lib/use-plan-projection.ts +157 -0
  233. package/src/lib/warn-once.ts +21 -0
  234. package/src/map-annotation/anchor.ts +92 -0
  235. package/src/map-annotation/index.ts +15 -0
  236. package/src/map-annotation/map-annotation.stories.tsx +113 -0
  237. package/src/map-annotation/map-annotation.test.tsx +106 -0
  238. package/src/map-annotation/map-annotation.tsx +356 -0
  239. package/src/map-arc/map-arc.tsx +26 -5
  240. package/src/map-canvas/map-canvas.stories.tsx +211 -0
  241. package/src/map-canvas/map-canvas.test.tsx +389 -0
  242. package/src/map-canvas/map-canvas.tsx +457 -17
  243. package/src/map-canvas/map-context.ts +11 -0
  244. package/src/map-cluster-layer/map-cluster-layer.tsx +14 -1
  245. package/src/map-controls/map-controls.stories.tsx +16 -0
  246. package/src/map-controls/map-controls.test.tsx +145 -1
  247. package/src/map-controls/map-controls.tsx +57 -12
  248. package/src/map-geojson/map-geojson.stories.tsx +153 -3
  249. package/src/map-geojson/map-geojson.test.tsx +453 -0
  250. package/src/map-geojson/map-geojson.tsx +591 -51
  251. package/src/map-inset/index.ts +1 -0
  252. package/src/map-inset/map-inset.stories.tsx +61 -0
  253. package/src/map-inset/map-inset.test.tsx +117 -0
  254. package/src/map-inset/map-inset.tsx +264 -0
  255. package/src/map-legend/index.ts +10 -0
  256. package/src/map-legend/map-corner.ts +23 -0
  257. package/src/map-legend/map-legend.stories.tsx +171 -0
  258. package/src/map-legend/map-legend.test.tsx +136 -0
  259. package/src/map-legend/map-legend.tsx +264 -0
  260. package/src/map-marker/map-marker.stories.tsx +136 -4
  261. package/src/map-marker/map-marker.test.tsx +328 -3
  262. package/src/map-marker/map-marker.tsx +370 -33
  263. package/src/map-north-arrow/index.ts +1 -0
  264. package/src/map-north-arrow/map-north-arrow.stories.tsx +65 -0
  265. package/src/map-north-arrow/map-north-arrow.test.tsx +39 -0
  266. package/src/map-north-arrow/map-north-arrow.tsx +82 -0
  267. package/src/map-plan/index.ts +13 -0
  268. package/src/map-plan/map-plan-legend.test.tsx +61 -0
  269. package/src/map-plan/map-plan-legend.tsx +83 -0
  270. package/src/map-plan/map-plan-overlay.stories.tsx +260 -0
  271. package/src/map-plan/map-plan-overlay.test.tsx +302 -0
  272. package/src/map-plan/map-plan-overlay.tsx +531 -0
  273. package/src/map-plan/map-plan-status.test.tsx +71 -0
  274. package/src/map-plan/map-plan-status.tsx +77 -0
  275. package/src/map-plan/map-plan-table.test.tsx +86 -0
  276. package/src/map-plan/map-plan-table.tsx +153 -0
  277. package/src/map-plan/use-plan-selection.ts +78 -0
  278. package/src/map-plan-image/index.ts +1 -0
  279. package/src/map-plan-image/map-plan-image.stories.tsx +301 -0
  280. package/src/map-plan-image/map-plan-image.test.tsx +176 -0
  281. package/src/map-plan-image/map-plan-image.tsx +279 -0
  282. package/src/map-popup/map-popup.tsx +15 -10
  283. package/src/map-route/index.ts +1 -1
  284. package/src/map-route/map-route.test.tsx +216 -0
  285. package/src/map-route/map-route.tsx +105 -6
  286. package/src/map-scale-bar/index.ts +9 -0
  287. package/src/map-scale-bar/map-scale-bar.stories.tsx +63 -0
  288. package/src/map-scale-bar/map-scale-bar.test.tsx +65 -0
  289. package/src/map-scale-bar/map-scale-bar.tsx +145 -0
  290. package/src/test-utils/locator-fixture.ts +75 -0
  291. package/src/test-utils/maplibre-mock.ts +256 -19
package/dist/index.js CHANGED
@@ -1,1490 +1,208 @@
1
1
  "use client";
2
2
  import "./index.css";
3
-
4
- // src/map-canvas/map-canvas.tsx
5
- import MapLibreGL from "maplibre-gl";
6
- import "maplibre-gl/dist/maplibre-gl.css";
3
+ import "./chunk-3HXFONRP.js";
7
4
  import {
8
- forwardRef,
9
- useCallback,
10
- useEffect as useEffect2,
11
- useImperativeHandle,
12
- useMemo,
13
- useRef,
14
- useState as useState2
15
- } from "react";
16
- import { Spinner, StatePanel, useLocale } from "@elabs-ai/components-ui";
17
- import { cn } from "@elabs-ai/components-ui/lib/cn";
18
-
19
- // src/map-canvas/map-context.ts
20
- import { createContext, use } from "react";
21
- var MapContext = createContext(null);
22
- function useMap() {
23
- const context = use(MapContext);
24
- if (!context) {
25
- throw new Error("useMap must be used within a <MapCanvas>");
26
- }
27
- return context;
28
- }
29
-
30
- // src/map-canvas/use-resolved-basemap-theme.ts
31
- import { resolveThemeIsDark } from "@elabs-ai/components-tokens";
32
- import { useEffect, useState } from "react";
33
- function getBrandTheme() {
34
- if (typeof document === "undefined") return null;
35
- const root = document.documentElement;
36
- if (root.getAttribute("data-theme")) return resolveThemeIsDark(root) ? "dark" : "light";
37
- if (root.classList.contains("dark")) return "dark";
38
- if (root.classList.contains("light")) return "light";
39
- return null;
40
- }
41
- function getSystemTheme() {
42
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
43
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
44
- }
45
- function getThemeAttribute() {
46
- if (typeof document === "undefined") return "";
47
- return document.documentElement.getAttribute("data-theme") ?? "";
48
- }
49
- function useResolvedBasemapTheme(themeProp) {
50
- const [detected, setDetected] = useState(() => getBrandTheme() ?? getSystemTheme());
51
- const [themeAttr, setThemeAttr] = useState(getThemeAttribute);
52
- useEffect(() => {
53
- const update = () => {
54
- setThemeAttr(getThemeAttribute());
55
- const brand = getBrandTheme();
56
- if (brand) setDetected(brand);
57
- };
58
- const observer = new MutationObserver(update);
59
- observer.observe(document.documentElement, {
60
- attributes: true,
61
- attributeFilter: ["data-theme", "class"]
62
- });
63
- const mediaQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
64
- const handleSystemChange = (e) => {
65
- if (!getBrandTheme()) setDetected(e.matches ? "dark" : "light");
66
- };
67
- mediaQuery?.addEventListener("change", handleSystemChange);
68
- return () => {
69
- observer.disconnect();
70
- mediaQuery?.removeEventListener("change", handleSystemChange);
71
- };
72
- }, []);
73
- const resolvedTheme = themeProp ?? detected;
74
- return { resolvedTheme, themeKey: `${themeAttr}:${resolvedTheme}` };
75
- }
76
-
77
- // src/map-canvas/map-canvas.tsx
78
- import { jsx, jsxs } from "react/jsx-runtime";
79
- var defaultStyles = {
80
- dark: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
81
- light: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
82
- };
83
- var blankMapStyle = {
84
- version: 8,
85
- sources: {},
86
- layers: [
87
- {
88
- id: "background",
89
- type: "background",
90
- paint: { "background-color": "transparent" }
91
- }
92
- ]
93
- };
94
- function MapLoadingOverlay() {
95
- return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-xs", children: /* @__PURE__ */ jsx(Spinner, { label: "Loading map", className: "size-5" }) });
96
- }
97
- function getViewport(map) {
98
- const center = map.getCenter();
99
- return {
100
- center: [center.lng, center.lat],
101
- zoom: map.getZoom(),
102
- bearing: map.getBearing(),
103
- pitch: map.getPitch()
104
- };
105
- }
106
- var MapCanvas = forwardRef(function MapCanvas2({
107
- children,
108
- className,
109
- theme: themeProp,
110
- styles,
111
- blank = false,
112
- projection,
113
- viewport,
114
- onViewportChange,
115
- loading = false,
116
- ...props
117
- }, ref) {
118
- const { t } = useLocale();
119
- const containerRef = useRef(null);
120
- const [mapInstance, setMapInstance] = useState2(null);
121
- const [initFailed, setInitFailed] = useState2(false);
122
- const [isLoaded, setIsLoaded] = useState2(false);
123
- const [isStyleLoaded, setIsStyleLoaded] = useState2(false);
124
- const currentStyleRef = useRef(null);
125
- const styleTimeoutRef = useRef(null);
126
- const internalUpdateRef = useRef(false);
127
- const { resolvedTheme, themeKey } = useResolvedBasemapTheme(themeProp);
128
- const isControlled = viewport !== void 0 && onViewportChange !== void 0;
129
- const onViewportChangeRef = useRef(onViewportChange);
130
- onViewportChangeRef.current = onViewportChange;
131
- const projectionRef = useRef(projection);
132
- projectionRef.current = projection;
133
- const mapStyles = useMemo(() => {
134
- if (styles) {
135
- return {
136
- dark: styles.dark ?? defaultStyles.dark,
137
- light: styles.light ?? defaultStyles.light
138
- };
139
- }
140
- if (blank) {
141
- return { dark: blankMapStyle, light: blankMapStyle };
142
- }
143
- return defaultStyles;
144
- }, [styles, blank]);
145
- useImperativeHandle(ref, () => mapInstance, [mapInstance]);
146
- const clearStyleTimeout = useCallback(() => {
147
- if (styleTimeoutRef.current) {
148
- clearTimeout(styleTimeoutRef.current);
149
- styleTimeoutRef.current = null;
150
- }
151
- }, []);
152
- useEffect2(() => {
153
- if (!containerRef.current) return;
154
- const initialStyle = resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light;
155
- currentStyleRef.current = initialStyle;
156
- let map;
157
- try {
158
- map = new MapLibreGL.Map({
159
- container: containerRef.current,
160
- style: initialStyle,
161
- renderWorldCopies: false,
162
- // Attribution control OFF by default — a maintainer decision for internal
163
- // use, taken deliberately and recorded in CHANGELOG.md + the map-components
164
- // rule. NOTE THE CONSTRAINT: the default Carto basemap serves OpenStreetMap
165
- // data, which is ODbL-licensed and requires the credit, and Carto's terms
166
- // require it too — so a surface that ships PUBLICLY on these tiles must turn
167
- // it back on with `attributionControl={{ compact: true }}` (it wins through
168
- // `...props` below), or move to tiles licensed without the requirement via
169
- // `styles` / `blank`.
170
- attributionControl: false,
171
- ...props,
172
- ...viewport
173
- });
174
- } catch {
175
- setInitFailed(true);
176
- return;
177
- }
178
- const styleDataHandler = () => {
179
- clearStyleTimeout();
180
- styleTimeoutRef.current = setTimeout(() => {
181
- setIsStyleLoaded(true);
182
- if (projectionRef.current) {
183
- map.setProjection(projectionRef.current);
184
- }
185
- }, 100);
186
- };
187
- const loadHandler = () => {
188
- setIsLoaded(true);
189
- map.getContainer().querySelectorAll("details.maplibregl-ctrl-attrib[open]").forEach((el) => {
190
- el.open = false;
191
- });
192
- };
193
- const handleMove = () => {
194
- if (internalUpdateRef.current) return;
195
- onViewportChangeRef.current?.(getViewport(map));
196
- };
197
- map.on("load", loadHandler);
198
- map.on("styledata", styleDataHandler);
199
- map.on("move", handleMove);
200
- setMapInstance(map);
201
- return () => {
202
- clearStyleTimeout();
203
- map.off("load", loadHandler);
204
- map.off("styledata", styleDataHandler);
205
- map.off("move", handleMove);
206
- map.remove();
207
- setIsLoaded(false);
208
- setIsStyleLoaded(false);
209
- setMapInstance(null);
210
- };
211
- }, []);
212
- useEffect2(() => {
213
- if (!mapInstance || !isControlled || !viewport) return;
214
- if (mapInstance.isMoving()) return;
215
- const current = getViewport(mapInstance);
216
- const next = {
217
- center: viewport.center ?? current.center,
218
- zoom: viewport.zoom ?? current.zoom,
219
- bearing: viewport.bearing ?? current.bearing,
220
- pitch: viewport.pitch ?? current.pitch
221
- };
222
- if (next.center[0] === current.center[0] && next.center[1] === current.center[1] && next.zoom === current.zoom && next.bearing === current.bearing && next.pitch === current.pitch) {
223
- return;
224
- }
225
- internalUpdateRef.current = true;
226
- mapInstance.jumpTo(next);
227
- internalUpdateRef.current = false;
228
- }, [mapInstance, isControlled, viewport]);
229
- useEffect2(() => {
230
- if (!mapInstance || !resolvedTheme) return;
231
- const newStyle = resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light;
232
- if (currentStyleRef.current === newStyle) return;
233
- clearStyleTimeout();
234
- currentStyleRef.current = newStyle;
235
- setIsStyleLoaded(false);
236
- mapInstance.setStyle(newStyle, { diff: true });
237
- }, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]);
238
- useEffect2(() => {
239
- if (!mapInstance || !isStyleLoaded || !projection) return;
240
- mapInstance.setProjection(projection);
241
- }, [mapInstance, isStyleLoaded, projection]);
242
- const contextValue = useMemo(
243
- () => ({
244
- map: mapInstance,
245
- isLoaded: isLoaded && isStyleLoaded,
246
- resolvedTheme,
247
- themeKey
248
- }),
249
- [mapInstance, isLoaded, isStyleLoaded, resolvedTheme, themeKey]
250
- );
251
- if (initFailed) {
252
- return /* @__PURE__ */ jsx("div", { className: cn("relative h-full w-full", className), children: /* @__PURE__ */ jsx(
253
- StatePanel,
254
- {
255
- kind: "error",
256
- title: t("maps.canvas.unavailableTitle"),
257
- description: t("maps.canvas.unavailableDescription"),
258
- className: "h-full"
259
- }
260
- ) });
261
- }
262
- return /* @__PURE__ */ jsx(MapContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative h-full w-full", className), children: [
263
- (!isLoaded || loading) && /* @__PURE__ */ jsx(MapLoadingOverlay, {}),
264
- mapInstance && children
265
- ] }) });
266
- });
267
-
268
- // src/map-marker/map-marker.tsx
269
- import MapLibreGL2 from "maplibre-gl";
270
- import { createContext as createContext2, use as use2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
271
- import { createPortal } from "react-dom";
272
- import { X } from "lucide-react";
273
- import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
274
- import { useLocale as useLocale2 } from "@elabs-ai/components-ui";
275
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
276
- var MarkerContext = createContext2(null);
277
- function useMarkerContext() {
278
- const context = use2(MarkerContext);
279
- if (!context) {
280
- throw new Error("MapMarker sub-components must be used within <MapMarker>");
281
- }
282
- return context;
283
- }
284
- function MapMarker({
285
- longitude,
286
- latitude,
287
- children,
288
- onClick,
289
- onMouseEnter,
290
- onMouseLeave,
291
- onDragStart,
292
- onDrag,
293
- onDragEnd,
294
- draggable = false,
295
- ...markerOptions
296
- }) {
297
- const { map } = useMap();
298
- const [marker, setMarker] = useState3(null);
299
- const callbacksRef = useRef2({
300
- onClick,
301
- onMouseEnter,
302
- onMouseLeave,
303
- onDragStart,
304
- onDrag,
305
- onDragEnd
306
- });
307
- callbacksRef.current = {
308
- onClick,
309
- onMouseEnter,
310
- onMouseLeave,
311
- onDragStart,
312
- onDrag,
313
- onDragEnd
314
- };
315
- const { anchor, className, offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;
316
- const initialRef = useRef2({ longitude, latitude, draggable });
317
- initialRef.current = { longitude, latitude, draggable };
318
- useEffect3(() => {
319
- const markerInstance = new MapLibreGL2.Marker({
320
- anchor,
321
- className,
322
- element: document.createElement("div"),
323
- draggable: initialRef.current.draggable
324
- }).setLngLat([initialRef.current.longitude, initialRef.current.latitude]);
325
- const handleClick = (e) => callbacksRef.current.onClick?.(e);
326
- const handleMouseEnter = (e) => callbacksRef.current.onMouseEnter?.(e);
327
- const handleMouseLeave = (e) => callbacksRef.current.onMouseLeave?.(e);
328
- markerInstance.getElement()?.addEventListener("click", handleClick);
329
- markerInstance.getElement()?.addEventListener("mouseenter", handleMouseEnter);
330
- markerInstance.getElement()?.addEventListener("mouseleave", handleMouseLeave);
331
- const handleDragStart = () => {
332
- const lngLat = markerInstance.getLngLat();
333
- callbacksRef.current.onDragStart?.({ lng: lngLat.lng, lat: lngLat.lat });
334
- };
335
- const handleDrag = () => {
336
- const lngLat = markerInstance.getLngLat();
337
- callbacksRef.current.onDrag?.({ lng: lngLat.lng, lat: lngLat.lat });
338
- };
339
- const handleDragEnd = () => {
340
- const lngLat = markerInstance.getLngLat();
341
- callbacksRef.current.onDragEnd?.({ lng: lngLat.lng, lat: lngLat.lat });
342
- };
343
- markerInstance.on("dragstart", handleDragStart);
344
- markerInstance.on("drag", handleDrag);
345
- markerInstance.on("dragend", handleDragEnd);
346
- setMarker(markerInstance);
347
- }, [anchor, className]);
348
- useEffect3(() => {
349
- if (!map || !marker) return;
350
- marker.addTo(map);
351
- return () => {
352
- marker.remove();
353
- };
354
- }, [map, marker]);
355
- useEffect3(() => {
356
- if (!marker) return;
357
- const current = marker.getLngLat();
358
- if (current.lng !== longitude || current.lat !== latitude) {
359
- marker.setLngLat([longitude, latitude]);
360
- }
361
- if (marker.isDraggable() !== draggable) {
362
- marker.setDraggable(draggable);
363
- }
364
- const currentOffset = marker.getOffset();
365
- const newOffset = offset ?? [0, 0];
366
- const [newOffsetX, newOffsetY] = Array.isArray(newOffset) ? newOffset : [newOffset.x, newOffset.y];
367
- if (currentOffset.x !== newOffsetX || currentOffset.y !== newOffsetY) {
368
- marker.setOffset(newOffset);
369
- }
370
- if (marker.getRotation() !== (rotation ?? 0)) {
371
- marker.setRotation(rotation ?? 0);
372
- }
373
- if (marker.getRotationAlignment() !== (rotationAlignment ?? "auto")) {
374
- marker.setRotationAlignment(rotationAlignment ?? "auto");
375
- }
376
- if (marker.getPitchAlignment() !== (pitchAlignment ?? "auto")) {
377
- marker.setPitchAlignment(pitchAlignment ?? "auto");
378
- }
379
- }, [marker, longitude, latitude, draggable, offset, rotation, rotationAlignment, pitchAlignment]);
380
- if (!marker) {
381
- return null;
382
- }
383
- return /* @__PURE__ */ jsx2(MarkerContext.Provider, { value: { marker, map }, children });
384
- }
385
- function MapMarkerContent({ children, className }) {
386
- const { marker } = useMarkerContext();
387
- return createPortal(
388
- /* @__PURE__ */ jsx2("div", { className: cn2("relative cursor-pointer", className), children: children || /* @__PURE__ */ jsx2(DefaultMarkerIcon, {}) }),
389
- marker.getElement()
390
- );
391
- }
392
- function DefaultMarkerIcon() {
393
- return /* @__PURE__ */ jsx2("div", { className: "relative size-4 rounded-full border-2 border-background bg-primary shadow-sm" });
394
- }
395
- function PopupCloseButton({ onClick }) {
396
- const { t } = useLocale2();
397
- return /* @__PURE__ */ jsx2(
398
- "button",
399
- {
400
- type: "button",
401
- onClick,
402
- "aria-label": t("maps.popup.close"),
403
- className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset",
404
- children: /* @__PURE__ */ jsx2(X, { className: "size-3.5", "aria-hidden": "true" })
405
- }
406
- );
407
- }
408
- function MapMarkerPopup({
409
- children,
410
- className,
411
- closeButton = false,
412
- ...popupOptions
413
- }) {
414
- const { marker, map } = useMarkerContext();
415
- const container = useMemo2(() => document.createElement("div"), []);
416
- const { offset, maxWidth } = popupOptions;
417
- const popup = useMemo2(() => {
418
- return new MapLibreGL2.Popup({
419
- offset: 16,
420
- ...popupOptions,
421
- closeButton: false
422
- }).setMaxWidth("none").setDOMContent(container);
423
- }, []);
424
- useEffect3(() => {
425
- if (!map) return;
426
- popup.setDOMContent(container);
427
- marker.setPopup(popup);
428
- return () => {
429
- marker.setPopup(null);
430
- };
431
- }, [map]);
432
- useEffect3(() => {
433
- popup.setOffset(offset ?? 16);
434
- if (maxWidth) {
435
- popup.setMaxWidth(maxWidth);
436
- }
437
- }, [popup, offset, maxWidth]);
438
- const handleClose = () => popup.remove();
439
- return createPortal(
440
- /* @__PURE__ */ jsxs2(
441
- "div",
442
- {
443
- className: cn2(
444
- "relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md",
445
- "animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance",
446
- className
447
- ),
448
- children: [
449
- closeButton && /* @__PURE__ */ jsx2(PopupCloseButton, { onClick: handleClose }),
450
- children
451
- ]
452
- }
453
- ),
454
- container
455
- );
456
- }
457
- function MapMarkerTooltip({ children, className, ...popupOptions }) {
458
- const { marker, map } = useMarkerContext();
459
- const container = useMemo2(() => document.createElement("div"), []);
460
- const { offset, maxWidth } = popupOptions;
461
- const tooltip = useMemo2(() => {
462
- return new MapLibreGL2.Popup({
463
- offset: 16,
464
- ...popupOptions,
465
- closeOnClick: true,
466
- closeButton: false
467
- }).setMaxWidth("none");
468
- }, []);
469
- useEffect3(() => {
470
- if (!map) return;
471
- tooltip.setDOMContent(container);
472
- const handleMouseEnter = () => {
473
- tooltip.setLngLat(marker.getLngLat()).addTo(map);
474
- };
475
- const handleMouseLeave = () => tooltip.remove();
476
- marker.getElement()?.addEventListener("mouseenter", handleMouseEnter);
477
- marker.getElement()?.addEventListener("mouseleave", handleMouseLeave);
478
- return () => {
479
- marker.getElement()?.removeEventListener("mouseenter", handleMouseEnter);
480
- marker.getElement()?.removeEventListener("mouseleave", handleMouseLeave);
481
- tooltip.remove();
482
- };
483
- }, [map]);
484
- useEffect3(() => {
485
- tooltip.setOffset(offset ?? 16);
486
- if (maxWidth) {
487
- tooltip.setMaxWidth(maxWidth);
488
- }
489
- }, [tooltip, offset, maxWidth]);
490
- return createPortal(
491
- /* @__PURE__ */ jsx2(
492
- "div",
493
- {
494
- className: cn2(
495
- "pointer-events-none rounded-md bg-foreground px-2 py-1 text-meta text-balance text-background shadow-md",
496
- "animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance",
497
- className
498
- ),
499
- children
500
- }
501
- ),
502
- container
503
- );
504
- }
505
- function MapMarkerLabel({ children, className, position = "top" }) {
506
- const { marker } = useMarkerContext();
507
- const positionClasses2 = {
508
- top: "bottom-full mb-1",
509
- bottom: "top-full mt-1"
510
- };
511
- return createPortal(
512
- /* @__PURE__ */ jsx2(
513
- "div",
514
- {
515
- className: cn2(
516
- "absolute left-1/2 -translate-x-1/2 whitespace-nowrap",
517
- "text-meta font-medium text-foreground",
518
- positionClasses2[position],
519
- className
520
- ),
521
- children
522
- }
523
- ),
524
- marker.getElement()
525
- );
526
- }
527
-
528
- // src/map-popup/map-popup.tsx
529
- import MapLibreGL3 from "maplibre-gl";
530
- import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3 } from "react";
531
- import { createPortal as createPortal2 } from "react-dom";
532
- import { X as X2 } from "lucide-react";
533
- import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
534
- import { useLocale as useLocale3 } from "@elabs-ai/components-ui";
535
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
536
- function MapPopup({
537
- longitude,
538
- latitude,
539
- onClose,
540
- children,
541
- className,
542
- closeButton = false,
543
- ...popupOptions
544
- }) {
545
- const { map } = useMap();
546
- const { t } = useLocale3();
547
- const onCloseRef = useRef3(onClose);
548
- onCloseRef.current = onClose;
549
- const container = useMemo3(() => document.createElement("div"), []);
550
- const { offset, maxWidth } = popupOptions;
551
- const popup = useMemo3(() => {
552
- return new MapLibreGL3.Popup({
553
- offset: 16,
554
- ...popupOptions,
555
- closeButton: false
556
- }).setMaxWidth("none").setLngLat([longitude, latitude]);
557
- }, []);
558
- useEffect4(() => {
559
- if (!map) return;
560
- const onCloseProp = () => onCloseRef.current?.();
561
- popup.on("close", onCloseProp);
562
- popup.setDOMContent(container);
563
- popup.addTo(map);
564
- return () => {
565
- popup.off("close", onCloseProp);
566
- if (popup.isOpen()) {
567
- popup.remove();
568
- }
569
- };
570
- }, [map]);
571
- useEffect4(() => {
572
- const current = popup.getLngLat();
573
- if (!current || current.lng !== longitude || current.lat !== latitude) {
574
- popup.setLngLat([longitude, latitude]);
575
- }
576
- popup.setOffset(offset ?? 16);
577
- if (maxWidth) {
578
- popup.setMaxWidth(maxWidth);
579
- }
580
- }, [popup, longitude, latitude, offset, maxWidth]);
581
- const handleClose = () => {
582
- popup.remove();
583
- };
584
- return createPortal2(
585
- /* @__PURE__ */ jsxs3(
586
- "div",
587
- {
588
- className: cn3(
589
- "relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md",
590
- "animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance",
591
- className
592
- ),
593
- children: [
594
- closeButton && /* @__PURE__ */ jsx3(
595
- "button",
596
- {
597
- type: "button",
598
- onClick: handleClose,
599
- "aria-label": t("maps.popup.close"),
600
- className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset",
601
- children: /* @__PURE__ */ jsx3(X2, { className: "size-3.5", "aria-hidden": "true" })
602
- }
603
- ),
604
- children
605
- ]
606
- }
607
- ),
608
- container
609
- );
610
- }
611
-
612
- // src/map-controls/map-controls.tsx
613
- import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState4 } from "react";
614
- import { Locate, Maximize, Minus, Plus } from "lucide-react";
615
- import { Spinner as Spinner2 } from "@elabs-ai/components-ui";
616
- import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
617
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
618
- var positionClasses = {
619
- "top-left": "top-2 left-2",
620
- "top-right": "top-2 right-2",
621
- "bottom-left": "bottom-2 left-2",
622
- "bottom-right": "bottom-10 right-2"
623
- };
624
- function ControlGroup({ children }) {
625
- return /* @__PURE__ */ jsx4("div", { className: "flex flex-col divide-y overflow-hidden rounded-md bg-surface-elevated shadow-ring-sm", children });
626
- }
627
- function ControlButton({
628
- onClick,
629
- label,
630
- children,
631
- disabled = false
632
- }) {
633
- return /* @__PURE__ */ jsx4(
634
- "button",
635
- {
636
- onClick,
637
- "aria-label": label,
638
- type: "button",
639
- className: cn4(
640
- "flex size-8 items-center justify-center text-foreground transition-colors duration-fast",
641
- "hover:bg-surface-muted",
642
- "focus-ring-inset",
643
- "disabled:pointer-events-none disabled:opacity-50"
644
- ),
645
- disabled,
646
- children
647
- }
648
- );
649
- }
650
- function MapControls({
651
- position = "bottom-right",
652
- showZoom = true,
653
- showCompass = false,
654
- showLocate = false,
655
- showFullscreen = false,
656
- className,
657
- onLocate,
658
- onLocateError
659
- }) {
660
- const { map } = useMap();
661
- const [waitingForLocation, setWaitingForLocation] = useState4(false);
662
- const handleZoomIn = useCallback2(() => {
663
- map?.zoomTo(map.getZoom() + 1, { duration: 300 });
664
- }, [map]);
665
- const handleZoomOut = useCallback2(() => {
666
- map?.zoomTo(map.getZoom() - 1, { duration: 300 });
667
- }, [map]);
668
- const handleResetBearing = useCallback2(() => {
669
- map?.resetNorthPitch({ duration: 300 });
670
- }, [map]);
671
- const handleLocate = useCallback2(() => {
672
- if (!("geolocation" in navigator)) return;
673
- setWaitingForLocation(true);
674
- navigator.geolocation.getCurrentPosition(
675
- (pos) => {
676
- const coords = {
677
- longitude: pos.coords.longitude,
678
- latitude: pos.coords.latitude
679
- };
680
- map?.flyTo({
681
- center: [coords.longitude, coords.latitude],
682
- zoom: 14,
683
- duration: 1500
684
- });
685
- onLocate?.(coords);
686
- setWaitingForLocation(false);
687
- },
688
- (error) => {
689
- onLocateError?.(error);
690
- setWaitingForLocation(false);
691
- }
692
- );
693
- }, [map, onLocate, onLocateError]);
694
- const handleFullscreen = useCallback2(() => {
695
- const container = map?.getContainer();
696
- if (!container) return;
697
- if (document.fullscreenElement) {
698
- void document.exitFullscreen();
699
- } else {
700
- void container.requestFullscreen();
701
- }
702
- }, [map]);
703
- return /* @__PURE__ */ jsxs4(
704
- "div",
705
- {
706
- className: cn4("absolute z-10 flex flex-col gap-1.5", positionClasses[position], className),
707
- children: [
708
- showZoom && /* @__PURE__ */ jsxs4(ControlGroup, { children: [
709
- /* @__PURE__ */ jsx4(ControlButton, { onClick: handleZoomIn, label: "Zoom in", children: /* @__PURE__ */ jsx4(Plus, { className: "size-4", "aria-hidden": "true" }) }),
710
- /* @__PURE__ */ jsx4(ControlButton, { onClick: handleZoomOut, label: "Zoom out", children: /* @__PURE__ */ jsx4(Minus, { className: "size-4", "aria-hidden": "true" }) })
711
- ] }),
712
- showCompass && /* @__PURE__ */ jsx4(ControlGroup, { children: /* @__PURE__ */ jsx4(CompassButton, { onClick: handleResetBearing }) }),
713
- showLocate && /* @__PURE__ */ jsx4(ControlGroup, { children: /* @__PURE__ */ jsx4(
714
- ControlButton,
715
- {
716
- onClick: handleLocate,
717
- label: "Find my location",
718
- disabled: waitingForLocation,
719
- children: waitingForLocation ? /* @__PURE__ */ jsx4(Spinner2, { label: "Locating", className: "size-4 text-foreground" }) : /* @__PURE__ */ jsx4(Locate, { className: "size-4", "aria-hidden": "true" })
720
- }
721
- ) }),
722
- showFullscreen && /* @__PURE__ */ jsx4(ControlGroup, { children: /* @__PURE__ */ jsx4(ControlButton, { onClick: handleFullscreen, label: "Toggle fullscreen", children: /* @__PURE__ */ jsx4(Maximize, { className: "size-4", "aria-hidden": "true" }) }) })
723
- ]
724
- }
725
- );
726
- }
727
- function CompassButton({ onClick }) {
728
- const { map } = useMap();
729
- const compassRef = useRef4(null);
730
- useEffect5(() => {
731
- if (!map || !compassRef.current) return;
732
- const compass = compassRef.current;
733
- const updateRotation = () => {
734
- const bearing = map.getBearing();
735
- const pitch = map.getPitch();
736
- compass.style.transform = `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`;
737
- };
738
- map.on("rotate", updateRotation);
739
- map.on("pitch", updateRotation);
740
- updateRotation();
741
- return () => {
742
- map.off("rotate", updateRotation);
743
- map.off("pitch", updateRotation);
744
- };
745
- }, [map]);
746
- return /* @__PURE__ */ jsx4(ControlButton, { onClick, label: "Reset bearing to north", children: /* @__PURE__ */ jsxs4(
747
- "svg",
748
- {
749
- ref: compassRef,
750
- viewBox: "0 0 24 24",
751
- "aria-hidden": "true",
752
- className: "size-5 transition-transform duration-fast",
753
- style: { transformStyle: "preserve-3d" },
754
- children: [
755
- /* @__PURE__ */ jsx4("path", { d: "M12 2L16 12H12V2Z", className: "fill-destructive" }),
756
- /* @__PURE__ */ jsx4("path", { d: "M12 2L8 12H12V2Z", className: "fill-destructive/50" }),
757
- /* @__PURE__ */ jsx4("path", { d: "M12 22L16 12H12V22Z", className: "fill-muted-foreground/60" }),
758
- /* @__PURE__ */ jsx4("path", { d: "M12 22L8 12H12V22Z", className: "fill-muted-foreground/30" })
759
- ]
760
- }
761
- ) });
762
- }
763
-
764
- // src/map-route/map-route.tsx
765
- import { useEffect as useEffect6, useId } from "react";
766
-
767
- // src/lib/use-token-color.ts
768
- import { resolveTokenColor } from "@elabs-ai/components-tokens";
769
- import { useLayoutEffect, useState as useState5 } from "react";
770
- function useTokenColor(name, fallback = "#000000") {
771
- const { map, themeKey } = useMap();
772
- const [color, setColor] = useState5(fallback);
773
- useLayoutEffect(() => {
774
- setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));
775
- }, [name, fallback, map, themeKey]);
776
- return color;
777
- }
778
-
779
- // src/map-route/map-route.tsx
780
- function MapRoute({
781
- id: propId,
782
- coordinates,
783
- color,
784
- width = 3,
785
- opacity = 0.8,
786
- dashArray,
787
- onClick,
788
- onMouseEnter,
789
- onMouseLeave,
790
- interactive = true
791
- }) {
792
- const { map, isLoaded } = useMap();
793
- const autoId = useId();
794
- const id = propId ?? autoId;
795
- const sourceId = `route-source-${id}`;
796
- const layerId = `route-layer-${id}`;
797
- const primary = useTokenColor("--primary");
798
- const lineColor = color ?? primary;
799
- useEffect6(() => {
800
- if (!isLoaded || !map) return;
801
- map.addSource(sourceId, {
802
- type: "geojson",
803
- data: {
804
- type: "Feature",
805
- properties: {},
806
- geometry: { type: "LineString", coordinates: [] }
807
- }
808
- });
809
- map.addLayer({
810
- id: layerId,
811
- type: "line",
812
- source: sourceId,
813
- layout: { "line-join": "round", "line-cap": "round" },
814
- paint: {
815
- "line-color": lineColor,
816
- "line-width": width,
817
- "line-opacity": opacity,
818
- ...dashArray && { "line-dasharray": dashArray }
819
- }
820
- });
821
- return () => {
822
- try {
823
- if (map.getLayer(layerId)) map.removeLayer(layerId);
824
- if (map.getSource(sourceId)) map.removeSource(sourceId);
825
- } catch {
826
- }
827
- };
828
- }, [isLoaded, map]);
829
- useEffect6(() => {
830
- if (!isLoaded || !map || coordinates.length < 2) return;
831
- const source = map.getSource(sourceId);
832
- if (source) {
833
- source.setData({
834
- type: "Feature",
835
- properties: {},
836
- geometry: { type: "LineString", coordinates }
837
- });
838
- }
839
- }, [isLoaded, map, coordinates, sourceId]);
840
- useEffect6(() => {
841
- if (!isLoaded || !map || !map.getLayer(layerId)) return;
842
- map.setPaintProperty(layerId, "line-color", lineColor);
843
- map.setPaintProperty(layerId, "line-width", width);
844
- map.setPaintProperty(layerId, "line-opacity", opacity);
845
- map.setPaintProperty(layerId, "line-dasharray", dashArray);
846
- }, [isLoaded, map, layerId, lineColor, width, opacity, dashArray]);
847
- useEffect6(() => {
848
- if (!isLoaded || !map || !interactive) return;
849
- const handleClick = () => {
850
- onClick?.();
851
- };
852
- const handleMouseEnter = () => {
853
- map.getCanvas().style.cursor = "pointer";
854
- onMouseEnter?.();
855
- };
856
- const handleMouseLeave = () => {
857
- map.getCanvas().style.cursor = "";
858
- onMouseLeave?.();
859
- };
860
- map.on("click", layerId, handleClick);
861
- map.on("mouseenter", layerId, handleMouseEnter);
862
- map.on("mouseleave", layerId, handleMouseLeave);
863
- return () => {
864
- map.off("click", layerId, handleClick);
865
- map.off("mouseenter", layerId, handleMouseEnter);
866
- map.off("mouseleave", layerId, handleMouseLeave);
867
- };
868
- }, [isLoaded, map, layerId, onClick, onMouseEnter, onMouseLeave, interactive]);
869
- return null;
870
- }
871
-
872
- // src/map-arc/map-arc.tsx
873
- import { useEffect as useEffect7, useId as useId2, useMemo as useMemo4, useRef as useRef5 } from "react";
874
-
875
- // src/lib/arc-math.ts
876
- function buildArcCoordinates(from, to, curvature, samples) {
877
- const [x0, y0] = from;
878
- const [xTo, y2] = to;
879
- const rawDx = xTo - x0;
880
- const x2 = rawDx > 180 ? xTo - 360 : rawDx < -180 ? xTo + 360 : xTo;
881
- const dx = x2 - x0;
882
- const dy = y2 - y0;
883
- const distance = Math.hypot(dx, dy);
884
- if (distance === 0 || curvature === 0) return [from, [x2, y2]];
885
- const mx = (x0 + x2) / 2;
886
- const my = (y0 + y2) / 2;
887
- const nx = -dy / distance;
888
- const ny = dx / distance;
889
- const offset = distance * curvature;
890
- const cx = mx + nx * offset;
891
- const cy = my + ny * offset;
892
- const points = [];
893
- const segments = Math.max(2, Math.floor(samples));
894
- for (let i = 0; i <= segments; i += 1) {
895
- const t = i / segments;
896
- const inv = 1 - t;
897
- const x = inv * inv * x0 + 2 * inv * t * cx + t * t * x2;
898
- const y = inv * inv * y0 + 2 * inv * t * cy + t * t * y2;
899
- points.push([x, y]);
900
- }
901
- return points;
902
- }
903
-
904
- // src/lib/merge-hover-paint.ts
905
- function mergeHoverPaint(paint, hoverPaint) {
906
- if (!hoverPaint) return paint;
907
- const merged = { ...paint };
908
- for (const [key, hoverValue] of Object.entries(hoverPaint)) {
909
- if (hoverValue === void 0) continue;
910
- const baseValue = merged[key];
911
- merged[key] = baseValue === void 0 ? hoverValue : ["case", ["boolean", ["feature-state", "hover"], false], hoverValue, baseValue];
912
- }
913
- return merged;
914
- }
915
-
916
- // src/map-arc/map-arc.tsx
917
- var DEFAULT_ARC_CURVATURE = 0.2;
918
- var DEFAULT_ARC_SAMPLES = 64;
919
- var ARC_HIT_MIN_WIDTH = 12;
920
- var ARC_HIT_PADDING = 6;
921
- var DEFAULT_ARC_LAYOUT = {
922
- "line-join": "round",
923
- "line-cap": "round"
924
- };
925
- function MapArc({
926
- data,
927
- id: propId,
928
- curvature = DEFAULT_ARC_CURVATURE,
929
- samples = DEFAULT_ARC_SAMPLES,
930
- paint,
931
- layout,
932
- hoverPaint,
933
- onClick,
934
- onHover,
935
- interactive = true,
936
- beforeId
937
- }) {
938
- const { map, isLoaded } = useMap();
939
- const autoId = useId2();
940
- const id = propId ?? autoId;
941
- const sourceId = `arc-source-${id}`;
942
- const layerId = `arc-layer-${id}`;
943
- const hitLayerId = `arc-hit-layer-${id}`;
944
- const primary = useTokenColor("--primary");
945
- const mergedPaint = useMemo4(
946
- () => mergeHoverPaint(
947
- { "line-color": primary, "line-width": 2, "line-opacity": 0.85, ...paint },
948
- hoverPaint
949
- ),
950
- [primary, paint, hoverPaint]
951
- );
952
- const mergedLayout = useMemo4(() => ({ ...DEFAULT_ARC_LAYOUT, ...layout }), [layout]);
953
- const hitWidth = useMemo4(() => {
954
- const w = paint?.["line-width"] ?? 2;
955
- const base = typeof w === "number" ? w : ARC_HIT_MIN_WIDTH;
956
- return Math.max(base + ARC_HIT_PADDING, ARC_HIT_MIN_WIDTH);
957
- }, [paint]);
958
- const geoJSON = useMemo4(
959
- () => ({
960
- type: "FeatureCollection",
961
- features: data.map((arc) => {
962
- const { from, to, ...properties } = arc;
963
- return {
964
- type: "Feature",
965
- properties,
966
- geometry: {
967
- type: "LineString",
968
- coordinates: buildArcCoordinates(from, to, curvature, samples)
969
- }
970
- };
971
- })
972
- }),
973
- [data, curvature, samples]
974
- );
975
- const latestRef = useRef5({ data, onClick, onHover });
976
- latestRef.current = { data, onClick, onHover };
977
- useEffect7(() => {
978
- if (!isLoaded || !map) return;
979
- map.addSource(sourceId, {
980
- type: "geojson",
981
- data: geoJSON,
982
- promoteId: "id"
983
- });
984
- map.addLayer(
985
- {
986
- id: hitLayerId,
987
- type: "line",
988
- source: sourceId,
989
- layout: DEFAULT_ARC_LAYOUT,
990
- paint: {
991
- "line-color": "transparent",
992
- "line-width": hitWidth,
993
- "line-opacity": 1
994
- }
995
- },
996
- beforeId
997
- );
998
- map.addLayer(
999
- {
1000
- id: layerId,
1001
- type: "line",
1002
- source: sourceId,
1003
- layout: mergedLayout,
1004
- paint: mergedPaint
1005
- },
1006
- beforeId
1007
- );
1008
- return () => {
1009
- try {
1010
- if (map.getLayer(layerId)) map.removeLayer(layerId);
1011
- if (map.getLayer(hitLayerId)) map.removeLayer(hitLayerId);
1012
- if (map.getSource(sourceId)) map.removeSource(sourceId);
1013
- } catch {
1014
- }
1015
- };
1016
- }, [isLoaded, map]);
1017
- useEffect7(() => {
1018
- if (!isLoaded || !map) return;
1019
- const source = map.getSource(sourceId);
1020
- source?.setData(geoJSON);
1021
- }, [isLoaded, map, geoJSON, sourceId]);
1022
- useEffect7(() => {
1023
- if (!isLoaded || !map || !map.getLayer(layerId)) return;
1024
- for (const [key, value] of Object.entries(mergedPaint)) {
1025
- map.setPaintProperty(layerId, key, value);
1026
- }
1027
- for (const [key, value] of Object.entries(mergedLayout)) {
1028
- map.setLayoutProperty(layerId, key, value);
1029
- }
1030
- if (map.getLayer(hitLayerId)) {
1031
- map.setPaintProperty(hitLayerId, "line-width", hitWidth);
1032
- }
1033
- }, [isLoaded, map, layerId, hitLayerId, mergedPaint, mergedLayout, hitWidth]);
1034
- useEffect7(() => {
1035
- if (!isLoaded || !map || !interactive) return;
1036
- let hoveredId = null;
1037
- const setHover = (next) => {
1038
- if (next === hoveredId) return;
1039
- const sourceExists = !!map.getSource(sourceId);
1040
- if (hoveredId != null && sourceExists) {
1041
- map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });
1042
- }
1043
- hoveredId = next;
1044
- if (next != null && sourceExists) {
1045
- map.setFeatureState({ source: sourceId, id: next }, { hover: true });
1046
- }
1047
- };
1048
- const findArc = (featureId) => featureId == null ? void 0 : latestRef.current.data.find((arc) => String(arc.id) === String(featureId));
1049
- const handleMouseMove = (e) => {
1050
- const featureId = e.features?.[0]?.id;
1051
- if (featureId == null || featureId === hoveredId) return;
1052
- setHover(featureId);
1053
- map.getCanvas().style.cursor = "pointer";
1054
- const arc = findArc(featureId);
1055
- if (arc) {
1056
- latestRef.current.onHover?.({
1057
- arc,
1058
- longitude: e.lngLat.lng,
1059
- latitude: e.lngLat.lat,
1060
- originalEvent: e
1061
- });
1062
- }
1063
- };
1064
- const handleMouseLeave = () => {
1065
- setHover(null);
1066
- map.getCanvas().style.cursor = "";
1067
- latestRef.current.onHover?.(null);
1068
- };
1069
- const handleClick = (e) => {
1070
- const arc = findArc(e.features?.[0]?.id);
1071
- if (!arc) return;
1072
- latestRef.current.onClick?.({
1073
- arc,
1074
- longitude: e.lngLat.lng,
1075
- latitude: e.lngLat.lat,
1076
- originalEvent: e
1077
- });
1078
- };
1079
- map.on("mousemove", hitLayerId, handleMouseMove);
1080
- map.on("mouseleave", hitLayerId, handleMouseLeave);
1081
- map.on("click", hitLayerId, handleClick);
1082
- return () => {
1083
- map.off("mousemove", hitLayerId, handleMouseMove);
1084
- map.off("mouseleave", hitLayerId, handleMouseLeave);
1085
- map.off("click", hitLayerId, handleClick);
1086
- setHover(null);
1087
- map.getCanvas().style.cursor = "";
1088
- };
1089
- }, [isLoaded, map, hitLayerId, sourceId, interactive]);
1090
- return null;
1091
- }
1092
-
1093
- // src/map-geojson/map-geojson.tsx
1094
- import { useEffect as useEffect8, useId as useId3, useMemo as useMemo5, useRef as useRef6 } from "react";
1095
- function MapGeoJSON({
1096
- data,
1097
- id: propId,
1098
- promoteId,
1099
- fillPaint,
1100
- linePaint,
1101
- fillHoverPaint,
1102
- onClick,
1103
- onHover,
1104
- interactive = false,
1105
- beforeId
1106
- }) {
1107
- const { map, isLoaded } = useMap();
1108
- const autoId = useId3();
1109
- const id = propId ?? autoId;
1110
- const sourceId = `geojson-source-${id}`;
1111
- const fillLayerId = `geojson-fill-${id}`;
1112
- const lineLayerId = `geojson-line-${id}`;
1113
- const defaultFill = useTokenColor("--border");
1114
- const defaultLine = useTokenColor("--background");
1115
- const showFill = fillPaint !== false;
1116
- const showLine = linePaint !== false;
1117
- const mergedFillPaint = useMemo5(
1118
- () => mergeHoverPaint({ "fill-color": defaultFill, ...fillPaint || {} }, fillHoverPaint),
1119
- [defaultFill, fillPaint, fillHoverPaint]
1120
- );
1121
- const mergedLinePaint = useMemo5(
1122
- () => ({
1123
- "line-color": defaultLine,
1124
- "line-width": 0.5,
1125
- ...linePaint || {}
1126
- }),
1127
- [defaultLine, linePaint]
1128
- );
1129
- const latestRef = useRef6({ onClick, onHover });
1130
- latestRef.current = { onClick, onHover };
1131
- useEffect8(() => {
1132
- if (!isLoaded || !map) return;
1133
- map.addSource(sourceId, {
1134
- type: "geojson",
1135
- data,
1136
- ...promoteId ? { promoteId } : {}
1137
- });
1138
- return () => {
1139
- try {
1140
- if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);
1141
- if (map.getLayer(fillLayerId)) map.removeLayer(fillLayerId);
1142
- if (map.getSource(sourceId)) map.removeSource(sourceId);
1143
- } catch {
1144
- }
1145
- };
1146
- }, [isLoaded, map]);
1147
- useEffect8(() => {
1148
- if (!isLoaded || !map) return;
1149
- const source = map.getSource(sourceId);
1150
- source?.setData(data);
1151
- }, [isLoaded, map, data, sourceId]);
1152
- useEffect8(() => {
1153
- if (!isLoaded || !map) return;
1154
- const source = map.getSource(sourceId);
1155
- if (!source) return;
1156
- if (showFill && !map.getLayer(fillLayerId)) {
1157
- map.addLayer(
1158
- {
1159
- id: fillLayerId,
1160
- type: "fill",
1161
- source: sourceId,
1162
- paint: mergedFillPaint
1163
- },
1164
- beforeId
1165
- );
1166
- } else if (!showFill && map.getLayer(fillLayerId)) {
1167
- map.removeLayer(fillLayerId);
1168
- }
1169
- if (showLine && !map.getLayer(lineLayerId)) {
1170
- map.addLayer(
1171
- {
1172
- id: lineLayerId,
1173
- type: "line",
1174
- source: sourceId,
1175
- paint: mergedLinePaint
1176
- },
1177
- beforeId
1178
- );
1179
- } else if (!showLine && map.getLayer(lineLayerId)) {
1180
- map.removeLayer(lineLayerId);
1181
- }
1182
- if (showFill && map.getLayer(fillLayerId)) {
1183
- for (const [key, value] of Object.entries(mergedFillPaint)) {
1184
- map.setPaintProperty(fillLayerId, key, value);
1185
- }
1186
- }
1187
- if (showLine && map.getLayer(lineLayerId)) {
1188
- for (const [key, value] of Object.entries(mergedLinePaint)) {
1189
- map.setPaintProperty(lineLayerId, key, value);
1190
- }
1191
- }
1192
- }, [
1193
- isLoaded,
1194
- map,
1195
- sourceId,
1196
- fillLayerId,
1197
- lineLayerId,
1198
- showFill,
1199
- showLine,
1200
- mergedFillPaint,
1201
- mergedLinePaint,
1202
- beforeId
1203
- ]);
1204
- useEffect8(() => {
1205
- if (!isLoaded || !map || !interactive || !showFill) return;
1206
- let hoveredId = null;
1207
- const setHover = (next) => {
1208
- if (next === hoveredId) return;
1209
- const sourceExists = !!map.getSource(sourceId);
1210
- if (hoveredId != null && sourceExists) {
1211
- map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });
1212
- }
1213
- hoveredId = next;
1214
- if (next != null && sourceExists) {
1215
- map.setFeatureState({ source: sourceId, id: next }, { hover: true });
1216
- }
1217
- };
1218
- const handleMouseMove = (e) => {
1219
- const feature = e.features?.[0];
1220
- if (!feature) return;
1221
- map.getCanvas().style.cursor = "pointer";
1222
- const featureId = feature.id;
1223
- if (featureId === hoveredId) return;
1224
- setHover(featureId ?? null);
1225
- latestRef.current.onHover?.({
1226
- feature,
1227
- longitude: e.lngLat.lng,
1228
- latitude: e.lngLat.lat,
1229
- originalEvent: e
1230
- });
1231
- };
1232
- const handleMouseLeave = () => {
1233
- setHover(null);
1234
- map.getCanvas().style.cursor = "";
1235
- latestRef.current.onHover?.(null);
1236
- };
1237
- const handleClick = (e) => {
1238
- const feature = e.features?.[0];
1239
- if (!feature) return;
1240
- latestRef.current.onClick?.({
1241
- feature,
1242
- longitude: e.lngLat.lng,
1243
- latitude: e.lngLat.lat,
1244
- originalEvent: e
1245
- });
1246
- };
1247
- map.on("mousemove", fillLayerId, handleMouseMove);
1248
- map.on("mouseleave", fillLayerId, handleMouseLeave);
1249
- map.on("click", fillLayerId, handleClick);
1250
- return () => {
1251
- map.off("mousemove", fillLayerId, handleMouseMove);
1252
- map.off("mouseleave", fillLayerId, handleMouseLeave);
1253
- map.off("click", fillLayerId, handleClick);
1254
- setHover(null);
1255
- map.getCanvas().style.cursor = "";
1256
- };
1257
- }, [isLoaded, map, fillLayerId, sourceId, interactive, showFill]);
1258
- return null;
1259
- }
1260
-
1261
- // src/map-cluster-layer/map-cluster-layer.tsx
1262
- import { useEffect as useEffect9, useId as useId4, useMemo as useMemo6 } from "react";
1263
- var DEFAULT_CLUSTER_THRESHOLDS = [100, 750];
1264
- function MapClusterLayer({
1265
- data,
1266
- clusterMaxZoom = 14,
1267
- clusterRadius = 50,
1268
- clusterColors,
1269
- clusterThresholds = DEFAULT_CLUSTER_THRESHOLDS,
1270
- pointColor,
1271
- onPointClick,
1272
- onClusterClick
1273
- }) {
1274
- const { map, isLoaded } = useMap();
1275
- const id = useId4();
1276
- const sourceId = `cluster-source-${id}`;
1277
- const clusterLayerId = `clusters-${id}`;
1278
- const clusterCountLayerId = `cluster-count-${id}`;
1279
- const unclusteredLayerId = `unclustered-point-${id}`;
1280
- const success = useTokenColor("--success");
1281
- const warning = useTokenColor("--warning");
1282
- const destructive = useTokenColor("--destructive");
1283
- const primary = useTokenColor("--primary");
1284
- const surface = useTokenColor("--background");
1285
- const resolvedClusterColors = useMemo6(
1286
- () => clusterColors ?? [success, warning, destructive],
1287
- [clusterColors, success, warning, destructive]
1288
- );
1289
- const resolvedPointColor = pointColor ?? primary;
1290
- useEffect9(() => {
1291
- if (!isLoaded || !map) return;
1292
- map.addSource(sourceId, {
1293
- type: "geojson",
1294
- data,
1295
- cluster: true,
1296
- clusterMaxZoom,
1297
- clusterRadius
1298
- });
1299
- map.addLayer({
1300
- id: clusterLayerId,
1301
- type: "circle",
1302
- source: sourceId,
1303
- filter: ["has", "point_count"],
1304
- paint: {
1305
- "circle-color": [
1306
- "step",
1307
- ["get", "point_count"],
1308
- resolvedClusterColors[0],
1309
- clusterThresholds[0],
1310
- resolvedClusterColors[1],
1311
- clusterThresholds[1],
1312
- resolvedClusterColors[2]
1313
- ],
1314
- "circle-radius": [
1315
- "step",
1316
- ["get", "point_count"],
1317
- 20,
1318
- clusterThresholds[0],
1319
- 30,
1320
- clusterThresholds[1],
1321
- 40
1322
- ],
1323
- "circle-stroke-width": 1,
1324
- "circle-stroke-color": surface,
1325
- "circle-opacity": 0.85
1326
- }
1327
- });
1328
- map.addLayer({
1329
- id: clusterCountLayerId,
1330
- type: "symbol",
1331
- source: sourceId,
1332
- filter: ["has", "point_count"],
1333
- layout: {
1334
- "text-field": "{point_count_abbreviated}",
1335
- "text-font": ["Open Sans"],
1336
- "text-size": 12
1337
- },
1338
- paint: {
1339
- "text-color": surface
1340
- }
1341
- });
1342
- map.addLayer({
1343
- id: unclusteredLayerId,
1344
- type: "circle",
1345
- source: sourceId,
1346
- filter: ["!", ["has", "point_count"]],
1347
- paint: {
1348
- "circle-color": resolvedPointColor,
1349
- "circle-radius": 5,
1350
- "circle-stroke-width": 2,
1351
- "circle-stroke-color": surface
1352
- }
1353
- });
1354
- return () => {
1355
- try {
1356
- if (map.getLayer(clusterCountLayerId)) map.removeLayer(clusterCountLayerId);
1357
- if (map.getLayer(unclusteredLayerId)) map.removeLayer(unclusteredLayerId);
1358
- if (map.getLayer(clusterLayerId)) map.removeLayer(clusterLayerId);
1359
- if (map.getSource(sourceId)) map.removeSource(sourceId);
1360
- } catch {
1361
- }
1362
- };
1363
- }, [isLoaded, map, sourceId]);
1364
- useEffect9(() => {
1365
- if (!isLoaded || !map || typeof data === "string") return;
1366
- const source = map.getSource(sourceId);
1367
- if (source) {
1368
- source.setData(data);
1369
- }
1370
- }, [isLoaded, map, data, sourceId]);
1371
- useEffect9(() => {
1372
- if (!isLoaded || !map) return;
1373
- if (map.getLayer(clusterLayerId)) {
1374
- map.setPaintProperty(clusterLayerId, "circle-color", [
1375
- "step",
1376
- ["get", "point_count"],
1377
- resolvedClusterColors[0],
1378
- clusterThresholds[0],
1379
- resolvedClusterColors[1],
1380
- clusterThresholds[1],
1381
- resolvedClusterColors[2]
1382
- ]);
1383
- map.setPaintProperty(clusterLayerId, "circle-radius", [
1384
- "step",
1385
- ["get", "point_count"],
1386
- 20,
1387
- clusterThresholds[0],
1388
- 30,
1389
- clusterThresholds[1],
1390
- 40
1391
- ]);
1392
- map.setPaintProperty(clusterLayerId, "circle-stroke-color", surface);
1393
- }
1394
- if (map.getLayer(clusterCountLayerId)) {
1395
- map.setPaintProperty(clusterCountLayerId, "text-color", surface);
1396
- }
1397
- if (map.getLayer(unclusteredLayerId)) {
1398
- map.setPaintProperty(unclusteredLayerId, "circle-color", resolvedPointColor);
1399
- map.setPaintProperty(unclusteredLayerId, "circle-stroke-color", surface);
1400
- }
1401
- }, [
1402
- isLoaded,
1403
- map,
1404
- clusterLayerId,
1405
- clusterCountLayerId,
1406
- unclusteredLayerId,
1407
- resolvedClusterColors,
1408
- clusterThresholds,
1409
- resolvedPointColor,
1410
- surface
1411
- ]);
1412
- useEffect9(() => {
1413
- if (!isLoaded || !map) return;
1414
- const handleClusterClick = async (e) => {
1415
- const features = map.queryRenderedFeatures(e.point, {
1416
- layers: [clusterLayerId]
1417
- });
1418
- const feature = features[0];
1419
- if (!feature) return;
1420
- const clusterId = feature.properties?.cluster_id;
1421
- const pointCount = feature.properties?.point_count;
1422
- const coordinates = feature.geometry.coordinates;
1423
- if (onClusterClick) {
1424
- onClusterClick(clusterId, coordinates, pointCount);
1425
- } else {
1426
- const source = map.getSource(sourceId);
1427
- const zoom = await source.getClusterExpansionZoom(clusterId);
1428
- map.easeTo({
1429
- center: coordinates,
1430
- zoom
1431
- });
1432
- }
1433
- };
1434
- const handlePointClick = (e) => {
1435
- const feature = e.features?.[0];
1436
- if (!onPointClick || !feature) return;
1437
- const coordinates = feature.geometry.coordinates.slice();
1438
- while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
1439
- coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
1440
- }
1441
- onPointClick(feature, coordinates);
1442
- };
1443
- const handleMouseEnterCluster = () => {
1444
- map.getCanvas().style.cursor = "pointer";
1445
- };
1446
- const handleMouseLeaveCluster = () => {
1447
- map.getCanvas().style.cursor = "";
1448
- };
1449
- const handleMouseEnterPoint = () => {
1450
- if (onPointClick) {
1451
- map.getCanvas().style.cursor = "pointer";
1452
- }
1453
- };
1454
- const handleMouseLeavePoint = () => {
1455
- map.getCanvas().style.cursor = "";
1456
- };
1457
- map.on("click", clusterLayerId, handleClusterClick);
1458
- map.on("click", unclusteredLayerId, handlePointClick);
1459
- map.on("mouseenter", clusterLayerId, handleMouseEnterCluster);
1460
- map.on("mouseleave", clusterLayerId, handleMouseLeaveCluster);
1461
- map.on("mouseenter", unclusteredLayerId, handleMouseEnterPoint);
1462
- map.on("mouseleave", unclusteredLayerId, handleMouseLeavePoint);
1463
- return () => {
1464
- map.off("click", clusterLayerId, handleClusterClick);
1465
- map.off("click", unclusteredLayerId, handlePointClick);
1466
- map.off("mouseenter", clusterLayerId, handleMouseEnterCluster);
1467
- map.off("mouseleave", clusterLayerId, handleMouseLeaveCluster);
1468
- map.off("mouseenter", unclusteredLayerId, handleMouseEnterPoint);
1469
- map.off("mouseleave", unclusteredLayerId, handleMouseLeavePoint);
1470
- };
1471
- }, [isLoaded, map, clusterLayerId, unclusteredLayerId, sourceId, onClusterClick, onPointClick]);
1472
- return null;
1473
- }
5
+ MapScaleBar,
6
+ computeMapScale,
7
+ metresPerPixel,
8
+ niceDistance
9
+ } from "./chunk-T4TJTVVA.js";
10
+ import "./chunk-NPUC3V37.js";
11
+ import {
12
+ MapPopup
13
+ } from "./chunk-UMOYRCDX.js";
14
+ import "./chunk-IAHK5MVT.js";
15
+ import {
16
+ MapRoute
17
+ } from "./chunk-CJJT6AEI.js";
18
+ import "./chunk-T3Z6TCDD.js";
19
+ import {
20
+ MapMarker,
21
+ MapMarkerContent,
22
+ MapMarkerLabel,
23
+ MapMarkerPopup,
24
+ MapMarkerTooltip
25
+ } from "./chunk-C4VI37TD.js";
26
+ import "./chunk-RK4BUMHS.js";
27
+ import {
28
+ MapNorthArrow
29
+ } from "./chunk-E7VPNINZ.js";
30
+ import "./chunk-D2RCPTRO.js";
31
+ import {
32
+ MapPlanImage
33
+ } from "./chunk-B3ZDBQ6I.js";
34
+ import "./chunk-NNBURJHY.js";
35
+ import {
36
+ MapPlanLegend
37
+ } from "./chunk-RV673TB4.js";
38
+ import {
39
+ MapPlanOverlay
40
+ } from "./chunk-FQ6V7BFZ.js";
41
+ import {
42
+ MapPlanStatus
43
+ } from "./chunk-LU2UEKFM.js";
44
+ import {
45
+ MapPlanTable
46
+ } from "./chunk-VFBRFGID.js";
47
+ import {
48
+ usePlanSelection
49
+ } from "./chunk-HCOBWIM4.js";
50
+ import "./chunk-LVYM6C5C.js";
51
+ import {
52
+ MapControls
53
+ } from "./chunk-S7N2WMVT.js";
54
+ import "./chunk-5PDVG53B.js";
55
+ import {
56
+ MapGeoJSON
57
+ } from "./chunk-WS4Z7ENS.js";
58
+ import "./chunk-DTV7JZVN.js";
59
+ import {
60
+ MapInset
61
+ } from "./chunk-PF3R2BXM.js";
62
+ import "./chunk-QU33KPMP.js";
63
+ import {
64
+ MapLegend
65
+ } from "./chunk-V2REM6UT.js";
66
+ import {
67
+ MAP_CORNERS
68
+ } from "./chunk-GWULZOMW.js";
69
+ import "./chunk-3SJN3IYT.js";
70
+ import {
71
+ MapArc
72
+ } from "./chunk-OEVVNPN3.js";
73
+ import "./chunk-Q2I5YODW.js";
74
+ import {
75
+ MapCanvas
76
+ } from "./chunk-XQT7YLJY.js";
77
+ import "./chunk-Z4EPSOB4.js";
78
+ import "./chunk-Z5OPDBDC.js";
79
+ import {
80
+ MapClusterLayer
81
+ } from "./chunk-JGJ6442I.js";
82
+ import {
83
+ ROUTE_ARROW_SIZE,
84
+ createRouteArrowImage,
85
+ routeArrowImageId
86
+ } from "./chunk-2VVJRT66.js";
87
+ import {
88
+ usePlanProjection
89
+ } from "./chunk-MIMDP3EU.js";
90
+ import {
91
+ useTokenColor
92
+ } from "./chunk-Y44MEKJV.js";
93
+ import "./chunk-643R7IJC.js";
94
+ import {
95
+ MapAnnotation,
96
+ MapAnnotationKey
97
+ } from "./chunk-QDWWP3N7.js";
98
+ import {
99
+ DEFAULT_MAP_HEIGHT,
100
+ MAP_BREAKPOINTS,
101
+ MAP_BREAKPOINT_THRESHOLDS,
102
+ isMapResponsiveByBreakpoint,
103
+ mapBreakpointForWidth,
104
+ resolveMapResponsive,
105
+ useMapBreakpoint,
106
+ useMapResponsive
107
+ } from "./chunk-W76NLFER.js";
108
+ import {
109
+ useMap
110
+ } from "./chunk-JYFERXZT.js";
111
+ import {
112
+ MAP_LABEL_ANCHORS,
113
+ mapAnchorGeometry,
114
+ mapAnchorTransform
115
+ } from "./chunk-4DANBD5B.js";
116
+ import {
117
+ buildArcCoordinates
118
+ } from "./chunk-BGCE3RR5.js";
119
+ import "./chunk-2FPB53NG.js";
120
+ import "./chunk-G7JDXDJN.js";
121
+ import "./chunk-RNSLG64F.js";
122
+ import {
123
+ PLAN_MERCATOR_SPAN,
124
+ createPlanCrs
125
+ } from "./chunk-GYDTTAVD.js";
126
+ import {
127
+ createPlanPatternTile,
128
+ planPatternImageId,
129
+ usePlanPatterns
130
+ } from "./chunk-FHV5IA6N.js";
131
+ import {
132
+ planBoundsOfGeometry,
133
+ planBoundsUnion,
134
+ planGroupId,
135
+ planGroupsFromRegions,
136
+ planRegionCentre,
137
+ planRegionsFromGeoJSON
138
+ } from "./chunk-WNU4IOGL.js";
139
+ import {
140
+ PLAN_FILL_OPACITY,
141
+ PLAN_STATUSES,
142
+ PLAN_STATUS_ENCODING,
143
+ planStatusMatch
144
+ } from "./chunk-E65Z3MSV.js";
1474
145
  export {
146
+ DEFAULT_MAP_HEIGHT,
147
+ MAP_BREAKPOINTS,
148
+ MAP_BREAKPOINT_THRESHOLDS,
149
+ MAP_CORNERS,
150
+ MAP_LABEL_ANCHORS,
151
+ MapAnnotation,
152
+ MapAnnotationKey,
1475
153
  MapArc,
1476
154
  MapCanvas,
1477
155
  MapClusterLayer,
1478
156
  MapControls,
1479
157
  MapGeoJSON,
158
+ MapInset,
159
+ MapLegend,
1480
160
  MapMarker,
1481
161
  MapMarkerContent,
1482
162
  MapMarkerLabel,
1483
163
  MapMarkerPopup,
1484
164
  MapMarkerTooltip,
165
+ MapNorthArrow,
166
+ MapPlanImage,
167
+ MapPlanLegend,
168
+ MapPlanOverlay,
169
+ MapPlanStatus,
170
+ MapPlanTable,
1485
171
  MapPopup,
1486
172
  MapRoute,
173
+ MapScaleBar,
174
+ PLAN_FILL_OPACITY,
175
+ PLAN_MERCATOR_SPAN,
176
+ PLAN_STATUSES,
177
+ PLAN_STATUS_ENCODING,
178
+ ROUTE_ARROW_SIZE,
1487
179
  buildArcCoordinates,
1488
- useMap
180
+ computeMapScale,
181
+ createPlanCrs,
182
+ createPlanPatternTile,
183
+ createRouteArrowImage,
184
+ isMapResponsiveByBreakpoint,
185
+ mapAnchorGeometry,
186
+ mapAnchorTransform,
187
+ mapBreakpointForWidth,
188
+ metresPerPixel,
189
+ niceDistance,
190
+ planBoundsOfGeometry,
191
+ planBoundsUnion,
192
+ planGroupId,
193
+ planGroupsFromRegions,
194
+ planPatternImageId,
195
+ planRegionCentre,
196
+ planRegionsFromGeoJSON,
197
+ planStatusMatch,
198
+ resolveMapResponsive,
199
+ routeArrowImageId,
200
+ useMap,
201
+ useMapBreakpoint,
202
+ useMapResponsive,
203
+ usePlanPatterns,
204
+ usePlanProjection,
205
+ usePlanSelection,
206
+ useTokenColor
1489
207
  };
1490
208
  //# sourceMappingURL=index.js.map