@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cleanup, render, waitFor } from "@testing-library/react";
|
|
1
|
+
import { act, cleanup, render, waitFor } from "@testing-library/react";
|
|
2
2
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
4
|
vi.mock("maplibre-gl", async () => {
|
|
@@ -6,9 +6,9 @@ vi.mock("maplibre-gl", async () => {
|
|
|
6
6
|
return createMaplibreMock();
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
import { MockMarker, resetMaplibreMock } from "../test-utils/maplibre-mock";
|
|
9
|
+
import { MockMap, MockMarker, MockPopup, resetMaplibreMock } from "../test-utils/maplibre-mock";
|
|
10
10
|
import { MapCanvas } from "../map-canvas";
|
|
11
|
-
import { MapMarker, MapMarkerContent, MapMarkerLabel } from "./map-marker";
|
|
11
|
+
import { MapMarker, MapMarkerContent, MapMarkerLabel, MapMarkerTooltip } from "./map-marker";
|
|
12
12
|
|
|
13
13
|
afterEach(() => {
|
|
14
14
|
cleanup();
|
|
@@ -64,6 +64,41 @@ describe("MapMarker", () => {
|
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
it("opens its tooltip on focus and Escape, not only under a pointer", async () => {
|
|
68
|
+
// MapLibre gives a marker a tab stop only inside `setPopup`, and binds no
|
|
69
|
+
// key handler at all — so a hover-only tooltip is unreachable (WCAG 2.1.1)
|
|
70
|
+
// and undismissable (1.4.13). The tooltip asks for both itself.
|
|
71
|
+
render(
|
|
72
|
+
<MapCanvas>
|
|
73
|
+
<MapMarker longitude={13.4} latitude={52.52}>
|
|
74
|
+
<MapMarkerContent />
|
|
75
|
+
<MapMarkerTooltip>Berlin</MapMarkerTooltip>
|
|
76
|
+
</MapMarker>
|
|
77
|
+
</MapCanvas>,
|
|
78
|
+
);
|
|
79
|
+
await waitFor(() => expect(MockPopup.instances).toHaveLength(1));
|
|
80
|
+
const element = MockMarker.instances[0]!.getElement();
|
|
81
|
+
const tooltip = MockPopup.instances[0]!;
|
|
82
|
+
expect(element.getAttribute("tabindex")).toBe("0");
|
|
83
|
+
|
|
84
|
+
expect(tooltip.isOpen()).toBe(false);
|
|
85
|
+
element.dispatchEvent(new FocusEvent("focus"));
|
|
86
|
+
expect(tooltip.isOpen()).toBe(true);
|
|
87
|
+
element.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
88
|
+
expect(tooltip.isOpen()).toBe(false);
|
|
89
|
+
|
|
90
|
+
element.dispatchEvent(new FocusEvent("focus"));
|
|
91
|
+
expect(tooltip.isOpen()).toBe(true);
|
|
92
|
+
element.dispatchEvent(new FocusEvent("blur"));
|
|
93
|
+
expect(tooltip.isOpen()).toBe(false);
|
|
94
|
+
|
|
95
|
+
// The pointer path is untouched.
|
|
96
|
+
element.dispatchEvent(new MouseEvent("mouseenter"));
|
|
97
|
+
expect(tooltip.isOpen()).toBe(true);
|
|
98
|
+
element.dispatchEvent(new MouseEvent("mouseleave"));
|
|
99
|
+
expect(tooltip.isOpen()).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
67
102
|
it("removes the marker on unmount", async () => {
|
|
68
103
|
const { unmount } = render(
|
|
69
104
|
<MapCanvas>
|
|
@@ -78,4 +113,349 @@ describe("MapMarker", () => {
|
|
|
78
113
|
unmount();
|
|
79
114
|
expect(MockMarker.instances[0]!.addedTo).toBeNull();
|
|
80
115
|
});
|
|
116
|
+
|
|
117
|
+
it("rebuilds the marker instance when `anchor` changes — MapLibre has no live setter for it", async () => {
|
|
118
|
+
const { rerender } = render(
|
|
119
|
+
<MapCanvas>
|
|
120
|
+
<MapMarker longitude={0} latitude={0} anchor="top">
|
|
121
|
+
<MapMarkerContent />
|
|
122
|
+
</MapMarker>
|
|
123
|
+
</MapCanvas>,
|
|
124
|
+
);
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(MockMarker.instances).toHaveLength(1);
|
|
127
|
+
expect(MockMarker.instances[0]!.options.anchor).toBe("top");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
rerender(
|
|
131
|
+
<MapCanvas>
|
|
132
|
+
<MapMarker longitude={0} latitude={0} anchor="bottom">
|
|
133
|
+
<MapMarkerContent />
|
|
134
|
+
</MapMarker>
|
|
135
|
+
</MapCanvas>,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
expect(MockMarker.instances).toHaveLength(2);
|
|
140
|
+
expect(MockMarker.instances[1]!.options.anchor).toBe("bottom");
|
|
141
|
+
});
|
|
142
|
+
// The old instance is detached, not mutated in place.
|
|
143
|
+
expect(MockMarker.instances[0]!.addedTo).toBeNull();
|
|
144
|
+
expect(MockMarker.instances[1]!.addedTo).not.toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("never touches `document` during the render phase (SSR-safe)", async () => {
|
|
148
|
+
// `renderToStaticMarkup` never runs effects — only the plain render
|
|
149
|
+
// functions — which is exactly the environment a real SSR host runs in.
|
|
150
|
+
// Deleting `document` reproduces that host having none: the old
|
|
151
|
+
// `useMemo`-based marker construction called `document.createElement`
|
|
152
|
+
// straight out of the render function and would throw here.
|
|
153
|
+
const { renderToStaticMarkup } = await import("react-dom/server");
|
|
154
|
+
const originalDocument = globalThis.document;
|
|
155
|
+
// @ts-expect-error -- simulating a server environment with no DOM
|
|
156
|
+
delete globalThis.document;
|
|
157
|
+
try {
|
|
158
|
+
expect(() =>
|
|
159
|
+
renderToStaticMarkup(
|
|
160
|
+
<MapCanvas>
|
|
161
|
+
<MapMarker longitude={0} latitude={0}>
|
|
162
|
+
<MapMarkerContent />
|
|
163
|
+
</MapMarker>
|
|
164
|
+
</MapCanvas>,
|
|
165
|
+
),
|
|
166
|
+
).not.toThrow();
|
|
167
|
+
} finally {
|
|
168
|
+
globalThis.document = originalDocument;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("keeps a marker hidden at the current tier off the map (showAt)", async () => {
|
|
173
|
+
const spy = vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({
|
|
174
|
+
width: 348,
|
|
175
|
+
height: 0,
|
|
176
|
+
top: 0,
|
|
177
|
+
left: 0,
|
|
178
|
+
right: 348,
|
|
179
|
+
bottom: 0,
|
|
180
|
+
x: 0,
|
|
181
|
+
y: 0,
|
|
182
|
+
toJSON: () => ({}),
|
|
183
|
+
});
|
|
184
|
+
render(
|
|
185
|
+
<MapCanvas>
|
|
186
|
+
<MapMarker longitude={1} latitude={1} showAt={{ base: true, narrow: false }}>
|
|
187
|
+
<MapMarkerContent />
|
|
188
|
+
</MapMarker>
|
|
189
|
+
<MapMarker longitude={2} latitude={2}>
|
|
190
|
+
<MapMarkerContent />
|
|
191
|
+
</MapMarker>
|
|
192
|
+
</MapCanvas>,
|
|
193
|
+
);
|
|
194
|
+
await waitFor(() => {
|
|
195
|
+
expect(MockMarker.instances).toHaveLength(2);
|
|
196
|
+
expect(MockMarker.instances[1]!.addedTo).not.toBeNull();
|
|
197
|
+
});
|
|
198
|
+
expect(MockMarker.instances[0]!.addedTo).toBeNull();
|
|
199
|
+
spy.mockRestore();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("draws its own label at one of eight positions, boxed, with a callout", async () => {
|
|
203
|
+
render(
|
|
204
|
+
<MapCanvas>
|
|
205
|
+
<MapMarker
|
|
206
|
+
longitude={0}
|
|
207
|
+
latitude={0}
|
|
208
|
+
label={{ text: "Hamilton", position: "bottom-left", box: true, callout: true }}
|
|
209
|
+
/>
|
|
210
|
+
</MapCanvas>,
|
|
211
|
+
);
|
|
212
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
213
|
+
const el = MockMarker.instances[0]!.element;
|
|
214
|
+
const label = await waitFor(() => {
|
|
215
|
+
const found = el.querySelector<HTMLElement>('[data-slot="map-marker-label"]');
|
|
216
|
+
expect(found).not.toBeNull();
|
|
217
|
+
return found!;
|
|
218
|
+
});
|
|
219
|
+
expect(label).toHaveTextContent("Hamilton");
|
|
220
|
+
expect(label).toHaveAttribute("data-position", "bottom-left");
|
|
221
|
+
expect(label).toHaveClass("bg-background/90");
|
|
222
|
+
expect(label.style.transform).toContain("translate(calc(-100% +");
|
|
223
|
+
expect(el.querySelector('[data-slot="map-marker-callout"] line')).not.toBeNull();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("MapMarker — a draggable marker answers the keyboard (c-4)", () => {
|
|
228
|
+
/** Fires a real key press on the marker element. */
|
|
229
|
+
function press(element: HTMLElement, key: string, shiftKey = false) {
|
|
230
|
+
element.dispatchEvent(new KeyboardEvent("keydown", { key, shiftKey, bubbles: true }));
|
|
231
|
+
element.dispatchEvent(new KeyboardEvent("keyup", { key, shiftKey, bubbles: true }));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
it("takes a tab stop, names its drag and steps in pixels on the arrow keys", async () => {
|
|
235
|
+
const onDragStart = vi.fn();
|
|
236
|
+
const onDrag = vi.fn();
|
|
237
|
+
const onDragEnd = vi.fn();
|
|
238
|
+
render(
|
|
239
|
+
<MapCanvas>
|
|
240
|
+
<MapMarker
|
|
241
|
+
draggable
|
|
242
|
+
latitude={52}
|
|
243
|
+
longitude={13}
|
|
244
|
+
onDrag={onDrag}
|
|
245
|
+
onDragEnd={onDragEnd}
|
|
246
|
+
onDragStart={onDragStart}
|
|
247
|
+
>
|
|
248
|
+
<MapMarkerContent />
|
|
249
|
+
</MapMarker>
|
|
250
|
+
</MapCanvas>,
|
|
251
|
+
);
|
|
252
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
253
|
+
const marker = MockMarker.instances[0]!;
|
|
254
|
+
const element = marker.getElement();
|
|
255
|
+
|
|
256
|
+
// WCAG 2.1.1: MapLibre gives a marker a tab stop only inside `setPopup`,
|
|
257
|
+
// so a popup-less draggable marker had neither one nor a key handler.
|
|
258
|
+
expect(element.getAttribute("tabindex")).toBe("0");
|
|
259
|
+
expect(element.getAttribute("role")).toBe("button");
|
|
260
|
+
expect(element.getAttribute("aria-label")).toMatch(/arrow keys/i);
|
|
261
|
+
|
|
262
|
+
// The mock projects 1° to 10 px, so one 8 px step east is +0.8°.
|
|
263
|
+
press(element, "ArrowRight");
|
|
264
|
+
expect(marker.lngLat.lng).toBeCloseTo(13.8, 5);
|
|
265
|
+
expect(onDragStart).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(onDrag).toHaveBeenCalledTimes(1);
|
|
267
|
+
expect(onDragEnd).toHaveBeenCalledTimes(1);
|
|
268
|
+
const settled = onDragEnd.mock.lastCall?.[0] as { lng: number; lat: number };
|
|
269
|
+
expect(settled.lng).toBeCloseTo(13.8, 5);
|
|
270
|
+
expect(settled.lat).toBeCloseTo(52, 5);
|
|
271
|
+
|
|
272
|
+
// Shift is the coarse step (40 px = 4°), and north is a SMALLER y.
|
|
273
|
+
press(element, "ArrowUp", true);
|
|
274
|
+
expect(marker.lngLat.lat).toBeCloseTo(56, 5);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("names what it moves when the caller says so, and leaves a static marker alone", async () => {
|
|
278
|
+
const { rerender } = render(
|
|
279
|
+
<MapCanvas>
|
|
280
|
+
<MapMarker draggable dragLabel="Depot location" latitude={0} longitude={0}>
|
|
281
|
+
<MapMarkerContent />
|
|
282
|
+
</MapMarker>
|
|
283
|
+
</MapCanvas>,
|
|
284
|
+
);
|
|
285
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
286
|
+
const element = MockMarker.instances[0]!.getElement();
|
|
287
|
+
expect(element.getAttribute("aria-label")).toBe("Depot location");
|
|
288
|
+
|
|
289
|
+
// Not draggable → no tab stop and no invented button role.
|
|
290
|
+
rerender(
|
|
291
|
+
<MapCanvas>
|
|
292
|
+
<MapMarker latitude={0} longitude={0}>
|
|
293
|
+
<MapMarkerContent />
|
|
294
|
+
</MapMarker>
|
|
295
|
+
</MapCanvas>,
|
|
296
|
+
);
|
|
297
|
+
await waitFor(() => expect(element.getAttribute("tabindex")).toBeNull());
|
|
298
|
+
expect(element.getAttribute("role")).toBeNull();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("steps the marker the right way by the right amount on every arrow key, and never lets the map's own keyboard pan handler see the press (c-4 fix round 2)", async () => {
|
|
302
|
+
render(
|
|
303
|
+
<MapCanvas>
|
|
304
|
+
<MapMarker draggable latitude={0} longitude={0}>
|
|
305
|
+
<MapMarkerContent />
|
|
306
|
+
</MapMarker>
|
|
307
|
+
</MapCanvas>,
|
|
308
|
+
);
|
|
309
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
310
|
+
const map = MockMap.instances[0]!;
|
|
311
|
+
const marker = MockMarker.instances[0]!;
|
|
312
|
+
const element = marker.getElement();
|
|
313
|
+
|
|
314
|
+
// Real MapLibre appends a marker's element straight into
|
|
315
|
+
// `map.getCanvasContainer()` — the SAME element its own `HandlerManager`
|
|
316
|
+
// listens on for `keydown` (bubble phase, no `defaultPrevented` check),
|
|
317
|
+
// where it runs its own arrow-key camera pan. The mock doesn't wire this
|
|
318
|
+
// parentage up on its own, so this test does, by hand, to stand in for
|
|
319
|
+
// that handler and prove ours never lets it see the key.
|
|
320
|
+
const canvasContainer = map.getCanvasContainer();
|
|
321
|
+
canvasContainer.appendChild(element);
|
|
322
|
+
const cameraPanSpy = vi.fn();
|
|
323
|
+
canvasContainer.addEventListener("keydown", cameraPanSpy);
|
|
324
|
+
|
|
325
|
+
// The mock projects 1° to 10 px: an 8 px fine step is 0.8°, a 40 px
|
|
326
|
+
// coarse (Shift) step is 4°.
|
|
327
|
+
press(element, "ArrowRight");
|
|
328
|
+
expect(marker.lngLat.lng).toBeCloseTo(0.8, 5);
|
|
329
|
+
press(element, "ArrowLeft");
|
|
330
|
+
expect(marker.lngLat.lng).toBeCloseTo(0, 5);
|
|
331
|
+
press(element, "ArrowUp");
|
|
332
|
+
expect(marker.lngLat.lat).toBeCloseTo(0.8, 5); // north = a LARGER lat
|
|
333
|
+
press(element, "ArrowDown");
|
|
334
|
+
expect(marker.lngLat.lat).toBeCloseTo(0, 5);
|
|
335
|
+
|
|
336
|
+
press(element, "ArrowRight", true);
|
|
337
|
+
expect(marker.lngLat.lng).toBeCloseTo(4, 5);
|
|
338
|
+
press(element, "ArrowLeft", true);
|
|
339
|
+
expect(marker.lngLat.lng).toBeCloseTo(0, 5);
|
|
340
|
+
press(element, "ArrowUp", true);
|
|
341
|
+
expect(marker.lngLat.lat).toBeCloseTo(4, 5);
|
|
342
|
+
press(element, "ArrowDown", true);
|
|
343
|
+
expect(marker.lngLat.lat).toBeCloseTo(0, 5);
|
|
344
|
+
|
|
345
|
+
// The map centre never moved, and its own keyboard pan handler never
|
|
346
|
+
// even saw one of these eight presses — a missing `stopPropagation()`
|
|
347
|
+
// is exactly what let it fire and pan the CAMERA the opposite way.
|
|
348
|
+
expect(cameraPanSpy).not.toHaveBeenCalled();
|
|
349
|
+
expect(map.getCenter()).toEqual({ lng: 0, lat: 0 });
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("MapMarker — a label stays inside the map box (c-7, c-8)", () => {
|
|
354
|
+
/** jsdom measures nothing, so both boxes are described explicitly. */
|
|
355
|
+
function stubRect(element: Element, left: number, top: number, width: number, height: number) {
|
|
356
|
+
element.getBoundingClientRect = () =>
|
|
357
|
+
({
|
|
358
|
+
left,
|
|
359
|
+
top,
|
|
360
|
+
width,
|
|
361
|
+
height,
|
|
362
|
+
right: left + width,
|
|
363
|
+
bottom: top + height,
|
|
364
|
+
x: left,
|
|
365
|
+
y: top,
|
|
366
|
+
toJSON: () => ({}),
|
|
367
|
+
}) as DOMRect;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function renderLabel(longitude: number, latitude: number) {
|
|
371
|
+
render(
|
|
372
|
+
<MapCanvas>
|
|
373
|
+
<MapMarker
|
|
374
|
+
longitude={longitude}
|
|
375
|
+
latitude={latitude}
|
|
376
|
+
label={{ text: "bottom-right", position: "bottom-right" }}
|
|
377
|
+
/>
|
|
378
|
+
</MapCanvas>,
|
|
379
|
+
);
|
|
380
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
381
|
+
const map = MockMap.instances[0]!;
|
|
382
|
+
const label = await waitFor(() => {
|
|
383
|
+
const found = MockMarker.instances[0]!.element.querySelector<HTMLElement>(
|
|
384
|
+
'[data-slot="map-marker-label"]',
|
|
385
|
+
);
|
|
386
|
+
expect(found).not.toBeNull();
|
|
387
|
+
return found!;
|
|
388
|
+
});
|
|
389
|
+
// A 364 x 480 map box — the width the reviewer measured at 380 px.
|
|
390
|
+
stubRect(map.getContainer(), 0, 0, 364, 480);
|
|
391
|
+
return { map, label };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
it("slides a label that would cross the map's edge back inside", async () => {
|
|
395
|
+
// The mock projects 1° to 10 px: [-160, 50] lands at (200, 400), inside.
|
|
396
|
+
const { map, label } = await renderLabel(-160, 50);
|
|
397
|
+
// The measured overflow: the glyph run ran to x = 368.12 in a box 364 wide.
|
|
398
|
+
stubRect(label, 296.89, 169.13, 71.23, 15);
|
|
399
|
+
act(() => map.emit("move"));
|
|
400
|
+
const shift = Number.parseFloat(label.style.translate);
|
|
401
|
+
expect(shift).toBeCloseTo(-8.12, 2);
|
|
402
|
+
expect(296.89 + shift + 71.23).toBeLessThanOrEqual(364);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("leaves a label that already fits exactly where it was", async () => {
|
|
406
|
+
const { map, label } = await renderLabel(-160, 50);
|
|
407
|
+
stubRect(label, 120, 200, 71.23, 15);
|
|
408
|
+
act(() => map.emit("move"));
|
|
409
|
+
expect(label.style.translate).toBe("");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("hides a label whose own point has left the map box", async () => {
|
|
413
|
+
// [0, 0] projects to (1800, 900) — far outside a 364 x 480 box. A name
|
|
414
|
+
// pinned to the edge beside no marker claims a place you cannot see.
|
|
415
|
+
const { map, label } = await renderLabel(0, 0);
|
|
416
|
+
stubRect(label, 1700, 880, 71.23, 15);
|
|
417
|
+
act(() => map.emit("move"));
|
|
418
|
+
expect(label.style.visibility).toBe("hidden");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("clamps a composed MapMarkerLabel too", async () => {
|
|
422
|
+
render(
|
|
423
|
+
<MapCanvas>
|
|
424
|
+
<MapMarker longitude={-160} latitude={50}>
|
|
425
|
+
<MapMarkerContent />
|
|
426
|
+
<MapMarkerLabel position="bottom">Brandenburg Gate</MapMarkerLabel>
|
|
427
|
+
</MapMarker>
|
|
428
|
+
</MapCanvas>,
|
|
429
|
+
);
|
|
430
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
431
|
+
const map = MockMap.instances[0]!;
|
|
432
|
+
const label = await waitFor(() => {
|
|
433
|
+
const found = MockMarker.instances[0]!.element.querySelector<HTMLElement>(
|
|
434
|
+
'[data-slot="map-marker-label"]',
|
|
435
|
+
);
|
|
436
|
+
expect(found).not.toBeNull();
|
|
437
|
+
return found!;
|
|
438
|
+
});
|
|
439
|
+
stubRect(map.getContainer(), 0, 0, 380, 480);
|
|
440
|
+
// The measured rect at 380 px: 54.43 px of the glyph run outside the start edge.
|
|
441
|
+
stubRect(label, -54.43, 294.29, 104.39, 15);
|
|
442
|
+
act(() => map.emit("move"));
|
|
443
|
+
const shift = Number.parseFloat(label.style.translate);
|
|
444
|
+
expect(shift).toBeCloseTo(58.43, 2);
|
|
445
|
+
expect(-54.43 + shift).toBeGreaterThanOrEqual(4);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// c-10: a marker MapLibre makes focusable (a popup, a keyboard drag) wore
|
|
449
|
+
// the browser's own focus ring instead of the theme's.
|
|
450
|
+
it("gives the marker element the house focus ring", async () => {
|
|
451
|
+
render(
|
|
452
|
+
<MapCanvas>
|
|
453
|
+
<MapMarker longitude={-160} latitude={50}>
|
|
454
|
+
<MapMarkerContent />
|
|
455
|
+
</MapMarker>
|
|
456
|
+
</MapCanvas>,
|
|
457
|
+
);
|
|
458
|
+
await waitFor(() => expect(MockMarker.instances).toHaveLength(1));
|
|
459
|
+
expect(MockMarker.instances[0]!.element.classList.contains("focus-ring")).toBe(true);
|
|
460
|
+
});
|
|
81
461
|
});
|