@elabs-ai/components-maps 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-2FPB53NG.js +36 -0
  3. package/dist/chunk-2FPB53NG.js.map +1 -0
  4. package/dist/chunk-2VVJRT66.js +37 -0
  5. package/dist/chunk-2VVJRT66.js.map +1 -0
  6. package/dist/chunk-3HXFONRP.js +2 -0
  7. package/dist/chunk-3HXFONRP.js.map +1 -0
  8. package/dist/chunk-3SJN3IYT.js +2 -0
  9. package/dist/chunk-3SJN3IYT.js.map +1 -0
  10. package/dist/chunk-4DANBD5B.js +55 -0
  11. package/dist/chunk-4DANBD5B.js.map +1 -0
  12. package/dist/chunk-5PDVG53B.js +2 -0
  13. package/dist/chunk-5PDVG53B.js.map +1 -0
  14. package/dist/chunk-643R7IJC.js +2 -0
  15. package/dist/chunk-643R7IJC.js.map +1 -0
  16. package/dist/chunk-B3ZDBQ6I.js +164 -0
  17. package/dist/chunk-B3ZDBQ6I.js.map +1 -0
  18. package/dist/chunk-BGCE3RR5.js +35 -0
  19. package/dist/chunk-BGCE3RR5.js.map +1 -0
  20. package/dist/chunk-C4VI37TD.js +485 -0
  21. package/dist/chunk-C4VI37TD.js.map +1 -0
  22. package/dist/chunk-CJJT6AEI.js +179 -0
  23. package/dist/chunk-CJJT6AEI.js.map +1 -0
  24. package/dist/chunk-D2RCPTRO.js +2 -0
  25. package/dist/chunk-D2RCPTRO.js.map +1 -0
  26. package/dist/chunk-DTV7JZVN.js +2 -0
  27. package/dist/chunk-DTV7JZVN.js.map +1 -0
  28. package/dist/chunk-E65Z3MSV.js +80 -0
  29. package/dist/chunk-E65Z3MSV.js.map +1 -0
  30. package/dist/chunk-E7VPNINZ.js +70 -0
  31. package/dist/chunk-E7VPNINZ.js.map +1 -0
  32. package/dist/chunk-FHV5IA6N.js +100 -0
  33. package/dist/chunk-FHV5IA6N.js.map +1 -0
  34. package/dist/chunk-FQ6V7BFZ.js +347 -0
  35. package/dist/chunk-FQ6V7BFZ.js.map +1 -0
  36. package/dist/chunk-G7JDXDJN.js +19 -0
  37. package/dist/chunk-G7JDXDJN.js.map +1 -0
  38. package/dist/chunk-GWULZOMW.js +21 -0
  39. package/dist/chunk-GWULZOMW.js.map +1 -0
  40. package/dist/chunk-GYDTTAVD.js +120 -0
  41. package/dist/chunk-GYDTTAVD.js.map +1 -0
  42. package/dist/chunk-HCOBWIM4.js +48 -0
  43. package/dist/chunk-HCOBWIM4.js.map +1 -0
  44. package/dist/chunk-IAHK5MVT.js +2 -0
  45. package/dist/chunk-IAHK5MVT.js.map +1 -0
  46. package/dist/chunk-JGJ6442I.js +235 -0
  47. package/dist/chunk-JGJ6442I.js.map +1 -0
  48. package/dist/chunk-JYFERXZT.js +18 -0
  49. package/dist/chunk-JYFERXZT.js.map +1 -0
  50. package/dist/chunk-LU2UEKFM.js +53 -0
  51. package/dist/chunk-LU2UEKFM.js.map +1 -0
  52. package/dist/chunk-LVYM6C5C.js +2 -0
  53. package/dist/chunk-LVYM6C5C.js.map +1 -0
  54. package/dist/chunk-MIMDP3EU.js +104 -0
  55. package/dist/chunk-MIMDP3EU.js.map +1 -0
  56. package/dist/chunk-NNBURJHY.js +2 -0
  57. package/dist/chunk-NNBURJHY.js.map +1 -0
  58. package/dist/chunk-NPUC3V37.js +2 -0
  59. package/dist/chunk-NPUC3V37.js.map +1 -0
  60. package/dist/chunk-OEVVNPN3.js +201 -0
  61. package/dist/chunk-OEVVNPN3.js.map +1 -0
  62. package/dist/chunk-PF3R2BXM.js +200 -0
  63. package/dist/chunk-PF3R2BXM.js.map +1 -0
  64. package/dist/chunk-Q2I5YODW.js +2 -0
  65. package/dist/chunk-Q2I5YODW.js.map +1 -0
  66. package/dist/chunk-QDWWP3N7.js +247 -0
  67. package/dist/chunk-QDWWP3N7.js.map +1 -0
  68. package/dist/chunk-QU33KPMP.js +2 -0
  69. package/dist/chunk-QU33KPMP.js.map +1 -0
  70. package/dist/chunk-RK4BUMHS.js +2 -0
  71. package/dist/chunk-RK4BUMHS.js.map +1 -0
  72. package/dist/chunk-RNSLG64F.js +26 -0
  73. package/dist/chunk-RNSLG64F.js.map +1 -0
  74. package/dist/chunk-RV673TB4.js +68 -0
  75. package/dist/chunk-RV673TB4.js.map +1 -0
  76. package/dist/chunk-S7N2WMVT.js +180 -0
  77. package/dist/chunk-S7N2WMVT.js.map +1 -0
  78. package/dist/chunk-T3Z6TCDD.js +2 -0
  79. package/dist/chunk-T3Z6TCDD.js.map +1 -0
  80. package/dist/chunk-T4TJTVVA.js +105 -0
  81. package/dist/chunk-T4TJTVVA.js.map +1 -0
  82. package/dist/chunk-UMOYRCDX.js +103 -0
  83. package/dist/chunk-UMOYRCDX.js.map +1 -0
  84. package/dist/chunk-V2REM6UT.js +185 -0
  85. package/dist/chunk-V2REM6UT.js.map +1 -0
  86. package/dist/chunk-VFBRFGID.js +89 -0
  87. package/dist/chunk-VFBRFGID.js.map +1 -0
  88. package/dist/chunk-W76NLFER.js +103 -0
  89. package/dist/chunk-W76NLFER.js.map +1 -0
  90. package/dist/chunk-WNU4IOGL.js +116 -0
  91. package/dist/chunk-WNU4IOGL.js.map +1 -0
  92. package/dist/chunk-WS4Z7ENS.js +494 -0
  93. package/dist/chunk-WS4Z7ENS.js.map +1 -0
  94. package/dist/chunk-XQT7YLJY.js +497 -0
  95. package/dist/chunk-XQT7YLJY.js.map +1 -0
  96. package/dist/chunk-Y44MEKJV.js +21 -0
  97. package/dist/chunk-Y44MEKJV.js.map +1 -0
  98. package/dist/chunk-Z4EPSOB4.js +53 -0
  99. package/dist/chunk-Z4EPSOB4.js.map +1 -0
  100. package/dist/chunk-Z5OPDBDC.js +2 -0
  101. package/dist/chunk-Z5OPDBDC.js.map +1 -0
  102. package/dist/index.d.ts +1241 -46
  103. package/dist/index.js +189 -1456
  104. package/dist/index.js.map +1 -1
  105. package/dist/lib/arc-math.js +8 -0
  106. package/dist/lib/arc-math.js.map +1 -0
  107. package/dist/lib/map-position.js +11 -0
  108. package/dist/lib/map-position.js.map +1 -0
  109. package/dist/lib/merge-hover-paint.js +10 -0
  110. package/dist/lib/merge-hover-paint.js.map +1 -0
  111. package/dist/lib/plan-crs.js +14 -0
  112. package/dist/lib/plan-crs.js.map +1 -0
  113. package/dist/lib/plan-patterns.js +14 -0
  114. package/dist/lib/plan-patterns.js.map +1 -0
  115. package/dist/lib/plan-regions.js +18 -0
  116. package/dist/lib/plan-regions.js.map +1 -0
  117. package/dist/lib/plan-status.js +14 -0
  118. package/dist/lib/plan-status.js.map +1 -0
  119. package/dist/lib/route-arrow.js +12 -0
  120. package/dist/lib/route-arrow.js.map +1 -0
  121. package/dist/lib/use-map-breakpoint.js +33 -0
  122. package/dist/lib/use-map-breakpoint.js.map +1 -0
  123. package/dist/lib/use-plan-projection.js +9 -0
  124. package/dist/lib/use-plan-projection.js.map +1 -0
  125. package/dist/lib/use-token-color.js +10 -0
  126. package/dist/lib/use-token-color.js.map +1 -0
  127. package/dist/lib/warn-once.js +10 -0
  128. package/dist/lib/warn-once.js.map +1 -0
  129. package/dist/map-annotation/anchor.js +14 -0
  130. package/dist/map-annotation/anchor.js.map +1 -0
  131. package/dist/map-annotation/index.js +21 -0
  132. package/dist/map-annotation/index.js.map +1 -0
  133. package/dist/map-annotation/map-annotation.js +18 -0
  134. package/dist/map-annotation/map-annotation.js.map +1 -0
  135. package/dist/map-arc/index.js +16 -0
  136. package/dist/map-arc/index.js.map +1 -0
  137. package/dist/map-arc/map-arc.js +13 -0
  138. package/dist/map-arc/map-arc.js.map +1 -0
  139. package/dist/map-canvas/index.css +11 -0
  140. package/dist/map-canvas/index.css.map +1 -0
  141. package/dist/map-canvas/index.js +20 -0
  142. package/dist/map-canvas/index.js.map +1 -0
  143. package/dist/map-canvas/map-canvas.css +11 -0
  144. package/dist/map-canvas/map-canvas.css.map +1 -0
  145. package/dist/map-canvas/map-canvas.js +17 -0
  146. package/dist/map-canvas/map-canvas.js.map +1 -0
  147. package/dist/map-canvas/map-context.js +11 -0
  148. package/dist/map-canvas/map-context.js.map +1 -0
  149. package/dist/map-canvas/use-resolved-basemap-theme.js +9 -0
  150. package/dist/map-canvas/use-resolved-basemap-theme.js.map +1 -0
  151. package/dist/map-cluster-layer/index.js +12 -0
  152. package/dist/map-cluster-layer/index.js.map +1 -0
  153. package/dist/map-cluster-layer/map-cluster-layer.js +12 -0
  154. package/dist/map-cluster-layer/map-cluster-layer.js.map +1 -0
  155. package/dist/map-controls/index.js +11 -0
  156. package/dist/map-controls/index.js.map +1 -0
  157. package/dist/map-controls/map-controls.js +11 -0
  158. package/dist/map-controls/map-controls.js.map +1 -0
  159. package/dist/map-geojson/index.js +13 -0
  160. package/dist/map-geojson/index.js.map +1 -0
  161. package/dist/map-geojson/map-geojson.js +13 -0
  162. package/dist/map-geojson/map-geojson.js.map +1 -0
  163. package/dist/map-inset/index.css +11 -0
  164. package/dist/map-inset/index.css.map +1 -0
  165. package/dist/map-inset/index.js +20 -0
  166. package/dist/map-inset/index.js.map +1 -0
  167. package/dist/map-inset/map-inset.css +11 -0
  168. package/dist/map-inset/map-inset.css.map +1 -0
  169. package/dist/map-inset/map-inset.js +20 -0
  170. package/dist/map-inset/map-inset.js.map +1 -0
  171. package/dist/map-legend/index.js +14 -0
  172. package/dist/map-legend/index.js.map +1 -0
  173. package/dist/map-legend/map-corner.js +10 -0
  174. package/dist/map-legend/map-corner.js.map +1 -0
  175. package/dist/map-legend/map-legend.js +11 -0
  176. package/dist/map-legend/map-legend.js.map +1 -0
  177. package/dist/map-marker/index.js +22 -0
  178. package/dist/map-marker/index.js.map +1 -0
  179. package/dist/map-marker/map-marker.js +22 -0
  180. package/dist/map-marker/map-marker.js.map +1 -0
  181. package/dist/map-north-arrow/index.js +11 -0
  182. package/dist/map-north-arrow/index.js.map +1 -0
  183. package/dist/map-north-arrow/map-north-arrow.js +11 -0
  184. package/dist/map-north-arrow/map-north-arrow.js.map +1 -0
  185. package/dist/map-plan/index.js +30 -0
  186. package/dist/map-plan/index.js.map +1 -0
  187. package/dist/map-plan/map-plan-legend.js +10 -0
  188. package/dist/map-plan/map-plan-legend.js.map +1 -0
  189. package/dist/map-plan/map-plan-overlay.js +13 -0
  190. package/dist/map-plan/map-plan-overlay.js.map +1 -0
  191. package/dist/map-plan/map-plan-status.js +9 -0
  192. package/dist/map-plan/map-plan-status.js.map +1 -0
  193. package/dist/map-plan/map-plan-table.js +10 -0
  194. package/dist/map-plan/map-plan-table.js.map +1 -0
  195. package/dist/map-plan/use-plan-selection.js +9 -0
  196. package/dist/map-plan/use-plan-selection.js.map +1 -0
  197. package/dist/map-plan-image/index.js +11 -0
  198. package/dist/map-plan-image/index.js.map +1 -0
  199. package/dist/map-plan-image/map-plan-image.js +11 -0
  200. package/dist/map-plan-image/map-plan-image.js.map +1 -0
  201. package/dist/map-popup/index.js +12 -0
  202. package/dist/map-popup/index.js.map +1 -0
  203. package/dist/map-popup/map-popup.js +12 -0
  204. package/dist/map-popup/map-popup.js.map +1 -0
  205. package/dist/map-route/index.js +12 -0
  206. package/dist/map-route/index.js.map +1 -0
  207. package/dist/map-route/map-route.js +12 -0
  208. package/dist/map-route/map-route.js.map +1 -0
  209. package/dist/map-scale-bar/index.js +18 -0
  210. package/dist/map-scale-bar/index.js.map +1 -0
  211. package/dist/map-scale-bar/map-scale-bar.js +18 -0
  212. package/dist/map-scale-bar/map-scale-bar.js.map +1 -0
  213. package/package.json +5 -5
  214. package/src/index.ts +83 -0
  215. package/src/lib/arc-math.test.ts +19 -0
  216. package/src/lib/arc-math.ts +6 -1
  217. package/src/lib/map-position.ts +77 -0
  218. package/src/lib/merge-hover-paint.ts +44 -13
  219. package/src/lib/plan-crs.parity.test.ts +39 -0
  220. package/src/lib/plan-crs.test.ts +248 -0
  221. package/src/lib/plan-crs.ts +252 -0
  222. package/src/lib/plan-patterns.test.ts +255 -0
  223. package/src/lib/plan-patterns.ts +160 -0
  224. package/src/lib/plan-regions.test.ts +186 -0
  225. package/src/lib/plan-regions.ts +209 -0
  226. package/src/lib/plan-status.test.ts +87 -0
  227. package/src/lib/plan-status.ts +147 -0
  228. package/src/lib/route-arrow.test.ts +88 -0
  229. package/src/lib/route-arrow.ts +59 -0
  230. package/src/lib/use-map-breakpoint.test.ts +83 -0
  231. package/src/lib/use-map-breakpoint.ts +238 -0
  232. package/src/lib/use-plan-projection.ts +157 -0
  233. package/src/lib/warn-once.ts +21 -0
  234. package/src/map-annotation/anchor.ts +92 -0
  235. package/src/map-annotation/index.ts +15 -0
  236. package/src/map-annotation/map-annotation.stories.tsx +113 -0
  237. package/src/map-annotation/map-annotation.test.tsx +106 -0
  238. package/src/map-annotation/map-annotation.tsx +356 -0
  239. package/src/map-arc/map-arc.tsx +26 -5
  240. package/src/map-canvas/map-canvas.stories.tsx +211 -0
  241. package/src/map-canvas/map-canvas.test.tsx +421 -1
  242. package/src/map-canvas/map-canvas.tsx +466 -20
  243. package/src/map-canvas/map-context.ts +11 -0
  244. package/src/map-cluster-layer/map-cluster-layer.tsx +14 -1
  245. package/src/map-controls/map-controls.stories.tsx +16 -0
  246. package/src/map-controls/map-controls.test.tsx +145 -1
  247. package/src/map-controls/map-controls.tsx +57 -12
  248. package/src/map-geojson/map-geojson.stories.tsx +153 -3
  249. package/src/map-geojson/map-geojson.test.tsx +453 -0
  250. package/src/map-geojson/map-geojson.tsx +591 -51
  251. package/src/map-inset/index.ts +1 -0
  252. package/src/map-inset/map-inset.stories.tsx +61 -0
  253. package/src/map-inset/map-inset.test.tsx +117 -0
  254. package/src/map-inset/map-inset.tsx +264 -0
  255. package/src/map-legend/index.ts +10 -0
  256. package/src/map-legend/map-corner.ts +23 -0
  257. package/src/map-legend/map-legend.stories.tsx +171 -0
  258. package/src/map-legend/map-legend.test.tsx +136 -0
  259. package/src/map-legend/map-legend.tsx +264 -0
  260. package/src/map-marker/map-marker.stories.tsx +136 -4
  261. package/src/map-marker/map-marker.test.tsx +383 -3
  262. package/src/map-marker/map-marker.tsx +414 -44
  263. package/src/map-north-arrow/index.ts +1 -0
  264. package/src/map-north-arrow/map-north-arrow.stories.tsx +65 -0
  265. package/src/map-north-arrow/map-north-arrow.test.tsx +39 -0
  266. package/src/map-north-arrow/map-north-arrow.tsx +82 -0
  267. package/src/map-plan/index.ts +13 -0
  268. package/src/map-plan/map-plan-legend.test.tsx +61 -0
  269. package/src/map-plan/map-plan-legend.tsx +83 -0
  270. package/src/map-plan/map-plan-overlay.stories.tsx +260 -0
  271. package/src/map-plan/map-plan-overlay.test.tsx +302 -0
  272. package/src/map-plan/map-plan-overlay.tsx +531 -0
  273. package/src/map-plan/map-plan-status.test.tsx +71 -0
  274. package/src/map-plan/map-plan-status.tsx +77 -0
  275. package/src/map-plan/map-plan-table.test.tsx +86 -0
  276. package/src/map-plan/map-plan-table.tsx +153 -0
  277. package/src/map-plan/use-plan-selection.ts +78 -0
  278. package/src/map-plan-image/index.ts +1 -0
  279. package/src/map-plan-image/map-plan-image.stories.tsx +301 -0
  280. package/src/map-plan-image/map-plan-image.test.tsx +176 -0
  281. package/src/map-plan-image/map-plan-image.tsx +279 -0
  282. package/src/map-popup/map-popup.tsx +18 -11
  283. package/src/map-route/index.ts +1 -1
  284. package/src/map-route/map-route.test.tsx +216 -0
  285. package/src/map-route/map-route.tsx +105 -6
  286. package/src/map-scale-bar/index.ts +9 -0
  287. package/src/map-scale-bar/map-scale-bar.stories.tsx +63 -0
  288. package/src/map-scale-bar/map-scale-bar.test.tsx +65 -0
  289. package/src/map-scale-bar/map-scale-bar.tsx +145 -0
  290. package/src/test-utils/locator-fixture.ts +75 -0
  291. package/src/test-utils/maplibre-mock.ts +267 -20
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A hand-simplified Lake Ontario locator fixture (≈ 20 vertices a shape, WGS 84
3
+ * lon / lat) for the locator-furniture stories and tests — two area markers
4
+ * and a few places. Offline and tiny on purpose; not survey-accurate.
5
+ */
6
+
7
+ export const LAKE_ONTARIO: GeoJSON.Feature<GeoJSON.Polygon, { name: string }> = {
8
+ type: "Feature",
9
+ properties: { name: "Lake Ontario" },
10
+ geometry: {
11
+ type: "Polygon",
12
+ coordinates: [
13
+ [
14
+ [-79.8, 43.28],
15
+ [-79.55, 43.47],
16
+ [-79.38, 43.63],
17
+ [-79.05, 43.78],
18
+ [-78.86, 43.87],
19
+ [-78.17, 43.95],
20
+ [-77.55, 43.99],
21
+ [-77.15, 43.86],
22
+ [-76.82, 43.95],
23
+ [-76.48, 44.21],
24
+ [-76.24, 44.19],
25
+ [-76.2, 43.95],
26
+ [-76.46, 43.52],
27
+ [-76.95, 43.28],
28
+ [-77.6, 43.26],
29
+ [-78.3, 43.37],
30
+ [-78.9, 43.33],
31
+ [-79.06, 43.26],
32
+ [-79.56, 43.2],
33
+ [-79.8, 43.28],
34
+ ],
35
+ ],
36
+ },
37
+ };
38
+
39
+ export const GREATER_TORONTO: GeoJSON.Feature<GeoJSON.Polygon, { name: string }> = {
40
+ type: "Feature",
41
+ properties: { name: "Greater Toronto Area" },
42
+ geometry: {
43
+ type: "Polygon",
44
+ coordinates: [
45
+ [
46
+ [-79.8, 43.3],
47
+ [-79.55, 43.47],
48
+ [-79.38, 43.63],
49
+ [-79.05, 43.78],
50
+ [-78.86, 43.87],
51
+ [-78.8, 44.15],
52
+ [-79.3, 44.28],
53
+ [-79.85, 44.08],
54
+ [-80.05, 43.72],
55
+ [-79.8, 43.3],
56
+ ],
57
+ ],
58
+ },
59
+ };
60
+
61
+ /** West, south, east, north of both areas — the locator's data extent. */
62
+ export const LOCATOR_BOUNDS: [[number, number], [number, number]] = [
63
+ [-80.05, 43.2],
64
+ [-76.2, 44.28],
65
+ ];
66
+
67
+ /** Places the locator labels, west → east. */
68
+ export const LOCATOR_PLACES = {
69
+ toronto: { longitude: -79.38, latitude: 43.65 },
70
+ hamilton: { longitude: -79.87, latitude: 43.25 },
71
+ rochester: { longitude: -77.61, latitude: 43.16 },
72
+ kingston: { longitude: -76.48, latitude: 44.23 },
73
+ lakeLabel: { longitude: -77.9, latitude: 43.62 },
74
+ gtaLabel: { longitude: -79.45, latitude: 43.93 },
75
+ } as const;
@@ -13,16 +13,112 @@
13
13
 
