@elabs-ai/components-maps 4.1.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 -1456
  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 +421 -1
  242. package/src/map-canvas/map-canvas.tsx +466 -20
  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 +383 -3
  262. package/src/map-marker/map-marker.tsx +414 -44
  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 +18 -11
  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 +267 -20
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/map-canvas/map-canvas.tsx"],"sourcesContent":["\"use client\";\n\nimport MapLibreGL from \"maplibre-gl\";\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport \"./maps.css\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { Spinner, StatePanel, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { createPlanCrs, type PlanCrs, type PlanExtent } from \"../lib/plan-crs\";\nimport { warnMapOnce } from \"../lib/warn-once\";\nimport { MapContext, type BasemapTheme } from \"./map-context\";\nimport { useResolvedBasemapTheme } from \"./use-resolved-basemap-theme\";\nimport {\n MapFrameContext,\n type MapFrameContextValue,\n type MapFrameSide,\n type MapHeight,\n type MapResponsive,\n mapBreakpointForWidth,\n resolveMapHeightStyle,\n useMeasuredMapBreakpoint,\n} from \"../lib/use-map-breakpoint\";\nimport {\n MapAnnotationKey,\n MapAnnotationRegistryProvider,\n useMapAnnotationRegistry,\n} from \"../map-annotation/map-annotation\";\n\n/**\n * Default basemaps: Carto's free light/dark GL styles. These serve ODbL-licensed\n * OpenStreetMap data, which requires attribution on a public surface — see the\n * `attributionControl` note in the map constructor below.\n */\nconst defaultStyles = {\n dark: \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n light: \"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json\",\n};\n\n// A tile-less, dependency-free style with a transparent background. Use it for\n// data visualizations (choropleths, arcs, dot maps) where you draw your own\n// layers and don't need a street basemap: `<MapCanvas blank>`. The transparent\n// background lets the themed container show through.\n/** Breathing room, in CSS pixels, between a fitted plan and the viewport edge. */\nconst PLAN_FIT_PADDING = 24;\n\nconst blankMapStyle: MapLibreGL.StyleSpecification = {\n version: 8,\n sources: {},\n layers: [\n {\n id: \"background\",\n type: \"background\",\n paint: { \"background-color\": \"transparent\" },\n },\n ],\n};\n\n/** Map viewport state. */\nexport interface MapViewport {\n /** Center coordinates [longitude, latitude]. */\n center: [number, number];\n /** Zoom level. */\n zoom: number;\n /** Bearing (rotation) in degrees. */\n bearing: number;\n /** Pitch (tilt) in degrees. */\n pitch: number;\n}\n\nexport type MapStyleOption = string | MapLibreGL.StyleSpecification;\n\n/** `\"mercator\"` / `\"globe\"`, or a full MapLibre projection spec. */\nexport type MapProjectionOption = \"mercator\" | \"globe\" | MapLibreGL.ProjectionSpecification;\n\n/** The gesture handlers static mode switches off (tooltips and hover keep working). */\nconst GESTURE_HANDLERS = [\n \"scrollZoom\",\n \"boxZoom\",\n \"dragRotate\",\n \"dragPan\",\n \"keyboard\",\n \"doubleClickZoom\",\n \"touchZoomRotate\",\n \"touchPitch\",\n] as const;\n\ntype GestureHandlerKey = (typeof GESTURE_HANDLERS)[number];\n\nconst STATIC_HANDLER_OPTIONS = Object.fromEntries(\n GESTURE_HANDLERS.map((key) => [key, false]),\n) as Record<GestureHandlerKey, false>;\n\nlet warnedProjection = false;\n\n/**\n * Apply a projection when this MapLibre build supports switching one\n * (`setProjection`, MapLibre 5+); otherwise leave the map as it is and say so\n * once — a globe is an extra, never a requirement.\n */\nfunction applyProjection(map: MapLibreGL.Map, projection: MapLibreGL.ProjectionSpecification) {\n if (typeof map.setProjection !== \"function\") {\n if (!warnedProjection) {\n warnedProjection = true;\n console.warn(\n \"[@elabs-ai/components-maps] This MapLibre build cannot switch projections; `projection` is ignored.\",\n );\n }\n return;\n }\n map.setProjection(projection);\n}\n\n/**\n * Hide (or restore) the basemap's OWN text labels — every symbol layer that\n * draws a `text-field` (c-6 / c-11).\n *\n * A basemap's labels are drawn for a full-size map. Inside a 96 px inset they\n * are sliced mid-word by the frame (\"EUROP\", \"AMERIC\") and louder than the\n * globe they caption; on a locator they print a place name the editorial\n * marker names again 16 px away. Both are the locator recipe's answer:\n * the basemap draws the ground, the map's own labels do the naming.\n *\n * `hidden` records what THIS call turned off, so restoring never reveals a\n * layer the style itself shipped hidden.\n */\nfunction applyBasemapLabels(map: MapLibreGL.Map, visible: boolean, hidden: Set<string>) {\n let style: MapLibreGL.StyleSpecification | undefined;\n try {\n style = map.getStyle?.();\n } catch {\n // style mid-reload\n return;\n }\n if (!style?.layers) return;\n if (visible) {\n for (const id of hidden) {\n if (map.getLayer(id)) map.setLayoutProperty(id, \"visibility\", \"visible\");\n }\n hidden.clear();\n return;\n }\n for (const layer of style.layers) {\n if (layer.type !== \"symbol\") continue;\n if (!layer.layout || !(\"text-field\" in layer.layout)) continue;\n if (hidden.has(layer.id)) continue;\n if (map.getLayoutProperty?.(layer.id, \"visibility\") === \"none\") continue;\n map.setLayoutProperty(layer.id, \"visibility\", \"none\");\n hidden.add(layer.id);\n }\n}\n\n/** A box's measured size as a comparable key, or `null` while it has none. */\nfunction boxSize(node: HTMLElement): string | null {\n const { width, height } = node.getBoundingClientRect();\n if (!(width > 0) || !(height > 0)) return null;\n return `${Math.round(width)}×${Math.round(height)}`;\n}\n\n/** The imperative handle exposed by `<MapCanvas ref>`: the MapLibre map itself. */\nexport type MapCanvasRef = MapLibreGL.Map;\n\nexport type MapCanvasProps = {\n children?: ReactNode;\n /** Additional CSS classes for the map container. */\n className?: string;\n /**\n * Basemap flavor. If not provided, it is derived from the active brand theme\n * (`data-theme` + that theme's own `color-scheme`), then a `dark`/`light` root class,\n * then the OS preference.\n */\n theme?: BasemapTheme;\n /** Custom map styles for light and dark themes. Overrides the default Carto styles. */\n styles?: {\n light?: MapStyleOption;\n dark?: MapStyleOption;\n };\n /**\n * Use a transparent, tile-less basemap instead of the default Carto street\n * basemap — a blank canvas. Used alone it renders nothing; add your own\n * layers on top (`<MapGeoJSON>`, `<MapArc>`, markers, …). Ideal for data\n * visualizations. Ignored when an explicit `styles` prop is provided.\n */\n blank?: boolean;\n /**\n * Draw the basemap's own place labels. `false` leaves the ground and hides\n * every text label the basemap style carries, so the map's own labels\n * (`MapMarker`'s `label`, `MapAnnotation`) are the only naming on it — the\n * locator recipe, and the default inside `MapInset`, where a full-size\n * label is sliced by the frame. Default `true`.\n */\n basemapLabels?: boolean;\n /**\n * Map projection: `\"mercator\"` (MapLibre's default) or `\"globe\"` for a 3D\n * globe view, or a full MapLibre projection spec. Feature-detected: a\n * MapLibre build that cannot switch projections ignores it (with a console\n * note).\n */\n projection?: MapProjectionOption;\n /**\n * `false` makes the map STATIC — the editorial / locator default: no zoom,\n * pan, rotate or keyboard handlers, the default cursor and no tab stop\n * (leave `<MapControls>` out of a static map). Hover and click still reach\n * layers and markers, so tooltips keep working. The viewport can still be set in code\n * (`viewport`, the ref). Default `true`.\n */\n interactive?: boolean;\n /**\n * The map's height: CSS px, or `{ aspect }` (width ÷ height), optionally per\n * tier — `{ base: { aspect: 1.6 }, narrow: { aspect: 1 } }`. Tiers are\n * measured on the map's own width (`narrow` < 480 px, `medium` < 768 px).\n * Unset, the map fills its parent as before; a parent with no height of its\n * own gets `DEFAULT_MAP_HEIGHT` (1.6 : 1, square at `narrow`) instead of 0.\n */\n height?: MapResponsive<MapHeight>;\n /**\n * Controlled viewport. When provided together with `onViewportChange`, the\n * map becomes controlled and the viewport is driven by this prop.\n */\n viewport?: Partial<MapViewport>;\n /**\n * Callback fired continuously as the viewport changes (pan, zoom, rotate,\n * pitch). Use standalone to observe changes, or with `viewport` for\n * controlled mode.\n */\n onViewportChange?: (viewport: MapViewport) => void;\n /** Show a loading overlay on the map (e.g. while the app fetches map data). */\n loading?: boolean;\n /**\n * Turn the canvas into a CUSTOM (non-geographic) plan map: a floor plan, a\n * factory layout, a train carriage, a rack elevation. Pass the plan's extent\n * (`{ width, height }` in the plan's own units) or a `createPlanCrs(…)`\n * result, and every layer inside then speaks PLAN coordinates instead of\n * lng/lat — shapes, routes, markers and popups alike.\n *\n * The plan is fitted on mount, pan and zoom are clamped to its extent, and\n * rotation and pitch are off (a rotated floor plan is unreadable). Pair it\n * with `blank` and, for a picture under the shapes, `<MapPlanImage>`.\n *\n * The plan coordinate system is synthesized on Web Mercator, so it is not a\n * georeference: distances belong to the plan, and a scale bar would lie.\n */\n plan?: PlanExtent | PlanCrs;\n /**\n * Fit a plan map to its extent on mount and whenever the extent changes\n * (default: true). Ignored on a geographic map, and skipped when the camera\n * is driven through `viewport`.\n */\n fitPlan?: boolean;\n} & Omit<MapLibreGL.MapOptions, \"container\" | \"style\" | \"interactive\">;\n\nfunction MapLoadingOverlay() {\n const { t } = useLocale();\n return (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-xs\">\n <Spinner label={t(\"maps.canvas.loading\")} className=\"size-5\" />\n </div>\n );\n}\n\nfunction getViewport(map: MapLibreGL.Map): MapViewport {\n const center = map.getCenter();\n return {\n center: [center.lng, center.lat],\n zoom: map.getZoom(),\n bearing: map.getBearing(),\n pitch: map.getPitch(),\n };\n}\n\n/**\n * The root map surface — a token/theme-aware MapLibre GL canvas. Compose the\n * other `@elabs-ai/components-maps` components (markers, popups, controls, layers) as\n * children; they reach the map through context (`useMap`).\n *\n * The ref exposes the raw MapLibre `Map` instance for imperative work\n * (`flyTo`, `fitBounds`, …).\n */\nexport const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCanvas(\n {\n children,\n className,\n theme: themeProp,\n styles,\n blank = false,\n basemapLabels = true,\n projection: projectionProp,\n viewport,\n onViewportChange,\n loading = false,\n interactive,\n height,\n plan: planProp,\n fitPlan = true,\n // Pulled out of `...props` so the constructor and the live plan-limit effect\n // below resolve them the same way.\n minZoom,\n maxZoom,\n maxBounds,\n ...props\n },\n ref,\n) {\n const { t } = useLocale();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [containerNode, setContainerNode] = useState<HTMLDivElement | null>(null);\n const setContainer = useCallback((node: HTMLDivElement | null) => {\n containerRef.current = node;\n setContainerNode(node);\n }, []);\n const breakpoint = useMeasuredMapBreakpoint(containerNode);\n const isStatic = interactive === false;\n const [mapInstance, setMapInstance] = useState<MapLibreGL.Map | null>(null);\n const [initFailed, setInitFailed] = useState(false);\n const [isLoaded, setIsLoaded] = useState(false);\n const [isStyleLoaded, setIsStyleLoaded] = useState(false);\n const currentStyleRef = useRef<MapStyleOption | null>(null);\n const styleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const internalUpdateRef = useRef(false);\n const { resolvedTheme, themeKey } = useResolvedBasemapTheme(themeProp);\n\n const isControlled = viewport !== undefined && onViewportChange !== undefined;\n\n const onViewportChangeRef = useRef(onViewportChange);\n onViewportChangeRef.current = onViewportChange;\n\n // A string shorthand becomes a stable spec object; a spec object passes\n // through as given.\n const projection = useMemo<MapLibreGL.ProjectionSpecification | undefined>(\n () => (typeof projectionProp === \"string\" ? { type: projectionProp } : projectionProp),\n [projectionProp],\n );\n\n // Read from the mount-only `styledata` handler below so a `projection` prop\n // change after mount isn't reapplied with the value captured at mount time.\n const projectionRef = useRef(projection);\n\n const mapStyles = useMemo(() => {\n // Explicit styles win. Otherwise `blank` opts into the transparent\n // tile-less basemap; with neither, fall back to the Carto defaults.\n if (styles) {\n return {\n dark: styles.dark ?? defaultStyles.dark,\n light: styles.light ?? defaultStyles.light,\n };\n }\n if (blank) {\n return { dark: blankMapStyle, light: blankMapStyle };\n }\n return defaultStyles;\n }, [styles, blank]);\n\n // Normalize `plan` from PRIMITIVES, so an inline `plan={{ width, height }}`\n // literal keeps a stable coordinate system instead of re-running every layer\n // effect on each render. A caller who passes their own memoized `PlanCrs`\n // keeps that identity untouched.\n const planIsCrs = typeof (planProp as PlanCrs | undefined)?.toLngLat === \"function\";\n const planExtent = planIsCrs ? undefined : (planProp as PlanExtent | undefined);\n const planWidth = planExtent?.width;\n const planHeight = planExtent?.height;\n const planOrigin = planExtent?.origin ?? \"top-left\";\n const planUnit = planExtent?.unit ?? \"px\";\n const derivedPlan = useMemo(\n () =>\n planWidth !== undefined && planHeight !== undefined\n ? createPlanCrs({\n width: planWidth,\n height: planHeight,\n origin: planOrigin,\n unit: planUnit,\n })\n : null,\n [planWidth, planHeight, planOrigin, planUnit],\n );\n const planCrs = planIsCrs ? ((planProp as PlanCrs) ?? null) : derivedPlan;\n\n // A plan is a flat drawing: the globe would bend it, so refuse the pairing\n // rather than render something the consumer cannot trust.\n const projectionIsGlobe = !!projection && projection.type !== \"mercator\";\n const effectiveProjection = planCrs && projectionIsGlobe ? undefined : projection;\n projectionRef.current = effectiveProjection;\n if (planCrs && projectionIsGlobe) {\n warnMapOnce(\n \"plan-projection\",\n `Ignored projection \"${projection?.type}\" on a plan map: a plan is flat, and only the mercator projection keeps it undistorted.`,\n );\n }\n\n // Camera options a plan needs. Merged UNDER `...props`, so an explicit prop\n // always wins.\n const planOptions = useMemo(() => {\n if (!planCrs) return null;\n return {\n minZoom: planCrs.minZoom,\n maxZoom: planCrs.maxZoom,\n maxBounds: planCrs.maxBounds() as MapLibreGL.LngLatBoundsLike,\n // A plan is 2D: rotation and tilt only make it harder to read.\n dragRotate: false,\n pitchWithRotate: false,\n touchPitch: false,\n bearing: 0,\n pitch: 0,\n // A plan is usually embedded in a page, so don't swallow the wheel.\n cooperativeGestures: true,\n };\n }, [planCrs]);\n\n // Expose the map instance to the parent component.\n useImperativeHandle(ref, () => mapInstance as MapLibreGL.Map, [mapInstance]);\n\n const clearStyleTimeout = useCallback(() => {\n if (styleTimeoutRef.current) {\n clearTimeout(styleTimeoutRef.current);\n styleTimeoutRef.current = null;\n }\n }, []);\n\n const heightRef = useRef(height);\n heightRef.current = height;\n\n // ── Keeping a bounds-framed map framed ─────────────────────────────────────\n // MapLibre fits `bounds` ONCE, against the box it measures at construction.\n // A box that settles later — a responsive height resolving after the first\n // paint, a side panel opening, a tier flip — leaves the viewport fitted to a\n // rectangle that no longer exists (a 348×218 fit kept on a 348×348 box put\n // North America on a Lake Ontario locator). So: re-fit whenever the box\n // changes size, until someone moves the map themselves.\n const { bounds, fitBoundsOptions } = props;\n const refitRef = useRef({ bounds, fitBoundsOptions });\n refitRef.current = { bounds, fitBoundsOptions };\n const isControlledRef = useRef(isControlled);\n isControlledRef.current = isControlled;\n /** `true` once a gesture (or the controlled `viewport`) owns the viewport. */\n const userMovedRef = useRef(false);\n /** The box size the current viewport was fitted against. */\n const fittedSizeRef = useRef<string | null>(null);\n\n const syncBox = useCallback((map: MapLibreGL.Map, node: HTMLElement) => {\n const size = boxSize(node);\n if (!size || size === fittedSizeRef.current) return;\n fittedSizeRef.current = size;\n map.resize();\n const { bounds: currentBounds, fitBoundsOptions: currentOptions } = refitRef.current;\n if (!currentBounds || userMovedRef.current || isControlledRef.current) return;\n map.fitBounds(currentBounds, { ...currentOptions, duration: 0 });\n }, []);\n\n // Initialize the map.\n useEffect(() => {\n if (!containerRef.current) return;\n\n // The tier is measured in a layout effect whose re-render lands AFTER this\n // effect, so the box may still carry the wide-tier height here. Size it for\n // its real tier first: MapLibre sizes its canvas (and fits `bounds`) from\n // the box at construction, and drops the first resize it observes.\n Object.assign(\n containerRef.current.style,\n resolveMapHeightStyle(\n heightRef.current,\n mapBreakpointForWidth(containerRef.current.getBoundingClientRect().width),\n ),\n );\n\n const initialStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n currentStyleRef.current = initialStyle;\n\n let map: MapLibreGL.Map;\n try {\n map = new MapLibreGL.Map({\n container: containerRef.current,\n style: initialStyle,\n renderWorldCopies: false,\n // Attribution control OFF by default — a maintainer decision for internal\n // use, taken deliberately and recorded in CHANGELOG.md + the map-components\n // rule. NOTE THE CONSTRAINT: the default Carto basemap serves OpenStreetMap\n // data, which is ODbL-licensed and requires the credit, and Carto's terms\n // require it too — so a surface that ships PUBLICLY on these tiles must turn\n // it back on with `attributionControl={{ compact: true }}` (it wins through\n // `...props` below), or move to tiles licensed without the requirement via\n // `styles` / `blank`.\n attributionControl: false,\n ...(planOptions ?? {}),\n // `bounds` fits the plan in the very first frame, so a plan map never\n // flashes at lng/lat 0,0 before the fit effect below runs. Skipped when\n // the caller drives the camera themselves.\n ...(planOptions && !viewport?.center\n ? {\n bounds: planCrs!.bounds as MapLibreGL.LngLatBoundsLike,\n fitBoundsOptions: { padding: PLAN_FIT_PADDING, animate: false },\n }\n : {}),\n ...(minZoom !== undefined ? { minZoom } : {}),\n ...(maxZoom !== undefined ? { maxZoom } : {}),\n ...(maxBounds !== undefined ? { maxBounds } : {}),\n ...props,\n ...viewport,\n // Static mode: gestures off, but MapLibre's own `interactive` stays on —\n // it would detach EVERY listener, tooltips and hover included.\n ...(isStatic ? STATIC_HANDLER_OPTIONS : {}),\n });\n } catch {\n // MapLibre throws at construction when WebGL is unavailable (headless\n // browsers without GPU, remote desktops). Degrade to a quiet panel\n // instead of an unhandled render error.\n setInitFailed(true);\n return;\n }\n\n const styleDataHandler = () => {\n clearStyleTimeout();\n // Delay so the style is fully processed before layer operations — avoids\n // race conditions on setStyle without force-updating every layer.\n styleTimeoutRef.current = setTimeout(() => {\n setIsStyleLoaded(true);\n if (projectionRef.current) {\n applyProjection(map, projectionRef.current);\n }\n }, 100);\n };\n // No-op under the default (`attributionControl: false` above). This exists for\n // the case a consumer turns the control back ON for a public surface: MapLibre\n // paints even a COMPACT control expanded on first render (`<details open>` +\n // `maplibregl-compact-show`), so the credits land as a text slab until someone\n // clicks the toggle. Collapse it to the labelled ⓘ button instead.\n const loadHandler = () => {\n setIsLoaded(true);\n map\n .getContainer()\n .querySelectorAll<HTMLDetailsElement>(\"details.maplibregl-ctrl-attrib[open]\")\n .forEach((el) => {\n el.open = false;\n });\n };\n\n // Viewport change handler — skip if triggered by an internal update.\n const handleMove = () => {\n if (internalUpdateRef.current) return;\n onViewportChangeRef.current?.(getViewport(map));\n };\n\n // A gesture carries the DOM event that caused it; our own `fitBounds` /\n // `jumpTo` does not. Once a person has moved the map, a later box change\n // resizes the canvas but must never yank the view back to `bounds`.\n const handleMoveStart = (event?: { originalEvent?: unknown }) => {\n if (event?.originalEvent) userMovedRef.current = true;\n };\n\n // The size the constructor fitted `bounds` against — the baseline every\n // later box change is compared to.\n fittedSizeRef.current = boxSize(containerRef.current);\n\n map.on(\"load\", loadHandler);\n map.on(\"styledata\", styleDataHandler);\n map.on(\"move\", handleMove);\n map.on(\"movestart\", handleMoveStart);\n setMapInstance(map);\n\n return () => {\n clearStyleTimeout();\n map.off(\"load\", loadHandler);\n map.off(\"styledata\", styleDataHandler);\n map.off(\"move\", handleMove);\n map.off(\"movestart\", handleMoveStart);\n map.remove();\n setIsLoaded(false);\n setIsStyleLoaded(false);\n setMapInstance(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only: map options are init-time; style/theme/projection changes are synced by the effects below\n }, []);\n\n // Sync controlled viewport to the map.\n useEffect(() => {\n if (!mapInstance || !isControlled || !viewport) return;\n if (mapInstance.isMoving()) return;\n\n const current = getViewport(mapInstance);\n const next = {\n center: viewport.center ?? current.center,\n zoom: viewport.zoom ?? current.zoom,\n bearing: viewport.bearing ?? current.bearing,\n pitch: viewport.pitch ?? current.pitch,\n };\n\n if (\n next.center[0] === current.center[0] &&\n next.center[1] === current.center[1] &&\n next.zoom === current.zoom &&\n next.bearing === current.bearing &&\n next.pitch === current.pitch\n ) {\n return;\n }\n\n internalUpdateRef.current = true;\n mapInstance.jumpTo(next);\n internalUpdateRef.current = false;\n }, [mapInstance, isControlled, viewport]);\n\n // Swap the basemap style when the theme flips.\n useEffect(() => {\n if (!mapInstance || !resolvedTheme) return;\n\n const newStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n\n if (currentStyleRef.current === newStyle) return;\n\n clearStyleTimeout();\n currentStyleRef.current = newStyle;\n setIsStyleLoaded(false);\n\n mapInstance.setStyle(newStyle, { diff: true });\n }, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]);\n\n // Sync projection when the prop changes after mount.\n useEffect(() => {\n if (!mapInstance || !isStyleLoaded || !effectiveProjection) return;\n applyProjection(mapInstance, effectiveProjection);\n }, [mapInstance, isStyleLoaded, effectiveProjection]);\n\n // Keep a plan map's camera limits live — unlike the geographic options above,\n // which are init-time. A plan's extent is often only known once its image has\n // decoded, and a multi-floor building can change it after mount.\n useEffect(() => {\n if (!mapInstance || !planCrs) return;\n mapInstance.setMinZoom(minZoom ?? planCrs.minZoom);\n mapInstance.setMaxZoom(maxZoom ?? planCrs.maxZoom);\n mapInstance.setMaxBounds((maxBounds ?? planCrs.maxBounds()) as MapLibreGL.LngLatBoundsLike);\n }, [mapInstance, planCrs, minZoom, maxZoom, maxBounds]);\n\n // Fit the plan once per extent. Keyed on the extent rather than on the object\n // so a floor swap at the same size does not yank the user's view.\n const fittedExtentRef = useRef<string | null>(null);\n useEffect(() => {\n if (!mapInstance || !isLoaded || !planCrs || !fitPlan) return;\n if (isControlled || viewport?.center) return;\n\n const { width, height, origin } = planCrs.extent;\n const extentKey = `${width}x${height}:${origin}`;\n if (fittedExtentRef.current === extentKey) return;\n fittedExtentRef.current = extentKey;\n\n mapInstance.fitBounds(planCrs.bounds as MapLibreGL.LngLatBoundsLike, {\n padding: PLAN_FIT_PADDING,\n animate: false,\n });\n }, [mapInstance, isLoaded, planCrs, fitPlan, isControlled, viewport?.center]);\n\n // Basemap labels on / off (c-6, c-11). Re-runs after every style load, so a\n // theme flip (which swaps the whole style) does not bring the labels back.\n const hiddenLabelLayersRef = useRef<Set<string>>(new Set());\n useEffect(() => {\n if (!mapInstance || !isStyleLoaded) return;\n applyBasemapLabels(mapInstance, basemapLabels, hiddenLabelLayersRef.current);\n }, [mapInstance, isStyleLoaded, basemapLabels, themeKey]);\n\n // Static mode on / off after mount. Untouched until the map is first made\n // static, so an interactive map keeps MapLibre's own setup exactly.\n const handlerOptionsRef = useRef<Partial<Record<GestureHandlerKey, unknown>>>(props);\n handlerOptionsRef.current = props;\n const wasStaticRef = useRef(false);\n useEffect(() => {\n if (!mapInstance) return;\n if (!isStatic && !wasStaticRef.current) return;\n wasStaticRef.current = true;\n for (const key of GESTURE_HANDLERS) {\n const handler = mapInstance[key] as { enable?: () => void; disable?: () => void } | undefined;\n if (isStatic) handler?.disable?.();\n else if (handlerOptionsRef.current[key] !== false) handler?.enable?.();\n }\n // MapLibre's grab cursor hangs off this class; a static map keeps the\n // default arrow (a layer's hover still sets its own pointer on the canvas).\n mapInstance.getCanvasContainer().classList.toggle(\"maplibregl-interactive\", !isStatic);\n mapInstance.getCanvas().tabIndex = isStatic ? -1 : 0;\n }, [mapInstance, isStatic]);\n\n // c-10: the canvas is a tab stop MapLibre owns, so before this it painted\n // the BROWSER's default focus ring (`1px auto rgb(0, 95, 204)` in light,\n // `rgb(153, 200, 255)` in dark) — visible, but not the theme's. The house\n // indicator is a utility class, so the element simply wears it.\n useEffect(() => {\n if (!mapInstance) return;\n mapInstance.getCanvas()?.classList.add(\"focus-ring\");\n }, [mapInstance]);\n\n // Watch the box: any size change resizes the canvas and re-fits `bounds`.\n useEffect(() => {\n if (!mapInstance || !containerNode) return undefined;\n const sync = () => syncBox(mapInstance, containerNode);\n sync();\n if (typeof ResizeObserver === \"undefined\") return undefined;\n const observer = new ResizeObserver(sync);\n observer.observe(containerNode);\n return () => observer.disconnect();\n }, [mapInstance, containerNode, syncBox]);\n\n // A tier change can swap the box's height (the default goes square at\n // `narrow`); sync at commit rather than wait on the observer's next frame.\n const heightStyle = resolveMapHeightStyle(height, breakpoint);\n const heightKey = `${heightStyle.height ?? \"\"}|${heightStyle.aspectRatio ?? \"\"}`;\n const appliedHeightKeyRef = useRef(heightKey);\n useEffect(() => {\n if (!mapInstance || !containerNode || heightKey === appliedHeightKeyRef.current) return;\n appliedHeightKeyRef.current = heightKey;\n syncBox(mapInstance, containerNode);\n }, [mapInstance, containerNode, heightKey, syncBox]);\n\n // Furniture outside the map box (legends `above` / `below`, the narrow\n // annotation key) portals into strips rendered only while something asks.\n const [slotRequests, setSlotRequests] = useState<Record<MapFrameSide, number>>({\n above: 0,\n below: 0,\n });\n const [aboveEl, setAboveEl] = useState<HTMLDivElement | null>(null);\n const [belowEl, setBelowEl] = useState<HTMLDivElement | null>(null);\n const requestSlot = useCallback((side: MapFrameSide) => {\n setSlotRequests((prev) => ({ ...prev, [side]: prev[side] + 1 }));\n return () => setSlotRequests((prev) => ({ ...prev, [side]: Math.max(0, prev[side] - 1) }));\n }, []);\n const frameValue = useMemo<MapFrameContextValue>(\n () => ({\n breakpoint,\n interactive: !isStatic,\n slots: { above: aboveEl, below: belowEl },\n requestSlot,\n }),\n [breakpoint, isStatic, aboveEl, belowEl, requestSlot],\n );\n const annotations = useMapAnnotationRegistry(breakpoint);\n const showBelow = slotRequests.below > 0 || annotations.rows.length > 0;\n\n const contextValue = useMemo(\n () => ({\n map: mapInstance,\n isLoaded: isLoaded && isStyleLoaded,\n resolvedTheme,\n themeKey,\n plan: planCrs,\n loading,\n }),\n [mapInstance, isLoaded, isStyleLoaded, resolvedTheme, themeKey, planCrs, loading],\n );\n\n if (initFailed) {\n return (\n <div className={cn(\"relative h-full w-full\", className)}>\n <StatePanel\n kind=\"error\"\n title={t(\"maps.canvas.unavailableTitle\")}\n description={t(\"maps.canvas.unavailableDescription\")}\n className=\"h-full\"\n />\n </div>\n );\n }\n\n return (\n <MapContext.Provider value={contextValue}>\n <MapFrameContext.Provider value={frameValue}>\n <MapAnnotationRegistryProvider value={annotations.value}>\n {slotRequests.above > 0 && (\n <div ref={setAboveEl} data-slot=\"map-canvas-above\" className=\"pb-2\" />\n )}\n <div\n ref={setContainer}\n data-slot=\"map-canvas\"\n data-map-breakpoint={breakpoint}\n data-interactive={isStatic ? \"false\" : undefined}\n className={cn(\"relative h-full w-full\", className)}\n style={heightStyle}\n >\n {(!isLoaded || loading) && <MapLoadingOverlay />}\n {/* SSR-safe: children render only when the map exists on the client. */}\n {mapInstance && children}\n </div>\n {showBelow && (\n <div data-slot=\"map-canvas-below\" className=\"flex flex-col gap-2 pt-2\">\n <div ref={setBelowEl} data-slot=\"map-canvas-below-furniture\" className=\"contents\" />\n {annotations.rows.length > 0 && <MapAnnotationKey rows={annotations.rows} />}\n </div>\n )}\n </MapAnnotationRegistryProvider>\n </MapFrameContext.Provider>\n </MapContext.Provider>\n );\n});\n"],"mappings":"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","names":["MapCanvas","height"]}
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import {
3
+ useMap
4
+ } from "./chunk-JYFERXZT.js";
5
+
6
+ // src/lib/use-token-color.ts
7
+ import { resolveTokenColor } from "@elabs-ai/components-tokens";
8
+ import { useLayoutEffect, useState } from "react";
9
+ function useTokenColor(name, fallback = "#000000") {
10
+ const { map, themeKey } = useMap();
11
+ const [color, setColor] = useState(fallback);
12
+ useLayoutEffect(() => {
13
+ setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));
14
+ }, [name, fallback, map, themeKey]);
15
+ return color;
16
+ }
17
+
18
+ export {
19
+ useTokenColor
20
+ };
21
+ //# sourceMappingURL=chunk-Y44MEKJV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/use-token-color.ts"],"sourcesContent":["\"use client\";\n\nimport { resolveTokenColor } from \"@elabs-ai/components-tokens\";\nimport { useLayoutEffect, useState } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\n/**\n * Resolve a semantic token (e.g. `\"--primary\"`) to a concrete color MapLibre's\n * WebGL paint can consume, re-resolving whenever the brand theme changes.\n * This is how default layer paints stay token-driven: WebGL can't read CSS\n * custom properties, so we read them at runtime off the map container.\n */\nexport function useTokenColor(name: string, fallback = \"#000000\"): string {\n const { map, themeKey } = useMap();\n const [color, setColor] = useState(fallback);\n\n useLayoutEffect(() => {\n setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));\n }, [name, fallback, map, themeKey]);\n\n return color;\n}\n"],"mappings":";;;;;;AAEA,SAAS,yBAAyB;AAClC,SAAS,iBAAiB,gBAAgB;AAUnC,SAAS,cAAc,MAAc,WAAW,WAAmB;AACxE,QAAM,EAAE,KAAK,SAAS,IAAI,OAAO;AACjC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ;AAE3C,kBAAgB,MAAM;AACpB,aAAS,kBAAkB,MAAM,EAAE,IAAI,KAAK,aAAa,GAAG,SAAS,CAAC,CAAC;AAAA,EACzE,GAAG,CAAC,MAAM,UAAU,KAAK,QAAQ,CAAC;AAElC,SAAO;AACT;","names":[]}
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ // src/map-canvas/use-resolved-basemap-theme.ts
4
+ import { resolveThemeIsDark } from "@elabs-ai/components-tokens";
5
+ import { useEffect, useState } from "react";
6
+ function getBrandTheme() {
7
+ if (typeof document === "undefined") return null;
8
+ const root = document.documentElement;
9
+ if (root.getAttribute("data-theme")) return resolveThemeIsDark(root) ? "dark" : "light";
10
+ if (root.classList.contains("dark")) return "dark";
11
+ if (root.classList.contains("light")) return "light";
12
+ return null;
13
+ }
14
+ function getSystemTheme() {
15
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
16
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
17
+ }
18
+ function getThemeAttribute() {
19
+ if (typeof document === "undefined") return "";
20
+ return document.documentElement.getAttribute("data-theme") ?? "";
21
+ }
22
+ function useResolvedBasemapTheme(themeProp) {
23
+ const [detected, setDetected] = useState(() => getBrandTheme() ?? getSystemTheme());
24
+ const [themeAttr, setThemeAttr] = useState(getThemeAttribute);
25
+ useEffect(() => {
26
+ const update = () => {
27
+ setThemeAttr(getThemeAttribute());
28
+ const brand = getBrandTheme();
29
+ if (brand) setDetected(brand);
30
+ };
31
+ const observer = new MutationObserver(update);
32
+ observer.observe(document.documentElement, {
33
+ attributes: true,
34
+ attributeFilter: ["data-theme", "class"]
35
+ });
36
+ const mediaQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
37
+ const handleSystemChange = (e) => {
38
+ if (!getBrandTheme()) setDetected(e.matches ? "dark" : "light");
39
+ };
40
+ mediaQuery?.addEventListener("change", handleSystemChange);
41
+ return () => {
42
+ observer.disconnect();
43
+ mediaQuery?.removeEventListener("change", handleSystemChange);
44
+ };
45
+ }, []);
46
+ const resolvedTheme = themeProp ?? detected;
47
+ return { resolvedTheme, themeKey: `${themeAttr}:${resolvedTheme}` };
48
+ }
49
+
50
+ export {
51
+ useResolvedBasemapTheme
52
+ };
53
+ //# sourceMappingURL=chunk-Z4EPSOB4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/map-canvas/use-resolved-basemap-theme.ts"],"sourcesContent":["\"use client\";\n\nimport { resolveThemeIsDark } from \"@elabs-ai/components-tokens\";\nimport { useEffect, useState } from \"react\";\n\nimport type { BasemapTheme } from \"./map-context\";\n\n/**\n * Brand theme → basemap flavor. When a `data-theme` is set (the ThemeProvider\n * contract) the theme's own `color-scheme` is authoritative via\n * `resolveThemeIsDark` — which is why a CONSUMER-AUTHORED dark theme gets the\n * dark basemap without registering anything here (ADR 0029; the old\n * `THEME_META[theme].dark` lookup only knew the themes this repo ships). A\n * `dark`/`light` class on `<html>` is honored for non-brand hosts (next-themes\n * et al.); the OS preference is only the last-resort fallback.\n */\nfunction getBrandTheme(): BasemapTheme | null {\n if (typeof document === \"undefined\") return null;\n const root = document.documentElement;\n if (root.getAttribute(\"data-theme\")) return resolveThemeIsDark(root) ? \"dark\" : \"light\";\n if (root.classList.contains(\"dark\")) return \"dark\";\n if (root.classList.contains(\"light\")) return \"light\";\n return null;\n}\n\nfunction getSystemTheme(): BasemapTheme {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\nfunction getThemeAttribute(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.documentElement.getAttribute(\"data-theme\") ?? \"\";\n}\n\n/**\n * Resolve the basemap flavor and a change-key for the active brand theme.\n * The observer stays active even with an explicit `theme` prop: token colors\n * (routes, clusters, …) must still re-resolve when `data-theme` changes.\n */\nexport function useResolvedBasemapTheme(themeProp?: BasemapTheme): {\n resolvedTheme: BasemapTheme;\n themeKey: string;\n} {\n const [detected, setDetected] = useState<BasemapTheme>(() => getBrandTheme() ?? getSystemTheme());\n const [themeAttr, setThemeAttr] = useState(getThemeAttribute);\n\n useEffect(() => {\n const update = () => {\n setThemeAttr(getThemeAttribute());\n const brand = getBrandTheme();\n if (brand) setDetected(brand);\n };\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\"],\n });\n\n const mediaQuery =\n typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(prefers-color-scheme: dark)\")\n : null;\n const handleSystemChange = (e: MediaQueryListEvent) => {\n if (!getBrandTheme()) setDetected(e.matches ? \"dark\" : \"light\");\n };\n mediaQuery?.addEventListener(\"change\", handleSystemChange);\n\n return () => {\n observer.disconnect();\n mediaQuery?.removeEventListener(\"change\", handleSystemChange);\n };\n }, []);\n\n const resolvedTheme = themeProp ?? detected;\n return { resolvedTheme, themeKey: `${themeAttr}:${resolvedTheme}` };\n}\n"],"mappings":";;;AAEA,SAAS,0BAA0B;AACnC,SAAS,WAAW,gBAAgB;AAapC,SAAS,gBAAqC;AAC5C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS;AACtB,MAAI,KAAK,aAAa,YAAY,EAAG,QAAO,mBAAmB,IAAI,IAAI,SAAS;AAChF,MAAI,KAAK,UAAU,SAAS,MAAM,EAAG,QAAO;AAC5C,MAAI,KAAK,UAAU,SAAS,OAAO,EAAG,QAAO;AAC7C,SAAO;AACT;AAEA,SAAS,iBAA+B;AACtC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,WAAY,QAAO;AACrF,SAAO,OAAO,WAAW,8BAA8B,EAAE,UAAU,SAAS;AAC9E;AAEA,SAAS,oBAA4B;AACnC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,SAAO,SAAS,gBAAgB,aAAa,YAAY,KAAK;AAChE;AAOO,SAAS,wBAAwB,WAGtC;AACA,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,MAAM,cAAc,KAAK,eAAe,CAAC;AAChG,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,iBAAiB;AAE5D,YAAU,MAAM;AACd,UAAM,SAAS,MAAM;AACnB,mBAAa,kBAAkB,CAAC;AAChC,YAAM,QAAQ,cAAc;AAC5B,UAAI,MAAO,aAAY,KAAK;AAAA,IAC9B;AACA,UAAM,WAAW,IAAI,iBAAiB,MAAM;AAC5C,aAAS,QAAQ,SAAS,iBAAiB;AAAA,MACzC,YAAY;AAAA,MACZ,iBAAiB,CAAC,cAAc,OAAO;AAAA,IACzC,CAAC;AAED,UAAM,aACJ,OAAO,OAAO,eAAe,aACzB,OAAO,WAAW,8BAA8B,IAChD;AACN,UAAM,qBAAqB,CAAC,MAA2B;AACrD,UAAI,CAAC,cAAc,EAAG,aAAY,EAAE,UAAU,SAAS,OAAO;AAAA,IAChE;AACA,gBAAY,iBAAiB,UAAU,kBAAkB;AAEzD,WAAO,MAAM;AACX,eAAS,WAAW;AACpB,kBAAY,oBAAoB,UAAU,kBAAkB;AAAA,IAC9D;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,aAAa;AACnC,SAAO,EAAE,eAAe,UAAU,GAAG,SAAS,IAAI,aAAa,GAAG;AACpE;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-Z5OPDBDC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}