@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.d.ts CHANGED
@@ -1,7 +1,122 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, HTMLAttributes } from 'react';
3
3
  import MapLibreGL, { MarkerOptions, PopupOptions } from 'maplibre-gl';
4
- export { LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MapMouseEvent, MapOptions, ProjectionSpecification, StyleSpecification } from 'maplibre-gl';
4
+ export { Coordinates, FitBoundsOptions, ImageSourceSpecification, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MapMouseEvent, MapOptions, ProjectionSpecification, StyleSpecification } from 'maplibre-gl';
5
+ import { LucideIcon } from 'lucide-react';
6
+ import { ColorScale, ColorScaleValue, ColorScaleSpec } from '@elabs-ai/components-ui';
7
+
8
+ /**
9
+ * A coordinate system for a CUSTOM, non-geographic plan — a floor plan, a
10
+ * factory layout, a train carriage, a rack elevation. Kept engine-free so it can
11
+ * be unit-tested (same convention as `arc-math.ts`).
12
+ *
13
+ * MapLibre GL JS renders Web Mercator and nothing else: it has no custom CRS and
14
+ * none is planned, so a plan coordinate system has to be SYNTHESIZED on top of
15
+ * Mercator. The same convention as Leaflet's `CRS.Simple` (origin top-left, one
16
+ * map unit = one pixel) and Qlik Sense's image background layer (corner
17
+ * coordinates in the image's own pixels, projection "undefined").
18
+ *
19
+ * The plan is mapped LINEARLY INTO NORMALIZED MERCATOR, not linearly into
20
+ * degrees. Normalized Mercator is what the screen shows, so one scale factor for
21
+ * both axes keeps the plan's aspect ratio exact at every zoom and every plan
22
+ * size. Mapping linearly into degrees — the naive reading of "undefined degrees"
23
+ * — stretches the plan vertically by Mercator's cos(lat) factor: a 1600 x 900
24
+ * plan comes out at aspect 1.996 instead of 1.778, 12% too wide.
25
+ *
26
+ * What this is NOT: a georeference. Mercator's y is non-linear in latitude, so
27
+ * lengths measured on the Earth are meaningless here. Distances belong to the
28
+ * plan (`distance()` returns plan units); never put a scale bar on a plan map,
29
+ * and never combine a plan with the globe projection.
30
+ */
31
+ /**
32
+ * How much of normalized Mercator the plan's LONG side spans. Normalizing on the
33
+ * long side caps the footprint whatever the aspect ratio: the tallest possible
34
+ * plan reaches +-66.5 deg latitude, comfortably inside Mercator's +-85.05 limit.
35
+ */
36
+ declare const PLAN_MERCATOR_SPAN = 0.5;
37
+ /** A point in plan units. */
38
+ interface PlanPoint {
39
+ x: number;
40
+ y: number;
41
+ }
42
+ /** The plan's own extent and units — everything the coordinate system needs. */
43
+ interface PlanExtent {
44
+ /** Plan width in plan units (image pixels, millimetres, metres — your choice). */
45
+ width: number;
46
+ /** Plan height in plan units. */
47
+ height: number;
48
+ /**
49
+ * Where plan (0, 0) sits. `"top-left"` (the default) matches an image and
50
+ * Leaflet's `CRS.Simple`; `"bottom-left"` matches y-up CAD/DXF exports.
51
+ */
52
+ origin?: "top-left" | "bottom-left";
53
+ /** Unit name, for labels and readouts only — the maths never reads it. */
54
+ unit?: string;
55
+ }
56
+ /** The four image-source corners MapLibre wants: top-left, top-right, bottom-right, bottom-left. */
57
+ type PlanImageCoordinates = [
58
+ [
59
+ number,
60
+ number
61
+ ],
62
+ [
63
+ number,
64
+ number
65
+ ],
66
+ [
67
+ number,
68
+ number
69
+ ],
70
+ [
71
+ number,
72
+ number
73
+ ]
74
+ ];
75
+ /** A two-way transform between plan units and lng/lat, plus everything derived from it. */
76
+ interface PlanCrs {
77
+ /** The extent this was built from, with defaults applied. */
78
+ readonly extent: Required<PlanExtent>;
79
+ /** Normalized Mercator units per plan unit. */
80
+ readonly scale: number;
81
+ /** Plan point -> `[lng, lat]`. */
82
+ toLngLat(point: PlanPoint | [number, number]): [number, number];
83
+ /** `[lng, lat]` -> plan point. */
84
+ toPlan(lngLat: [number, number] | {
85
+ lng: number;
86
+ lat: number;
87
+ }): PlanPoint;
88
+ /** Deep-convert GeoJSON authored in plan units into lng/lat GeoJSON. */
89
+ toGeoJSON<T>(data: T): T;
90
+ /** The plan's bounds as `[[west, south], [east, north]]`. */
91
+ readonly bounds: [[number, number], [number, number]];
92
+ /** Bounds grown by `padFraction` of the long side — a pan limit that still shows the edges. */
93
+ maxBounds(padFraction?: number): [[number, number], [number, number]];
94
+ /** The plan's four corners, ready for a MapLibre `image` or `canvas` source. */
95
+ readonly imageCoordinates: PlanImageCoordinates;
96
+ /** The zoom at which the plan's long side covers `px` CSS pixels. */
97
+ zoomForLongSidePixels(px: number): number;
98
+ /** Zoom at which the plan's long side is 128 px — small but still findable. */
99
+ readonly minZoom: number;
100
+ /** Zoom at which the plan's long side is 32768 px — a desk, a machine, a seat. */
101
+ readonly maxZoom: number;
102
+ /** Straight-line distance between two plan points, in plan units. */
103
+ distance(a: PlanPoint, b: PlanPoint): number;
104
+ }
105
+ /**
106
+ * Build a plan coordinate system from the plan's extent.
107
+ *
108
+ * ```tsx
109
+ * const plan = createPlanCrs({ width: 2400, height: 1600, unit: "cm" });
110
+ * <MapCanvas blank plan={plan}>
111
+ * <MapPlanImage src={floorPlanPng} />
112
+ * <MapGeoJSON data={roomsInPlanUnits} promoteId="id" />
113
+ * </MapCanvas>
114
+ * ```
115
+ *
116
+ * Throws when the extent is not a pair of finite, positive numbers — a NaN
117
+ * coordinate system is unfixable three layers downstream.
118
+ */
119
+ declare function createPlanCrs(extent: PlanExtent): PlanCrs;
5
120
 
6
121
  /** Light-or-dark flavor of the active basemap (derived from the brand theme). */
7
122
  type BasemapTheme = "light" | "dark";
@@ -17,10 +132,78 @@ interface MapContextValue {
17
132
  * as a dependency key to re-resolve semantic token colors for WebGL paint.
18
133
  */
19
134
  themeKey: string;
135
+ /**
136
+ * The plan coordinate system when this canvas is a CUSTOM (non-geographic)
137
+ * plan map — a floor plan, factory layout or carriage — and `null` for an
138
+ * ordinary geographic map. Layer components read it to convert the plan
139
+ * coordinates they are handed, and to report plan coordinates back.
140
+ */
141
+ plan: PlanCrs | null;
142
+ /** True while the consumer has asked for a loading overlay over the map. */
143
+ loading: boolean;
20
144
  }
21
145
  /** Access the map instance + load state from any descendant of `<MapCanvas>`. */
22
146
  declare function useMap(): MapContextValue;
23
147
 
148
+ /** A map container's width tier — the same three tiers as a chart's. */
149
+ type MapBreakpoint = "narrow" | "medium" | "wide";
150
+ /** Every tier, narrowest first. */
151
+ declare const MAP_BREAKPOINTS: readonly MapBreakpoint[];
152
+ /**
153
+ * A width (CSS px) below `narrow` is narrow; below `medium` is medium; anything
154
+ * else is wide. A boundary belongs to the wider tier (480 → medium). Same
155
+ * numbers as `CHART_BREAKPOINT_THRESHOLDS`.
156
+ */
157
+ declare const MAP_BREAKPOINT_THRESHOLDS: {
158
+ readonly narrow: 480;
159
+ readonly medium: 768;
160
+ };
161
+ /**
162
+ * The tier for a measured container width. `0` or a non-finite width means
163
+ * "not measured yet" (server render, first pass, `display: none`) and
164
+ * resolves to `wide`, so a first paint matches the pre-tier layout.
165
+ */
166
+ declare function mapBreakpointForWidth(width: number): MapBreakpoint;
167
+ /**
168
+ * Per-tier values, desktop-first: `base` is the WIDE value (the opposite of
169
+ * Tailwind's unprefixed, mobile-first class); overrides go down.
170
+ */
171
+ interface MapResponsiveByBreakpoint<T> {
172
+ /** The value at `wide`, and the fallback for every tier that sets nothing. */
173
+ base: T;
174
+ /** At `medium` — and at `narrow` too, unless `narrow` is set. */
175
+ medium?: T;
176
+ /** At `narrow` only. */
177
+ narrow?: T;
178
+ }
179
+ /**
180
+ * One value for every tier, or per-tier values. `T` must never be an object
181
+ * type with its own `base` key (that key marks the per-tier form). Read it
182
+ * through {@link resolveMapResponsive} / {@link useMapResponsive}.
183
+ */
184
+ type MapResponsive<T> = T | MapResponsiveByBreakpoint<T>;
185
+ /** True for the per-tier form: a plain object with its own `base` key. */
186
+ declare function isMapResponsiveByBreakpoint<T>(value: MapResponsive<T>): value is MapResponsiveByBreakpoint<T>;
187
+ /**
188
+ * The value for `breakpoint`: `wide` → `base`; `medium` → `medium ?? base`;
189
+ * `narrow` → `narrow ?? medium ?? base`. "Not set" means `undefined`.
190
+ */
191
+ declare function resolveMapResponsive<T>(value: MapResponsive<T>, breakpoint: MapBreakpoint): T;
192
+ /** CSS px, or the map's width ÷ height (`{ aspect: 2 }` = twice as wide as tall). */
193
+ type MapHeight = number | {
194
+ aspect: number;
195
+ };
196
+ /**
197
+ * The height a `<MapCanvas>` falls back to when its parent gives it none:
198
+ * 1.6 : 1, and square at `narrow` — an aspect-locked map at 380 px would
199
+ * otherwise shrink to a strip nobody can read.
200
+ */
201
+ declare const DEFAULT_MAP_HEIGHT: MapResponsiveByBreakpoint<MapHeight>;
202
+ /** The enclosing map's tier (`wide` outside a `<MapCanvas>`). */
203
+ declare function useMapBreakpoint(): MapBreakpoint;
204
+ /** `value` resolved at the enclosing map's tier. */
205
+ declare function useMapResponsive<T>(value: MapResponsive<T>): T;
206
+
24
207
  /** Map viewport state. */