14
14
  type Handler = (...args: any[]) => void;
15
15
 
16
+ export class MockHandler {
17
+ enabled = true;
18
+ enable() {
19
+ this.enabled = true;
20
+ }
21
+ disable() {
22
+ this.enabled = false;
23
+ }
24
+ isEnabled() {
25
+ return this.enabled;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * One persistent instance per source id, so a component that subscribes to its
31
+ * own source (`source.on("error", …)`) or updates it in place
32
+ * (`setCoordinates`/`updateImage`) can be asserted against. The spec fields are
33
+ * spread onto the instance, so reads like `source.type` still work.
34
+ */
35
+ export class MockSource {
36
+ static instances: MockSource[] = [];
37
+
38
+ id: string;
39
+ spec: Record<string, any>;
40
+ handlers = new Map<string, Set<Handler>>();
41
+ setDataCalls: unknown[] = [];
42
+ coordinatesCalls: unknown[] = [];
43
+ updateImageCalls: unknown[] = [];
44
+
45
+ constructor(id: string, spec: Record<string, any>) {
46
+ this.id = id;
47
+ this.spec = spec;
48
+ Object.assign(this, spec);
49
+ MockSource.instances.push(this);
50
+ }
51
+
52
+ setData(data: unknown) {
53
+ this.setDataCalls.push(data);
54
+ return this;
55
+ }
56
+ setCoordinates(coordinates: unknown) {
57
+ this.coordinatesCalls.push(coordinates);
58
+ return this;
59
+ }
60
+ updateImage(options: unknown) {
61
+ this.updateImageCalls.push(options);
62
+ return this;
63
+ }
64
+ async getClusterExpansionZoom() {
65
+ return 2;
66
+ }
67
+ on(event: string, handler: Handler) {
68
+ if (!this.handlers.has(event)) this.handlers.set(event, new Set());
69
+ this.handlers.get(event)!.add(handler);
70
+ return this;
71
+ }
72
+ off(event: string, handler: Handler) {
73
+ this.handlers.get(event)?.delete(handler);
74
+ return this;
75
+ }
76
+ /** Fire an event on this source — e.g. a failed image fetch. */
77
+ emit(event: string, ...args: unknown[]) {
78
+ this.handlers.get(event)?.forEach((handler) => {
79
+ handler(...args);
80
+ });
81
+ }
82
+ }
83
+
16
84
  export class MockMap {
17
85
  static instances: MockMap[] = [];
18
86
 
19
87
  handlers = new Map<string, Set<Handler>>();
20
88
  container: HTMLElement;
21
89
  removed = false;
22
- sources = new Map<string, any>();
90
+ sources = new Map<string, MockSource>();
23
91
  layers = new Map<string, any>();
24
92
  canvas = document.createElement("canvas");
93
+ /** Starts with MapLibre's interactive class, as a real interactive map's does. */
94
+ canvasContainer = Object.assign(document.createElement("div"), {
95
+ className: "maplibregl-canvas-container maplibregl-interactive",
96
+ });
25
97
  zoomToCalls: unknown[] = [];
98
+ images = new Map<string, unknown>();
99
+ center = { lng: 0, lat: 0 };
100
+ zoom = 1;
101
+ bearing = 0;
102
+
103
+ /** Gesture handlers, each with an `enabled` flag static mode flips. */
104
+ scrollZoom = new MockHandler();
105
+ boxZoom = new MockHandler();
106
+ dragRotate = new MockHandler();
107
+ dragPan = new MockHandler();
108
+ keyboard = new MockHandler();
109
+ doubleClickZoom = new MockHandler();
110
+ touchZoomRotate = new MockHandler();
111
+ touchPitch = new MockHandler();
112
+ addLayerCalls: unknown[] = [];
113
+ paint = new Map<string, unknown>();
114
+ layout = new Map<string, unknown>();
115
+ featureStateCalls: unknown[] = [];
116
+ /** `[id, image, options]` per `addImage` — the pattern registration asserts `pixelRatio`. */
117
+ addImageCalls: [string, unknown, unknown][] = [];
118
+ updateImageCalls: [string, unknown][] = [];
119
+ minZoom: number | undefined;
120
+ maxZoom: number | undefined;
121
+ maxBounds: unknown;
26
122
 
27
123
  /** The full options object the component constructed the map with. */
28
124
  options: Record<string, any>;
@@ -30,6 +126,10 @@ export class MockMap {
30
126
  constructor(options: { container: HTMLElement } & Record<string, any>) {
31
127
  this.container = options.container;
32
128
  this.options = options;
129
+ // What MapLibre would measure: the box's height rule at construction time,
130
+ // and whether that rule could still be mid-transition when it measured.
131
+ this.aspectAtConstruction = options.container.style.aspectRatio;
132
+ this.transitionAtConstruction = options.container.style.transitionProperty;
33
133
  MockMap.instances.push(this);
34
134
  }
35
135
 
@@ -55,18 +155,47 @@ export class MockMap {
55
155
  return this;
56
156
  }
57
157
 
158
+ /** Manually re-fire an event's handlers — e.g. a second `styledata` after a style reload. */
159
+ emit(event: string, layerId?: string, ...args: unknown[]) {
160
+ this.handlers.get(this.key(event, layerId))?.forEach((handler) => {
161
+ handler(...args);
162
+ });
163
+ }
164
+
58
165
  remove() {
59
166
  this.removed = true;
60
167
  }
61
168
 
62
169
  getCenter() {
63
- return { lng: 0, lat: 0 };
170
+ return this.center;
64
171
  }
65
172
  getZoom() {
66
- return 1;
173
+ return this.zoom;
67
174
  }
68
175
  getBearing() {
69
- return 0;
176
+ return this.bearing;
177
+ }
178
+ /**
179
+ * A flat equirectangular stand-in: 1° = 10 px from the container's top-left.
180
+ * Accepts both shapes MapLibre's own `project` does — a `[lng, lat]` pair
181
+ * and a `LngLat`.
182
+ */
183
+ project(lngLat: [number, number] | { lng: number; lat: number }) {
184
+ const [lng, lat] = Array.isArray(lngLat) ? lngLat : [lngLat.lng, lngLat.lat];
185
+ return { x: (lng + 180) * 10, y: (90 - lat) * 10 };
186
+ }
187
+ /** Inverse of {@link project} — the same flat equirectangular stand-in. */
188
+ unproject(point: [number, number] | { x: number; y: number }) {
189
+ const [x, y] = Array.isArray(point) ? point : [point.x, point.y];
190
+ return { lng: x / 10 - 180, lat: 90 - y / 10 };
191
+ }
192
+ getBounds() {
193
+ return {
194
+ getWest: () => -10,
195
+ getSouth: () => -10,
196
+ getEast: () => 10,
197
+ getNorth: () => 10,
198
+ };
70
199
  }
71
200
  getPitch() {
72
201
  return 0;
@@ -74,9 +203,19 @@ export class MockMap {
74
203
  isMoving() {
75
204
  return false;
76
205
  }
77
- jumpTo() {}
206
+ /** Every `jumpTo(options)` the wrapper asked for, in order. */
207
+ jumpToCalls: any[] = [];
208
+ jumpTo(options?: any) {
209
+ this.jumpToCalls.push(options);
210
+ }
78
211
  easeTo() {}
79
212
  flyTo() {}
213
+ /** Every `fitBounds(bounds, options)` the wrapper asked for, in order. */
214
+ fitBoundsCalls: { bounds: unknown; options: unknown }[] = [];
215
+ fitBounds(bounds: unknown, options?: unknown) {
216
+ this.fitBoundsCalls.push({ bounds, options });
217
+ return this;
218
+ }
80
219
  zoomTo(zoom: number, options?: unknown) {
81
220
  this.zoomToCalls.push([zoom, options]);
82
221
  }
@@ -89,19 +228,39 @@ export class MockMap {
89
228
  getCanvas() {
90
229
  return this.canvas;
91
230
  }
231
+ aspectAtConstruction = "";
232
+ transitionAtConstruction = "";
233
+ resizeCount = 0;
234
+ resize() {
235
+ this.resizeCount += 1;
236
+ return this;
237
+ }
238
+ getCanvasContainer() {
239
+ return this.canvasContainer;
240
+ }
241
+ /** `[id, image, options]` per call — the pattern registration asserts `pixelRatio`. */
242
+ addImage(id: string, image: unknown, options?: unknown) {
243
+ this.addImageCalls.push([id, image, options]);
244
+ this.images.set(id, image);
245
+ }
246
+ hasImage(id: string) {
247
+ return this.images.has(id);
248
+ }
249
+ removeImage(id: string) {
250
+ this.images.delete(id);
251
+ }
92
252
  addSource(id: string, source: any) {
93
- this.sources.set(id, source);
253
+ this.sources.set(id, new MockSource(id, source));
94
254
  }
95
255
  getSource(id: string) {
96
- const source = this.sources.get(id);
97
- if (!source) return undefined;
98
- return { ...source, setData: () => {}, getClusterExpansionZoom: async () => 2 };
256
+ return this.sources.get(id);
99
257
  }
100
258
  removeSource(id: string) {
101
259
  this.sources.delete(id);
102
260
  }
103
- addLayer(layer: { id: string }) {
261
+ addLayer(layer: { id: string } & Record<string, any>, beforeId?: string) {
104
262
  this.layers.set(layer.id, layer);
263
+ this.addLayerCalls.push([layer, beforeId]);
105
264
  }
106
265
  getLayer(id: string) {
107
266
  return this.layers.get(id);
@@ -109,11 +268,55 @@ export class MockMap {
109
268
  removeLayer(id: string) {
110
269
  this.layers.delete(id);
111
270
  }
112
- setPaintProperty() {}
113
- setLayoutProperty() {}
114
- setFeatureState() {}
115
- queryRenderedFeatures() {
116
- return [];
271
+ setPaintProperty(layerId: string, name: string, value: unknown) {
272
+ this.paint.set(`${layerId}:${name}`, value);
273
+ }
274
+ /** Layout properties the wrapper set, keyed `<layerId>:<property>`. */
275
+ layoutProperties = new Map<string, unknown>();
276
+ setLayoutProperty(layerId: string, property: string, value: unknown) {
277
+ this.layoutProperties.set(`${layerId}:${property}`, value);
278
+ this.layout.set(`${layerId}:${property}`, value);
279
+ }
280
+ getLayoutProperty(layerId: string, property: string) {
281
+ return this.layoutProperties.get(`${layerId}:${property}`);
282
+ }
283
+ /**
284
+ * The style the map is showing. Like MapLibre's own, it lists every layer
285
+ * the map holds — a test describes a basemap's symbol layers by adding them
286
+ * with `addLayer`.
287
+ */
288
+ getStyle() {
289
+ return { layers: [...this.layers.values()] };
290
+ }
291
+ /** Observable feature-state, keyed by feature id — the hover highlight. */
292
+ featureStates = new Map<string | number, Record<string, unknown>>();
293
+ setFeatureState(target: { source: string; id: string | number }, state: Record<string, unknown>) {
294
+ this.featureStateCalls.push([target, state]);
295
+ this.featureStates.set(target.id, { ...this.featureStates.get(target.id), ...state });
296
+ }
297
+ /** What the next `queryRenderedFeatures` should return; set it per test. */
298
+ queryRenderedFeaturesResult: any[] = [];
299
+ queryRenderedFeaturesCalls: unknown[] = [];
300
+ queryRenderedFeatures(geometry?: unknown, options?: unknown) {
301
+ this.queryRenderedFeaturesCalls.push([geometry, options]);
302
+ return this.queryRenderedFeaturesResult;
303
+ }
304
+
305
+ // Camera limits — a plan map clamps pan and zoom to its extent.
306
+ setMinZoom(zoom: number) {
307
+ this.minZoom = zoom;
308
+ }
309
+ setMaxZoom(zoom: number) {
310
+ this.maxZoom = zoom;
311
+ }
312
+ setMaxBounds(bounds: unknown) {
313
+ this.maxBounds = bounds;
314
+ }
315
+
316
+ /** A canvas-generated tile re-registered in place, `[id, image]` per call. */
317
+ updateImage(id: string, image: unknown) {
318
+ this.updateImageCalls.push([id, image]);
319
+ this.images.set(id, image);
117
320
  }
118
321
  }
119
322
 
@@ -124,14 +327,20 @@ export class MockMarker {
124
327
  addedTo: MockMap | null = null;
125
328
  popup: unknown = null;
126
329
  lngLat = { lng: 0, lat: 0 };
330
+ /** The full options object the component constructed the marker with. */
331
+ options: Record<string, any>;
127
332
 
128
- constructor(options: { element?: HTMLElement } = {}) {
333
+ constructor(options: { element?: HTMLElement } & Record<string, any> = {}) {
129
334
  this.element = options.element ?? document.createElement("div");
335
+ this.options = options;
130
336
  MockMarker.instances.push(this);
131
337
  }
132
338
 
133
- setLngLat(lngLat: [number, number]) {
134
- this.lngLat = { lng: lngLat[0], lat: lngLat[1] };
339
+ /** Accepts both shapes MapLibre does: a `[lng, lat]` pair and a `LngLat`. */
340
+ setLngLat(lngLat: [number, number] | { lng: number; lat: number }) {
341
+ this.lngLat = Array.isArray(lngLat)
342
+ ? { lng: lngLat[0], lat: lngLat[1] }
343
+ : { lng: lngLat.lng, lat: lngLat.lat };
135
344
  return this;
136
345
  }
137
346
  getLngLat() {
@@ -193,6 +402,8 @@ export class MockPopup {
193
402
  static instances: MockPopup[] = [];
194
403
 
195
404
  content: HTMLElement | null = null;
405
+ /** Whether the popup is currently on a map — what `addTo`/`remove` toggle. */
406
+ open = false;
196
407
 
197
408
  constructor() {
198
409
  MockPopup.instances.push(this);
@@ -215,13 +426,15 @@ export class MockPopup {
215
426
  return this;
216
427
  }
217
428
  addTo() {
429
+ this.open = true;
218
430
  return this;
219
431
  }
220
432
  remove() {
433
+ this.open = false;
221
434
  return this;
222
435
  }
223
436
  isOpen() {
224
- return false;
437
+ return this.open;
225
438
  }
226
439
  on() {
227
440
  return this;
@@ -231,19 +444,53 @@ export class MockPopup {
231
444
  }
232
445
  }
233
446
 
447
+ /**
448
+ * The real Web Mercator transform, so a plan coordinate system behaves under
449
+ * test exactly as it does in a browser.
450
+ */
451
+ export class MockMercatorCoordinate {
452
+ constructor(
453
+ public x: number,
454
+ public y: number,
455
+ public z = 0,
456
+ ) {}
457
+
458
+ static fromLngLat(lngLat: { lng: number; lat: number } | [number, number], altitude = 0) {
459
+ const [lng, lat] = Array.isArray(lngLat) ? lngLat : [lngLat.lng, lngLat.lat];
460
+ const x = (180 + lng) / 360;
461
+ const y =
462
+ (180 - (180 / Math.PI) * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360))) / 360;
463
+ return new MockMercatorCoordinate(x, y, altitude);
464
+ }
465
+
466
+ toLngLat() {
467
+ const lng = this.x * 360 - 180;
468
+ const y2 = 180 - this.y * 360;
469
+ const lat = (360 / Math.PI) * (Math.atan(Math.exp((y2 * Math.PI) / 180)) - Math.PI / 4);
470
+ return { lng, lat };
471
+ }
472
+ }
473
+
234
474
  /** Reset the per-class instance registries between tests. */
235
475
  export function resetMaplibreMock() {
236
476
  MockMap.instances = [];
237
477
  MockMarker.instances = [];
238
478
  MockPopup.instances = [];
479
+ MockSource.instances = [];
239
480
  }
240
481
 
241
482
  /** The module shape to return from `vi.mock("maplibre-gl", ...)`. */
242
483
  export function createMaplibreMock() {
243
484
  return {
244
- default: { Map: MockMap, Marker: MockMarker, Popup: MockPopup },
485
+ default: {
486
+ Map: MockMap,
487
+ Marker: MockMarker,
488
+ Popup: MockPopup,
489
+ MercatorCoordinate: MockMercatorCoordinate,
490
+ },
245
491
  Map: MockMap,
246
492
  Marker: MockMarker,
247
493
  Popup: MockPopup,
494
+ MercatorCoordinate: MockMercatorCoordinate,
248
495
  };
249
496
  }