@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
@@ -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
 
@@ -67,13 +167,35 @@ export class MockMap {
67
167
  }
68
168
 
69
169
  getCenter() {
70
- return { lng: 0, lat: 0 };
170
+ return this.center;
71
171
  }
72
172
  getZoom() {
73
- return 1;
173
+ return this.zoom;
74
174
  }
75
175
  getBearing() {
76
- 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
+ };
77
199
  }
78
200
  getPitch() {
79
201
  return 0;
@@ -81,9 +203,19 @@ export class MockMap {
81
203
  isMoving() {
82
204
  return false;
83
205
  }
84
- jumpTo() {}
206
+ /** Every `jumpTo(options)` the wrapper asked for, in order. */
207
+ jumpToCalls: any[] = [];
208
+ jumpTo(options?: any) {
209
+ this.jumpToCalls.push(options);
210
+ }
85
211
  easeTo() {}
86
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
+ }
87
219
  zoomTo(zoom: number, options?: unknown) {
88
220
  this.zoomToCalls.push([zoom, options]);
89
221
  }
@@ -96,19 +228,39 @@ export class MockMap {
96
228
  getCanvas() {
97
229
  return this.canvas;
98
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
+ }
99
252
  addSource(id: string, source: any) {
100
- this.sources.set(id, source);
253
+ this.sources.set(id, new MockSource(id, source));
101
254
  }
102
255
  getSource(id: string) {
103
- const source = this.sources.get(id);
104
- if (!source) return undefined;
105
- return { ...source, setData: () => {}, getClusterExpansionZoom: async () => 2 };
256
+ return this.sources.get(id);
106
257
  }
107
258
  removeSource(id: string) {
108
259
  this.sources.delete(id);
109
260
  }
110
- addLayer(layer: { id: string }) {
261
+ addLayer(layer: { id: string } & Record<string, any>, beforeId?: string) {
111
262
  this.layers.set(layer.id, layer);
263
+ this.addLayerCalls.push([layer, beforeId]);
112
264
  }
113
265
  getLayer(id: string) {
114
266
  return this.layers.get(id);
@@ -116,11 +268,55 @@ export class MockMap {
116
268
  removeLayer(id: string) {
117
269
  this.layers.delete(id);
118
270
  }
119
- setPaintProperty() {}
120
- setLayoutProperty() {}
121
- setFeatureState() {}
122
- queryRenderedFeatures() {
123
- 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);
124
320
  }
125
321
  }
126
322
 
@@ -140,8 +336,11 @@ export class MockMarker {
140
336
  MockMarker.instances.push(this);
141
337
  }
142
338
 
143
- setLngLat(lngLat: [number, number]) {
144
- 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 };
145
344
  return this;
146
345
  }
147
346
  getLngLat() {
@@ -203,6 +402,8 @@ export class MockPopup {
203
402
  static instances: MockPopup[] = [];
204
403
 
205
404
  content: HTMLElement | null = null;
405
+ /** Whether the popup is currently on a map — what `addTo`/`remove` toggle. */
406
+ open = false;
206
407
 
207
408
  constructor() {
208
409
  MockPopup.instances.push(this);
@@ -225,13 +426,15 @@ export class MockPopup {
225
426
  return this;
226
427
  }
227
428
  addTo() {
429
+ this.open = true;
228
430
  return this;
229
431
  }
230
432
  remove() {
433
+ this.open = false;
231
434
  return this;
232
435
  }
233
436
  isOpen() {
234
- return false;
437
+ return this.open;
235
438
  }
236
439
  on() {
237
440
  return this;
@@ -241,19 +444,53 @@ export class MockPopup {
241
444
  }
242
445
  }
243
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
+
244
474
  /** Reset the per-class instance registries between tests. */
245
475
  export function resetMaplibreMock() {
246
476
  MockMap.instances = [];
247
477
  MockMarker.instances = [];
248
478
  MockPopup.instances = [];
479
+ MockSource.instances = [];
249
480
  }
250
481
 
251
482
  /** The module shape to return from `vi.mock("maplibre-gl", ...)`. */
252
483
  export function createMaplibreMock() {
253
484
  return {
254
- default: { Map: MockMap, Marker: MockMarker, Popup: MockPopup },
485
+ default: {
486
+ Map: MockMap,
487
+ Marker: MockMarker,
488
+ Popup: MockPopup,
489
+ MercatorCoordinate: MockMercatorCoordinate,
490
+ },
255
491
  Map: MockMap,
256
492
  Marker: MockMarker,
257
493
  Popup: MockPopup,
494
+ MercatorCoordinate: MockMercatorCoordinate,
258
495
  };
259
496
  }