@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.
- package/README.md +1 -1
- package/dist/chunk-2FPB53NG.js +36 -0
- package/dist/chunk-2FPB53NG.js.map +1 -0
- package/dist/chunk-2VVJRT66.js +37 -0
- package/dist/chunk-2VVJRT66.js.map +1 -0
- package/dist/chunk-3HXFONRP.js +2 -0
- package/dist/chunk-3HXFONRP.js.map +1 -0
- package/dist/chunk-3SJN3IYT.js +2 -0
- package/dist/chunk-3SJN3IYT.js.map +1 -0
- package/dist/chunk-4DANBD5B.js +55 -0
- package/dist/chunk-4DANBD5B.js.map +1 -0
- package/dist/chunk-5PDVG53B.js +2 -0
- package/dist/chunk-5PDVG53B.js.map +1 -0
- package/dist/chunk-643R7IJC.js +2 -0
- package/dist/chunk-643R7IJC.js.map +1 -0
- package/dist/chunk-B3ZDBQ6I.js +164 -0
- package/dist/chunk-B3ZDBQ6I.js.map +1 -0
- package/dist/chunk-BGCE3RR5.js +35 -0
- package/dist/chunk-BGCE3RR5.js.map +1 -0
- package/dist/chunk-C4VI37TD.js +485 -0
- package/dist/chunk-C4VI37TD.js.map +1 -0
- package/dist/chunk-CJJT6AEI.js +179 -0
- package/dist/chunk-CJJT6AEI.js.map +1 -0
- package/dist/chunk-D2RCPTRO.js +2 -0
- package/dist/chunk-D2RCPTRO.js.map +1 -0
- package/dist/chunk-DTV7JZVN.js +2 -0
- package/dist/chunk-DTV7JZVN.js.map +1 -0
- package/dist/chunk-E65Z3MSV.js +80 -0
- package/dist/chunk-E65Z3MSV.js.map +1 -0
- package/dist/chunk-E7VPNINZ.js +70 -0
- package/dist/chunk-E7VPNINZ.js.map +1 -0
- package/dist/chunk-FHV5IA6N.js +100 -0
- package/dist/chunk-FHV5IA6N.js.map +1 -0
- package/dist/chunk-FQ6V7BFZ.js +347 -0
- package/dist/chunk-FQ6V7BFZ.js.map +1 -0
- package/dist/chunk-G7JDXDJN.js +19 -0
- package/dist/chunk-G7JDXDJN.js.map +1 -0
- package/dist/chunk-GWULZOMW.js +21 -0
- package/dist/chunk-GWULZOMW.js.map +1 -0
- package/dist/chunk-GYDTTAVD.js +120 -0
- package/dist/chunk-GYDTTAVD.js.map +1 -0
- package/dist/chunk-HCOBWIM4.js +48 -0
- package/dist/chunk-HCOBWIM4.js.map +1 -0
- package/dist/chunk-IAHK5MVT.js +2 -0
- package/dist/chunk-IAHK5MVT.js.map +1 -0
- package/dist/chunk-JGJ6442I.js +235 -0
- package/dist/chunk-JGJ6442I.js.map +1 -0
- package/dist/chunk-JYFERXZT.js +18 -0
- package/dist/chunk-JYFERXZT.js.map +1 -0
- package/dist/chunk-LU2UEKFM.js +53 -0
- package/dist/chunk-LU2UEKFM.js.map +1 -0
- package/dist/chunk-LVYM6C5C.js +2 -0
- package/dist/chunk-LVYM6C5C.js.map +1 -0
- package/dist/chunk-MIMDP3EU.js +104 -0
- package/dist/chunk-MIMDP3EU.js.map +1 -0
- package/dist/chunk-NNBURJHY.js +2 -0
- package/dist/chunk-NNBURJHY.js.map +1 -0
- package/dist/chunk-NPUC3V37.js +2 -0
- package/dist/chunk-NPUC3V37.js.map +1 -0
- package/dist/chunk-OEVVNPN3.js +201 -0
- package/dist/chunk-OEVVNPN3.js.map +1 -0
- package/dist/chunk-PF3R2BXM.js +200 -0
- package/dist/chunk-PF3R2BXM.js.map +1 -0
- package/dist/chunk-Q2I5YODW.js +2 -0
- package/dist/chunk-Q2I5YODW.js.map +1 -0
- package/dist/chunk-QDWWP3N7.js +247 -0
- package/dist/chunk-QDWWP3N7.js.map +1 -0
- package/dist/chunk-QU33KPMP.js +2 -0
- package/dist/chunk-QU33KPMP.js.map +1 -0
- package/dist/chunk-RK4BUMHS.js +2 -0
- package/dist/chunk-RK4BUMHS.js.map +1 -0
- package/dist/chunk-RNSLG64F.js +26 -0
- package/dist/chunk-RNSLG64F.js.map +1 -0
- package/dist/chunk-RV673TB4.js +68 -0
- package/dist/chunk-RV673TB4.js.map +1 -0
- package/dist/chunk-S7N2WMVT.js +180 -0
- package/dist/chunk-S7N2WMVT.js.map +1 -0
- package/dist/chunk-T3Z6TCDD.js +2 -0
- package/dist/chunk-T3Z6TCDD.js.map +1 -0
- package/dist/chunk-T4TJTVVA.js +105 -0
- package/dist/chunk-T4TJTVVA.js.map +1 -0
- package/dist/chunk-UMOYRCDX.js +103 -0
- package/dist/chunk-UMOYRCDX.js.map +1 -0
- package/dist/chunk-V2REM6UT.js +185 -0
- package/dist/chunk-V2REM6UT.js.map +1 -0
- package/dist/chunk-VFBRFGID.js +89 -0
- package/dist/chunk-VFBRFGID.js.map +1 -0
- package/dist/chunk-W76NLFER.js +103 -0
- package/dist/chunk-W76NLFER.js.map +1 -0
- package/dist/chunk-WNU4IOGL.js +116 -0
- package/dist/chunk-WNU4IOGL.js.map +1 -0
- package/dist/chunk-WS4Z7ENS.js +494 -0
- package/dist/chunk-WS4Z7ENS.js.map +1 -0
- package/dist/chunk-XQT7YLJY.js +497 -0
- package/dist/chunk-XQT7YLJY.js.map +1 -0
- package/dist/chunk-Y44MEKJV.js +21 -0
- package/dist/chunk-Y44MEKJV.js.map +1 -0
- package/dist/chunk-Z4EPSOB4.js +53 -0
- package/dist/chunk-Z4EPSOB4.js.map +1 -0
- package/dist/chunk-Z5OPDBDC.js +2 -0
- package/dist/chunk-Z5OPDBDC.js.map +1 -0
- package/dist/index.d.ts +1241 -46
- package/dist/index.js +189 -1456
- package/dist/index.js.map +1 -1
- package/dist/lib/arc-math.js +8 -0
- package/dist/lib/arc-math.js.map +1 -0
- package/dist/lib/map-position.js +11 -0
- package/dist/lib/map-position.js.map +1 -0
- package/dist/lib/merge-hover-paint.js +10 -0
- package/dist/lib/merge-hover-paint.js.map +1 -0
- package/dist/lib/plan-crs.js +14 -0
- package/dist/lib/plan-crs.js.map +1 -0
- package/dist/lib/plan-patterns.js +14 -0
- package/dist/lib/plan-patterns.js.map +1 -0
- package/dist/lib/plan-regions.js +18 -0
- package/dist/lib/plan-regions.js.map +1 -0
- package/dist/lib/plan-status.js +14 -0
- package/dist/lib/plan-status.js.map +1 -0
- package/dist/lib/route-arrow.js +12 -0
- package/dist/lib/route-arrow.js.map +1 -0
- package/dist/lib/use-map-breakpoint.js +33 -0
- package/dist/lib/use-map-breakpoint.js.map +1 -0
- package/dist/lib/use-plan-projection.js +9 -0
- package/dist/lib/use-plan-projection.js.map +1 -0
- package/dist/lib/use-token-color.js +10 -0
- package/dist/lib/use-token-color.js.map +1 -0
- package/dist/lib/warn-once.js +10 -0
- package/dist/lib/warn-once.js.map +1 -0
- package/dist/map-annotation/anchor.js +14 -0
- package/dist/map-annotation/anchor.js.map +1 -0
- package/dist/map-annotation/index.js +21 -0
- package/dist/map-annotation/index.js.map +1 -0
- package/dist/map-annotation/map-annotation.js +18 -0
- package/dist/map-annotation/map-annotation.js.map +1 -0
- package/dist/map-arc/index.js +16 -0
- package/dist/map-arc/index.js.map +1 -0
- package/dist/map-arc/map-arc.js +13 -0
- package/dist/map-arc/map-arc.js.map +1 -0
- package/dist/map-canvas/index.css +11 -0
- package/dist/map-canvas/index.css.map +1 -0
- package/dist/map-canvas/index.js +20 -0
- package/dist/map-canvas/index.js.map +1 -0
- package/dist/map-canvas/map-canvas.css +11 -0
- package/dist/map-canvas/map-canvas.css.map +1 -0
- package/dist/map-canvas/map-canvas.js +17 -0
- package/dist/map-canvas/map-canvas.js.map +1 -0
- package/dist/map-canvas/map-context.js +11 -0
- package/dist/map-canvas/map-context.js.map +1 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js +9 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js.map +1 -0
- package/dist/map-cluster-layer/index.js +12 -0
- package/dist/map-cluster-layer/index.js.map +1 -0
- package/dist/map-cluster-layer/map-cluster-layer.js +12 -0
- package/dist/map-cluster-layer/map-cluster-layer.js.map +1 -0
- package/dist/map-controls/index.js +11 -0
- package/dist/map-controls/index.js.map +1 -0
- package/dist/map-controls/map-controls.js +11 -0
- package/dist/map-controls/map-controls.js.map +1 -0
- package/dist/map-geojson/index.js +13 -0
- package/dist/map-geojson/index.js.map +1 -0
- package/dist/map-geojson/map-geojson.js +13 -0
- package/dist/map-geojson/map-geojson.js.map +1 -0
- package/dist/map-inset/index.css +11 -0
- package/dist/map-inset/index.css.map +1 -0
- package/dist/map-inset/index.js +20 -0
- package/dist/map-inset/index.js.map +1 -0
- package/dist/map-inset/map-inset.css +11 -0
- package/dist/map-inset/map-inset.css.map +1 -0
- package/dist/map-inset/map-inset.js +20 -0
- package/dist/map-inset/map-inset.js.map +1 -0
- package/dist/map-legend/index.js +14 -0
- package/dist/map-legend/index.js.map +1 -0
- package/dist/map-legend/map-corner.js +10 -0
- package/dist/map-legend/map-corner.js.map +1 -0
- package/dist/map-legend/map-legend.js +11 -0
- package/dist/map-legend/map-legend.js.map +1 -0
- package/dist/map-marker/index.js +22 -0
- package/dist/map-marker/index.js.map +1 -0
- package/dist/map-marker/map-marker.js +22 -0
- package/dist/map-marker/map-marker.js.map +1 -0
- package/dist/map-north-arrow/index.js +11 -0
- package/dist/map-north-arrow/index.js.map +1 -0
- package/dist/map-north-arrow/map-north-arrow.js +11 -0
- package/dist/map-north-arrow/map-north-arrow.js.map +1 -0
- package/dist/map-plan/index.js +30 -0
- package/dist/map-plan/index.js.map +1 -0
- package/dist/map-plan/map-plan-legend.js +10 -0
- package/dist/map-plan/map-plan-legend.js.map +1 -0
- package/dist/map-plan/map-plan-overlay.js +13 -0
- package/dist/map-plan/map-plan-overlay.js.map +1 -0
- package/dist/map-plan/map-plan-status.js +9 -0
- package/dist/map-plan/map-plan-status.js.map +1 -0
- package/dist/map-plan/map-plan-table.js +10 -0
- package/dist/map-plan/map-plan-table.js.map +1 -0
- package/dist/map-plan/use-plan-selection.js +9 -0
- package/dist/map-plan/use-plan-selection.js.map +1 -0
- package/dist/map-plan-image/index.js +11 -0
- package/dist/map-plan-image/index.js.map +1 -0
- package/dist/map-plan-image/map-plan-image.js +11 -0
- package/dist/map-plan-image/map-plan-image.js.map +1 -0
- package/dist/map-popup/index.js +12 -0
- package/dist/map-popup/index.js.map +1 -0
- package/dist/map-popup/map-popup.js +12 -0
- package/dist/map-popup/map-popup.js.map +1 -0
- package/dist/map-route/index.js +12 -0
- package/dist/map-route/index.js.map +1 -0
- package/dist/map-route/map-route.js +12 -0
- package/dist/map-route/map-route.js.map +1 -0
- package/dist/map-scale-bar/index.js +18 -0
- package/dist/map-scale-bar/index.js.map +1 -0
- package/dist/map-scale-bar/map-scale-bar.js +18 -0
- package/dist/map-scale-bar/map-scale-bar.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +83 -0
- package/src/lib/arc-math.test.ts +19 -0
- package/src/lib/arc-math.ts +6 -1
- package/src/lib/map-position.ts +77 -0
- package/src/lib/merge-hover-paint.ts +44 -13
- package/src/lib/plan-crs.parity.test.ts +39 -0
- package/src/lib/plan-crs.test.ts +248 -0
- package/src/lib/plan-crs.ts +252 -0
- package/src/lib/plan-patterns.test.ts +255 -0
- package/src/lib/plan-patterns.ts +160 -0
- package/src/lib/plan-regions.test.ts +186 -0
- package/src/lib/plan-regions.ts +209 -0
- package/src/lib/plan-status.test.ts +87 -0
- package/src/lib/plan-status.ts +147 -0
- package/src/lib/route-arrow.test.ts +88 -0
- package/src/lib/route-arrow.ts +59 -0
- package/src/lib/use-map-breakpoint.test.ts +83 -0
- package/src/lib/use-map-breakpoint.ts +238 -0
- package/src/lib/use-plan-projection.ts +157 -0
- package/src/lib/warn-once.ts +21 -0
- package/src/map-annotation/anchor.ts +92 -0
- package/src/map-annotation/index.ts +15 -0
- package/src/map-annotation/map-annotation.stories.tsx +113 -0
- package/src/map-annotation/map-annotation.test.tsx +106 -0
- package/src/map-annotation/map-annotation.tsx +356 -0
- package/src/map-arc/map-arc.tsx +26 -5
- package/src/map-canvas/map-canvas.stories.tsx +211 -0
- package/src/map-canvas/map-canvas.test.tsx +421 -1
- package/src/map-canvas/map-canvas.tsx +466 -20
- package/src/map-canvas/map-context.ts +11 -0
- package/src/map-cluster-layer/map-cluster-layer.tsx +14 -1
- package/src/map-controls/map-controls.stories.tsx +16 -0
- package/src/map-controls/map-controls.test.tsx +145 -1
- package/src/map-controls/map-controls.tsx +57 -12
- package/src/map-geojson/map-geojson.stories.tsx +153 -3
- package/src/map-geojson/map-geojson.test.tsx +453 -0
- package/src/map-geojson/map-geojson.tsx +591 -51
- package/src/map-inset/index.ts +1 -0
- package/src/map-inset/map-inset.stories.tsx +61 -0
- package/src/map-inset/map-inset.test.tsx +117 -0
- package/src/map-inset/map-inset.tsx +264 -0
- package/src/map-legend/index.ts +10 -0
- package/src/map-legend/map-corner.ts +23 -0
- package/src/map-legend/map-legend.stories.tsx +171 -0
- package/src/map-legend/map-legend.test.tsx +136 -0
- package/src/map-legend/map-legend.tsx +264 -0
- package/src/map-marker/map-marker.stories.tsx +136 -4
- package/src/map-marker/map-marker.test.tsx +383 -3
- package/src/map-marker/map-marker.tsx +414 -44
- package/src/map-north-arrow/index.ts +1 -0
- package/src/map-north-arrow/map-north-arrow.stories.tsx +65 -0
- package/src/map-north-arrow/map-north-arrow.test.tsx +39 -0
- package/src/map-north-arrow/map-north-arrow.tsx +82 -0
- package/src/map-plan/index.ts +13 -0
- package/src/map-plan/map-plan-legend.test.tsx +61 -0
- package/src/map-plan/map-plan-legend.tsx +83 -0
- package/src/map-plan/map-plan-overlay.stories.tsx +260 -0
- package/src/map-plan/map-plan-overlay.test.tsx +302 -0
- package/src/map-plan/map-plan-overlay.tsx +531 -0
- package/src/map-plan/map-plan-status.test.tsx +71 -0
- package/src/map-plan/map-plan-status.tsx +77 -0
- package/src/map-plan/map-plan-table.test.tsx +86 -0
- package/src/map-plan/map-plan-table.tsx +153 -0
- package/src/map-plan/use-plan-selection.ts +78 -0
- package/src/map-plan-image/index.ts +1 -0
- package/src/map-plan-image/map-plan-image.stories.tsx +301 -0
- package/src/map-plan-image/map-plan-image.test.tsx +176 -0
- package/src/map-plan-image/map-plan-image.tsx +279 -0
- package/src/map-popup/map-popup.tsx +18 -11
- package/src/map-route/index.ts +1 -1
- package/src/map-route/map-route.test.tsx +216 -0
- package/src/map-route/map-route.tsx +105 -6
- package/src/map-scale-bar/index.ts +9 -0
- package/src/map-scale-bar/map-scale-bar.stories.tsx +63 -0
- package/src/map-scale-bar/map-scale-bar.test.tsx +65 -0
- package/src/map-scale-bar/map-scale-bar.tsx +145 -0
- package/src/test-utils/locator-fixture.ts +75 -0
- package/src/test-utils/maplibre-mock.ts +267 -20
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { cleanup, render, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
vi.mock("maplibre-gl", async () => {
|
|
5
|
+
const { createMaplibreMock } = await import("../test-utils/maplibre-mock");
|
|
6
|
+
return createMaplibreMock();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
import { MockMap, MockSource, resetMaplibreMock } from "../test-utils/maplibre-mock";
|
|
10
|
+
import { MapCanvas } from "../map-canvas";
|
|
11
|
+
import { createPlanCrs } from "../lib/plan-crs";
|
|
12
|
+
import { resetMapWarnings } from "../lib/warn-once";
|
|
13
|
+
import { MapGeoJSON } from "./map-geojson";
|
|
14
|
+
|
|
15
|
+
const AREA: GeoJSON.Feature = {
|
|
16
|
+
type: "Feature",
|
|
17
|
+
properties: {},
|
|
18
|
+
geometry: {
|
|
19
|
+
type: "Polygon",
|
|
20
|
+
coordinates: [
|
|
21
|
+
[
|
|
22
|
+
[0, 0],
|
|
23
|
+
[1, 0],
|
|
24
|
+
[1, 1],
|
|
25
|
+
[0, 0],
|
|
26
|
+
],
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const PLAN = { width: 1000, height: 500 };
|
|
32
|
+
|
|
33
|
+
const rooms: GeoJSON.FeatureCollection<GeoJSON.Polygon, { id: string }> = {
|
|
34
|
+
type: "FeatureCollection",
|
|
35
|
+
features: [
|
|
36
|
+
{
|
|
37
|
+
type: "Feature",
|
|
38
|
+
properties: { id: "studio" },
|
|
39
|
+
geometry: {
|
|
40
|
+
type: "Polygon",
|
|
41
|
+
coordinates: [
|
|
42
|
+
[
|
|
43
|
+
[0, 0],
|
|
44
|
+
[200, 0],
|
|
45
|
+
[200, 100],
|
|
46
|
+
[0, 100],
|
|
47
|
+
[0, 0],
|
|
48
|
+
],
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function lastMap() {
|
|
56
|
+
return MockMap.instances.at(-1)!;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sourceFor(map: MockMap) {
|
|
60
|
+
return [...map.sources.values()][0]!;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// jsdom has no 2D canvas: stand in a minimal one for the stripe tile.
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation(function (
|
|
66
|
+
this: HTMLCanvasElement,
|
|
67
|
+
) {
|
|
68
|
+
const width = this.width;
|
|
69
|
+
const height = this.height;
|
|
70
|
+
return {
|
|
71
|
+
beginPath() {},
|
|
72
|
+
moveTo() {},
|
|
73
|
+
lineTo() {},
|
|
74
|
+
stroke() {},
|
|
75
|
+
getImageData: () => ({ width, height, data: new Uint8ClampedArray(width * height * 4) }),
|
|
76
|
+
} as unknown as CanvasRenderingContext2D;
|
|
77
|
+
} as never);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
vi.restoreAllMocks();
|
|
82
|
+
cleanup();
|
|
83
|
+
resetMaplibreMock();
|
|
84
|
+
resetMapWarnings();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("MapGeoJSON locator paints", () => {
|
|
88
|
+
it("adds a stripe pattern layer over a tinted fill", async () => {
|
|
89
|
+
render(
|
|
90
|
+
<MapCanvas>
|
|
91
|
+
<MapGeoJSON id="lake" data={AREA} pattern={{ kind: "stripes", width: 2, gap: 4 }} />
|
|
92
|
+
</MapCanvas>,
|
|
93
|
+
);
|
|
94
|
+
const map = MockMap.instances[0]!;
|
|
95
|
+
await waitFor(() => expect(map.getLayer("geojson-pattern-lake")).toBeDefined());
|
|
96
|
+
expect(map.getLayer("geojson-pattern-lake").paint).toEqual({
|
|
97
|
+
"fill-pattern": "geojson-pattern-image-lake",
|
|
98
|
+
});
|
|
99
|
+
expect(map.hasImage("geojson-pattern-image-lake")).toBe(true);
|
|
100
|
+
// The base fill drops to a tint so the stripes read.
|
|
101
|
+
expect(map.getLayer("geojson-fill-lake").paint["fill-opacity"]).toBe(0.25);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("adds a blurred vignette line and honours fillOpacity", async () => {
|
|
105
|
+
render(
|
|
106
|
+
<MapCanvas>
|
|
107
|
+
<MapGeoJSON id="gta" data={AREA} fillOpacity={0.6} vignette={{ width: 10, opacity: 0.5 }} />
|
|
108
|
+
</MapCanvas>,
|
|
109
|
+
);
|
|
110
|
+
const map = MockMap.instances[0]!;
|
|
111
|
+
await waitFor(() => expect(map.getLayer("geojson-vignette-gta")).toBeDefined());
|
|
112
|
+
const paint = map.getLayer("geojson-vignette-gta").paint;
|
|
113
|
+
expect(paint["line-width"]).toBe(20);
|
|
114
|
+
expect(paint["line-blur"]).toBe(10);
|
|
115
|
+
expect(paint["line-opacity"]).toBe(0.5);
|
|
116
|
+
expect(map.getLayer("geojson-fill-gta").paint["fill-opacity"]).toBe(0.6);
|
|
117
|
+
expect(map.getLayer("geojson-pattern-gta")).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("removes the extra layers and the tile image on unmount", async () => {
|
|
121
|
+
const { unmount } = render(
|
|
122
|
+
<MapCanvas>
|
|
123
|
+
<MapGeoJSON id="x" data={AREA} pattern={{ kind: "stripes" }} vignette={{}} />
|
|
124
|
+
</MapCanvas>,
|
|
125
|
+
);
|
|
126
|
+
const map = MockMap.instances[0]!;
|
|
127
|
+
await waitFor(() => expect(map.getLayer("geojson-pattern-x")).toBeDefined());
|
|
128
|
+
unmount();
|
|
129
|
+
expect(map.getLayer("geojson-pattern-x")).toBeUndefined();
|
|
130
|
+
expect(map.getLayer("geojson-vignette-x")).toBeUndefined();
|
|
131
|
+
expect(map.hasImage("geojson-pattern-image-x")).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("MapGeoJSON — the values answer the keyboard (c-2)", () => {
|
|
136
|
+
type RegionProps = { name: string; value: number };
|
|
137
|
+
|
|
138
|
+
const REGIONS: GeoJSON.FeatureCollection<GeoJSON.Polygon, RegionProps> = {
|
|
139
|
+
type: "FeatureCollection",
|
|
140
|
+
features: [
|
|
141
|
+
{ name: "North", value: 0.7, x: 0 },
|
|
142
|
+
{ name: "South", value: 0.29, x: 4 },
|
|
143
|
+
].map(({ name, value, x }) => ({
|
|
144
|
+
type: "Feature" as const,
|
|
145
|
+
id: name,
|
|
146
|
+
properties: { name, value },
|
|
147
|
+
geometry: {
|
|
148
|
+
type: "Polygon" as const,
|
|
149
|
+
coordinates: [
|
|
150
|
+
[
|
|
151
|
+
[x, 10],
|
|
152
|
+
[x + 2, 10],
|
|
153
|
+
[x + 2, 14],
|
|
154
|
+
[x, 10],
|
|
155
|
+
],
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
it("gives every region a tab stop whose name is its value, and fires the same onHover", async () => {
|
|
162
|
+
const onHover = vi.fn();
|
|
163
|
+
const { container } = render(
|
|
164
|
+
<MapCanvas blank>
|
|
165
|
+
<MapGeoJSON<RegionProps>
|
|
166
|
+
data={REGIONS}
|
|
167
|
+
featureLabel={(feature) => `${feature.properties.name}: ${feature.properties.value}`}
|
|
168
|
+
interactive
|
|
169
|
+
id="regions"
|
|
170
|
+
onHover={onHover}
|
|
171
|
+
promoteId="name"
|
|
172
|
+
/>
|
|
173
|
+
</MapCanvas>,
|
|
174
|
+
);
|
|
175
|
+
// The fill layer (and with it the hover handle the keyboard list drives)
|
|
176
|
+
// only exists once the map reports loaded.
|
|
177
|
+
await waitFor(() =>
|
|
178
|
+
expect(MockMap.instances[0]!.getLayer("geojson-fill-regions")).toBeDefined(),
|
|
179
|
+
);
|
|
180
|
+
// WCAG 2.1.1 / 1.3.1: the fill layer lives in WebGL, so before this the
|
|
181
|
+
// choropleth rendered NO dom — the values were reachable by mouse only.
|
|
182
|
+
const list = await waitFor(() => {
|
|
183
|
+
const element = container.querySelector('[data-slot="map-geojson-keyboard-list"]');
|
|
184
|
+
expect(element).not.toBeNull();
|
|
185
|
+
return element as HTMLElement;
|
|
186
|
+
});
|
|
187
|
+
const buttons = [...list.querySelectorAll("button")];
|
|
188
|
+
expect(buttons).toHaveLength(2);
|
|
189
|
+
expect(buttons[0]).toHaveAccessibleName("North: 0.7");
|
|
190
|
+
expect(buttons[1]).toHaveAccessibleName("South: 0.29");
|
|
191
|
+
|
|
192
|
+
buttons[1]?.focus();
|
|
193
|
+
expect(document.activeElement).toBe(buttons[1]);
|
|
194
|
+
expect(onHover).toHaveBeenCalledTimes(1);
|
|
195
|
+
const reached = onHover.mock.lastCall?.[0];
|
|
196
|
+
expect(reached.feature.properties.name).toBe("South");
|
|
197
|
+
// The bounding-box centre of the South polygon, where a pointer hover
|
|
198
|
+
// would have landed. `originalEvent` is null: no mouse was involved.
|
|
199
|
+
expect(reached.longitude).toBeCloseTo(5, 5);
|
|
200
|
+
expect(reached.latitude).toBeCloseTo(12, 5);
|
|
201
|
+
expect(reached.originalEvent).toBeNull();
|
|
202
|
+
// The same feature-state the pointer sets, so the region lights up too.
|
|
203
|
+
expect([...MockMap.instances[0]!.featureStates.entries()]).toEqual([
|
|
204
|
+
["South", { hover: true }],
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
buttons[1]?.blur();
|
|
208
|
+
expect(onHover).toHaveBeenLastCalledWith(null);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("renders no list for a non-interactive map or a URL source", async () => {
|
|
212
|
+
const { container, rerender } = render(
|
|
213
|
+
<MapCanvas blank>
|
|
214
|
+
<MapGeoJSON data={REGIONS} />
|
|
215
|
+
</MapCanvas>,
|
|
216
|
+
);
|
|
217
|
+
await waitFor(() => expect(MockMap.instances).toHaveLength(1));
|
|
218
|
+
expect(container.querySelector('[data-slot="map-geojson-keyboard-list"]')).toBeNull();
|
|
219
|
+
|
|
220
|
+
rerender(
|
|
221
|
+
<MapCanvas blank>
|
|
222
|
+
<MapGeoJSON data="https://example.test/regions.geojson" interactive />
|
|
223
|
+
</MapCanvas>,
|
|
224
|
+
);
|
|
225
|
+
expect(container.querySelector('[data-slot="map-geojson-keyboard-list"]')).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("MapGeoJSON", () => {
|
|
230
|
+
it("leaves geographic data untouched, object identity included", async () => {
|
|
231
|
+
const geographic: GeoJSON.FeatureCollection<GeoJSON.Point> = {
|
|
232
|
+
type: "FeatureCollection",
|
|
233
|
+
features: [
|
|
234
|
+
{
|
|
235
|
+
type: "Feature",
|
|
236
|
+
properties: {},
|
|
237
|
+
geometry: { type: "Point", coordinates: [13.4, 52.52] },
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
render(
|
|
243
|
+
<MapCanvas>
|
|
244
|
+
<MapGeoJSON data={geographic} />
|
|
245
|
+
</MapCanvas>,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
await waitFor(() => expect(MockSource.instances.length).toBeGreaterThan(0));
|
|
249
|
+
const source = sourceFor(lastMap());
|
|
250
|
+
expect(source.spec.data).toBe(geographic);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("does not make a geographic layer interactive", async () => {
|
|
254
|
+
render(
|
|
255
|
+
<MapCanvas>
|
|
256
|
+
<MapGeoJSON id="regions" data={rooms} promoteId="id" />
|
|
257
|
+
</MapCanvas>,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const map = await waitFor(() => {
|
|
261
|
+
const instance = lastMap();
|
|
262
|
+
expect(instance.getLayer("geojson-fill-regions")).toBeDefined();
|
|
263
|
+
return instance;
|
|
264
|
+
});
|
|
265
|
+
expect([...map.handlers.keys()].some((key) => key.startsWith("click"))).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("converts plan units into the plan's coordinate system", async () => {
|
|
269
|
+
render(
|
|
270
|
+
<MapCanvas plan={PLAN}>
|
|
271
|
+
<MapGeoJSON data={rooms} promoteId="id" />
|
|
272
|
+
</MapCanvas>,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
await waitFor(() => expect(MockSource.instances.length).toBeGreaterThan(0));
|
|
276
|
+
const crs = createPlanCrs(PLAN);
|
|
277
|
+
const source = sourceFor(lastMap());
|
|
278
|
+
const converted = source.spec.data as GeoJSON.FeatureCollection<GeoJSON.Polygon>;
|
|
279
|
+
|
|
280
|
+
expect(converted.features[0]!.geometry.coordinates[0]![0]).toEqual(
|
|
281
|
+
crs.toLngLat({ x: 0, y: 0 }),
|
|
282
|
+
);
|
|
283
|
+
expect(converted.features[0]!.geometry.coordinates[0]![2]).toEqual(
|
|
284
|
+
crs.toLngLat({ x: 200, y: 100 }),
|
|
285
|
+
);
|
|
286
|
+
// The caller's data is never mutated.
|
|
287
|
+
expect(rooms.features[0]!.geometry.coordinates[0]![0]).toEqual([0, 0]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("binds interaction by default on a plan, and reports the plan point back", async () => {
|
|
291
|
+
const onClick = vi.fn();
|
|
292
|
+
render(
|
|
293
|
+
<MapCanvas plan={PLAN}>
|
|
294
|
+
<MapGeoJSON data={rooms} promoteId="id" onClick={onClick} />
|
|
295
|
+
</MapCanvas>,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const map = await waitFor(() => {
|
|
299
|
+
const instance = lastMap();
|
|
300
|
+
expect(instance.handlers.has("click")).toBe(true);
|
|
301
|
+
return instance;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const crs = createPlanCrs(PLAN);
|
|
305
|
+
const [lng, lat] = crs.toLngLat({ x: 120, y: 60 });
|
|
306
|
+
map.queryRenderedFeaturesResult = [{ id: "studio", properties: { id: "studio" } }];
|
|
307
|
+
map.emit("click", undefined, {
|
|
308
|
+
point: { x: 10, y: 10 },
|
|
309
|
+
lngLat: { lng, lat },
|
|
310
|
+
features: [],
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
314
|
+
const event = onClick.mock.calls[0]![0];
|
|
315
|
+
expect(event.plan.x).toBeCloseTo(120, 6);
|
|
316
|
+
expect(event.plan.y).toBeCloseTo(60, 6);
|
|
317
|
+
expect(event.longitude).toBe(lng);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("falls back to a padded box only when nothing is under the pointer", async () => {
|
|
321
|
+
const onClick = vi.fn();
|
|
322
|
+
render(
|
|
323
|
+
<MapCanvas plan={PLAN}>
|
|
324
|
+
<MapGeoJSON id="plan" data={rooms} promoteId="id" onClick={onClick} pickPadding={11} />
|
|
325
|
+
</MapCanvas>,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const map = await waitFor(() => {
|
|
329
|
+
const instance = lastMap();
|
|
330
|
+
expect(instance.handlers.has("click")).toBe(true);
|
|
331
|
+
return instance;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
map.queryRenderedFeaturesResult = [];
|
|
335
|
+
map.emit("click", undefined, {
|
|
336
|
+
point: { x: 40, y: 40 },
|
|
337
|
+
lngLat: { lng: 0, lat: 0 },
|
|
338
|
+
features: [],
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Exact hit first, then the padded box: two queries, the second one a box.
|
|
342
|
+
expect(map.queryRenderedFeaturesCalls).toHaveLength(2);
|
|
343
|
+
expect(map.queryRenderedFeaturesCalls[1]).toEqual([
|
|
344
|
+
[
|
|
345
|
+
[29, 29],
|
|
346
|
+
[51, 51],
|
|
347
|
+
],
|
|
348
|
+
{ layers: ["geojson-fill-plan"] },
|
|
349
|
+
]);
|
|
350
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("adds an invisible wide hit layer when there is no fill to click", async () => {
|
|
354
|
+
render(
|
|
355
|
+
<MapCanvas plan={PLAN}>
|
|
356
|
+
<MapGeoJSON id="walls" data={rooms} promoteId="id" fillPaint={false} hitWidth={20} />
|
|
357
|
+
</MapCanvas>,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const map = await waitFor(() => {
|
|
361
|
+
const instance = lastMap();
|
|
362
|
+
expect(instance.getLayer("geojson-hit-walls")).toBeDefined();
|
|
363
|
+
return instance;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const layer = map.getLayer("geojson-hit-walls");
|
|
367
|
+
expect(layer.paint["line-opacity"]).toBe(0);
|
|
368
|
+
expect(layer.paint["line-width"]).toBe(20);
|
|
369
|
+
expect(map.getLayer("geojson-fill-walls")).toBeUndefined();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("paints selection through feature-state, and clears what falls out of it", async () => {
|
|
373
|
+
const { rerender } = render(
|
|
374
|
+
<MapCanvas plan={PLAN}>
|
|
375
|
+
<MapGeoJSON id="plan" data={rooms} promoteId="id" selectedId="studio" />
|
|
376
|
+
</MapCanvas>,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const map = await waitFor(() => {
|
|
380
|
+
const instance = lastMap();
|
|
381
|
+
expect(instance.featureStateCalls.length).toBeGreaterThan(0);
|
|
382
|
+
return instance;
|
|
383
|
+
});
|
|
384
|
+
expect(map.featureStateCalls).toContainEqual([
|
|
385
|
+
{ source: "geojson-source-plan", id: "studio" },
|
|
386
|
+
{ selected: true },
|
|
387
|
+
]);
|
|
388
|
+
|
|
389
|
+
rerender(
|
|
390
|
+
<MapCanvas plan={PLAN}>
|
|
391
|
+
<MapGeoJSON id="plan" data={rooms} promoteId="id" selectedId={null} />
|
|
392
|
+
</MapCanvas>,
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
await waitFor(() =>
|
|
396
|
+
expect(map.featureStateCalls).toContainEqual([
|
|
397
|
+
{ source: "geojson-source-plan", id: "studio" },
|
|
398
|
+
{ selected: false },
|
|
399
|
+
]),
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("merges selected paint outside hover, so a selected shape stays selected", async () => {
|
|
404
|
+
render(
|
|
405
|
+
<MapCanvas plan={PLAN}>
|
|
406
|
+
<MapGeoJSON
|
|
407
|
+
id="plan"
|
|
408
|
+
data={rooms}
|
|
409
|
+
promoteId="id"
|
|
410
|
+
fillPaint={{ "fill-opacity": 0.1 }}
|
|
411
|
+
fillHoverPaint={{ "fill-opacity": 0.2 }}
|
|
412
|
+
fillSelectedPaint={{ "fill-opacity": 0.4 }}
|
|
413
|
+
/>
|
|
414
|
+
</MapCanvas>,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const map = await waitFor(() => {
|
|
418
|
+
const instance = lastMap();
|
|
419
|
+
expect(instance.getLayer("geojson-fill-plan")).toBeDefined();
|
|
420
|
+
return instance;
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
expect(map.getLayer("geojson-fill-plan").paint["fill-opacity"]).toEqual([
|
|
424
|
+
"case",
|
|
425
|
+
["boolean", ["feature-state", "selected"], false],
|
|
426
|
+
0.4,
|
|
427
|
+
["case", ["boolean", ["feature-state", "hover"], false], 0.2, 0.1],
|
|
428
|
+
]);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("lets the consumer drive the highlight with hoveredId", async () => {
|
|
432
|
+
const { rerender } = render(
|
|
433
|
+
<MapCanvas plan={PLAN}>
|
|
434
|
+
<MapGeoJSON id="plan" data={rooms} promoteId="id" hoveredId={null} />
|
|
435
|
+
</MapCanvas>,
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const map = await waitFor(() => lastMap());
|
|
439
|
+
|
|
440
|
+
rerender(
|
|
441
|
+
<MapCanvas plan={PLAN}>
|
|
442
|
+
<MapGeoJSON id="plan" data={rooms} promoteId="id" hoveredId="studio" />
|
|
443
|
+
</MapCanvas>,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
await waitFor(() =>
|
|
447
|
+
expect(map.featureStateCalls).toContainEqual([
|
|
448
|
+
{ source: "geojson-source-plan", id: "studio" },
|
|
449
|
+
{ hover: true },
|
|
450
|
+
]),
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
});
|