25
208
  interface MapViewport {
26
209
  /** Center coordinates [longitude, latitude]. */
@@ -33,6 +216,8 @@ interface MapViewport {
33
216
  pitch: number;
34
217
  }
35
218
  type MapStyleOption = string | MapLibreGL.StyleSpecification;
219
+ /** `"mercator"` / `"globe"`, or a full MapLibre projection spec. */
220
+ type MapProjectionOption = "mercator" | "globe" | MapLibreGL.ProjectionSpecification;
36
221
  /** The imperative handle exposed by `<MapCanvas ref>`: the MapLibre map itself. */
37
222
  type MapCanvasRef = MapLibreGL.Map;
38
223
  type MapCanvasProps = {
@@ -57,8 +242,37 @@ type MapCanvasProps = {
57
242
  * visualizations. Ignored when an explicit `styles` prop is provided.
58
243
  */
59
244
  blank?: boolean;
60
- /** Map projection type. Use `{ type: "globe" }` for a 3D globe view. */
61
- projection?: MapLibreGL.ProjectionSpecification;
245
+ /**
246
+ * Draw the basemap's own place labels. `false` leaves the ground and hides
247
+ * every text label the basemap style carries, so the map's own labels
248
+ * (`MapMarker`'s `label`, `MapAnnotation`) are the only naming on it — the
249
+ * locator recipe, and the default inside `MapInset`, where a full-size
250
+ * label is sliced by the frame. Default `true`.
251
+ */
252
+ basemapLabels?: boolean;
253
+ /**
254
+ * Map projection: `"mercator"` (MapLibre's default) or `"globe"` for a 3D
255
+ * globe view, or a full MapLibre projection spec. Feature-detected: a
256
+ * MapLibre build that cannot switch projections ignores it (with a console
257
+ * note).
258
+ */
259
+ projection?: MapProjectionOption;
260
+ /**
261
+ * `false` makes the map STATIC — the editorial / locator default: no zoom,
262
+ * pan, rotate or keyboard handlers, the default cursor and no tab stop
263
+ * (leave `<MapControls>` out of a static map). Hover and click still reach
264
+ * layers and markers, so tooltips keep working. The viewport can still be set in code
265
+ * (`viewport`, the ref). Default `true`.
266
+ */
267
+ interactive?: boolean;
268
+ /**
269
+ * The map's height: CSS px, or `{ aspect }` (width ÷ height), optionally per
270
+ * tier — `{ base: { aspect: 1.6 }, narrow: { aspect: 1 } }`. Tiers are
271
+ * measured on the map's own width (`narrow` < 480 px, `medium` < 768 px).
272
+ * Unset, the map fills its parent as before; a parent with no height of its
273
+ * own gets `DEFAULT_MAP_HEIGHT` (1.6 : 1, square at `narrow`) instead of 0.
274
+ */
275
+ height?: MapResponsive<MapHeight>;
62
276
  /**
63
277
  * Controlled viewport. When provided together with `onViewportChange`, the
64
278
  * map becomes controlled and the viewport is driven by this prop.
@@ -72,7 +286,28 @@ type MapCanvasProps = {
72
286
  onViewportChange?: (viewport: MapViewport) => void;
73
287
  /** Show a loading overlay on the map (e.g. while the app fetches map data). */
74
288
  loading?: boolean;
75
- } & Omit<MapLibreGL.MapOptions, "container" | "style">;
289
+ /**
290
+ * Turn the canvas into a CUSTOM (non-geographic) plan map: a floor plan, a
291
+ * factory layout, a train carriage, a rack elevation. Pass the plan's extent
292
+ * (`{ width, height }` in the plan's own units) or a `createPlanCrs(…)`
293
+ * result, and every layer inside then speaks PLAN coordinates instead of
294
+ * lng/lat — shapes, routes, markers and popups alike.
295
+ *
296
+ * The plan is fitted on mount, pan and zoom are clamped to its extent, and
297
+ * rotation and pitch are off (a rotated floor plan is unreadable). Pair it
298
+ * with `blank` and, for a picture under the shapes, `<MapPlanImage>`.
299
+ *
300
+ * The plan coordinate system is synthesized on Web Mercator, so it is not a
301
+ * georeference: distances belong to the plan, and a scale bar would lie.
302
+ */
303
+ plan?: PlanExtent | PlanCrs;
304
+ /**
305
+ * Fit a plan map to its extent on mount and whenever the extent changes
306
+ * (default: true). Ignored on a geographic map, and skipped when the camera
307
+ * is driven through `viewport`.
308
+ */
309
+ fitPlan?: boolean;
310
+ } & Omit<MapLibreGL.MapOptions, "container" | "style" | "interactive">;
76
311
  /**
77
312
  * The root map surface — a token/theme-aware MapLibre GL canvas. Compose the
78
313
  * other `@elabs-ai/components-maps` components (markers, popups, controls, layers) as
@@ -103,8 +338,37 @@ declare const MapCanvas: react.ForwardRefExoticComponent<{
103
338
  * visualizations. Ignored when an explicit `styles` prop is provided.
104
339
  */
105
340
  blank?: boolean;
106
- /** Map projection type. Use `{ type: "globe" }` for a 3D globe view. */
107
- projection?: MapLibreGL.ProjectionSpecification;
341
+ /**
342
+ * Draw the basemap's own place labels. `false` leaves the ground and hides
343
+ * every text label the basemap style carries, so the map's own labels
344
+ * (`MapMarker`'s `label`, `MapAnnotation`) are the only naming on it — the
345
+ * locator recipe, and the default inside `MapInset`, where a full-size
346
+ * label is sliced by the frame. Default `true`.
347
+ */
348
+ basemapLabels?: boolean;
349
+ /**
350
+ * Map projection: `"mercator"` (MapLibre's default) or `"globe"` for a 3D
351
+ * globe view, or a full MapLibre projection spec. Feature-detected: a
352
+ * MapLibre build that cannot switch projections ignores it (with a console
353
+ * note).
354
+ */
355
+ projection?: MapProjectionOption;
356
+ /**
357
+ * `false` makes the map STATIC — the editorial / locator default: no zoom,
358
+ * pan, rotate or keyboard handlers, the default cursor and no tab stop
359
+ * (leave `<MapControls>` out of a static map). Hover and click still reach
360
+ * layers and markers, so tooltips keep working. The viewport can still be set in code
361
+ * (`viewport`, the ref). Default `true`.
362
+ */
363
+ interactive?: boolean;
364
+ /**
365
+ * The map's height: CSS px, or `{ aspect }` (width ÷ height), optionally per
366
+ * tier — `{ base: { aspect: 1.6 }, narrow: { aspect: 1 } }`. Tiers are
367
+ * measured on the map's own width (`narrow` < 480 px, `medium` < 768 px).
368
+ * Unset, the map fills its parent as before; a parent with no height of its
369
+ * own gets `DEFAULT_MAP_HEIGHT` (1.6 : 1, square at `narrow`) instead of 0.
370
+ */
371
+ height?: MapResponsive<MapHeight>;
108
372
  /**
109
373
  * Controlled viewport. When provided together with `onViewportChange`, the
110
374
  * map becomes controlled and the viewport is driven by this prop.
@@ -118,43 +382,145 @@ declare const MapCanvas: react.ForwardRefExoticComponent<{
118
382
  onViewportChange?: (viewport: MapViewport) => void;
119
383
  /** Show a loading overlay on the map (e.g. while the app fetches map data). */
120
384
  loading?: boolean;
121
- } & Omit<MapLibreGL.MapOptions, "container" | "style"> & react.RefAttributes<MapLibreGL.Map>>;
385
+ /**
386
+ * Turn the canvas into a CUSTOM (non-geographic) plan map: a floor plan, a
387
+ * factory layout, a train carriage, a rack elevation. Pass the plan's extent
388
+ * (`{ width, height }` in the plan's own units) or a `createPlanCrs(…)`
389
+ * result, and every layer inside then speaks PLAN coordinates instead of
390
+ * lng/lat — shapes, routes, markers and popups alike.
391
+ *
392
+ * The plan is fitted on mount, pan and zoom are clamped to its extent, and
393
+ * rotation and pitch are off (a rotated floor plan is unreadable). Pair it
394
+ * with `blank` and, for a picture under the shapes, `<MapPlanImage>`.
395
+ *
396
+ * The plan coordinate system is synthesized on Web Mercator, so it is not a
397
+ * georeference: distances belong to the plan, and a scale bar would lie.
398
+ */
399
+ plan?: PlanExtent | PlanCrs;
400
+ /**
401
+ * Fit a plan map to its extent on mount and whenever the extent changes
402
+ * (default: true). Ignored on a geographic map, and skipped when the camera
403
+ * is driven through `viewport`.
404
+ */
405
+ fitPlan?: boolean;
406
+ } & Omit<MapLibreGL.MapOptions, "container" | "style" | "interactive"> & react.RefAttributes<MapLibreGL.Map>>;
407
+
408
+ /**
409
+ * The eight label anchors a map label or annotation can sit at, relative to
410
+ * its point. Compass-style and PHYSICAL on purpose: east of Toronto is east in
411
+ * every writing direction, so a map label never mirrors in RTL.
412
+ */
413
+ type MapLabelAnchor = "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
414
+ /** Every anchor, clockwise from `top` (for stories and validation). */
415
+ declare const MAP_LABEL_ANCHORS: readonly MapLabelAnchor[];
416
+ /**
417
+ * Where a label box sits for `anchor`, `distance` px away from its point:
418
+ * `dx` / `dy` is the attach point (the box's nearest edge or corner) relative
419
+ * to the point, and `fx` / `fy` the box's own offset as a fraction of its
420
+ * width / height (`0`, `-0.5` or `-1`), so the box grows away from the point.
421
+ */
422
+ interface MapAnchorGeometry {
423
+ dx: number;
424
+ dy: number;
425
+ fx: number;
426
+ fy: number;
427
+ }
428
+ declare function mapAnchorGeometry(anchor: MapLabelAnchor, distance: number): MapAnchorGeometry;
429
+ /** The CSS transform that puts a label box at `anchor`, `distance` px from its point. */
430
+ declare function mapAnchorTransform(anchor: MapLabelAnchor, distance: number): string;
122
431
 
123
- type MapMarkerProps = {
124
- /** Longitude coordinate for the marker position. */
432
+ /** A geographic position — the only kind an ordinary basemap understands. */
433
+ type MapGeoPosition = {
434
+ /** Longitude coordinate. */
125
435
  longitude: number;
126
- /** Latitude coordinate for the marker position. */
436
+ /** Latitude coordinate. */
127
437
  latitude: number;
128
- /** Marker sub-components (MapMarkerContent, MapMarkerPopup, MapMarkerTooltip, MapMarkerLabel). */
129
- children: ReactNode;
438
+ x?: never;
439
+ y?: never;
440
+ };
441
+ /**
442
+ * A position in PLAN units, for a canvas that declares a `plan` extent: metres
443
+ * along the hall, millimetres across the carriage, pixels into the drawing.
444
+ */
445
+ type MapPlanPosition = {
446
+ /** Horizontal position in plan units. */
447
+ x: number;
448
+ /** Vertical position in plan units, from the plan's own origin. */
449
+ y: number;
450
+ longitude?: never;
451
+ latitude?: never;
452
+ };
453
+ /**
454
+ * Either a geographic position or a plan one — never a mix. The `never`-typed
455
+ * twins make `{ longitude, x }` a type error rather than a silent winner.
456
+ */
457
+ type MapPosition = MapGeoPosition | MapPlanPosition;
458
+
459
+ /**
460
+ * A marker's own text label — the locator-map label: one of eight positions,
461
+ * optionally on a box, optionally pushed out on a callout line.
462
+ */
463
+ interface MapMarkerLabelSpec {
464
+ /** The label text. */
465
+ text: string;
466
+ /** Where the label sits relative to the point (default `"top"`). */
467
+ position?: MapLabelAnchor;
468
+ /** Set the label on a background box so it reads over a busy basemap. */
469
+ box?: boolean;
470
+ /** Push the label further out and draw a callout line back to the point. */
471
+ callout?: boolean;
472
+ }
473
+ type MapMarkerProps = MapPosition & {
474
+ /**
475
+ * Marker sub-components (MapMarkerContent, MapMarkerPopup, MapMarkerTooltip,
476
+ * MapMarkerLabel). Optional when `label` alone marks the place — an inline
477
+ * area label, for example.
478
+ */
479
+ children?: ReactNode;
480
+ /**
481
+ * Whether the marker shows at a tier (default `true` everywhere):
482
+ * `{ base: true, narrow: false }` drops it from a phone-width map. The
483
+ * The editorial advice applies — duplicate a marker with a shorter label and
484
+ * the opposite `showAt` rather than cramming one label into both.
485
+ */
486
+ showAt?: MapResponsive<boolean>;
487
+ /** A text label drawn with the marker (see {@link MapMarkerLabelSpec}). */
488
+ label?: MapMarkerLabelSpec;
130
489
  /** Callback when the marker is clicked. */
131
490
  onClick?: (e: MouseEvent) => void;
132
491
  /** Callback when the mouse enters the marker. */
133
492
  onMouseEnter?: (e: MouseEvent) => void;
134
493
  /** Callback when the mouse leaves the marker. */
135
494
  onMouseLeave?: (e: MouseEvent) => void;
136
- /** Callback when a drag starts (requires `draggable`). */
495
+ /** Callback when a drag starts (requires `draggable`). The plan point is `null` off a plan. */
137
496
  onDragStart?: (lngLat: {
138
497
  lng: number;
139
498
  lat: number;
140
- }) => void;
141
- /** Callback during a drag (requires `draggable`). */
499
+ }, plan: PlanPoint | null) => void;
500
+ /** Callback during a drag (requires `draggable`). The plan point is `null` off a plan. */
142
501
  onDrag?: (lngLat: {
143
502
  lng: number;
144
503
  lat: number;
145
- }) => void;
146
- /** Callback when a drag ends (requires `draggable`). */
504
+ }, plan: PlanPoint | null) => void;
505
+ /** Callback when a drag ends (requires `draggable`). The plan point is `null` off a plan. */
147
506
  onDragEnd?: (lngLat: {
148
507
  lng: number;
149
508
  lat: number;
150
- }) => void;
509
+ }, plan: PlanPoint | null) => void;
510
+ /**
511
+ * The accessible name of a `draggable` marker — say what it moves ("Depot
512
+ * location"). Defaults to the locale seam's generic name. Ignored when the
513
+ * marker is not draggable.
514
+ */
515
+ dragLabel?: string;
151
516
  } & Omit<MarkerOptions, "element">;
152
517
  /**
153
- * A marker anchored at a lng/lat. Compose the pieces you need:
154
- * `MapMarkerContent` (the visual), `MapMarkerLabel`, `MapMarkerPopup` (opens
155
- * on click) and `MapMarkerTooltip` (shows on hover).
518
+ * A marker anchored at a lng/lat or, on a canvas with a `plan` extent, at a
519
+ * plan `x`/`y`. Compose the pieces you need: `MapMarkerContent` (the visual),
520
+ * `MapMarkerLabel`, `MapMarkerPopup` (opens on click) and `MapMarkerTooltip`
521
+ * (shows on hover).
156
522
  */
157
- declare function MapMarker({ longitude, latitude, children, onClick, onMouseEnter, onMouseLeave, onDragStart, onDrag, onDragEnd, draggable, ...markerOptions }: MapMarkerProps): react.JSX.Element | null;
523
+ declare function MapMarker({ longitude: longitudeProp, latitude: latitudeProp, x, y, children, onClick, onMouseEnter, onMouseLeave, onDragStart, onDrag, onDragEnd, draggable, dragLabel, showAt, label, ...markerOptions }: MapMarkerProps): react.JSX.Element | null;
158
524
  interface MapMarkerContentProps {
159
525
  /** Custom marker content. Defaults to a primary-colored dot. */
160
526
  children?: ReactNode;
@@ -197,11 +563,7 @@ interface MapMarkerLabelProps {
197
563
  */
198
564
  declare function MapMarkerLabel({ children, className, position }: MapMarkerLabelProps): react.ReactPortal;
199
565
 
200
- type MapPopupProps = {
201
- /** Longitude coordinate for the popup position. */
202
- longitude: number;
203
- /** Latitude coordinate for the popup position. */
204
- latitude: number;
566
+ type MapPopupProps = MapPosition & {
205
567
  /** Callback when the popup is closed. */
206
568
  onClose?: () => void;
207
569
  /** Popup content. */
@@ -212,15 +574,20 @@ type MapPopupProps = {
212
574
  closeButton?: boolean;
213
575
  } & Omit<PopupOptions, "className" | "closeButton">;
214
576
  /**
215
- * A standalone popup anchored at a lng/lat (not attached to a marker)
216
- * typically rendered conditionally from app state (e.g. after a layer click).
577
+ * A standalone popup anchored at a lng/lat or, on a canvas with a `plan`
578
+ * extent, at a plan `x`/`y`. Not attached to a marker; typically rendered
579
+ * conditionally from app state (e.g. after a layer click).
217
580
  */
218
- declare function MapPopup({ longitude, latitude, onClose, children, className, closeButton, ...popupOptions }: MapPopupProps): react.ReactPortal;
581
+ declare function MapPopup({ longitude: longitudeProp, latitude: latitudeProp, x, y, onClose, children, className, closeButton, ...popupOptions }: MapPopupProps): react.ReactPortal;
219
582
 
220
583
  interface MapControlsProps {
221
584
  /** Position of the controls on the map (default: "bottom-right"). */
222
585
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
223
- /** Show zoom in/out buttons (default: true). */
586
+ /**
587
+ * Show zoom in/out buttons. Default: true on an interactive map, false on a
588
+ * static one (`<MapCanvas interactive={false}>`), whose whole point is a
589
+ * fixed view — set `showZoom` explicitly to override either way.
590
+ */
224
591
  showZoom?: boolean;
225
592
  /** Show a compass button to reset bearing/pitch (default: false). */
226
593
  showCompass?: boolean;
@@ -228,6 +595,18 @@ interface MapControlsProps {
228
595
  showLocate?: boolean;
229
596
  /** Show a fullscreen toggle button (default: false). */
230
597
  showFullscreen?: boolean;
598
+ /**
599
+ * Show a button that frames the whole plan again — a plan map has an edge to
600
+ * come back to, so this defaults to ON there and off on a geographic map.
601
+ */
602
+ showFit?: boolean;
603
+ /**
604
+ * What the fit button frames. Defaults to the plan's own extent; pass bounds
605
+ * to frame something else (one wing, the selected cluster).
606
+ */
607
+ fitBounds?: [[number, number], [number, number]];
608
+ /** Called after the camera has been asked to frame the plan. */
609
+ onFit?: () => void;
231
610
  /** Additional CSS classes for the controls container. */
232
611
  className?: string;
233
612
  /** Callback with user coordinates when located. */
@@ -239,12 +618,17 @@ interface MapControlsProps {
239
618
  onLocateError?: (error: GeolocationPositionError) => void;
240
619
  }
241
620
  /** Branded zoom / compass / locate / fullscreen controls. Render inside `<MapCanvas>`. */
242
- declare function MapControls({ position, showZoom, showCompass, showLocate, showFullscreen, className, onLocate, onLocateError, }: MapControlsProps): react.JSX.Element;
621
+ declare function MapControls({ position, showZoom, showCompass, showLocate, showFullscreen, showFit, fitBounds, className, onLocate, onLocateError, onFit, }: MapControlsProps): react.JSX.Element | null;
243
622
 
623
+ /** Which way the route runs, drawn as chevrons along the line. */
624
+ type MapRouteDirection = "none" | "forward" | "backward";
244
625
  interface MapRouteProps {
245
626
  /** Optional unique identifier for the route layer. */
246
627
  id?: string;
247
- /** Array of [longitude, latitude] coordinate pairs defining the route. */
628
+ /**
629
+ * Array of [longitude, latitude] coordinate pairs defining the route — or, in
630
+ * a `<MapCanvas plan>`, [x, y] pairs in plan units.
631
+ */
248
632
  coordinates: [number, number][];
249
633
  /** Line color as a CSS color value. Defaults to the theme's `--primary` token. */
250
634
  color?: string;
@@ -254,6 +638,14 @@ interface MapRouteProps {
254
638
  opacity?: number;
255
639
  /** Dash pattern [dash length, gap length] for dashed lines. */
256
640
  dashArray?: [number, number];
641
+ /**
642
+ * Draw chevrons along the line to show which way it runs (default: "none").
643
+ * A conveyor, a one-way aisle and a walking route all need this, and an arrow
644
+ * is a second channel that greyscale keeps.
645
+ */
646
+ direction?: MapRouteDirection;
647
+ /** Distance between chevrons in px (default: 80). */
648
+ directionSpacing?: number;
257
649
  /** Callback when the route line is clicked. */
258
650
  onClick?: () => void;
259
651
  /** Callback when the mouse enters the route line. */
@@ -264,15 +656,18 @@ interface MapRouteProps {
264
656
  interactive?: boolean;
265
657
  }
266
658
  /** A GeoJSON line layer for routes/paths. Renders nothing itself — it draws on the map. */
267
- declare function MapRoute({ id: propId, coordinates, color, width, opacity, dashArray, onClick, onMouseEnter, onMouseLeave, interactive, }: MapRouteProps): null;
659
+ declare function MapRoute({ id: propId, coordinates, color, width, opacity, dashArray, direction, directionSpacing, onClick, onMouseEnter, onMouseLeave, interactive, }: MapRouteProps): null;
268
660
 
269
661
  /** A single arc to render inside `<MapArc data={...}>`. */
270
662
  type MapArcDatum = {
271
663
  /** Unique identifier for this arc. Required for hover state tracking and event payloads. */
272
664
  id: string | number;
273
- /** Start coordinate as [longitude, latitude]. */
665
+ /**
666
+ * Start coordinate as [longitude, latitude] — or, inside a `<MapCanvas plan>`,
667
+ * as [x, y] in plan units.
668
+ */
274
669
  from: [number, number];
275
- /** End coordinate as [longitude, latitude]. */
670
+ /** End coordinate, in the same units as `from`. */
276
671
  to: [number, number];
277
672
  };
278
673
  /** Event payload passed to MapArc interaction callbacks. */
@@ -283,6 +678,8 @@ type MapArcEvent<T extends MapArcDatum = MapArcDatum> = {
283
678
  longitude: number;
284
679
  /** Latitude of the cursor at the time of the event. */
285
680
  latitude: number;
681
+ /** The cursor in plan units, on a plan map; `null` on a geographic one. */
682
+ plan: PlanPoint | null;
286
683
  /** The underlying MapLibre mouse event for advanced use cases. */
287
684
  originalEvent: MapLibreGL.MapMouseEvent;
288
685
  };
@@ -333,6 +730,11 @@ type MapArcProps<T extends MapArcDatum = MapArcDatum> = {
333
730
  /**
334
731
  * Curved great-circle-style arcs between coordinate pairs (flight paths,
335
732
  * network links). Pairs well with `<MapCanvas blank projection={{ type: "globe" }}>`.
733
+ *
734
+ * Inside a `<MapCanvas plan>` the same component draws plan-space curves — a
735
+ * material flow between two machine cells, a walking route across a floor. The
736
+ * curve is built in PLAN units and each sample is then converted, because a
737
+ * straight lng/lat line bows once Mercator has had its say.
336
738
  */
337
739
  declare function MapArc<T extends MapArcDatum = MapArcDatum>({ data, id: propId, curvature, samples, paint, layout, hoverPaint, onClick, onHover, interactive, beforeId, }: MapArcProps<T>): null;
338
740
 
@@ -344,12 +746,39 @@ declare function MapArc<T extends MapArcDatum = MapArcDatum>({ data, id: propId,
344
746
  * relative to the origin so arcs cross the antimeridian via the shorter
345
747
  * great-circle direction — resulting longitudes may fall outside [-180, 180],
346
748
  * which MapLibre renders correctly on the globe projection.
749
+ *
750
+ * `wrapLongitude: false` turns that unwrap off, for coordinates that are not
751
+ * longitudes at all: on a plan map, an x of 900 in a 1600-unit-wide hall is a
752
+ * position, and shifting it by 360 would move the arc's end across the floor.
347
753
  */
348
- declare function buildArcCoordinates(from: [number, number], to: [number, number], curvature: number, samples: number): [number, number][];
754
+ declare function buildArcCoordinates(from: [number, number], to: [number, number], curvature: number, samples: number, wrapLongitude?: boolean): [number, number][];
349
755
 
350
756
  type MapGeoJSONData<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = GeoJSON.FeatureCollection<GeoJSON.Geometry, P> | GeoJSON.Feature<GeoJSON.Geometry, P> | GeoJSON.Geometry | string;
351
757
  type MapFillPaint = NonNullable<MapLibreGL.FillLayerSpecification["paint"]>;
352
758
  type MapLinePaint = NonNullable<MapLibreGL.LineLayerSpecification["paint"]>;
759
+ /**
760
+ * A hatch drawn over the fill — the locator-map "area marker" look. `width` is
761
+ * a stripe's thickness and `gap` the space between stripes, both in CSS px
762
+ * measured across the 45° stripes. Stripes take the fill colour (a plain
763
+ * `fill-color`; an expression falls back to the `--foreground` token).
764
+ */
765
+ interface MapGeoJSONPattern {
766
+ kind: "stripes";
767
+ /** Stripe thickness in px (default 2). */
768
+ width?: number;
769
+ /** Space between stripes in px (default 4). */
770
+ gap?: number;
771
+ }
772
+ /**
773
+ * A soft glow around the area's edge — a second, blurred line layer under the
774
+ * fill, in the fill colour. `width` is how far the glow reaches out, in px.
775
+ */
776
+ interface MapGeoJSONVignette {
777
+ /** Glow reach in px (default 12). */
778
+ width?: number;
779
+ /** Glow opacity, 0–1 (default 0.35). */
780
+ opacity?: number;
781
+ }
353
782
  /** A rendered feature with strongly-typed `properties`. */
354
783
  type MapGeoJSONFeature<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = Omit<MapLibreGL.MapGeoJSONFeature, "properties"> & {
355
784
  properties: P;
@@ -362,17 +791,32 @@ type MapGeoJSONEvent<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProper
362
791
  longitude: number;
363
792
  /** Latitude of the cursor at the time of the event. */
364
793
  latitude: number;
365
- /** The underlying MapLibre mouse event for advanced use cases. */
366
- originalEvent: MapLibreGL.MapLayerMouseEvent;
794
+ /**
795
+ * The cursor in PLAN units, when the canvas declares a `plan` extent —
796
+ * otherwise `null`. What a plan consumer actually wants: metres along the
797
+ * hall, not a synthesized longitude.
798
+ */
799
+ plan: PlanPoint | null;
800
+ /**
801
+ * The underlying MapLibre mouse event for advanced use cases. `null` when
802
+ * the feature was reached from the KEYBOARD rather than a pointer — there is
803
+ * no mouse event behind that (c-4/c-2, WCAG 2.1.1).
804
+ */
805
+ originalEvent: MapLibreGL.MapLayerMouseEvent | null;
367
806
  };
368
807
  type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {
369
- /** GeoJSON data (FeatureCollection, Feature, Geometry) or a URL to fetch it from. */
808
+ /**
809
+ * GeoJSON data (FeatureCollection, Feature, Geometry) or a URL to fetch it
810
+ * from. On a canvas with a `plan` extent the coordinates are PLAN units —
811
+ * `[x, y]` in the plan's own drawing units — and are converted for you.
812
+ */
370
813
  data: MapGeoJSONData<P>;
371
814
  /** Optional unique identifier prefix for the source/layers. Auto-generated if not provided. */
372
815
  id?: string;
373
816
  /**
374
- * Feature property to promote to the feature `id`. Required for hover
375
- * feature-state (`fillHoverPaint`) and stable `onHover`/`onClick` payloads.
817
+ * Feature property to promote to the feature `id`. Required for hover and
818
+ * selected feature-state (`fillHoverPaint`, `selectedId`) and stable
819
+ * `onHover`/`onClick` payloads.
376
820
  */
377
821
  promoteId?: string;
378
822
  /**
@@ -394,12 +838,66 @@ type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProper
394
838
  * as a `case` expression keyed on hover feature-state. Requires `promoteId`.
395
839
  */
396
840
  fillHoverPaint?: MapFillPaint;
841
+ /**
842
+ * The fill layer's opacity, 0–1 — shorthand for `fillPaint["fill-opacity"]`
843
+ * (which wins when both are set). Default 1, or 0.25 with a `pattern`, so
844
+ * the stripes read over a tint of the same colour.
845
+ */
846
+ fillOpacity?: number;
847
+ /** Hatch the areas with stripes (see {@link MapGeoJSONPattern}). */
848
+ pattern?: MapGeoJSONPattern;
849
+ /** Glow around the areas' edges (see {@link MapGeoJSONVignette}). */
850
+ vignette?: MapGeoJSONVignette;
851
+ /** The outline equivalent of `fillHoverPaint`. Requires `promoteId`. */
852
+ lineHoverPaint?: MapLinePaint;
853
+ /**
854
+ * Paint merged onto the fill layer for the feature(s) named by `selectedId`.
855
+ * Applied outside hover, so a selected feature still looks selected while the
856
+ * cursor is over it. Requires `promoteId`.
857
+ */
858
+ fillSelectedPaint?: MapFillPaint;
859
+ /** The outline equivalent of `fillSelectedPaint`. Requires `promoteId`. */
860
+ lineSelectedPaint?: MapLinePaint;
861
+ /**
862
+ * The selected feature id(s) — the promoted `promoteId` values. Selection is
863
+ * the consumer's state; this only paints it.
864
+ */
865
+ selectedId?: string | number | readonly (string | number)[] | null;
866
+ /**
867
+ * Take over the highlight: when set, the hover paint follows THIS id instead
868
+ * of the pointer. That is how `MapPlanOverlay` makes a keyboard-focused room
869
+ * light up exactly as a hovered one does — one channel, one appearance.
870
+ */
871
+ hoveredId?: string | number | null;
397
872
  /** Callback when a feature is clicked. */
398
873
  onClick?: (e: MapGeoJSONEvent<P>) => void;
399
874
  /** Callback fired when the hovered feature changes; `null` when the cursor leaves. */
400
875
  onHover?: (e: MapGeoJSONEvent<P> | null) => void;
401
- /** Whether features respond to mouse events (default: false). */
876
+ /**
877
+ * Whether features respond to mouse events. Defaults to `false` on a
878
+ * geographic canvas and `true` on a plan canvas, where the shapes ARE the
879
+ * subject rather than a backdrop.
880
+ */
402
881
  interactive?: boolean;
882
+ /**
883
+ * The text of a feature in the keyboard list an `interactive` map renders —
884
+ * one visually-hidden button per feature, so the values a pointer reveals
885
+ * on hover are reachable by Tab and readable by a screen reader (WCAG 2.1.1
886
+ * and 1.3.1). Say what the region is AND what it is worth — the same
887
+ * sentence a hover readout prints. Defaults to the promoted id.
888
+ */
889
+ featureLabel?: (feature: MapGeoJSONFeature<P>, index: number) => string;
890
+ /**
891
+ * How far, in px, a click may miss a shape and still land on it. Defaults to
892
+ * 8 px on a plan (so a 20 px seat is a 36 px touch target) and 0 elsewhere.
893
+ * An exact hit always wins over a padded one.
894
+ */
895
+ pickPadding?: number;
896
+ /**
897
+ * Width, in px, of the invisible line that makes an outline-only shape
898
+ * touchable (default 16). Only used when `fillPaint` is `false`.
899
+ */
900
+ hitWidth?: number;
403
901
  /** Optional MapLibre layer id to insert the layers before (z-order control). */
404
902
  beforeId?: string;
405
903
  };
@@ -408,8 +906,18 @@ type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProper
408
906
  * `MapRoute` / `MapArc` — drop it inside `<MapCanvas>` (typically with `blank`)
409
907
  * for choropleths and region/data maps. For full control over expressions and
410
908
  * multiple layers, manage layers directly via `useMap()` instead.
909
+ *
910
+ * On a canvas that declares a plan extent it is also the shape layer of a
911
+ * custom (non-geographic) map: rooms, machine cells, aisles and seats written
912
+ * in the plan's own units.
913
+ *
914
+ * ```tsx
915
+ * <MapCanvas blank plan={{ width: 2400, height: 1600, unit: "cm" }}>
916
+ * <MapGeoJSON data={roomsInCentimetres} promoteId="id" selectedId={roomId} />
917
+ * </MapCanvas>
918
+ * ```
411
919
  */
412
- declare function MapGeoJSON<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({ data, id: propId, promoteId, fillPaint, linePaint, fillHoverPaint, onClick, onHover, interactive, beforeId, }: MapGeoJSONProps<P>): null;
920
+ declare function MapGeoJSON<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({ data, id: propId, promoteId, fillPaint, linePaint, fillHoverPaint, fillOpacity, pattern, vignette, lineHoverPaint, fillSelectedPaint, lineSelectedPaint, selectedId, hoveredId, onClick, onHover, interactive: interactiveProp, featureLabel, pickPadding, hitWidth, beforeId, }: MapGeoJSONProps<P>): react.JSX.Element | null;
413
921
 
414
922
  type MapClusterLayerProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {
415
923
  /** GeoJSON FeatureCollection data or a URL to fetch GeoJSON from. */
@@ -436,7 +944,694 @@ type MapClusterLayerProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonP
436
944
  * Clustered point rendering for large point datasets. Cluster circles step
437
945
  * through the status tokens (success → warning → destructive) as the point
438
946
  * count grows; strokes and count labels use the page surface for contrast.
947
+ *
948
+ * NOT for a plan map. The count inside each circle is a `symbol` `text-field`,
949
+ * and a blank style — which every plan uses — ships no glyph endpoint, so the
950
+ * counts would silently render as nothing at all. Cluster plan points yourself
951
+ * and draw the groups with `MapGeoJSON`, or put the count in the DOM overlay.
439
952
  */
440
953
  declare function MapClusterLayer<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({ data, clusterMaxZoom, clusterRadius, clusterColors, clusterThresholds, pointColor, onPointClick, onClusterClick, }: MapClusterLayerProps<P>): null;
441
954
 
442
- export { type BasemapTheme, MapArc, type MapArcDatum, type MapArcEvent, type MapArcLineLayout, type MapArcLinePaint, type MapArcProps, MapCanvas, type MapCanvasProps, type MapCanvasRef, MapClusterLayer, type MapClusterLayerProps, type MapContextValue, MapControls, type MapControlsProps, type MapFillPaint, MapGeoJSON, type MapGeoJSONData, type MapGeoJSONEvent, type MapGeoJSONFeature, type MapGeoJSONProps, type MapLinePaint, MapMarker, MapMarkerContent, type MapMarkerContentProps, MapMarkerLabel, type MapMarkerLabelProps, MapMarkerPopup, type MapMarkerPopupProps, type MapMarkerProps, MapMarkerTooltip, type MapMarkerTooltipProps, MapPopup, type MapPopupProps, MapRoute, type MapRouteProps, type MapStyleOption, type MapViewport, buildArcCoordinates, useMap };
955
+ /**
956
+ * The picture under a plan: a URL, or an already-decoded image, bitmap or
957
+ * canvas. A `{ light, dark }` pair swaps with the active theme — a plan drawing
958
+ * has its own baked-in ink, and one asset rarely reads well on both surfaces.
959
+ */
960
+ type MapPlanImageSource = string | HTMLImageElement | ImageBitmap | HTMLCanvasElement;
961
+ type MapPlanImageProps = {
962
+ /** The plan picture. Pass `{ light, dark }` to swap it with the theme. */
963
+ src: MapPlanImageSource | {
964
+ light: MapPlanImageSource;
965
+ dark: MapPlanImageSource;
966
+ };
967
+ /**
968
+ * What the picture shows, for anyone who cannot see it ("Level 3 floor plan,
969
+ * 14 rooms around a central core"). A plan drawn into WebGL has no accessible
970
+ * name of its own, so this is the only description of it.
971
+ */
972
+ alt?: string;
973
+ /** Optional identifier for the source/layer. Auto-generated if not provided. */
974
+ id?: string;
975
+ /**
976
+ * Where the picture sits, in PLAN units. Defaults to the whole plan extent, so
977
+ * a single full-bleed plan needs nothing here. Use it to place one wing, one
978
+ * machine cell or one carriage inside a larger plan.
979
+ */
980
+ extent?: {
981
+ x: number;
982
+ y: number;
983
+ width: number;
984
+ height: number;
985
+ };
986
+ /**
987
+ * The four corners in lng/lat (top-left, top-right, bottom-right, bottom-left)
988
+ * — an escape hatch that also works on an ordinary geographic canvas, like
989
+ * Leaflet's `imageOverlay`. Wins over `extent`.
990
+ */
991
+ coordinates?: MapLibreGL.Coordinates;
992
+ /**
993
+ * Opacity, 0–1 (default 1). A photographic or ink-heavy plan usually reads
994
+ * best held back around 0.35 so the data drawn on top stays the focus.
995
+ */
996
+ opacity?: number;
997
+ /** Hide the picture without tearing the source down — a multi-floor switch (default true). */
998
+ visible?: boolean;
999
+ /** `"nearest"` keeps CAD line art crisp when zoomed past its native resolution. */
1000
+ resampling?: "linear" | "nearest";
1001
+ /** MapLibre layer id to insert this layer before (z-order control). */
1002
+ beforeId?: string;
1003
+ /** Fired once the picture has been fetched and decoded. */
1004
+ onLoad?: () => void;
1005
+ /** Fired when the picture cannot be loaded. The plan itself keeps working without it. */
1006
+ onError?: (error: Error) => void;
1007
+ };
1008
+ /**
1009
+ * The plan picture under the shapes — a floor plan, a factory layout, a deck
1010
+ * plan. The map equivalent of Qlik Sense's image background layer and Leaflet's
1011
+ * `imageOverlay`: by default it covers the whole plan extent declared on
1012
+ * `<MapCanvas plan>`, so its corners need no coordinates at all.
1013
+ *
1014
+ * Render it FIRST among the canvas children so the picture sits under the data,
1015
+ * or pin the order with `beforeId`.
1016
+ *
1017
+ * ```tsx
1018
+ * <MapCanvas blank plan={{ width: 2400, height: 1600, unit: "cm" }}>
1019
+ * <MapPlanImage src={floorPlanUrl} alt="Level 3 floor plan" opacity={0.35} />
1020
+ * <MapGeoJSON data={roomsInPlanUnits} promoteId="id" />
1021
+ * </MapCanvas>
1022
+ * ```
1023
+ *
1024
+ * A picture is optional and often worth skipping: a plan drawn as GeoJSON in
1025
+ * plan units paints from tokens, themes correctly, and never blurs.
1026
+ */
1027
+ declare function MapPlanImage({ src, alt, id: propId, extent, coordinates, opacity, visible, resampling, beforeId, onLoad, onError, }: MapPlanImageProps): react.JSX.Element;
1028
+
1029
+ /** An axis-aligned rectangle in PLAN units. */
1030
+ interface PlanRegionBounds {
1031
+ x: number;
1032
+ y: number;
1033
+ width: number;
1034
+ height: number;
1035
+ }
1036
+ /**
1037
+ * One interactive area of a plan — a room, a machine cell, a seat. The overlay
1038
+ * needs a box and a name, not the geometry: a room's shape is drawn by WebGL,
1039
+ * while the keyboard proxy, the label and the readout all work from its box.
1040
+ */
1041
+ interface MapPlanRegion {
1042
+ /** Stable id. Must match the feature id the shape layer promotes. */
1043
+ id: string;
1044
+ /** The visible label, and the start of the button's accessible name. */
1045
+ label: string;
1046
+ /** Shown instead of `label` when the box is too small for the full one. */
1047
+ shortLabel?: string;
1048
+ /** Appended to the accessible name — a status word, a seat count, an owner. */
1049
+ description?: string;
1050
+ /** The region's box, in plan units. */
1051
+ bounds: PlanRegionBounds;
1052
+ /** Group id, for plans with more regions than a keyboard can walk (a carriage, a wing). */
1053
+ group?: string;
1054
+ /** The group's own label, used by group mode. */
1055
+ groupLabel?: string;
1056
+ }
1057
+ /** The centre of a region's box, in plan units. */
1058
+ declare function planRegionCentre(bounds: PlanRegionBounds): PlanPoint;
1059
+ /** The bounding box of any geometry, in the units its coordinates are written in. */
1060
+ declare function planBoundsOfGeometry(geometry: GeoJSON.Geometry): PlanRegionBounds | null;
1061
+ /** A field read either from a feature property or computed from the properties. */
1062
+ type PlanRegionField<P, T> = string | ((properties: P, index: number) => T);
1063
+ interface PlanRegionFields<P extends GeoJSON.GeoJsonProperties> {
1064
+ /** Property name, or a function, giving each region's id. */
1065
+ id: PlanRegionField<P, string>;
1066
+ /** Property name, or a function, giving each region's visible label. */
1067
+ label: PlanRegionField<P, string>;
1068
+ /** Optional short label for a small box. */
1069
+ shortLabel?: PlanRegionField<P, string | undefined>;
1070
+ /** Optional extra words for the accessible name. */
1071
+ description?: PlanRegionField<P, string | undefined>;
1072
+ /** Optional group id. */
1073
+ group?: PlanRegionField<P, string | undefined>;
1074
+ /** Optional group label. */
1075
+ groupLabel?: PlanRegionField<P, string | undefined>;
1076
+ }
1077
+ /**
1078
+ * Derive the overlay's regions from the same GeoJSON the shape layer draws, so
1079
+ * the keyboard proxy can never drift out of step with the picture.
1080
+ *
1081
+ * ```ts
1082
+ * const regions = planRegionsFromGeoJSON(rooms, { id: "id", label: "name" });
1083
+ * ```
1084
+ */
1085
+ declare function planRegionsFromGeoJSON<P extends GeoJSON.GeoJsonProperties>(data: GeoJSON.FeatureCollection<GeoJSON.Geometry, P>, fields: PlanRegionFields<P>): MapPlanRegion[];
1086
+ /** The id the overlay gives a group's own button, namespaced away from region ids. */
1087
+ declare function planGroupId(group: string): string;
1088
+ /** One group of regions, with the synthetic region that stands for the whole of it. */
1089
+ interface MapPlanGroup {
1090
+ /** The group's key, as it appeared on its regions. */
1091
+ key: string;
1092
+ /** A region covering every member's box — what group mode puts a button on. */
1093
+ region: MapPlanRegion;
1094
+ /** The group's regions, in reading order. */
1095
+ members: readonly MapPlanRegion[];
1096
+ }
1097
+ /** The smallest box containing all of them. `null` for an empty list. */
1098
+ declare function planBoundsUnion(boxes: readonly PlanRegionBounds[]): PlanRegionBounds | null;
1099
+ /**
1100
+ * Bundle regions by their `group`, in first-seen order.
1101
+ *
1102
+ * A region with no `group` becomes a group of one, keyed by its own id: group
1103
+ * mode then still shows everything, instead of quietly dropping whatever was
1104
+ * left ungrouped.
1105
+ */
1106
+ declare function planGroupsFromRegions(regions: readonly MapPlanRegion[]): MapPlanGroup[];
1107
+
1108
+ /** How the overlay lays its buttons out. */
1109
+ type MapPlanOverlayMode = "regions" | "groups";
1110
+ type MapPlanOverlayProps = {
1111
+ /**
1112
+ * The interactive regions, in reading order. Keep the array MEMOIZED — it
1113
+ * drives the projection pass.
1114
+ */
1115
+ regions: readonly MapPlanRegion[];
1116
+ /**
1117
+ * Accessible name for the set of regions ("Rooms on level 3"). Supplied by the
1118
+ * consumer so it can be localized.
1119
+ */
1120
+ label: string;
1121
+ /** The selected region id(s) — the same value handed to `<MapGeoJSON selectedId>`. */
1122
+ selectedId?: string | readonly string[] | null;
1123
+ /** Fired when a region is activated by click, `Enter` or `Space`; `null` on `Escape`. */
1124
+ onSelect?: (id: string | null, region: MapPlanRegion | null) => void;
1125
+ /**
1126
+ * Fired when the pointer or keyboard focus moves to another region. Feed it to
1127
+ * `<MapGeoJSON hoveredId>` so the shape and the proxy highlight together.
1128
+ */
1129
+ onActiveChange?: (id: string | null, region: MapPlanRegion | null) => void;
1130
+ /**
1131
+ * `"regions"` (default) puts one button on every region. `"groups"` puts one
1132
+ * button on each `group` instead, and `Enter` descends into its members — the
1133
+ * answer for a plan with more regions than a keyboard can reasonably walk (a
1134
+ * train with 192 seats), where the first question is always "which coach?".
1135
+ */
1136
+ mode?: MapPlanOverlayMode;
1137
+ /**
1138
+ * The accessible description of a group's button — "64 seats", localized by the
1139
+ * consumer. Group mode only.
1140
+ */
1141
+ describeGroup?: (group: MapPlanGroup) => string;
1142
+ /** Draw each region's label in the overlay (default true). */
1143
+ showLabels?: boolean;
1144
+ /** Hide the labels below this zoom, when a fitted plan is too small to read. */
1145
+ labelMinZoom?: number;
1146
+ /**
1147
+ * Let the regions take the pointer as well as the keyboard (default true).
1148
+ * With `false` the overlay is keyboard-only and clicks reach the canvas, where
1149
+ * `<MapGeoJSON onClick>` picks the shape itself — the right choice for
1150
+ * interlocking, non-rectangular shapes whose boxes overlap.
1151
+ */
1152
+ capturePointer?: boolean;
1153
+ /** Extra classes for the overlay root. */
1154
+ className?: string;
1155
+ };
1156
+ /**
1157
+ * The keyboard and label layer of a plan. MapLibre draws every shape into one
1158
+ * WebGL canvas, so a room is not a DOM element and cannot be focused, named or
1159
+ * given a pressed state. This overlay puts one real `<button aria-pressed>` over
1160
+ * each region — the shapes stay in WebGL, the semantics come back to the DOM.
1161
+ *
1162
+ * ```tsx
1163
+ * <MapCanvas blank plan={{ width: 2400, height: 1600, unit: "cm" }}>
1164
+ * <MapGeoJSON data={rooms} promoteId="id" selectedId={selection.selectedIds} hoveredId={active} />
1165
+ * <MapPlanOverlay
1166
+ * regions={regions}
1167
+ * label={t("floor.rooms")}
1168
+ * selectedId={selection.selectedIds}
1169
+ * onSelect={(id) => selection.select(id)}
1170
+ * onActiveChange={setActive}
1171
+ * />
1172
+ * </MapCanvas>
1173
+ * ```
1174
+ *
1175
+ * Roughly 250 regions stay comfortable and 500 is the hard ceiling: past that a
1176
+ * `Tab`-and-arrow walk is no longer a usable way to reach one seat, and the
1177
+ * regions want `mode="groups"` instead — one button per coach or wing, `Enter`
1178
+ * to go in, `Escape` to come back out, `PageUp`/`PageDown` to change group at
1179
+ * either level.
1180
+ */
1181
+ declare function MapPlanOverlay({ regions, label, selectedId, onSelect, onActiveChange, mode, describeGroup, showLabels, labelMinZoom, capturePointer, className, }: MapPlanOverlayProps): react.JSX.Element;
1182
+
1183
+ /** What a region on a plan can be. */
1184
+ type PlanStatus = "free" | "occupied" | "warning" | "down";
1185
+ /** Every {@link PlanStatus}, in legend order. */
1186
+ declare const PLAN_STATUSES: readonly PlanStatus[];
1187
+ /** The generated hatch tile a status is textured with — see `lib/plan-patterns.ts`. */
1188
+ type PlanPatternKind = "none" | "diagonal" | "cross" | "dense";
1189
+ /** The outline style a status is drawn with: the second non-colour channel. */
1190
+ type PlanDash = "solid" | "dotted" | "dashed" | "dot-dash";
1191
+ interface PlanStatusEncoding {
1192
+ /** The status tone, for the token rungs below. */
1193
+ tone: "success" | "neutral" | "warning" | "destructive";
1194
+ /** The hatch tile registered with `map.addImage`. */
1195
+ pattern: PlanPatternKind;
1196
+ /** The outline style. */
1197
+ dash: PlanDash;
1198
+ /** `line-dasharray` for the outline; `undefined` keeps it solid. */
1199
+ dashArray: number[] | undefined;
1200
+ /**
1201
+ * A glyph, on the two states that must never be confused with a working one.
1202
+ * `undefined` elsewhere: a glyph on every neutral region is noise.
1203
+ */
1204
+ icon: LucideIcon | undefined;
1205
+ /** Stable glyph name, so a test can assert the four states read apart. */
1206
+ glyph: "none" | "clock" | "alert";
1207
+ /** The token the shape's own ink resolves from (the ≥3:1 mark rung). */
1208
+ colorToken: string;
1209
+ /** Mark (fill-rung) utility, for a legend swatch. */
1210
+ markClass: string;
1211
+ /** Ink (`-text` rung) utility, for coloured text on an ordinary surface. */
1212
+ textClass: string;
1213
+ }
1214
+ /**
1215
+ * The one table every plan surface reads status from — map paint, legend, table
1216
+ * and readout alike. Frozen, so a surface cannot quietly drift from the others.
1217
+ */
1218
+ declare const PLAN_STATUS_ENCODING: Readonly<Record<PlanStatus, PlanStatusEncoding>>;
1219
+ /** Fill opacity per interaction state — the feature-state channel, in one place. */
1220
+ declare const PLAN_FILL_OPACITY: Readonly<{
1221
+ rest: 0.16;
1222
+ hover: 0.28;
1223
+ selected: 0.36;
1224
+ }>;
1225
+ /**
1226
+ * A MapLibre `match` expression over a status property, so one paint value can
1227
+ * serve every status without a layer per status.
1228
+ *
1229
+ * ```ts
1230
+ * "line-dasharray": planStatusMatch("status", (encoding) => encoding.dashArray ?? [1, 0])
1231
+ * ```
1232
+ */
1233
+ declare function planStatusMatch<T>(property: string, value: (encoding: PlanStatusEncoding, status: PlanStatus) => T, fallback?: T): unknown[];
1234
+
1235
+ /** Words for each status, supplied by the consumer so they can be localized. */
1236
+ type PlanStatusLabels = Partial<Record<PlanStatus, string>>;
1237
+ interface MapPlanLegendProps {
1238
+ /** The word for each status. A status with no word is left out of the legend. */
1239
+ labels: PlanStatusLabels;
1240
+ /** Only show these statuses, in this order. Defaults to all four, in table order. */
1241
+ statuses?: readonly PlanStatus[];
1242
+ /** Optional count per status, e.g. how many rooms are free right now. */
1243
+ counts?: Partial<Record<PlanStatus, number>>;
1244
+ /** Accessible name for the legend as a whole. */
1245
+ label?: string;
1246
+ className?: string;
1247
+ }
1248
+ /**
1249
+ * The key to the plan's status channels — and the place a sighted user learns
1250
+ * that the dashes and textures mean something.
1251
+ *
1252
+ * Each entry repeats the status three ways: the tone as a swatch, the outline
1253
+ * style as a rule under it, and the word. The two states that must never be
1254
+ * confused with a working one also carry their glyph. In greyscale the outline
1255
+ * styles and the words still tell them apart.
1256
+ */
1257
+ declare function MapPlanLegend({ labels, statuses, counts, label, className, }: MapPlanLegendProps): react.JSX.Element | null;
1258
+
1259
+ interface MapPlanTableColumn {
1260
+ /** Column key, used for the cell lookup and the React key. */
1261
+ key: string;
1262
+ /** Column header text. */
1263
+ header: string;
1264
+ /** Right-align the column — numbers belong on the end edge. */
1265
+ numeric?: boolean;
1266
+ /** The cell's content for one region. */
1267
+ cell: (region: MapPlanRegion) => ReactNode;
1268
+ }
1269
+ interface MapPlanTableProps {
1270
+ /** The same regions the plan draws. */
1271
+ regions: readonly MapPlanRegion[];
1272
+ /** What the table is a table of ("Every room on level 3, with its state"). */
1273
+ caption: string;
1274
+ /** Extra columns after the name. The status column is built in when `status` is given. */
1275
+ columns?: readonly MapPlanTableColumn[];
1276
+ /** A region's status, if the plan has one. Adds a status column with the word. */
1277
+ status?: (region: MapPlanRegion) => PlanStatus | undefined;
1278
+ /** The word for each status — supplied by the consumer, so it can be localized. */
1279
+ statusLabels?: Partial<Record<PlanStatus, string>>;
1280
+ /** Header for the built-in status column. */
1281
+ statusHeader?: string;
1282
+ /** Header for the name column. */
1283
+ nameHeader?: string;
1284
+ /** The selected region id(s), mirrored as a row state. */
1285
+ selectedId?: string | readonly string[] | null;
1286
+ /** Called when a row is chosen — the table is a second way to reach a region. */
1287
+ onSelect?: (id: string, region: MapPlanRegion) => void;
1288
+ /**
1289
+ * Hide the table on screen and keep it for print (default false). A WebGL
1290
+ * canvas prints blank, so a plan that is meant to be printed needs this.
1291
+ */
1292
+ printOnly?: boolean;
1293
+ className?: string;
1294
+ }
1295
+ /**
1296
+ * The plan as words — the parallel channel for anyone who cannot see the canvas,
1297
+ * for anyone who needs to sort or search it, and for the printer, which gets a
1298
+ * blank rectangle where the WebGL canvas was.
1299
+ *
1300
+ * Visible by default rather than `sr-only`: the information is useful to
1301
+ * everyone, and a hidden twin quietly rots.
1302
+ */
1303
+ declare function MapPlanTable({ regions, caption, columns, status, statusLabels, statusHeader, nameHeader, selectedId, onSelect, printOnly, className, }: MapPlanTableProps): react.JSX.Element;
1304
+
1305
+ interface MapPlanStatusProps {
1306
+ /**
1307
+ * The sentence to announce. Change it when something a person needs to hear
1308
+ * changed — a selection, a status transition — never on every tick of a number.
1309
+ */
1310
+ message: string;
1311
+ /** Milliseconds between announcements (default 2000). */
1312
+ coalesceMs?: number;
1313
+ /** Also show the message (default false: it is an announcement, not chrome). */
1314
+ visible?: boolean;
1315
+ className?: string;
1316
+ }
1317
+ /**
1318
+ * ONE live region per plan. A WebGL canvas announces nothing by itself, and a
1319
+ * plan that changes — a machine goes down, a room frees up — has to say so
1320
+ * without stealing focus.
1321
+ *
1322
+ * Announcements are coalesced: a live region that re-reads a ticking number is
1323
+ * worse than silence, because it talks over everything else the person is doing.
1324
+ */
1325
+ declare function MapPlanStatus({ message, coalesceMs, visible, className, }: MapPlanStatusProps): react.JSX.Element;
1326
+
1327
+ interface UsePlanSelectionOptions {
1328
+ /** Allow more than one region to be selected at a time (default false). */
1329
+ multiple?: boolean;
1330
+ /** The ids selected to begin with. */
1331
+ initialSelectedIds?: readonly string[];
1332
+ /** Called whenever the selection changes. */
1333
+ onChange?: (selectedIds: string[]) => void;
1334
+ }
1335
+ interface PlanSelection {
1336
+ /** The single selected id, or the last of a multiple selection; `null` for none. */
1337
+ selectedId: string | null;
1338
+ /** Every selected id, in selection order. */
1339
+ selectedIds: string[];
1340
+ isSelected: (id: string) => boolean;
1341
+ /** Toggle a region; `null` clears everything. */
1342
+ select: (id: string | null) => void;
1343
+ clear: () => void;
1344
+ }
1345
+ /**
1346
+ * Selection state for a plan, keyed by region **id** rather than index — a
1347
+ * status tick that re-orders the regions must not move the selection with it.
1348
+ *
1349
+ * Hand `selectedIds` to `<MapGeoJSON selectedId>` for the paint and to
1350
+ * `<MapPlanOverlay selectedId>` for the pressed state, so both read one source.
1351
+ */
1352
+ declare function usePlanSelection({ multiple, initialSelectedIds, onChange, }?: UsePlanSelectionOptions): PlanSelection;
1353
+
1354
+ /** Where a region ended up on screen, in CSS pixels relative to the canvas. */
1355
+ interface PlanScreenBox {
1356
+ x: number;
1357
+ y: number;
1358
+ width: number;
1359
+ height: number;
1360
+ }
1361
+ interface UsePlanProjectionOptions {
1362
+ map: MapLibreGL.Map | null;
1363
+ plan: PlanCrs | null;
1364
+ /**
1365
+ * The regions to track. Keep this array MEMOIZED: it is an effect dependency,
1366
+ * and a fresh array every render re-binds the map listeners every render.
1367
+ */
1368
+ regions: readonly MapPlanRegion[];
1369
+ /** Turn tracking off (while the map is still loading, say). */
1370
+ enabled?: boolean;
1371
+ /** Called after each frame's DOM writes, with the boxes that were written. */
1372
+ onFrame?: (boxes: ReadonlyMap<string, PlanScreenBox>) => void;
1373
+ }
1374
+ /**
1375
+ * ONE projection pass for every plan overlay element, coalesced into a single
1376
+ * `requestAnimationFrame` and writing straight to the DOM — no React state per
1377
+ * frame, no per-element listener. A MapLibre `Marker` binds its own `move`
1378
+ * listener and projects itself, so forty rooms would mean eighty listeners and
1379
+ * forty uncoordinated writes; this is one listener set and one write pass.
1380
+ *
1381
+ * Each region is projected from its FOUR plan corners to a screen box, so the
1382
+ * box stays right even where the projection is not a plain scale.
1383
+ */
1384
+ declare function usePlanProjection({ map, plan, regions, enabled, onFrame, }: UsePlanProjectionOptions): {
1385
+ register: (id: string, element: HTMLElement | null) => void;
1386
+ refresh: () => void;
1387
+ boxes: react.RefObject<Map<string, PlanScreenBox>>;
1388
+ };
1389
+
1390
+ /** Image ids are stable and namespaced, so two plans on one map cannot collide. */
1391
+ declare function planPatternImageId(kind: PlanPatternKind): string;
1392
+ /**
1393
+ * Draw one seamless tile. Returns `null` for `"none"`, and for any environment
1394
+ * with no 2D context (jsdom) — the dash channel then carries the meaning alone,
1395
+ * which is why status has two non-colour channels rather than one.
1396
+ */
1397
+ declare function createPlanPatternTile(kind: PlanPatternKind, ink: string): HTMLCanvasElement | null;
1398
+ /**
1399
+ * Register (and re-register) the status textures on a map.
1400
+ *
1401
+ * Re-registration is not optional: `map.setStyle` — which a theme swap performs —
1402
+ * drops every image the style holds, and a `fill-pattern` naming a missing image
1403
+ * renders nothing at all. So the images are (re)added on `styledata` as well as
1404
+ * whenever the ink changes.
1405
+ *
1406
+ * @param ink The colour to draw the hatch in, already resolved from a token.
1407
+ */
1408
+ declare function usePlanPatterns(map: MapLibreGL.Map | null, isLoaded: boolean, ink: string): void;
1409
+
1410
+ /**
1411
+ * Resolve a semantic token (e.g. `"--primary"`) to a concrete color MapLibre's
1412
+ * WebGL paint can consume, re-resolving whenever the brand theme changes.
1413
+ * This is how default layer paints stay token-driven: WebGL can't read CSS
1414
+ * custom properties, so we read them at runtime off the map container.
1415
+ */
1416
+ declare function useTokenColor(name: string, fallback?: string): string;
1417
+
1418
+ /**
1419
+ * The direction chevron for `<MapRoute direction>`, drawn on a canvas and
1420
+ * registered with `map.addImage`.
1421
+ *
1422
+ * An icon rather than a text glyph on purpose: a blank MapLibre style ships no
1423
+ * glyph endpoint, so a `symbol` layer's `text-field` renders nothing at all —
1424
+ * while `icon-image` needs no network and no sprite.
1425
+ */
1426
+ /** Tile edge, in px. Drawn at `pixelRatio: 2`, so it reads ~12 px on screen. */
1427
+ declare const ROUTE_ARROW_SIZE = 24;
1428
+ /** Image id for one route's chevron. Per route, because the ink is per route. */
1429
+ declare function routeArrowImageId(routeId: string): string;
1430
+ /** A raw RGBA image MapLibre accepts, without depending on the DOM's `ImageData`. */
1431
+ interface RouteArrowImage {
1432
+ width: number;
1433
+ height: number;
1434
+ data: Uint8ClampedArray;
1435
+ }
1436
+ /**
1437
+ * A chevron pointing along +x. MapLibre aligns a line-placed symbol's x axis
1438
+ * with the line's direction, so +x is "forward"; `icon-rotate: 180` is backward.
1439
+ *
1440
+ * Returns `null` where there is no 2D context (jsdom) — the caller then leaves
1441
+ * the arrow layer out rather than pointing it at a missing image, which
1442
+ * MapLibre would draw as nothing while warning on every frame.
1443
+ */
1444
+ declare function createRouteArrowImage(ink: string): RouteArrowImage | null;
1445
+
1446
+ /**
1447
+ * The four corners map furniture (legend, scale bar, north arrow, inset) can
1448
+ * sit in, inside the map box. Chrome, not geography: the corners are LOGICAL
1449
+ * (`start` / `end`), so "top-left" is the reading-start corner and mirrors in
1450
+ * RTL like the rest of the page.
1451
+ */
1452
+ type MapCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
1453
+ /** Every corner, clockwise from top-left (for stories and validation). */
1454
+ declare const MAP_CORNERS: readonly MapCorner[];
1455
+
1456
+ /** The mark a key row shows: a point, an area or a line marker. */
1457
+ type MapLegendShape = "circle" | "square" | "line";
1458
+ /** One row of a marker key. */
1459
+ interface MapLegendItem {
1460
+ /** Stable identity; defaults to `label`. */
1461
+ id?: string;
1462
+ label: string;
1463
+ /** Any CSS colour — use a token reference (`var(--chart-1)`), never a literal. */
1464
+ color: string;
1465
+ /** Default `"circle"`. */
1466
+ shape?: MapLegendShape;
1467
+ }
1468
+ /**
1469
+ * A colour scale to key: one `colorScaleFor` already built (share it with the
1470
+ * layer it colours), or the values + spec to build one here.
1471
+ */
1472
+ type MapLegendScale = ColorScale | {
1473
+ values: readonly ColorScaleValue[];
1474
+ spec: ColorScaleSpec;
1475
+ };
1476
+ /** Outside the map box (`above` / `below`) or over one of its corners. */
1477
+ type MapLegendPosition = "above" | "below" | MapCorner;
1478
+ /** `list`: one row per item. `grid`: items flow into as many columns as fit. */
1479
+ type MapLegendLayout = "list" | "grid";
1480
+ interface MapLegendProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
1481
+ /** A heading for the key. */
1482
+ title?: ReactNode;
1483
+ /** Marker key rows. */
1484
+ items?: readonly MapLegendItem[];
1485
+ /** A colour ramp (continuous / stepped) or category key, from `colorScaleFor`. */
1486
+ scale?: MapLegendScale;
1487
+ /** Default `"list"`. */
1488
+ layout?: MapLegendLayout;
1489
+ /**
1490
+ * Where the key sits, optionally per tier — for example
1491
+ * `{ base: "top-left", narrow: "below" }` so it stops covering a phone-width
1492
+ * map. Default `"above"`.
1493
+ */
1494
+ position?: MapResponsive<MapLegendPosition>;
1495
+ /** Formats a ramp's tick values (default: the locale's number format). */
1496
+ formatValue?: (value: number) => string;
1497
+ }
1498
+ /**
1499
+ * MapLegend — the map's key: marker rows (`items`) and / or a colour ramp
1500
+ * (`scale`, built with `colorScaleFor` from `@elabs-ai/components-ui`, so its
1501
+ * colours are token references in every theme). It sits above or below the
1502
+ * map box, or over a corner, per tier; the house legend look (`text-meta`,
1503
+ * dot markers) matches `charts`' `ChartLegend`. Render inside `<MapCanvas>`.
1504
+ */
1505
+ declare const MapLegend: react.ForwardRefExoticComponent<MapLegendProps & react.RefAttributes<HTMLDivElement>>;
1506
+
1507
+ /** Metric (`km`, falling back to metres) or imperial (`mi`, falling back to feet). */
1508
+ type MapScaleUnit = "km" | "mi";
1509
+ /** Metres one CSS px covers at `latitude` and `zoom` (Web Mercator). */
1510
+ declare function metresPerPixel(latitude: number, zoom: number): number;
1511
+ /** The largest 1 / 2 / 5 × 10ⁿ that is ≤ `max`. */
1512
+ declare function niceDistance(max: number): number;
1513
+ interface MapScale {
1514
+ /** The bar's length in CSS px. */
1515
+ px: number;
1516
+ /** The distance the bar stands for, in `unit`. */
1517
+ value: number;
1518
+ /** An `Intl.NumberFormat` unit identifier. */
1519
+ unit: "kilometer" | "meter" | "mile" | "foot";
1520
+ }
1521
+ /**
1522
+ * A round distance that fits in `maxPx` at this latitude and zoom, in `unit`
1523
+ * — kilometres (metres below 1 km) or miles (feet below 1 mi).
1524
+ */
1525
+ declare function computeMapScale(latitude: number, zoom: number, unit: MapScaleUnit, maxPx: number): MapScale;
1526
+ interface MapScaleBarProps extends HTMLAttributes<HTMLDivElement> {
1527
+ /** `km` (default) or `mi`. */
1528
+ unit?: MapScaleUnit;
1529
+ /** Corner of the map (default `"bottom-left"`). */
1530
+ position?: MapCorner;
1531
+ }
1532
+ /**
1533
+ * A scale bar for the map's centre latitude — a round distance ("50 km") and
1534
+ * a bar of that length, re-measured as the map moves or resizes. The bar gets
1535
+ * shorter at `narrow`. Render inside `<MapCanvas>`.
1536
+ */
1537
+ declare const MapScaleBar: react.ForwardRefExoticComponent<MapScaleBarProps & react.RefAttributes<HTMLDivElement>>;
1538
+
1539
+ interface MapNorthArrowProps extends HTMLAttributes<HTMLDivElement> {
1540
+ /** Corner of the map (default `"top-left"`). */
1541
+ position?: MapCorner;
1542
+ /** The arrow's accessible name (default `"North"`). */
1543
+ label?: string;
1544
+ }
1545
+ /**
1546
+ * A north arrow that appears only while the map is rotated (bearing ≠ 0) and
1547
+ * turns with it — a north-up map needs none. Static furniture, not a control:
1548
+ * `<MapControls showCompass>` is the button that resets the bearing. Render
1549
+ * inside `<MapCanvas>`.
1550
+ */
1551
+ declare const MapNorthArrow: react.ForwardRefExoticComponent<MapNorthArrowProps & react.RefAttributes<HTMLDivElement>>;
1552
+
1553
+ /** A globe with the main view marked, or a zoomed-out regional map around it. */
1554
+ type MapInsetKind = "globe" | "region";
1555
+ interface MapInsetProps extends HTMLAttributes<HTMLDivElement> {
1556
+ /** Default `"globe"`. */
1557
+ kind?: MapInsetKind;
1558
+ /** Corner of the main map (default `"top-right"`). */
1559
+ position?: MapCorner;
1560
+ /**
1561
+ * Side length in CSS px, optionally per tier (default
1562
+ * `{ base: 128, narrow: 88 }`). Never more than 40 % of the main map's width.
1563
+ */
1564
+ size?: MapResponsive<number>;
1565
+ /** Basemap styles for the inset (default: the main map's defaults). */
1566
+ styles?: MapCanvasProps["styles"];
1567
+ /** A tile-less inset — pair it with your own layers via `styles`. */
1568
+ blank?: boolean;
1569
+ /**
1570
+ * Draw the basemap's own place labels inside the inset. Default `false`
1571
+ * (c-6): a basemap's labels are sized for a full map, so in an 88–128 px
1572
+ * frame they are sliced mid-word ("EUROP", "AMERIC") and read louder than
1573
+ * the globe they caption. An inset is a locator SHAPE, not a second map.
1574
+ */
1575
+ basemapLabels?: boolean;
1576
+ }
1577
+ /**
1578
+ * MapInset — a small locator map in a corner of the main map: a globe
1579
+ * (`kind="globe"`) or a zoomed-out regional map (`kind="region"`), with the
1580
+ * main map's current view marked (a box, or a dot when the box would be too
1581
+ * small to see). It is a second, static `<MapCanvas>` that follows the main
1582
+ * view; decorative, so hidden from assistive tech and never a tab stop.
1583
+ * Render inside `<MapCanvas>`.
1584
+ */
1585
+ declare const MapInset: react.ForwardRefExoticComponent<MapInsetProps & react.RefAttributes<HTMLDivElement>>;
1586
+
1587
+ /** One row of the narrow key. */
1588
+ interface MapAnnotationKeyRow {
1589
+ id: string;
1590
+ number: number;
1591
+ text: ReactNode;
1592
+ }
1593
+ interface MapAnnotationKeyProps extends HTMLAttributes<HTMLOListElement> {
1594
+ /** The rows, in reading order. */
1595
+ rows: readonly MapAnnotationKeyRow[];
1596
+ }
1597
+ /**
1598
+ * The numbered list `<MapCanvas>` renders under the map at `narrow`, where each
1599
+ * `<MapAnnotation>` shows only its number on the map: "1 Lake Ontario, …".
1600
+ * NOT `aria-hidden` — at `narrow` this list is the only place the text lives.
1601
+ */
1602
+ declare const MapAnnotationKey: react.ForwardRefExoticComponent<MapAnnotationKeyProps & react.RefAttributes<HTMLOListElement>>;
1603
+ /** A leader line from the point to the text; `arrow` puts an arrowhead at the point. */
1604
+ interface MapAnnotationConnector {
1605
+ arrow?: boolean;
1606
+ }
1607
+ interface MapAnnotationProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
1608
+ /** Longitude of the point the note is about. */
1609
+ longitude: number;
1610
+ /** Latitude of the point the note is about. */
1611
+ latitude: number;
1612
+ /** The note. Plain text reads best; it moves into the key at `narrow`. */
1613
+ text: ReactNode;
1614
+ /** Where the text sits relative to the point (default `"top-right"`). */
1615
+ anchor?: MapLabelAnchor;
1616
+ /**
1617
+ * The leader line from the point to the text (default: a plain line). Pass
1618
+ * `false` to set the text right beside the point with no line.
1619
+ */
1620
+ connector?: MapAnnotationConnector | false;
1621
+ /**
1622
+ * Whether the note shows at a tier (default `true` everywhere). A hidden note
1623
+ * takes no number, so the key stays gap-free.
1624
+ */
1625
+ showAt?: MapResponsive<boolean>;
1626
+ /** Paint order among annotations: a higher one paints on top (0–8, default 0). */
1627
+ priority?: number;
1628
+ }
1629
+ /**
1630
+ * A text note pinned to a lon / lat with a leader line — an HTML overlay that
1631
+ * follows the map. At `narrow` the text gives way to a numbered marker and
1632
+ * `<MapCanvas>` lists the notes, in reading order, in a key under the map.
1633
+ * The text box clamps inside the map on both axes. Render inside `<MapCanvas>`.
1634
+ */
1635
+ declare const MapAnnotation: react.ForwardRefExoticComponent<MapAnnotationProps & react.RefAttributes<HTMLDivElement>>;
1636
+
1637
+ export { type BasemapTheme, DEFAULT_MAP_HEIGHT, MAP_BREAKPOINTS, MAP_BREAKPOINT_THRESHOLDS, MAP_CORNERS, MAP_LABEL_ANCHORS, type MapAnchorGeometry, MapAnnotation, type MapAnnotationConnector, MapAnnotationKey, type MapAnnotationKeyProps, type MapAnnotationKeyRow, type MapAnnotationProps, MapArc, type MapArcDatum, type MapArcEvent, type MapArcLineLayout, type MapArcLinePaint, type MapArcProps, type MapBreakpoint, MapCanvas, type MapCanvasProps, type MapCanvasRef, MapClusterLayer, type MapClusterLayerProps, type MapContextValue, MapControls, type MapControlsProps, type MapCorner, type MapFillPaint, MapGeoJSON, type MapGeoJSONData, type MapGeoJSONEvent, type MapGeoJSONFeature, type MapGeoJSONPattern, type MapGeoJSONProps, type MapGeoJSONVignette, type MapGeoPosition, type MapHeight, MapInset, type MapInsetKind, type MapInsetProps, type MapLabelAnchor, MapLegend, type MapLegendItem, type MapLegendLayout, type MapLegendPosition, type MapLegendProps, type MapLegendScale, type MapLegendShape, type MapLinePaint, MapMarker, MapMarkerContent, type MapMarkerContentProps, MapMarkerLabel, type MapMarkerLabelProps, type MapMarkerLabelSpec, MapMarkerPopup, type MapMarkerPopupProps, type MapMarkerProps, MapMarkerTooltip, type MapMarkerTooltipProps, MapNorthArrow, type MapNorthArrowProps, type MapPlanGroup, MapPlanImage, type MapPlanImageProps, type MapPlanImageSource, MapPlanLegend, type MapPlanLegendProps, MapPlanOverlay, type MapPlanOverlayMode, type MapPlanOverlayProps, type MapPlanPosition, type MapPlanRegion, MapPlanStatus, type MapPlanStatusProps, MapPlanTable, type MapPlanTableColumn, type MapPlanTableProps, MapPopup, type MapPopupProps, type MapPosition, type MapProjectionOption, type MapResponsive, type MapResponsiveByBreakpoint, MapRoute, type MapRouteDirection, type MapRouteProps, type MapScale, MapScaleBar, type MapScaleBarProps, type MapScaleUnit, type MapStyleOption, type MapViewport, PLAN_FILL_OPACITY, PLAN_MERCATOR_SPAN, PLAN_STATUSES, PLAN_STATUS_ENCODING, type PlanCrs, type PlanDash, type PlanExtent, type PlanImageCoordinates, type PlanPatternKind, type PlanPoint, type PlanRegionBounds, type PlanRegionField, type PlanRegionFields, type PlanScreenBox, type PlanSelection, type PlanStatus, type PlanStatusEncoding, type PlanStatusLabels, ROUTE_ARROW_SIZE, type RouteArrowImage, type UsePlanProjectionOptions, type UsePlanSelectionOptions, buildArcCoordinates, computeMapScale, createPlanCrs, createPlanPatternTile, createRouteArrowImage, isMapResponsiveByBreakpoint, mapAnchorGeometry, mapAnchorTransform, mapBreakpointForWidth, metresPerPixel, niceDistance, planBoundsOfGeometry, planBoundsUnion, planGroupId, planGroupsFromRegions, planPatternImageId, planRegionCentre, planRegionsFromGeoJSON, planStatusMatch, resolveMapResponsive, routeArrowImageId, useMap, useMapBreakpoint, useMapResponsive, usePlanPatterns, usePlanProjection, usePlanSelection, useTokenColor };