@elabs-ai/components-maps 4.0.0 → 4.2.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 +6 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +39 -24
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/map-canvas/map-canvas.test.tsx +32 -1
- package/src/map-canvas/map-canvas.tsx +11 -5
- package/src/map-controls/map-controls.tsx +1 -1
- package/src/map-marker/map-marker.test.tsx +55 -0
- package/src/map-marker/map-marker.tsx +49 -16
- package/src/map-popup/map-popup.tsx +4 -2
- package/src/test-utils/maplibre-mock.ts +11 -1
package/README.md
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
> MapLibre GL maps: MapCanvas, markers, popups, controls, routes, arcs, GeoJSON, clusters.
|
|
7
7
|
|
|
8
8
|
Part of **brand-ui**, a source-owned, token-driven React component system.
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Published to the **public npm registry** under the `@elabs-ai` scope — it
|
|
10
|
+
installs like any other npm dependency, with no registry configuration and
|
|
11
|
+
no token required. See `docs/CONSUMING.md`.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
"@elabs-ai/components-maps": "workspace:*"
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @elabs-ai/components-tokens @elabs-ai/components-maps
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
## Set up styling (do not skip)
|
|
@@ -74,7 +73,7 @@ prompt for migrating an existing project: `docs/CONSUMING.md`.
|
|
|
74
73
|
|
|
75
74
|
## License
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
MIT
|
|
78
77
|
|
|
79
78
|
<!-- brand-ui:gen:readme:end -->
|
|
80
79
|
|
package/dist/index.d.ts
CHANGED
|
@@ -154,7 +154,7 @@ type MapMarkerProps = {
|
|
|
154
154
|
* `MapMarkerContent` (the visual), `MapMarkerLabel`, `MapMarkerPopup` (opens
|
|
155
155
|
* on click) and `MapMarkerTooltip` (shows on hover).
|
|
156
156
|
*/
|
|
157
|
-
declare function MapMarker({ longitude, latitude, children, onClick, onMouseEnter, onMouseLeave, onDragStart, onDrag, onDragEnd, draggable, ...markerOptions }: MapMarkerProps): react.JSX.Element;
|
|
157
|
+
declare function MapMarker({ longitude, latitude, children, onClick, onMouseEnter, onMouseLeave, onDragStart, onDrag, onDragEnd, draggable, ...markerOptions }: MapMarkerProps): react.JSX.Element | null;
|
|
158
158
|
interface MapMarkerContentProps {
|
|
159
159
|
/** Custom marker content. Defaults to a primary-colored dot. */
|
|
160
160
|
children?: ReactNode;
|
|
@@ -192,7 +192,7 @@ interface MapMarkerLabelProps {
|
|
|
192
192
|
/**
|
|
193
193
|
* A small always-visible text label above or below the marker. Portaled into
|
|
194
194
|
* the marker element so it anchors to the marker whether composed as a sibling
|
|
195
|
-
* of `MapMarkerContent` or nested inside it (
|
|
195
|
+
* of `MapMarkerContent` or nested inside it (a cross-theme sweep caught the
|
|
196
196
|
* sibling composition rendering the label against the map container instead).
|
|
197
197
|
*/
|
|
198
198
|
declare function MapMarkerLabel({ children, className, position }: MapMarkerLabelProps): react.ReactPortal;
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useRef,
|
|
14
14
|
useState as useState2
|
|
15
15
|
} from "react";
|
|
16
|
-
import { Spinner, StatePanel } from "@elabs-ai/components-ui";
|
|
16
|
+
import { Spinner, StatePanel, useLocale } from "@elabs-ai/components-ui";
|
|
17
17
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
18
|
|
|
19
19
|
// src/map-canvas/map-context.ts
|
|
@@ -115,6 +115,7 @@ var MapCanvas = forwardRef(function MapCanvas2({
|
|
|
115
115
|
loading = false,
|
|
116
116
|
...props
|
|
117
117
|
}, ref) {
|
|
118
|
+
const { t } = useLocale();
|
|
118
119
|
const containerRef = useRef(null);
|
|
119
120
|
const [mapInstance, setMapInstance] = useState2(null);
|
|
120
121
|
const [initFailed, setInitFailed] = useState2(false);
|
|
@@ -127,6 +128,8 @@ var MapCanvas = forwardRef(function MapCanvas2({
|
|
|
127
128
|
const isControlled = viewport !== void 0 && onViewportChange !== void 0;
|
|
128
129
|
const onViewportChangeRef = useRef(onViewportChange);
|
|
129
130
|
onViewportChangeRef.current = onViewportChange;
|
|
131
|
+
const projectionRef = useRef(projection);
|
|
132
|
+
projectionRef.current = projection;
|
|
130
133
|
const mapStyles = useMemo(() => {
|
|
131
134
|
if (styles) {
|
|
132
135
|
return {
|
|
@@ -176,8 +179,8 @@ var MapCanvas = forwardRef(function MapCanvas2({
|
|
|
176
179
|
clearStyleTimeout();
|
|
177
180
|
styleTimeoutRef.current = setTimeout(() => {
|
|
178
181
|
setIsStyleLoaded(true);
|
|
179
|
-
if (
|
|
180
|
-
map.setProjection(
|
|
182
|
+
if (projectionRef.current) {
|
|
183
|
+
map.setProjection(projectionRef.current);
|
|
181
184
|
}
|
|
182
185
|
}, 100);
|
|
183
186
|
};
|
|
@@ -250,8 +253,8 @@ var MapCanvas = forwardRef(function MapCanvas2({
|
|
|
250
253
|
StatePanel,
|
|
251
254
|
{
|
|
252
255
|
kind: "error",
|
|
253
|
-
title: "
|
|
254
|
-
description: "
|
|
256
|
+
title: t("maps.canvas.unavailableTitle"),
|
|
257
|
+
description: t("maps.canvas.unavailableDescription"),
|
|
255
258
|
className: "h-full"
|
|
256
259
|
}
|
|
257
260
|
) });
|
|
@@ -264,10 +267,11 @@ var MapCanvas = forwardRef(function MapCanvas2({
|
|
|
264
267
|
|
|
265
268
|
// src/map-marker/map-marker.tsx
|
|
266
269
|
import MapLibreGL2 from "maplibre-gl";
|
|
267
|
-
import { createContext as createContext2, use as use2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
270
|
+
import { createContext as createContext2, use as use2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
|
|
268
271
|
import { createPortal } from "react-dom";
|
|
269
272
|
import { X } from "lucide-react";
|
|
270
273
|
import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
|
|
274
|
+
import { useLocale as useLocale2 } from "@elabs-ai/components-ui";
|
|
271
275
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
272
276
|
var MarkerContext = createContext2(null);
|
|
273
277
|
function useMarkerContext() {
|
|
@@ -291,6 +295,7 @@ function MapMarker({
|
|
|
291
295
|
...markerOptions
|
|
292
296
|
}) {
|
|
293
297
|
const { map } = useMap();
|
|
298
|
+
const [marker, setMarker] = useState3(null);
|
|
294
299
|
const callbacksRef = useRef2({
|
|
295
300
|
onClick,
|
|
296
301
|
onMouseEnter,
|
|
@@ -307,12 +312,16 @@ function MapMarker({
|
|
|
307
312
|
onDrag,
|
|
308
313
|
onDragEnd
|
|
309
314
|
};
|
|
310
|
-
const
|
|
315
|
+
const { anchor, className, offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;
|
|
316
|
+
const initialRef = useRef2({ longitude, latitude, draggable });
|
|
317
|
+
initialRef.current = { longitude, latitude, draggable };
|
|
318
|
+
useEffect3(() => {
|
|
311
319
|
const markerInstance = new MapLibreGL2.Marker({
|
|
312
|
-
|
|
320
|
+
anchor,
|
|
321
|
+
className,
|
|
313
322
|
element: document.createElement("div"),
|
|
314
|
-
draggable
|
|
315
|
-
}).setLngLat([longitude, latitude]);
|
|
323
|
+
draggable: initialRef.current.draggable
|
|
324
|
+
}).setLngLat([initialRef.current.longitude, initialRef.current.latitude]);
|
|
316
325
|
const handleClick = (e) => callbacksRef.current.onClick?.(e);
|
|
317
326
|
const handleMouseEnter = (e) => callbacksRef.current.onMouseEnter?.(e);
|
|
318
327
|
const handleMouseLeave = (e) => callbacksRef.current.onMouseLeave?.(e);
|
|
@@ -334,17 +343,17 @@ function MapMarker({
|
|
|
334
343
|
markerInstance.on("dragstart", handleDragStart);
|
|
335
344
|
markerInstance.on("drag", handleDrag);
|
|
336
345
|
markerInstance.on("dragend", handleDragEnd);
|
|
337
|
-
|
|
338
|
-
}, []);
|
|
346
|
+
setMarker(markerInstance);
|
|
347
|
+
}, [anchor, className]);
|
|
339
348
|
useEffect3(() => {
|
|
340
|
-
if (!map) return;
|
|
349
|
+
if (!map || !marker) return;
|
|
341
350
|
marker.addTo(map);
|
|
342
351
|
return () => {
|
|
343
352
|
marker.remove();
|
|
344
353
|
};
|
|
345
|
-
}, [map]);
|
|
346
|
-
const { offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;
|
|
354
|
+
}, [map, marker]);
|
|
347
355
|
useEffect3(() => {
|
|
356
|
+
if (!marker) return;
|
|
348
357
|
const current = marker.getLngLat();
|
|
349
358
|
if (current.lng !== longitude || current.lat !== latitude) {
|
|
350
359
|
marker.setLngLat([longitude, latitude]);
|
|
@@ -368,6 +377,9 @@ function MapMarker({
|
|
|
368
377
|
marker.setPitchAlignment(pitchAlignment ?? "auto");
|
|
369
378
|
}
|
|
370
379
|
}, [marker, longitude, latitude, draggable, offset, rotation, rotationAlignment, pitchAlignment]);
|
|
380
|
+
if (!marker) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
371
383
|
return /* @__PURE__ */ jsx2(MarkerContext.Provider, { value: { marker, map }, children });
|
|
372
384
|
}
|
|
373
385
|
function MapMarkerContent({ children, className }) {
|
|
@@ -381,13 +393,14 @@ function DefaultMarkerIcon() {
|
|
|
381
393
|
return /* @__PURE__ */ jsx2("div", { className: "relative size-4 rounded-full border-2 border-background bg-primary shadow-sm" });
|
|
382
394
|
}
|
|
383
395
|
function PopupCloseButton({ onClick }) {
|
|
396
|
+
const { t } = useLocale2();
|
|
384
397
|
return /* @__PURE__ */ jsx2(
|
|
385
398
|
"button",
|
|
386
399
|
{
|
|
387
400
|
type: "button",
|
|
388
401
|
onClick,
|
|
389
|
-
"aria-label": "
|
|
390
|
-
className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus
|
|
402
|
+
"aria-label": t("maps.popup.close"),
|
|
403
|
+
className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset",
|
|
391
404
|
children: /* @__PURE__ */ jsx2(X, { className: "size-3.5", "aria-hidden": "true" })
|
|
392
405
|
}
|
|
393
406
|
);
|
|
@@ -518,6 +531,7 @@ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3 } from
|
|
|
518
531
|
import { createPortal as createPortal2 } from "react-dom";
|
|
519
532
|
import { X as X2 } from "lucide-react";
|
|
520
533
|
import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
|
|
534
|
+
import { useLocale as useLocale3 } from "@elabs-ai/components-ui";
|
|
521
535
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
522
536
|
function MapPopup({
|
|
523
537
|
longitude,
|
|
@@ -529,6 +543,7 @@ function MapPopup({
|
|
|
529
543
|
...popupOptions
|
|
530
544
|
}) {
|
|
531
545
|
const { map } = useMap();
|
|
546
|
+
const { t } = useLocale3();
|
|
532
547
|
const onCloseRef = useRef3(onClose);
|
|
533
548
|
onCloseRef.current = onClose;
|
|
534
549
|
const container = useMemo3(() => document.createElement("div"), []);
|
|
@@ -581,8 +596,8 @@ function MapPopup({
|
|
|
581
596
|
{
|
|
582
597
|
type: "button",
|
|
583
598
|
onClick: handleClose,
|
|
584
|
-
"aria-label": "
|
|
585
|
-
className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus
|
|
599
|
+
"aria-label": t("maps.popup.close"),
|
|
600
|
+
className: "absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset",
|
|
586
601
|
children: /* @__PURE__ */ jsx3(X2, { className: "size-3.5", "aria-hidden": "true" })
|
|
587
602
|
}
|
|
588
603
|
),
|
|
@@ -595,7 +610,7 @@ function MapPopup({
|
|
|
595
610
|
}
|
|
596
611
|
|
|
597
612
|
// src/map-controls/map-controls.tsx
|
|
598
|
-
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as
|
|
613
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState4 } from "react";
|
|
599
614
|
import { Locate, Maximize, Minus, Plus } from "lucide-react";
|
|
600
615
|
import { Spinner as Spinner2 } from "@elabs-ai/components-ui";
|
|
601
616
|
import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
|
|
@@ -624,7 +639,7 @@ function ControlButton({
|
|
|
624
639
|
className: cn4(
|
|
625
640
|
"flex size-8 items-center justify-center text-foreground transition-colors duration-fast",
|
|
626
641
|
"hover:bg-surface-muted",
|
|
627
|
-
"focus-
|
|
642
|
+
"focus-ring-inset",
|
|
628
643
|
"disabled:pointer-events-none disabled:opacity-50"
|
|
629
644
|
),
|
|
630
645
|
disabled,
|
|
@@ -643,7 +658,7 @@ function MapControls({
|
|
|
643
658
|
onLocateError
|
|
644
659
|
}) {
|
|
645
660
|
const { map } = useMap();
|
|
646
|
-
const [waitingForLocation, setWaitingForLocation] =
|
|
661
|
+
const [waitingForLocation, setWaitingForLocation] = useState4(false);
|
|
647
662
|
const handleZoomIn = useCallback2(() => {
|
|
648
663
|
map?.zoomTo(map.getZoom() + 1, { duration: 300 });
|
|
649
664
|
}, [map]);
|
|
@@ -751,10 +766,10 @@ import { useEffect as useEffect6, useId } from "react";
|
|
|
751
766
|
|
|
752
767
|
// src/lib/use-token-color.ts
|
|
753
768
|
import { resolveTokenColor } from "@elabs-ai/components-tokens";
|
|
754
|
-
import { useLayoutEffect, useState as
|
|
769
|
+
import { useLayoutEffect, useState as useState5 } from "react";
|
|
755
770
|
function useTokenColor(name, fallback = "#000000") {
|
|
756
771
|
const { map, themeKey } = useMap();
|
|
757
|
-
const [color, setColor] =
|
|
772
|
+
const [color, setColor] = useState5(fallback);
|
|
758
773
|
useLayoutEffect(() => {
|
|
759
774
|
setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));
|
|
760
775
|
}, [name, fallback, map, themeKey]);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/map-canvas/map-canvas.tsx","../src/map-canvas/map-context.ts","../src/map-canvas/use-resolved-basemap-theme.ts","../src/map-marker/map-marker.tsx","../src/map-popup/map-popup.tsx","../src/map-controls/map-controls.tsx","../src/map-route/map-route.tsx","../src/lib/use-token-color.ts","../src/map-arc/map-arc.tsx","../src/lib/arc-math.ts","../src/lib/merge-hover-paint.ts","../src/map-geojson/map-geojson.tsx","../src/map-cluster-layer/map-cluster-layer.tsx"],"sourcesContent":["\"use client\";\n\nimport MapLibreGL from \"maplibre-gl\";\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport \"./maps.css\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { Spinner, StatePanel } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { MapContext, type BasemapTheme } from \"./map-context\";\nimport { useResolvedBasemapTheme } from \"./use-resolved-basemap-theme\";\n\n/**\n * Default basemaps: Carto's free light/dark GL styles. These serve ODbL-licensed\n * OpenStreetMap data, which requires attribution on a public surface — see the\n * `attributionControl` note in the map constructor below.\n */\nconst defaultStyles = {\n dark: \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n light: \"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json\",\n};\n\n// A tile-less, dependency-free style with a transparent background. Use it for\n// data visualizations (choropleths, arcs, dot maps) where you draw your own\n// layers and don't need a street basemap: `<MapCanvas blank>`. The transparent\n// background lets the themed container show through.\nconst blankMapStyle: MapLibreGL.StyleSpecification = {\n version: 8,\n sources: {},\n layers: [\n {\n id: \"background\",\n type: \"background\",\n paint: { \"background-color\": \"transparent\" },\n },\n ],\n};\n\n/** Map viewport state. */\nexport interface MapViewport {\n /** Center coordinates [longitude, latitude]. */\n center: [number, number];\n /** Zoom level. */\n zoom: number;\n /** Bearing (rotation) in degrees. */\n bearing: number;\n /** Pitch (tilt) in degrees. */\n pitch: number;\n}\n\nexport type MapStyleOption = string | MapLibreGL.StyleSpecification;\n\n/** The imperative handle exposed by `<MapCanvas ref>`: the MapLibre map itself. */\nexport type MapCanvasRef = MapLibreGL.Map;\n\nexport type MapCanvasProps = {\n children?: ReactNode;\n /** Additional CSS classes for the map container. */\n className?: string;\n /**\n * Basemap flavor. If not provided, it is derived from the active brand theme\n * (`data-theme` + that theme's own `color-scheme`), then a `dark`/`light` root class,\n * then the OS preference.\n */\n theme?: BasemapTheme;\n /** Custom map styles for light and dark themes. Overrides the default Carto styles. */\n styles?: {\n light?: MapStyleOption;\n dark?: MapStyleOption;\n };\n /**\n * Use a transparent, tile-less basemap instead of the default Carto street\n * basemap — a blank canvas. Used alone it renders nothing; add your own\n * layers on top (`<MapGeoJSON>`, `<MapArc>`, markers, …). Ideal for data\n * visualizations. Ignored when an explicit `styles` prop is provided.\n */\n blank?: boolean;\n /** Map projection type. Use `{ type: \"globe\" }` for a 3D globe view. */\n projection?: MapLibreGL.ProjectionSpecification;\n /**\n * Controlled viewport. When provided together with `onViewportChange`, the\n * map becomes controlled and the viewport is driven by this prop.\n */\n viewport?: Partial<MapViewport>;\n /**\n * Callback fired continuously as the viewport changes (pan, zoom, rotate,\n * pitch). Use standalone to observe changes, or with `viewport` for\n * controlled mode.\n */\n onViewportChange?: (viewport: MapViewport) => void;\n /** Show a loading overlay on the map (e.g. while the app fetches map data). */\n loading?: boolean;\n} & Omit<MapLibreGL.MapOptions, \"container\" | \"style\">;\n\nfunction MapLoadingOverlay() {\n return (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-xs\">\n <Spinner label=\"Loading map\" className=\"size-5\" />\n </div>\n );\n}\n\nfunction getViewport(map: MapLibreGL.Map): MapViewport {\n const center = map.getCenter();\n return {\n center: [center.lng, center.lat],\n zoom: map.getZoom(),\n bearing: map.getBearing(),\n pitch: map.getPitch(),\n };\n}\n\n/**\n * The root map surface — a token/theme-aware MapLibre GL canvas. Compose the\n * other `@elabs-ai/components-maps` components (markers, popups, controls, layers) as\n * children; they reach the map through context (`useMap`).\n *\n * The ref exposes the raw MapLibre `Map` instance for imperative work\n * (`flyTo`, `fitBounds`, …).\n */\nexport const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCanvas(\n {\n children,\n className,\n theme: themeProp,\n styles,\n blank = false,\n projection,\n viewport,\n onViewportChange,\n loading = false,\n ...props\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [mapInstance, setMapInstance] = useState<MapLibreGL.Map | null>(null);\n const [initFailed, setInitFailed] = useState(false);\n const [isLoaded, setIsLoaded] = useState(false);\n const [isStyleLoaded, setIsStyleLoaded] = useState(false);\n const currentStyleRef = useRef<MapStyleOption | null>(null);\n const styleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const internalUpdateRef = useRef(false);\n const { resolvedTheme, themeKey } = useResolvedBasemapTheme(themeProp);\n\n const isControlled = viewport !== undefined && onViewportChange !== undefined;\n\n const onViewportChangeRef = useRef(onViewportChange);\n onViewportChangeRef.current = onViewportChange;\n\n const mapStyles = useMemo(() => {\n // Explicit styles win. Otherwise `blank` opts into the transparent\n // tile-less basemap; with neither, fall back to the Carto defaults.\n if (styles) {\n return {\n dark: styles.dark ?? defaultStyles.dark,\n light: styles.light ?? defaultStyles.light,\n };\n }\n if (blank) {\n return { dark: blankMapStyle, light: blankMapStyle };\n }\n return defaultStyles;\n }, [styles, blank]);\n\n // Expose the map instance to the parent component.\n useImperativeHandle(ref, () => mapInstance as MapLibreGL.Map, [mapInstance]);\n\n const clearStyleTimeout = useCallback(() => {\n if (styleTimeoutRef.current) {\n clearTimeout(styleTimeoutRef.current);\n styleTimeoutRef.current = null;\n }\n }, []);\n\n // Initialize the map.\n useEffect(() => {\n if (!containerRef.current) return;\n\n const initialStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n currentStyleRef.current = initialStyle;\n\n let map: MapLibreGL.Map;\n try {\n map = new MapLibreGL.Map({\n container: containerRef.current,\n style: initialStyle,\n renderWorldCopies: false,\n // Attribution control OFF by default — a maintainer decision for internal\n // use, taken deliberately and recorded in CHANGELOG.md + the map-components\n // rule. NOTE THE CONSTRAINT: the default Carto basemap serves OpenStreetMap\n // data, which is ODbL-licensed and requires the credit, and Carto's terms\n // require it too — so a surface that ships PUBLICLY on these tiles must turn\n // it back on with `attributionControl={{ compact: true }}` (it wins through\n // `...props` below), or move to tiles licensed without the requirement via\n // `styles` / `blank`.\n attributionControl: false,\n ...props,\n ...viewport,\n });\n } catch {\n // MapLibre throws at construction when WebGL is unavailable (headless\n // browsers without GPU, remote desktops). Degrade to a quiet panel\n // instead of an unhandled render error.\n setInitFailed(true);\n return;\n }\n\n const styleDataHandler = () => {\n clearStyleTimeout();\n // Delay so the style is fully processed before layer operations — avoids\n // race conditions on setStyle without force-updating every layer.\n styleTimeoutRef.current = setTimeout(() => {\n setIsStyleLoaded(true);\n if (projection) {\n map.setProjection(projection);\n }\n }, 100);\n };\n // No-op under the default (`attributionControl: false` above). This exists for\n // the case a consumer turns the control back ON for a public surface: MapLibre\n // paints even a COMPACT control expanded on first render (`<details open>` +\n // `maplibregl-compact-show`), so the credits land as a text slab until someone\n // clicks the toggle. Collapse it to the labelled ⓘ button instead.\n const loadHandler = () => {\n setIsLoaded(true);\n map\n .getContainer()\n .querySelectorAll<HTMLDetailsElement>(\"details.maplibregl-ctrl-attrib[open]\")\n .forEach((el) => {\n el.open = false;\n });\n };\n\n // Viewport change handler — skip if triggered by an internal update.\n const handleMove = () => {\n if (internalUpdateRef.current) return;\n onViewportChangeRef.current?.(getViewport(map));\n };\n\n map.on(\"load\", loadHandler);\n map.on(\"styledata\", styleDataHandler);\n map.on(\"move\", handleMove);\n setMapInstance(map);\n\n return () => {\n clearStyleTimeout();\n map.off(\"load\", loadHandler);\n map.off(\"styledata\", styleDataHandler);\n map.off(\"move\", handleMove);\n map.remove();\n setIsLoaded(false);\n setIsStyleLoaded(false);\n setMapInstance(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only: map options are init-time; style/theme/projection changes are synced by the effects below\n }, []);\n\n // Sync controlled viewport to the map.\n useEffect(() => {\n if (!mapInstance || !isControlled || !viewport) return;\n if (mapInstance.isMoving()) return;\n\n const current = getViewport(mapInstance);\n const next = {\n center: viewport.center ?? current.center,\n zoom: viewport.zoom ?? current.zoom,\n bearing: viewport.bearing ?? current.bearing,\n pitch: viewport.pitch ?? current.pitch,\n };\n\n if (\n next.center[0] === current.center[0] &&\n next.center[1] === current.center[1] &&\n next.zoom === current.zoom &&\n next.bearing === current.bearing &&\n next.pitch === current.pitch\n ) {\n return;\n }\n\n internalUpdateRef.current = true;\n mapInstance.jumpTo(next);\n internalUpdateRef.current = false;\n }, [mapInstance, isControlled, viewport]);\n\n // Swap the basemap style when the theme flips.\n useEffect(() => {\n if (!mapInstance || !resolvedTheme) return;\n\n const newStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n\n if (currentStyleRef.current === newStyle) return;\n\n clearStyleTimeout();\n currentStyleRef.current = newStyle;\n setIsStyleLoaded(false);\n\n mapInstance.setStyle(newStyle, { diff: true });\n }, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]);\n\n // Sync projection when the prop changes after mount.\n useEffect(() => {\n if (!mapInstance || !isStyleLoaded || !projection) return;\n mapInstance.setProjection(projection);\n }, [mapInstance, isStyleLoaded, projection]);\n\n const contextValue = useMemo(\n () => ({\n map: mapInstance,\n isLoaded: isLoaded && isStyleLoaded,\n resolvedTheme,\n themeKey,\n }),\n [mapInstance, isLoaded, isStyleLoaded, resolvedTheme, themeKey],\n );\n\n if (initFailed) {\n return (\n <div className={cn(\"relative h-full w-full\", className)}>\n <StatePanel\n kind=\"error\"\n title=\"Map unavailable\"\n description=\"This browser can’t render WebGL maps.\"\n className=\"h-full\"\n />\n </div>\n );\n }\n\n return (\n <MapContext.Provider value={contextValue}>\n <div ref={containerRef} className={cn(\"relative h-full w-full\", className)}>\n {(!isLoaded || loading) && <MapLoadingOverlay />}\n {/* SSR-safe: children render only when the map exists on the client. */}\n {mapInstance && children}\n </div>\n </MapContext.Provider>\n );\n});\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { createContext, use } from \"react\";\n\n/** Light-or-dark flavor of the active basemap (derived from the brand theme). */\nexport type BasemapTheme = \"light\" | \"dark\";\n\nexport interface MapContextValue {\n /** The live MapLibre map instance (`null` until the map has mounted). */\n map: MapLibreGL.Map | null;\n /** True once the map AND its style are fully loaded — gate layer operations on this. */\n isLoaded: boolean;\n /** Which basemap flavor is active. */\n resolvedTheme: BasemapTheme;\n /**\n * Changes whenever the active brand theme changes. Layer components use it\n * as a dependency key to re-resolve semantic token colors for WebGL paint.\n */\n themeKey: string;\n}\n\nexport const MapContext = createContext<MapContextValue | null>(null);\n\n/** Access the map instance + load state from any descendant of `<MapCanvas>`. */\nexport function useMap(): MapContextValue {\n const context = use(MapContext);\n if (!context) {\n throw new Error(\"useMap must be used within a <MapCanvas>\");\n }\n return context;\n}\n","\"use client\";\n\nimport { resolveThemeIsDark } from \"@elabs-ai/components-tokens\";\nimport { useEffect, useState } from \"react\";\n\nimport type { BasemapTheme } from \"./map-context\";\n\n/**\n * Brand theme → basemap flavor. When a `data-theme` is set (the ThemeProvider\n * contract) the theme's own `color-scheme` is authoritative via\n * `resolveThemeIsDark` — which is why a CONSUMER-AUTHORED dark theme gets the\n * dark basemap without registering anything here (ADR 0029; the old\n * `THEME_META[theme].dark` lookup only knew the themes this repo ships). A\n * `dark`/`light` class on `<html>` is honored for non-brand hosts (next-themes\n * et al.); the OS preference is only the last-resort fallback.\n */\nfunction getBrandTheme(): BasemapTheme | null {\n if (typeof document === \"undefined\") return null;\n const root = document.documentElement;\n if (root.getAttribute(\"data-theme\")) return resolveThemeIsDark(root) ? \"dark\" : \"light\";\n if (root.classList.contains(\"dark\")) return \"dark\";\n if (root.classList.contains(\"light\")) return \"light\";\n return null;\n}\n\nfunction getSystemTheme(): BasemapTheme {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\nfunction getThemeAttribute(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.documentElement.getAttribute(\"data-theme\") ?? \"\";\n}\n\n/**\n * Resolve the basemap flavor and a change-key for the active brand theme.\n * The observer stays active even with an explicit `theme` prop: token colors\n * (routes, clusters, …) must still re-resolve when `data-theme` changes.\n */\nexport function useResolvedBasemapTheme(themeProp?: BasemapTheme): {\n resolvedTheme: BasemapTheme;\n themeKey: string;\n} {\n const [detected, setDetected] = useState<BasemapTheme>(() => getBrandTheme() ?? getSystemTheme());\n const [themeAttr, setThemeAttr] = useState(getThemeAttribute);\n\n useEffect(() => {\n const update = () => {\n setThemeAttr(getThemeAttribute());\n const brand = getBrandTheme();\n if (brand) setDetected(brand);\n };\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\"],\n });\n\n const mediaQuery =\n typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(prefers-color-scheme: dark)\")\n : null;\n const handleSystemChange = (e: MediaQueryListEvent) => {\n if (!getBrandTheme()) setDetected(e.matches ? \"dark\" : \"light\");\n };\n mediaQuery?.addEventListener(\"change\", handleSystemChange);\n\n return () => {\n observer.disconnect();\n mediaQuery?.removeEventListener(\"change\", handleSystemChange);\n };\n }, []);\n\n const resolvedTheme = themeProp ?? detected;\n return { resolvedTheme, themeKey: `${themeAttr}:${resolvedTheme}` };\n}\n","\"use client\";\n\nimport MapLibreGL, { type MarkerOptions, type PopupOptions } from \"maplibre-gl\";\nimport { createContext, use, useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\ntype MarkerContextValue = {\n marker: MapLibreGL.Marker;\n map: MapLibreGL.Map | null;\n};\n\nconst MarkerContext = createContext<MarkerContextValue | null>(null);\n\nfunction useMarkerContext() {\n const context = use(MarkerContext);\n if (!context) {\n throw new Error(\"MapMarker sub-components must be used within <MapMarker>\");\n }\n return context;\n}\n\nexport type MapMarkerProps = {\n /** Longitude coordinate for the marker position. */\n longitude: number;\n /** Latitude coordinate for the marker position. */\n latitude: number;\n /** Marker sub-components (MapMarkerContent, MapMarkerPopup, MapMarkerTooltip, MapMarkerLabel). */\n children: ReactNode;\n /** Callback when the marker is clicked. */\n onClick?: (e: MouseEvent) => void;\n /** Callback when the mouse enters the marker. */\n onMouseEnter?: (e: MouseEvent) => void;\n /** Callback when the mouse leaves the marker. */\n onMouseLeave?: (e: MouseEvent) => void;\n /** Callback when a drag starts (requires `draggable`). */\n onDragStart?: (lngLat: { lng: number; lat: number }) => void;\n /** Callback during a drag (requires `draggable`). */\n onDrag?: (lngLat: { lng: number; lat: number }) => void;\n /** Callback when a drag ends (requires `draggable`). */\n onDragEnd?: (lngLat: { lng: number; lat: number }) => void;\n} & Omit<MarkerOptions, \"element\">;\n\n/**\n * A marker anchored at a lng/lat. Compose the pieces you need:\n * `MapMarkerContent` (the visual), `MapMarkerLabel`, `MapMarkerPopup` (opens\n * on click) and `MapMarkerTooltip` (shows on hover).\n */\nexport function MapMarker({\n longitude,\n latitude,\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n draggable = false,\n ...markerOptions\n}: MapMarkerProps) {\n const { map } = useMap();\n\n const callbacksRef = useRef({\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n });\n callbacksRef.current = {\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n };\n\n const marker = useMemo(() => {\n const markerInstance = new MapLibreGL.Marker({\n ...markerOptions,\n element: document.createElement(\"div\"),\n draggable,\n }).setLngLat([longitude, latitude]);\n\n const handleClick = (e: MouseEvent) => callbacksRef.current.onClick?.(e);\n const handleMouseEnter = (e: MouseEvent) => callbacksRef.current.onMouseEnter?.(e);\n const handleMouseLeave = (e: MouseEvent) => callbacksRef.current.onMouseLeave?.(e);\n\n markerInstance.getElement()?.addEventListener(\"click\", handleClick);\n markerInstance.getElement()?.addEventListener(\"mouseenter\", handleMouseEnter);\n markerInstance.getElement()?.addEventListener(\"mouseleave\", handleMouseLeave);\n\n const handleDragStart = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDragStart?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n const handleDrag = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDrag?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n const handleDragEnd = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDragEnd?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n\n markerInstance.on(\"dragstart\", handleDragStart);\n markerInstance.on(\"drag\", handleDrag);\n markerInstance.on(\"dragend\", handleDragEnd);\n\n return markerInstance;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; position/options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n marker.addTo(map);\n\n return () => {\n marker.remove();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `marker` is stable (created once above)\n }, [map]);\n\n const { offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;\n\n useEffect(() => {\n const current = marker.getLngLat();\n if (current.lng !== longitude || current.lat !== latitude) {\n marker.setLngLat([longitude, latitude]);\n }\n\n if (marker.isDraggable() !== draggable) {\n marker.setDraggable(draggable);\n }\n\n const currentOffset = marker.getOffset();\n const newOffset = offset ?? [0, 0];\n const [newOffsetX, newOffsetY] = Array.isArray(newOffset)\n ? newOffset\n : [newOffset.x, newOffset.y];\n if (currentOffset.x !== newOffsetX || currentOffset.y !== newOffsetY) {\n marker.setOffset(newOffset);\n }\n\n if (marker.getRotation() !== (rotation ?? 0)) {\n marker.setRotation(rotation ?? 0);\n }\n if (marker.getRotationAlignment() !== (rotationAlignment ?? \"auto\")) {\n marker.setRotationAlignment(rotationAlignment ?? \"auto\");\n }\n if (marker.getPitchAlignment() !== (pitchAlignment ?? \"auto\")) {\n marker.setPitchAlignment(pitchAlignment ?? \"auto\");\n }\n }, [marker, longitude, latitude, draggable, offset, rotation, rotationAlignment, pitchAlignment]);\n\n return <MarkerContext.Provider value={{ marker, map }}>{children}</MarkerContext.Provider>;\n}\n\nexport interface MapMarkerContentProps {\n /** Custom marker content. Defaults to a primary-colored dot. */\n children?: ReactNode;\n /** Additional CSS classes for the marker container. */\n className?: string;\n}\n\n/** The marker's visual, portaled into the MapLibre marker element. */\nexport function MapMarkerContent({ children, className }: MapMarkerContentProps) {\n const { marker } = useMarkerContext();\n\n return createPortal(\n <div className={cn(\"relative cursor-pointer\", className)}>\n {children || <DefaultMarkerIcon />}\n </div>,\n marker.getElement(),\n );\n}\n\nfunction DefaultMarkerIcon() {\n return (\n <div className=\"relative size-4 rounded-full border-2 border-background bg-primary shadow-sm\" />\n );\n}\n\nfunction PopupCloseButton({ onClick }: { onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Close popup\"\n className=\"absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n );\n}\n\nexport type MapMarkerPopupProps = {\n /** Popup content. */\n children: ReactNode;\n /** Additional CSS classes for the popup container. */\n className?: string;\n /** Show a close button in the popup (default: false). */\n closeButton?: boolean;\n} & Omit<PopupOptions, \"className\" | \"closeButton\">;\n\n/** A popup attached to the marker — MapLibre toggles it on marker click. */\nexport function MapMarkerPopup({\n children,\n className,\n closeButton = false,\n ...popupOptions\n}: MapMarkerPopupProps) {\n const { marker, map } = useMarkerContext();\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const popup = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeButton: false,\n })\n .setMaxWidth(\"none\")\n .setDOMContent(container);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n popup.setDOMContent(container);\n marker.setPopup(popup);\n\n return () => {\n marker.setPopup(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- popup/marker/container are stable\n }, [map]);\n\n // Sync popup options when they change.\n useEffect(() => {\n popup.setOffset(offset ?? 16);\n if (maxWidth) {\n popup.setMaxWidth(maxWidth);\n }\n }, [popup, offset, maxWidth]);\n\n const handleClose = () => popup.remove();\n\n return createPortal(\n <div\n className={cn(\n \"relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {closeButton && <PopupCloseButton onClick={handleClose} />}\n {children}\n </div>,\n container,\n );\n}\n\nexport type MapMarkerTooltipProps = {\n /** Tooltip content. */\n children: ReactNode;\n /** Additional CSS classes for the tooltip container. */\n className?: string;\n} & Omit<PopupOptions, \"className\" | \"closeButton\" | \"closeOnClick\">;\n\n/** A hover tooltip attached to the marker. */\nexport function MapMarkerTooltip({ children, className, ...popupOptions }: MapMarkerTooltipProps) {\n const { marker, map } = useMarkerContext();\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const tooltip = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeOnClick: true,\n closeButton: false,\n }).setMaxWidth(\"none\");\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n tooltip.setDOMContent(container);\n\n const handleMouseEnter = () => {\n tooltip.setLngLat(marker.getLngLat()).addTo(map);\n };\n const handleMouseLeave = () => tooltip.remove();\n\n marker.getElement()?.addEventListener(\"mouseenter\", handleMouseEnter);\n marker.getElement()?.addEventListener(\"mouseleave\", handleMouseLeave);\n\n return () => {\n marker.getElement()?.removeEventListener(\"mouseenter\", handleMouseEnter);\n marker.getElement()?.removeEventListener(\"mouseleave\", handleMouseLeave);\n tooltip.remove();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- tooltip/marker/container are stable\n }, [map]);\n\n // Sync tooltip options when they change.\n useEffect(() => {\n tooltip.setOffset(offset ?? 16);\n if (maxWidth) {\n tooltip.setMaxWidth(maxWidth);\n }\n }, [tooltip, offset, maxWidth]);\n\n return createPortal(\n <div\n className={cn(\n \"pointer-events-none rounded-md bg-foreground px-2 py-1 text-meta text-balance text-background shadow-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {children}\n </div>,\n container,\n );\n}\n\nexport interface MapMarkerLabelProps {\n /** Label text content. */\n children: ReactNode;\n /** Additional CSS classes for the label. */\n className?: string;\n /** Position of the label relative to the marker (default: \"top\"). */\n position?: \"top\" | \"bottom\";\n}\n\n/**\n * A small always-visible text label above or below the marker. Portaled into\n * the marker element so it anchors to the marker whether composed as a sibling\n * of `MapMarkerContent` or nested inside it (the three-theme sweep caught the\n * sibling composition rendering the label against the map container instead).\n */\nexport function MapMarkerLabel({ children, className, position = \"top\" }: MapMarkerLabelProps) {\n const { marker } = useMarkerContext();\n const positionClasses = {\n top: \"bottom-full mb-1\",\n bottom: \"top-full mt-1\",\n };\n\n return createPortal(\n <div\n className={cn(\n \"absolute left-1/2 -translate-x-1/2 whitespace-nowrap\",\n \"text-meta font-medium text-foreground\",\n positionClasses[position],\n className,\n )}\n >\n {children}\n </div>,\n marker.getElement(),\n );\n}\n","\"use client\";\n\nimport MapLibreGL, { type PopupOptions } from \"maplibre-gl\";\nimport { useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\nexport type MapPopupProps = {\n /** Longitude coordinate for the popup position. */\n longitude: number;\n /** Latitude coordinate for the popup position. */\n latitude: number;\n /** Callback when the popup is closed. */\n onClose?: () => void;\n /** Popup content. */\n children: ReactNode;\n /** Additional CSS classes for the popup container. */\n className?: string;\n /** Show a close button in the popup (default: false). */\n closeButton?: boolean;\n} & Omit<PopupOptions, \"className\" | \"closeButton\">;\n\n/**\n * A standalone popup anchored at a lng/lat (not attached to a marker) —\n * typically rendered conditionally from app state (e.g. after a layer click).\n */\nexport function MapPopup({\n longitude,\n latitude,\n onClose,\n children,\n className,\n closeButton = false,\n ...popupOptions\n}: MapPopupProps) {\n const { map } = useMap();\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const popup = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeButton: false,\n })\n .setMaxWidth(\"none\")\n .setLngLat([longitude, latitude]);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; position/options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n const onCloseProp = () => onCloseRef.current?.();\n\n popup.on(\"close\", onCloseProp);\n\n popup.setDOMContent(container);\n popup.addTo(map);\n\n return () => {\n popup.off(\"close\", onCloseProp);\n if (popup.isOpen()) {\n popup.remove();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- popup/container are stable\n }, [map]);\n\n // Sync popup position and options when they change.\n useEffect(() => {\n const current = popup.getLngLat();\n if (!current || current.lng !== longitude || current.lat !== latitude) {\n popup.setLngLat([longitude, latitude]);\n }\n popup.setOffset(offset ?? 16);\n if (maxWidth) {\n popup.setMaxWidth(maxWidth);\n }\n }, [popup, longitude, latitude, offset, maxWidth]);\n\n const handleClose = () => {\n popup.remove();\n };\n\n return createPortal(\n <div\n className={cn(\n \"relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {closeButton && (\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label=\"Close popup\"\n className=\"absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n )}\n {children}\n </div>,\n container,\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { Locate, Maximize, Minus, Plus } from \"lucide-react\";\nimport { Spinner } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\nexport interface MapControlsProps {\n /** Position of the controls on the map (default: \"bottom-right\"). */\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n /** Show zoom in/out buttons (default: true). */\n showZoom?: boolean;\n /** Show a compass button to reset bearing/pitch (default: false). */\n showCompass?: boolean;\n /** Show a locate button to fly to the user's location (default: false). */\n showLocate?: boolean;\n /** Show a fullscreen toggle button (default: false). */\n showFullscreen?: boolean;\n /** Additional CSS classes for the controls container. */\n className?: string;\n /** Callback with user coordinates when located. */\n onLocate?: (coords: { longitude: number; latitude: number }) => void;\n /** Callback when geolocation fails or is denied. */\n onLocateError?: (error: GeolocationPositionError) => void;\n}\n\n// bottom-right sits above MapLibre's attribution control — keep it visible.\nconst positionClasses = {\n \"top-left\": \"top-2 left-2\",\n \"top-right\": \"top-2 right-2\",\n \"bottom-left\": \"bottom-2 left-2\",\n \"bottom-right\": \"bottom-10 right-2\",\n};\n\nfunction ControlGroup({ children }: { children: ReactNode }) {\n return (\n <div className=\"flex flex-col divide-y overflow-hidden rounded-md bg-surface-elevated shadow-ring-sm\">\n {children}\n </div>\n );\n}\n\nfunction ControlButton({\n onClick,\n label,\n children,\n disabled = false,\n}: {\n onClick: () => void;\n label: string;\n children: ReactNode;\n disabled?: boolean;\n}) {\n return (\n <button\n onClick={onClick}\n aria-label={label}\n type=\"button\"\n className={cn(\n \"flex size-8 items-center justify-center text-foreground transition-colors duration-fast\",\n \"hover:bg-surface-muted\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n )}\n disabled={disabled}\n >\n {children}\n </button>\n );\n}\n\n/** Branded zoom / compass / locate / fullscreen controls. Render inside `<MapCanvas>`. */\nexport function MapControls({\n position = \"bottom-right\",\n showZoom = true,\n showCompass = false,\n showLocate = false,\n showFullscreen = false,\n className,\n onLocate,\n onLocateError,\n}: MapControlsProps) {\n const { map } = useMap();\n const [waitingForLocation, setWaitingForLocation] = useState(false);\n\n const handleZoomIn = useCallback(() => {\n map?.zoomTo(map.getZoom() + 1, { duration: 300 });\n }, [map]);\n\n const handleZoomOut = useCallback(() => {\n map?.zoomTo(map.getZoom() - 1, { duration: 300 });\n }, [map]);\n\n const handleResetBearing = useCallback(() => {\n map?.resetNorthPitch({ duration: 300 });\n }, [map]);\n\n const handleLocate = useCallback(() => {\n if (!(\"geolocation\" in navigator)) return;\n setWaitingForLocation(true);\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n const coords = {\n longitude: pos.coords.longitude,\n latitude: pos.coords.latitude,\n };\n map?.flyTo({\n center: [coords.longitude, coords.latitude],\n zoom: 14,\n duration: 1500,\n });\n onLocate?.(coords);\n setWaitingForLocation(false);\n },\n (error) => {\n onLocateError?.(error);\n setWaitingForLocation(false);\n },\n );\n }, [map, onLocate, onLocateError]);\n\n const handleFullscreen = useCallback(() => {\n const container = map?.getContainer();\n if (!container) return;\n if (document.fullscreenElement) {\n void document.exitFullscreen();\n } else {\n void container.requestFullscreen();\n }\n }, [map]);\n\n return (\n <div\n className={cn(\"absolute z-10 flex flex-col gap-1.5\", positionClasses[position], className)}\n >\n {showZoom && (\n <ControlGroup>\n <ControlButton onClick={handleZoomIn} label=\"Zoom in\">\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n <ControlButton onClick={handleZoomOut} label=\"Zoom out\">\n <Minus className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n </ControlGroup>\n )}\n {showCompass && (\n <ControlGroup>\n <CompassButton onClick={handleResetBearing} />\n </ControlGroup>\n )}\n {showLocate && (\n <ControlGroup>\n <ControlButton\n onClick={handleLocate}\n label=\"Find my location\"\n disabled={waitingForLocation}\n >\n {waitingForLocation ? (\n <Spinner label=\"Locating\" className=\"size-4 text-foreground\" />\n ) : (\n <Locate className=\"size-4\" aria-hidden=\"true\" />\n )}\n </ControlButton>\n </ControlGroup>\n )}\n {showFullscreen && (\n <ControlGroup>\n <ControlButton onClick={handleFullscreen} label=\"Toggle fullscreen\">\n <Maximize className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n </ControlGroup>\n )}\n </div>\n );\n}\n\nfunction CompassButton({ onClick }: { onClick: () => void }) {\n const { map } = useMap();\n const compassRef = useRef<SVGSVGElement>(null);\n\n useEffect(() => {\n if (!map || !compassRef.current) return;\n\n const compass = compassRef.current;\n\n const updateRotation = () => {\n const bearing = map.getBearing();\n const pitch = map.getPitch();\n compass.style.transform = `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`;\n };\n\n map.on(\"rotate\", updateRotation);\n map.on(\"pitch\", updateRotation);\n updateRotation();\n\n return () => {\n map.off(\"rotate\", updateRotation);\n map.off(\"pitch\", updateRotation);\n };\n }, [map]);\n\n return (\n <ControlButton onClick={onClick} label=\"Reset bearing to north\">\n <svg\n ref={compassRef}\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n className=\"size-5 transition-transform duration-fast\"\n style={{ transformStyle: \"preserve-3d\" }}\n >\n <path d=\"M12 2L16 12H12V2Z\" className=\"fill-destructive\" />\n <path d=\"M12 2L8 12H12V2Z\" className=\"fill-destructive/50\" />\n <path d=\"M12 22L16 12H12V22Z\" className=\"fill-muted-foreground/60\" />\n <path d=\"M12 22L8 12H12V22Z\" className=\"fill-muted-foreground/30\" />\n </svg>\n </ControlButton>\n );\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport interface MapRouteProps {\n /** Optional unique identifier for the route layer. */\n id?: string;\n /** Array of [longitude, latitude] coordinate pairs defining the route. */\n coordinates: [number, number][];\n /** Line color as a CSS color value. Defaults to the theme's `--primary` token. */\n color?: string;\n /** Line width in pixels (default: 3). */\n width?: number;\n /** Line opacity from 0 to 1 (default: 0.8). */\n opacity?: number;\n /** Dash pattern [dash length, gap length] for dashed lines. */\n dashArray?: [number, number];\n /** Callback when the route line is clicked. */\n onClick?: () => void;\n /** Callback when the mouse enters the route line. */\n onMouseEnter?: () => void;\n /** Callback when the mouse leaves the route line. */\n onMouseLeave?: () => void;\n /** Whether the route is interactive — shows a pointer cursor on hover (default: true). */\n interactive?: boolean;\n}\n\n/** A GeoJSON line layer for routes/paths. Renders nothing itself — it draws on the map. */\nexport function MapRoute({\n id: propId,\n coordinates,\n color,\n width = 3,\n opacity = 0.8,\n dashArray,\n onClick,\n onMouseEnter,\n onMouseLeave,\n interactive = true,\n}: MapRouteProps) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `route-source-${id}`;\n const layerId = `route-layer-${id}`;\n\n const primary = useTokenColor(\"--primary\");\n const lineColor = color ?? primary;\n\n // Add source and layer on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data: {\n type: \"Feature\",\n properties: {},\n geometry: { type: \"LineString\", coordinates: [] },\n },\n });\n\n map.addLayer({\n id: layerId,\n type: \"line\",\n source: sourceId,\n layout: { \"line-join\": \"round\", \"line-cap\": \"round\" },\n paint: {\n \"line-color\": lineColor,\n \"line-width\": width,\n \"line-opacity\": opacity,\n ...(dashArray && { \"line-dasharray\": dashArray }),\n },\n });\n\n return () => {\n try {\n if (map.getLayer(layerId)) map.removeLayer(layerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layer created once per map; data + paint are synced by the effects below\n }, [isLoaded, map]);\n\n // When coordinates change, update the source data.\n useEffect(() => {\n if (!isLoaded || !map || coordinates.length < 2) return;\n\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n if (source) {\n source.setData({\n type: \"Feature\",\n properties: {},\n geometry: { type: \"LineString\", coordinates },\n });\n }\n }, [isLoaded, map, coordinates, sourceId]);\n\n // Sync paint when styling (or the resolved theme color) changes.\n useEffect(() => {\n if (!isLoaded || !map || !map.getLayer(layerId)) return;\n\n map.setPaintProperty(layerId, \"line-color\", lineColor);\n map.setPaintProperty(layerId, \"line-width\", width);\n map.setPaintProperty(layerId, \"line-opacity\", opacity);\n map.setPaintProperty(layerId, \"line-dasharray\", dashArray);\n }, [isLoaded, map, layerId, lineColor, width, opacity, dashArray]);\n\n // Handle click and hover events.\n useEffect(() => {\n if (!isLoaded || !map || !interactive) return;\n\n const handleClick = () => {\n onClick?.();\n };\n const handleMouseEnter = () => {\n map.getCanvas().style.cursor = \"pointer\";\n onMouseEnter?.();\n };\n const handleMouseLeave = () => {\n map.getCanvas().style.cursor = \"\";\n onMouseLeave?.();\n };\n\n map.on(\"click\", layerId, handleClick);\n map.on(\"mouseenter\", layerId, handleMouseEnter);\n map.on(\"mouseleave\", layerId, handleMouseLeave);\n\n return () => {\n map.off(\"click\", layerId, handleClick);\n map.off(\"mouseenter\", layerId, handleMouseEnter);\n map.off(\"mouseleave\", layerId, handleMouseLeave);\n };\n }, [isLoaded, map, layerId, onClick, onMouseEnter, onMouseLeave, interactive]);\n\n return null;\n}\n","\"use client\";\n\nimport { resolveTokenColor } from \"@elabs-ai/components-tokens\";\nimport { useLayoutEffect, useState } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\n/**\n * Resolve a semantic token (e.g. `\"--primary\"`) to a concrete color MapLibre's\n * WebGL paint can consume, re-resolving whenever the brand theme changes.\n * This is how default layer paints stay token-driven: WebGL can't read CSS\n * custom properties, so we read them at runtime off the map container.\n */\nexport function useTokenColor(name: string, fallback = \"#000000\"): string {\n const { map, themeKey } = useMap();\n const [color, setColor] = useState(fallback);\n\n useLayoutEffect(() => {\n setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));\n }, [name, fallback, map, themeKey]);\n\n return color;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { buildArcCoordinates } from \"../lib/arc-math\";\nimport { mergeHoverPaint } from \"../lib/merge-hover-paint\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\n/** A single arc to render inside `<MapArc data={...}>`. */\nexport type MapArcDatum = {\n /** Unique identifier for this arc. Required for hover state tracking and event payloads. */\n id: string | number;\n /** Start coordinate as [longitude, latitude]. */\n from: [number, number];\n /** End coordinate as [longitude, latitude]. */\n to: [number, number];\n};\n\n/** Event payload passed to MapArc interaction callbacks. */\nexport type MapArcEvent<T extends MapArcDatum = MapArcDatum> = {\n /** The arc datum that was hovered or clicked. */\n arc: T;\n /** Longitude of the cursor at the time of the event. */\n longitude: number;\n /** Latitude of the cursor at the time of the event. */\n latitude: number;\n /** The underlying MapLibre mouse event for advanced use cases. */\n originalEvent: MapLibreGL.MapMouseEvent;\n};\n\nexport type MapArcLinePaint = NonNullable<MapLibreGL.LineLayerSpecification[\"paint\"]>;\nexport type MapArcLineLayout = NonNullable<MapLibreGL.LineLayerSpecification[\"layout\"]>;\n\nexport type MapArcProps<T extends MapArcDatum = MapArcDatum> = {\n /** Array of arcs to render. Each arc must have a unique `id`. */\n data: T[];\n /** Optional unique identifier prefix for the arc source/layers. Auto-generated if not provided. */\n id?: string;\n /**\n * How far each arc bows away from a straight line. `0` renders straight\n * lines; higher values bend further; negative values bend to the opposite\n * side. Arcs cross the antimeridian via the shorter direction. (default: 0.2)\n */\n curvature?: number;\n /** Number of samples used to render each curve. Higher = smoother. (default: 64) */\n samples?: number;\n /**\n * MapLibre paint properties for the arc layer. Merged on top of theme-aware\n * defaults (`line-color` = the `--primary` token, `line-width: 2`,\n * `line-opacity: 0.85`). Any value can be a MapLibre expression for\n * per-feature styling; every field on each arc datum (besides `from`/`to`)\n * is exposed via `[\"get\", ...]`.\n */\n paint?: MapArcLinePaint;\n /** MapLibre layout properties for the arc layer. Defaults to rounded joins/caps. */\n layout?: MapArcLineLayout;\n /**\n * Paint properties applied to the arc currently under the cursor. Each key\n * is merged into `paint` as a `case` expression keyed on per-feature hover\n * state, so only the hovered arc changes appearance.\n */\n hoverPaint?: MapArcLinePaint;\n /** Callback when an arc is clicked. */\n onClick?: (e: MapArcEvent<T>) => void;\n /**\n * Callback fired when the hovered arc changes. Receives the cursor's\n * lng/lat at the moment of entry, and `null` when the cursor leaves the\n * last hovered arc.\n */\n onHover?: (e: MapArcEvent<T> | null) => void;\n /** Whether arcs respond to mouse events (default: true). */\n interactive?: boolean;\n /** Optional MapLibre layer id to insert the arc layers before (z-order control). */\n beforeId?: string;\n};\n\nconst DEFAULT_ARC_CURVATURE = 0.2;\nconst DEFAULT_ARC_SAMPLES = 64;\nconst ARC_HIT_MIN_WIDTH = 12;\nconst ARC_HIT_PADDING = 6;\n\nconst DEFAULT_ARC_LAYOUT: MapArcLineLayout = {\n \"line-join\": \"round\",\n \"line-cap\": \"round\",\n};\n\n/**\n * Curved great-circle-style arcs between coordinate pairs (flight paths,\n * network links). Pairs well with `<MapCanvas blank projection={{ type: \"globe\" }}>`.\n */\nexport function MapArc<T extends MapArcDatum = MapArcDatum>({\n data,\n id: propId,\n curvature = DEFAULT_ARC_CURVATURE,\n samples = DEFAULT_ARC_SAMPLES,\n paint,\n layout,\n hoverPaint,\n onClick,\n onHover,\n interactive = true,\n beforeId,\n}: MapArcProps<T>) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `arc-source-${id}`;\n const layerId = `arc-layer-${id}`;\n const hitLayerId = `arc-hit-layer-${id}`;\n\n const primary = useTokenColor(\"--primary\");\n\n const mergedPaint = useMemo(\n () =>\n mergeHoverPaint(\n { \"line-color\": primary, \"line-width\": 2, \"line-opacity\": 0.85, ...paint },\n hoverPaint,\n ),\n [primary, paint, hoverPaint],\n );\n const mergedLayout = useMemo(() => ({ ...DEFAULT_ARC_LAYOUT, ...layout }), [layout]);\n\n const hitWidth = useMemo(() => {\n const w = paint?.[\"line-width\"] ?? 2;\n const base = typeof w === \"number\" ? w : ARC_HIT_MIN_WIDTH;\n return Math.max(base + ARC_HIT_PADDING, ARC_HIT_MIN_WIDTH);\n }, [paint]);\n\n const geoJSON = useMemo<GeoJSON.FeatureCollection<GeoJSON.LineString>>(\n () => ({\n type: \"FeatureCollection\",\n features: data.map((arc) => {\n const { from, to, ...properties } = arc;\n return {\n type: \"Feature\",\n properties,\n geometry: {\n type: \"LineString\",\n coordinates: buildArcCoordinates(from, to, curvature, samples),\n },\n };\n }),\n }),\n [data, curvature, samples],\n );\n\n const latestRef = useRef({ data, onClick, onHover });\n latestRef.current = { data, onClick, onHover };\n\n // Add source and layers on mount. The invisible hit layer widens the\n // pointer target so thin arcs stay hoverable/clickable.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data: geoJSON,\n promoteId: \"id\",\n });\n\n map.addLayer(\n {\n id: hitLayerId,\n type: \"line\",\n source: sourceId,\n layout: DEFAULT_ARC_LAYOUT,\n paint: {\n \"line-color\": \"transparent\",\n \"line-width\": hitWidth,\n \"line-opacity\": 1,\n },\n },\n beforeId,\n );\n\n map.addLayer(\n {\n id: layerId,\n type: \"line\",\n source: sourceId,\n layout: mergedLayout,\n paint: mergedPaint,\n },\n beforeId,\n );\n\n return () => {\n try {\n if (map.getLayer(layerId)) map.removeLayer(layerId);\n if (map.getLayer(hitLayerId)) map.removeLayer(hitLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layers created once per map; data + paint are synced by the effects below\n }, [isLoaded, map]);\n\n // Sync features when data / curvature / samples change.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource | undefined;\n source?.setData(geoJSON);\n }, [isLoaded, map, geoJSON, sourceId]);\n\n // Sync paint/layout when they change.\n useEffect(() => {\n if (!isLoaded || !map || !map.getLayer(layerId)) return;\n for (const [key, value] of Object.entries(mergedPaint)) {\n map.setPaintProperty(layerId, key as keyof MapArcLinePaint, value as never);\n }\n for (const [key, value] of Object.entries(mergedLayout)) {\n map.setLayoutProperty(layerId, key as keyof MapArcLineLayout, value as never);\n }\n if (map.getLayer(hitLayerId)) {\n map.setPaintProperty(hitLayerId, \"line-width\", hitWidth);\n }\n }, [isLoaded, map, layerId, hitLayerId, mergedPaint, mergedLayout, hitWidth]);\n\n // Interaction handlers (bound to the hit layer).\n useEffect(() => {\n if (!isLoaded || !map || !interactive) return;\n\n let hoveredId: string | number | null = null;\n\n const setHover = (next: string | number | null) => {\n if (next === hoveredId) return;\n const sourceExists = !!map.getSource(sourceId);\n if (hoveredId != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });\n }\n hoveredId = next;\n if (next != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: next }, { hover: true });\n }\n };\n\n const findArc = (featureId: string | number | undefined) =>\n featureId == null\n ? undefined\n : latestRef.current.data.find((arc) => String(arc.id) === String(featureId));\n\n const handleMouseMove = (e: MapLibreGL.MapLayerMouseEvent) => {\n const featureId = e.features?.[0]?.id as string | number | undefined;\n if (featureId == null || featureId === hoveredId) return;\n\n setHover(featureId);\n map.getCanvas().style.cursor = \"pointer\";\n\n const arc = findArc(featureId);\n if (arc) {\n latestRef.current.onHover?.({\n arc: arc as T,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n }\n };\n\n const handleMouseLeave = () => {\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n latestRef.current.onHover?.(null);\n };\n\n const handleClick = (e: MapLibreGL.MapLayerMouseEvent) => {\n const arc = findArc(e.features?.[0]?.id as string | number | undefined);\n if (!arc) return;\n latestRef.current.onClick?.({\n arc: arc as T,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n map.on(\"mousemove\", hitLayerId, handleMouseMove);\n map.on(\"mouseleave\", hitLayerId, handleMouseLeave);\n map.on(\"click\", hitLayerId, handleClick);\n\n return () => {\n map.off(\"mousemove\", hitLayerId, handleMouseMove);\n map.off(\"mouseleave\", hitLayerId, handleMouseLeave);\n map.off(\"click\", hitLayerId, handleClick);\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n };\n }, [isLoaded, map, hitLayerId, sourceId, interactive]);\n\n return null;\n}\n","/** Pure geometry for `<MapArc>` — kept engine-free so it can be unit-tested. */\n\n/**\n * Sample a quadratic Bézier between `from` and `to` in lng/lat space.\n * `curvature` is how far the arc bows away from a straight line (0 = straight;\n * negative bends to the opposite side). The destination longitude is unwrapped\n * relative to the origin so arcs cross the antimeridian via the shorter\n * great-circle direction — resulting longitudes may fall outside [-180, 180],\n * which MapLibre renders correctly on the globe projection.\n */\nexport function buildArcCoordinates(\n from: [number, number],\n to: [number, number],\n curvature: number,\n samples: number,\n): [number, number][] {\n const [x0, y0] = from;\n const [xTo, y2] = to;\n const rawDx = xTo - x0;\n const x2 = rawDx > 180 ? xTo - 360 : rawDx < -180 ? xTo + 360 : xTo;\n const dx = x2 - x0;\n const dy = y2 - y0;\n const distance = Math.hypot(dx, dy);\n\n if (distance === 0 || curvature === 0) return [from, [x2, y2]];\n\n const mx = (x0 + x2) / 2;\n const my = (y0 + y2) / 2;\n const nx = -dy / distance;\n const ny = dx / distance;\n const offset = distance * curvature;\n const cx = mx + nx * offset;\n const cy = my + ny * offset;\n\n const points: [number, number][] = [];\n const segments = Math.max(2, Math.floor(samples));\n for (let i = 0; i <= segments; i += 1) {\n const t = i / segments;\n const inv = 1 - t;\n const x = inv * inv * x0 + 2 * inv * t * cx + t * t * x2;\n const y = inv * inv * y0 + 2 * inv * t * cy + t * t * y2;\n points.push([x, y]);\n }\n return points;\n}\n","/**\n * Merge hover paint into base paint as MapLibre `case` expressions keyed on\n * the per-feature `hover` feature-state, so only the hovered feature changes\n * appearance.\n */\nexport function mergeHoverPaint<T extends Record<string, unknown>>(\n paint: T,\n hoverPaint: T | undefined,\n): T {\n if (!hoverPaint) return paint;\n const merged: Record<string, unknown> = { ...paint };\n for (const [key, hoverValue] of Object.entries(hoverPaint)) {\n if (hoverValue === undefined) continue;\n const baseValue = merged[key];\n merged[key] =\n baseValue === undefined\n ? hoverValue\n : [\"case\", [\"boolean\", [\"feature-state\", \"hover\"], false], hoverValue, baseValue];\n }\n return merged as T;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { mergeHoverPaint } from \"../lib/merge-hover-paint\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport type MapGeoJSONData<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n | GeoJSON.FeatureCollection<GeoJSON.Geometry, P>\n | GeoJSON.Feature<GeoJSON.Geometry, P>\n | GeoJSON.Geometry\n | string;\n\nexport type MapFillPaint = NonNullable<MapLibreGL.FillLayerSpecification[\"paint\"]>;\nexport type MapLinePaint = NonNullable<MapLibreGL.LineLayerSpecification[\"paint\"]>;\n\n/** A rendered feature with strongly-typed `properties`. */\nexport type MapGeoJSONFeature<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n Omit<MapLibreGL.MapGeoJSONFeature, \"properties\"> & { properties: P };\n\n/** Event payload passed to MapGeoJSON interaction callbacks. */\nexport type MapGeoJSONEvent<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {\n /** The feature under the cursor, with its typed GeoJSON properties. */\n feature: MapGeoJSONFeature<P>;\n /** Longitude of the cursor at the time of the event. */\n longitude: number;\n /** Latitude of the cursor at the time of the event. */\n latitude: number;\n /** The underlying MapLibre mouse event for advanced use cases. */\n originalEvent: MapLibreGL.MapLayerMouseEvent;\n};\n\nexport type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {\n /** GeoJSON data (FeatureCollection, Feature, Geometry) or a URL to fetch it from. */\n data: MapGeoJSONData<P>;\n /** Optional unique identifier prefix for the source/layers. Auto-generated if not provided. */\n id?: string;\n /**\n * Feature property to promote to the feature `id`. Required for hover\n * feature-state (`fillHoverPaint`) and stable `onHover`/`onClick` payloads.\n */\n promoteId?: string;\n /**\n * Paint for the polygon fill layer. Merged on top of a theme-aware neutral\n * default (the `--border` token — a mid neutral that reads on the page\n * surface in every theme). Pass `false` to omit the fill layer entirely\n * (e.g. outlines only).\n */\n fillPaint?: MapFillPaint | false;\n /**\n * Paint for the outline layer. Merged on top of a hairline default\n * (`line-color` = the `--background` token, `line-width` = 0.5) for thin\n * separators. Override `line-color` if your container differs, or pass\n * `false` to omit the layer.\n */\n linePaint?: MapLinePaint | false;\n /**\n * Paint merged onto the fill layer for the feature under the cursor, applied\n * as a `case` expression keyed on hover feature-state. Requires `promoteId`.\n */\n fillHoverPaint?: MapFillPaint;\n /** Callback when a feature is clicked. */\n onClick?: (e: MapGeoJSONEvent<P>) => void;\n /** Callback fired when the hovered feature changes; `null` when the cursor leaves. */\n onHover?: (e: MapGeoJSONEvent<P> | null) => void;\n /** Whether features respond to mouse events (default: false). */\n interactive?: boolean;\n /** Optional MapLibre layer id to insert the layers before (z-order control). */\n beforeId?: string;\n};\n\n/**\n * Renders arbitrary GeoJSON as fill + outline layers on the map. Composes like\n * `MapRoute` / `MapArc` — drop it inside `<MapCanvas>` (typically with `blank`)\n * for choropleths and region/data maps. For full control over expressions and\n * multiple layers, manage layers directly via `useMap()` instead.\n */\nexport function MapGeoJSON<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({\n data,\n id: propId,\n promoteId,\n fillPaint,\n linePaint,\n fillHoverPaint,\n onClick,\n onHover,\n interactive = false,\n beforeId,\n}: MapGeoJSONProps<P>) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `geojson-source-${id}`;\n const fillLayerId = `geojson-fill-${id}`;\n const lineLayerId = `geojson-line-${id}`;\n\n // Theme-driven neutral defaults: landmass = the mid-neutral `--border` rung,\n // separators = the page surface. Both re-resolve on theme change.\n const defaultFill = useTokenColor(\"--border\");\n const defaultLine = useTokenColor(\"--background\");\n\n const showFill = fillPaint !== false;\n const showLine = linePaint !== false;\n\n const mergedFillPaint = useMemo(\n () => mergeHoverPaint({ \"fill-color\": defaultFill, ...(fillPaint || {}) }, fillHoverPaint),\n [defaultFill, fillPaint, fillHoverPaint],\n );\n const mergedLinePaint = useMemo(\n () => ({\n \"line-color\": defaultLine,\n \"line-width\": 0.5,\n ...(linePaint || {}),\n }),\n [defaultLine, linePaint],\n );\n const latestRef = useRef({ onClick, onHover });\n latestRef.current = { onClick, onHover };\n\n // Add source on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data,\n ...(promoteId ? { promoteId } : {}),\n });\n\n return () => {\n try {\n if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);\n if (map.getLayer(fillLayerId)) map.removeLayer(fillLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source created once per map; data/layers are synced by the effects below\n }, [isLoaded, map]);\n\n // Sync data when it changes.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource | undefined;\n source?.setData(data as never);\n }, [isLoaded, map, data, sourceId]);\n\n // Sync layers and paint when visibility or styling changes.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n const source = map.getSource(sourceId);\n if (!source) return;\n\n if (showFill && !map.getLayer(fillLayerId)) {\n map.addLayer(\n {\n id: fillLayerId,\n type: \"fill\",\n source: sourceId,\n paint: mergedFillPaint,\n },\n beforeId,\n );\n } else if (!showFill && map.getLayer(fillLayerId)) {\n map.removeLayer(fillLayerId);\n }\n\n if (showLine && !map.getLayer(lineLayerId)) {\n map.addLayer(\n {\n id: lineLayerId,\n type: \"line\",\n source: sourceId,\n paint: mergedLinePaint,\n },\n beforeId,\n );\n } else if (!showLine && map.getLayer(lineLayerId)) {\n map.removeLayer(lineLayerId);\n }\n\n if (showFill && map.getLayer(fillLayerId)) {\n for (const [key, value] of Object.entries(mergedFillPaint)) {\n map.setPaintProperty(fillLayerId, key as keyof MapFillPaint, value as never);\n }\n }\n if (showLine && map.getLayer(lineLayerId)) {\n for (const [key, value] of Object.entries(mergedLinePaint)) {\n map.setPaintProperty(lineLayerId, key as keyof MapLinePaint, value as never);\n }\n }\n }, [\n isLoaded,\n map,\n sourceId,\n fillLayerId,\n lineLayerId,\n showFill,\n showLine,\n mergedFillPaint,\n mergedLinePaint,\n beforeId,\n ]);\n\n // Interaction handlers (bound to the fill layer).\n useEffect(() => {\n if (!isLoaded || !map || !interactive || !showFill) return;\n\n let hoveredId: string | number | null = null;\n\n const setHover = (next: string | number | null) => {\n if (next === hoveredId) return;\n const sourceExists = !!map.getSource(sourceId);\n if (hoveredId != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });\n }\n hoveredId = next;\n if (next != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: next }, { hover: true });\n }\n };\n\n const handleMouseMove = (e: MapLibreGL.MapLayerMouseEvent) => {\n const feature = e.features?.[0];\n if (!feature) return;\n map.getCanvas().style.cursor = \"pointer\";\n\n const featureId = feature.id;\n if (featureId === hoveredId) return;\n setHover(featureId ?? null);\n latestRef.current.onHover?.({\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n const handleMouseLeave = () => {\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n latestRef.current.onHover?.(null);\n };\n\n const handleClick = (e: MapLibreGL.MapLayerMouseEvent) => {\n const feature = e.features?.[0];\n if (!feature) return;\n latestRef.current.onClick?.({\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n map.on(\"mousemove\", fillLayerId, handleMouseMove);\n map.on(\"mouseleave\", fillLayerId, handleMouseLeave);\n map.on(\"click\", fillLayerId, handleClick);\n\n return () => {\n map.off(\"mousemove\", fillLayerId, handleMouseMove);\n map.off(\"mouseleave\", fillLayerId, handleMouseLeave);\n map.off(\"click\", fillLayerId, handleClick);\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n };\n }, [isLoaded, map, fillLayerId, sourceId, interactive, showFill]);\n\n return null;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport type MapClusterLayerProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n {\n /** GeoJSON FeatureCollection data or a URL to fetch GeoJSON from. */\n data: string | GeoJSON.FeatureCollection<GeoJSON.Point, P>;\n /** Maximum zoom level to cluster points on (default: 14). */\n clusterMaxZoom?: number;\n /** Radius of each cluster when clustering points, in pixels (default: 50). */\n clusterRadius?: number;\n /**\n * Colors for cluster circles: [small, medium, large] based on point count.\n * Defaults to the theme's `--success`/`--warning`/`--destructive` tokens.\n */\n clusterColors?: [string, string, string];\n /** Point-count thresholds for the color/size steps: [medium, large] (default: [100, 750]). */\n clusterThresholds?: [number, number];\n /** Color for unclustered individual points. Defaults to the theme's `--primary` token. */\n pointColor?: string;\n /** Callback when an unclustered point is clicked. */\n onPointClick?: (\n feature: GeoJSON.Feature<GeoJSON.Point, P>,\n coordinates: [number, number],\n ) => void;\n /** Callback when a cluster is clicked. If not provided, zooms into the cluster. */\n onClusterClick?: (clusterId: number, coordinates: [number, number], pointCount: number) => void;\n };\n\nconst DEFAULT_CLUSTER_THRESHOLDS: [number, number] = [100, 750];\n\n/**\n * Clustered point rendering for large point datasets. Cluster circles step\n * through the status tokens (success → warning → destructive) as the point\n * count grows; strokes and count labels use the page surface for contrast.\n */\nexport function MapClusterLayer<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({\n data,\n clusterMaxZoom = 14,\n clusterRadius = 50,\n clusterColors,\n clusterThresholds = DEFAULT_CLUSTER_THRESHOLDS,\n pointColor,\n onPointClick,\n onClusterClick,\n}: MapClusterLayerProps<P>) {\n const { map, isLoaded } = useMap();\n const id = useId();\n const sourceId = `cluster-source-${id}`;\n const clusterLayerId = `clusters-${id}`;\n const clusterCountLayerId = `cluster-count-${id}`;\n const unclusteredLayerId = `unclustered-point-${id}`;\n\n const success = useTokenColor(\"--success\");\n const warning = useTokenColor(\"--warning\");\n const destructive = useTokenColor(\"--destructive\");\n const primary = useTokenColor(\"--primary\");\n const surface = useTokenColor(\"--background\");\n\n const resolvedClusterColors = useMemo<[string, string, string]>(\n () => clusterColors ?? [success, warning, destructive],\n [clusterColors, success, warning, destructive],\n );\n const resolvedPointColor = pointColor ?? primary;\n\n // Add source and layers on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data,\n cluster: true,\n clusterMaxZoom,\n clusterRadius,\n });\n\n map.addLayer({\n id: clusterLayerId,\n type: \"circle\",\n source: sourceId,\n filter: [\"has\", \"point_count\"],\n paint: {\n \"circle-color\": [\n \"step\",\n [\"get\", \"point_count\"],\n resolvedClusterColors[0],\n clusterThresholds[0],\n resolvedClusterColors[1],\n clusterThresholds[1],\n resolvedClusterColors[2],\n ],\n \"circle-radius\": [\n \"step\",\n [\"get\", \"point_count\"],\n 20,\n clusterThresholds[0],\n 30,\n clusterThresholds[1],\n 40,\n ],\n \"circle-stroke-width\": 1,\n \"circle-stroke-color\": surface,\n \"circle-opacity\": 0.85,\n },\n });\n\n map.addLayer({\n id: clusterCountLayerId,\n type: \"symbol\",\n source: sourceId,\n filter: [\"has\", \"point_count\"],\n layout: {\n \"text-field\": \"{point_count_abbreviated}\",\n \"text-font\": [\"Open Sans\"],\n \"text-size\": 12,\n },\n paint: {\n \"text-color\": surface,\n },\n });\n\n map.addLayer({\n id: unclusteredLayerId,\n type: \"circle\",\n source: sourceId,\n filter: [\"!\", [\"has\", \"point_count\"]],\n paint: {\n \"circle-color\": resolvedPointColor,\n \"circle-radius\": 5,\n \"circle-stroke-width\": 2,\n \"circle-stroke-color\": surface,\n },\n });\n\n return () => {\n try {\n if (map.getLayer(clusterCountLayerId)) map.removeLayer(clusterCountLayerId);\n if (map.getLayer(unclusteredLayerId)) map.removeLayer(unclusteredLayerId);\n if (map.getLayer(clusterLayerId)) map.removeLayer(clusterLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layers created once per map; data + paint are synced by the effects below\n }, [isLoaded, map, sourceId]);\n\n // Update source data when the data prop changes (only for non-URL data).\n useEffect(() => {\n if (!isLoaded || !map || typeof data === \"string\") return;\n\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n if (source) {\n source.setData(data);\n }\n }, [isLoaded, map, data, sourceId]);\n\n // Sync layer styles when props (or the resolved theme colors) change.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n if (map.getLayer(clusterLayerId)) {\n map.setPaintProperty(clusterLayerId, \"circle-color\", [\n \"step\",\n [\"get\", \"point_count\"],\n resolvedClusterColors[0],\n clusterThresholds[0],\n resolvedClusterColors[1],\n clusterThresholds[1],\n resolvedClusterColors[2],\n ]);\n map.setPaintProperty(clusterLayerId, \"circle-radius\", [\n \"step\",\n [\"get\", \"point_count\"],\n 20,\n clusterThresholds[0],\n 30,\n clusterThresholds[1],\n 40,\n ]);\n map.setPaintProperty(clusterLayerId, \"circle-stroke-color\", surface);\n }\n\n if (map.getLayer(clusterCountLayerId)) {\n map.setPaintProperty(clusterCountLayerId, \"text-color\", surface);\n }\n\n if (map.getLayer(unclusteredLayerId)) {\n map.setPaintProperty(unclusteredLayerId, \"circle-color\", resolvedPointColor);\n map.setPaintProperty(unclusteredLayerId, \"circle-stroke-color\", surface);\n }\n }, [\n isLoaded,\n map,\n clusterLayerId,\n clusterCountLayerId,\n unclusteredLayerId,\n resolvedClusterColors,\n clusterThresholds,\n resolvedPointColor,\n surface,\n ]);\n\n // Handle click events.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n // Cluster click handler — zoom into the cluster by default.\n const handleClusterClick = async (e: MapLibreGL.MapMouseEvent) => {\n const features = map.queryRenderedFeatures(e.point, {\n layers: [clusterLayerId],\n });\n const feature = features[0];\n if (!feature) return;\n const clusterId = feature.properties?.cluster_id as number;\n const pointCount = feature.properties?.point_count as number;\n const coordinates = (feature.geometry as GeoJSON.Point).coordinates as [number, number];\n\n if (onClusterClick) {\n onClusterClick(clusterId, coordinates, pointCount);\n } else {\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n const zoom = await source.getClusterExpansionZoom(clusterId);\n map.easeTo({\n center: coordinates,\n zoom,\n });\n }\n };\n\n // Unclustered point click handler.\n const handlePointClick = (\n e: MapLibreGL.MapMouseEvent & {\n features?: MapLibreGL.MapGeoJSONFeature[];\n },\n ) => {\n const feature = e.features?.[0];\n if (!onPointClick || !feature) return;\n const coordinates = (feature.geometry as GeoJSON.Point).coordinates.slice() as [\n number,\n number,\n ];\n\n // Handle world copies.\n while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {\n coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;\n }\n\n onPointClick(feature as unknown as GeoJSON.Feature<GeoJSON.Point, P>, coordinates);\n };\n\n // Cursor style handlers.\n const handleMouseEnterCluster = () => {\n map.getCanvas().style.cursor = \"pointer\";\n };\n const handleMouseLeaveCluster = () => {\n map.getCanvas().style.cursor = \"\";\n };\n const handleMouseEnterPoint = () => {\n if (onPointClick) {\n map.getCanvas().style.cursor = \"pointer\";\n }\n };\n const handleMouseLeavePoint = () => {\n map.getCanvas().style.cursor = \"\";\n };\n\n map.on(\"click\", clusterLayerId, handleClusterClick);\n map.on(\"click\", unclusteredLayerId, handlePointClick);\n map.on(\"mouseenter\", clusterLayerId, handleMouseEnterCluster);\n map.on(\"mouseleave\", clusterLayerId, handleMouseLeaveCluster);\n map.on(\"mouseenter\", unclusteredLayerId, handleMouseEnterPoint);\n map.on(\"mouseleave\", unclusteredLayerId, handleMouseLeavePoint);\n\n return () => {\n map.off(\"click\", clusterLayerId, handleClusterClick);\n map.off(\"click\", unclusteredLayerId, handlePointClick);\n map.off(\"mouseenter\", clusterLayerId, handleMouseEnterCluster);\n map.off(\"mouseleave\", clusterLayerId, handleMouseLeaveCluster);\n map.off(\"mouseenter\", unclusteredLayerId, handleMouseEnterPoint);\n map.off(\"mouseleave\", unclusteredLayerId, handleMouseLeavePoint);\n };\n }, [isLoaded, map, clusterLayerId, unclusteredLayerId, sourceId, onClusterClick, onPointClick]);\n\n return null;\n}\n"],"mappings":"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","names":["useEffect","useState","MapCanvas","useState","useEffect","MapLibreGL","createContext","use","useEffect","useMemo","useRef","cn","jsx","jsxs","createContext","use","useRef","useMemo","MapLibreGL","useEffect","cn","positionClasses","MapLibreGL","useEffect","useMemo","useRef","createPortal","X","cn","jsx","jsxs","useRef","useMemo","MapLibreGL","useEffect","createPortal","cn","X","useCallback","useEffect","useRef","useState","Spinner","cn","jsx","jsxs","cn","useState","useCallback","Spinner","useRef","useEffect","useEffect","useState","useState","useEffect","useEffect","useId","useMemo","useRef","useId","useMemo","useRef","useEffect","useEffect","useId","useMemo","useRef","useId","useMemo","useRef","useEffect","useEffect","useId","useMemo","useId","useMemo","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/map-canvas/map-canvas.tsx","../src/map-canvas/map-context.ts","../src/map-canvas/use-resolved-basemap-theme.ts","../src/map-marker/map-marker.tsx","../src/map-popup/map-popup.tsx","../src/map-controls/map-controls.tsx","../src/map-route/map-route.tsx","../src/lib/use-token-color.ts","../src/map-arc/map-arc.tsx","../src/lib/arc-math.ts","../src/lib/merge-hover-paint.ts","../src/map-geojson/map-geojson.tsx","../src/map-cluster-layer/map-cluster-layer.tsx"],"sourcesContent":["\"use client\";\n\nimport MapLibreGL from \"maplibre-gl\";\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport \"./maps.css\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { Spinner, StatePanel, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { MapContext, type BasemapTheme } from \"./map-context\";\nimport { useResolvedBasemapTheme } from \"./use-resolved-basemap-theme\";\n\n/**\n * Default basemaps: Carto's free light/dark GL styles. These serve ODbL-licensed\n * OpenStreetMap data, which requires attribution on a public surface — see the\n * `attributionControl` note in the map constructor below.\n */\nconst defaultStyles = {\n dark: \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n light: \"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json\",\n};\n\n// A tile-less, dependency-free style with a transparent background. Use it for\n// data visualizations (choropleths, arcs, dot maps) where you draw your own\n// layers and don't need a street basemap: `<MapCanvas blank>`. The transparent\n// background lets the themed container show through.\nconst blankMapStyle: MapLibreGL.StyleSpecification = {\n version: 8,\n sources: {},\n layers: [\n {\n id: \"background\",\n type: \"background\",\n paint: { \"background-color\": \"transparent\" },\n },\n ],\n};\n\n/** Map viewport state. */\nexport interface MapViewport {\n /** Center coordinates [longitude, latitude]. */\n center: [number, number];\n /** Zoom level. */\n zoom: number;\n /** Bearing (rotation) in degrees. */\n bearing: number;\n /** Pitch (tilt) in degrees. */\n pitch: number;\n}\n\nexport type MapStyleOption = string | MapLibreGL.StyleSpecification;\n\n/** The imperative handle exposed by `<MapCanvas ref>`: the MapLibre map itself. */\nexport type MapCanvasRef = MapLibreGL.Map;\n\nexport type MapCanvasProps = {\n children?: ReactNode;\n /** Additional CSS classes for the map container. */\n className?: string;\n /**\n * Basemap flavor. If not provided, it is derived from the active brand theme\n * (`data-theme` + that theme's own `color-scheme`), then a `dark`/`light` root class,\n * then the OS preference.\n */\n theme?: BasemapTheme;\n /** Custom map styles for light and dark themes. Overrides the default Carto styles. */\n styles?: {\n light?: MapStyleOption;\n dark?: MapStyleOption;\n };\n /**\n * Use a transparent, tile-less basemap instead of the default Carto street\n * basemap — a blank canvas. Used alone it renders nothing; add your own\n * layers on top (`<MapGeoJSON>`, `<MapArc>`, markers, …). Ideal for data\n * visualizations. Ignored when an explicit `styles` prop is provided.\n */\n blank?: boolean;\n /** Map projection type. Use `{ type: \"globe\" }` for a 3D globe view. */\n projection?: MapLibreGL.ProjectionSpecification;\n /**\n * Controlled viewport. When provided together with `onViewportChange`, the\n * map becomes controlled and the viewport is driven by this prop.\n */\n viewport?: Partial<MapViewport>;\n /**\n * Callback fired continuously as the viewport changes (pan, zoom, rotate,\n * pitch). Use standalone to observe changes, or with `viewport` for\n * controlled mode.\n */\n onViewportChange?: (viewport: MapViewport) => void;\n /** Show a loading overlay on the map (e.g. while the app fetches map data). */\n loading?: boolean;\n} & Omit<MapLibreGL.MapOptions, \"container\" | \"style\">;\n\nfunction MapLoadingOverlay() {\n return (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-xs\">\n <Spinner label=\"Loading map\" className=\"size-5\" />\n </div>\n );\n}\n\nfunction getViewport(map: MapLibreGL.Map): MapViewport {\n const center = map.getCenter();\n return {\n center: [center.lng, center.lat],\n zoom: map.getZoom(),\n bearing: map.getBearing(),\n pitch: map.getPitch(),\n };\n}\n\n/**\n * The root map surface — a token/theme-aware MapLibre GL canvas. Compose the\n * other `@elabs-ai/components-maps` components (markers, popups, controls, layers) as\n * children; they reach the map through context (`useMap`).\n *\n * The ref exposes the raw MapLibre `Map` instance for imperative work\n * (`flyTo`, `fitBounds`, …).\n */\nexport const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCanvas(\n {\n children,\n className,\n theme: themeProp,\n styles,\n blank = false,\n projection,\n viewport,\n onViewportChange,\n loading = false,\n ...props\n },\n ref,\n) {\n const { t } = useLocale();\n const containerRef = useRef<HTMLDivElement>(null);\n const [mapInstance, setMapInstance] = useState<MapLibreGL.Map | null>(null);\n const [initFailed, setInitFailed] = useState(false);\n const [isLoaded, setIsLoaded] = useState(false);\n const [isStyleLoaded, setIsStyleLoaded] = useState(false);\n const currentStyleRef = useRef<MapStyleOption | null>(null);\n const styleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const internalUpdateRef = useRef(false);\n const { resolvedTheme, themeKey } = useResolvedBasemapTheme(themeProp);\n\n const isControlled = viewport !== undefined && onViewportChange !== undefined;\n\n const onViewportChangeRef = useRef(onViewportChange);\n onViewportChangeRef.current = onViewportChange;\n\n // Read from the mount-only `styledata` handler below so a `projection` prop\n // change after mount isn't reapplied with the value captured at mount time.\n const projectionRef = useRef(projection);\n projectionRef.current = projection;\n\n const mapStyles = useMemo(() => {\n // Explicit styles win. Otherwise `blank` opts into the transparent\n // tile-less basemap; with neither, fall back to the Carto defaults.\n if (styles) {\n return {\n dark: styles.dark ?? defaultStyles.dark,\n light: styles.light ?? defaultStyles.light,\n };\n }\n if (blank) {\n return { dark: blankMapStyle, light: blankMapStyle };\n }\n return defaultStyles;\n }, [styles, blank]);\n\n // Expose the map instance to the parent component.\n useImperativeHandle(ref, () => mapInstance as MapLibreGL.Map, [mapInstance]);\n\n const clearStyleTimeout = useCallback(() => {\n if (styleTimeoutRef.current) {\n clearTimeout(styleTimeoutRef.current);\n styleTimeoutRef.current = null;\n }\n }, []);\n\n // Initialize the map.\n useEffect(() => {\n if (!containerRef.current) return;\n\n const initialStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n currentStyleRef.current = initialStyle;\n\n let map: MapLibreGL.Map;\n try {\n map = new MapLibreGL.Map({\n container: containerRef.current,\n style: initialStyle,\n renderWorldCopies: false,\n // Attribution control OFF by default — a maintainer decision for internal\n // use, taken deliberately and recorded in CHANGELOG.md + the map-components\n // rule. NOTE THE CONSTRAINT: the default Carto basemap serves OpenStreetMap\n // data, which is ODbL-licensed and requires the credit, and Carto's terms\n // require it too — so a surface that ships PUBLICLY on these tiles must turn\n // it back on with `attributionControl={{ compact: true }}` (it wins through\n // `...props` below), or move to tiles licensed without the requirement via\n // `styles` / `blank`.\n attributionControl: false,\n ...props,\n ...viewport,\n });\n } catch {\n // MapLibre throws at construction when WebGL is unavailable (headless\n // browsers without GPU, remote desktops). Degrade to a quiet panel\n // instead of an unhandled render error.\n setInitFailed(true);\n return;\n }\n\n const styleDataHandler = () => {\n clearStyleTimeout();\n // Delay so the style is fully processed before layer operations — avoids\n // race conditions on setStyle without force-updating every layer.\n styleTimeoutRef.current = setTimeout(() => {\n setIsStyleLoaded(true);\n if (projectionRef.current) {\n map.setProjection(projectionRef.current);\n }\n }, 100);\n };\n // No-op under the default (`attributionControl: false` above). This exists for\n // the case a consumer turns the control back ON for a public surface: MapLibre\n // paints even a COMPACT control expanded on first render (`<details open>` +\n // `maplibregl-compact-show`), so the credits land as a text slab until someone\n // clicks the toggle. Collapse it to the labelled ⓘ button instead.\n const loadHandler = () => {\n setIsLoaded(true);\n map\n .getContainer()\n .querySelectorAll<HTMLDetailsElement>(\"details.maplibregl-ctrl-attrib[open]\")\n .forEach((el) => {\n el.open = false;\n });\n };\n\n // Viewport change handler — skip if triggered by an internal update.\n const handleMove = () => {\n if (internalUpdateRef.current) return;\n onViewportChangeRef.current?.(getViewport(map));\n };\n\n map.on(\"load\", loadHandler);\n map.on(\"styledata\", styleDataHandler);\n map.on(\"move\", handleMove);\n setMapInstance(map);\n\n return () => {\n clearStyleTimeout();\n map.off(\"load\", loadHandler);\n map.off(\"styledata\", styleDataHandler);\n map.off(\"move\", handleMove);\n map.remove();\n setIsLoaded(false);\n setIsStyleLoaded(false);\n setMapInstance(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only: map options are init-time; style/theme/projection changes are synced by the effects below\n }, []);\n\n // Sync controlled viewport to the map.\n useEffect(() => {\n if (!mapInstance || !isControlled || !viewport) return;\n if (mapInstance.isMoving()) return;\n\n const current = getViewport(mapInstance);\n const next = {\n center: viewport.center ?? current.center,\n zoom: viewport.zoom ?? current.zoom,\n bearing: viewport.bearing ?? current.bearing,\n pitch: viewport.pitch ?? current.pitch,\n };\n\n if (\n next.center[0] === current.center[0] &&\n next.center[1] === current.center[1] &&\n next.zoom === current.zoom &&\n next.bearing === current.bearing &&\n next.pitch === current.pitch\n ) {\n return;\n }\n\n internalUpdateRef.current = true;\n mapInstance.jumpTo(next);\n internalUpdateRef.current = false;\n }, [mapInstance, isControlled, viewport]);\n\n // Swap the basemap style when the theme flips.\n useEffect(() => {\n if (!mapInstance || !resolvedTheme) return;\n\n const newStyle = resolvedTheme === \"dark\" ? mapStyles.dark : mapStyles.light;\n\n if (currentStyleRef.current === newStyle) return;\n\n clearStyleTimeout();\n currentStyleRef.current = newStyle;\n setIsStyleLoaded(false);\n\n mapInstance.setStyle(newStyle, { diff: true });\n }, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]);\n\n // Sync projection when the prop changes after mount.\n useEffect(() => {\n if (!mapInstance || !isStyleLoaded || !projection) return;\n mapInstance.setProjection(projection);\n }, [mapInstance, isStyleLoaded, projection]);\n\n const contextValue = useMemo(\n () => ({\n map: mapInstance,\n isLoaded: isLoaded && isStyleLoaded,\n resolvedTheme,\n themeKey,\n }),\n [mapInstance, isLoaded, isStyleLoaded, resolvedTheme, themeKey],\n );\n\n if (initFailed) {\n return (\n <div className={cn(\"relative h-full w-full\", className)}>\n <StatePanel\n kind=\"error\"\n title={t(\"maps.canvas.unavailableTitle\")}\n description={t(\"maps.canvas.unavailableDescription\")}\n className=\"h-full\"\n />\n </div>\n );\n }\n\n return (\n <MapContext.Provider value={contextValue}>\n <div ref={containerRef} className={cn(\"relative h-full w-full\", className)}>\n {(!isLoaded || loading) && <MapLoadingOverlay />}\n {/* SSR-safe: children render only when the map exists on the client. */}\n {mapInstance && children}\n </div>\n </MapContext.Provider>\n );\n});\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { createContext, use } from \"react\";\n\n/** Light-or-dark flavor of the active basemap (derived from the brand theme). */\nexport type BasemapTheme = \"light\" | \"dark\";\n\nexport interface MapContextValue {\n /** The live MapLibre map instance (`null` until the map has mounted). */\n map: MapLibreGL.Map | null;\n /** True once the map AND its style are fully loaded — gate layer operations on this. */\n isLoaded: boolean;\n /** Which basemap flavor is active. */\n resolvedTheme: BasemapTheme;\n /**\n * Changes whenever the active brand theme changes. Layer components use it\n * as a dependency key to re-resolve semantic token colors for WebGL paint.\n */\n themeKey: string;\n}\n\nexport const MapContext = createContext<MapContextValue | null>(null);\n\n/** Access the map instance + load state from any descendant of `<MapCanvas>`. */\nexport function useMap(): MapContextValue {\n const context = use(MapContext);\n if (!context) {\n throw new Error(\"useMap must be used within a <MapCanvas>\");\n }\n return context;\n}\n","\"use client\";\n\nimport { resolveThemeIsDark } from \"@elabs-ai/components-tokens\";\nimport { useEffect, useState } from \"react\";\n\nimport type { BasemapTheme } from \"./map-context\";\n\n/**\n * Brand theme → basemap flavor. When a `data-theme` is set (the ThemeProvider\n * contract) the theme's own `color-scheme` is authoritative via\n * `resolveThemeIsDark` — which is why a CONSUMER-AUTHORED dark theme gets the\n * dark basemap without registering anything here (ADR 0029; the old\n * `THEME_META[theme].dark` lookup only knew the themes this repo ships). A\n * `dark`/`light` class on `<html>` is honored for non-brand hosts (next-themes\n * et al.); the OS preference is only the last-resort fallback.\n */\nfunction getBrandTheme(): BasemapTheme | null {\n if (typeof document === \"undefined\") return null;\n const root = document.documentElement;\n if (root.getAttribute(\"data-theme\")) return resolveThemeIsDark(root) ? \"dark\" : \"light\";\n if (root.classList.contains(\"dark\")) return \"dark\";\n if (root.classList.contains(\"light\")) return \"light\";\n return null;\n}\n\nfunction getSystemTheme(): BasemapTheme {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\nfunction getThemeAttribute(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.documentElement.getAttribute(\"data-theme\") ?? \"\";\n}\n\n/**\n * Resolve the basemap flavor and a change-key for the active brand theme.\n * The observer stays active even with an explicit `theme` prop: token colors\n * (routes, clusters, …) must still re-resolve when `data-theme` changes.\n */\nexport function useResolvedBasemapTheme(themeProp?: BasemapTheme): {\n resolvedTheme: BasemapTheme;\n themeKey: string;\n} {\n const [detected, setDetected] = useState<BasemapTheme>(() => getBrandTheme() ?? getSystemTheme());\n const [themeAttr, setThemeAttr] = useState(getThemeAttribute);\n\n useEffect(() => {\n const update = () => {\n setThemeAttr(getThemeAttribute());\n const brand = getBrandTheme();\n if (brand) setDetected(brand);\n };\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\"],\n });\n\n const mediaQuery =\n typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(prefers-color-scheme: dark)\")\n : null;\n const handleSystemChange = (e: MediaQueryListEvent) => {\n if (!getBrandTheme()) setDetected(e.matches ? \"dark\" : \"light\");\n };\n mediaQuery?.addEventListener(\"change\", handleSystemChange);\n\n return () => {\n observer.disconnect();\n mediaQuery?.removeEventListener(\"change\", handleSystemChange);\n };\n }, []);\n\n const resolvedTheme = themeProp ?? detected;\n return { resolvedTheme, themeKey: `${themeAttr}:${resolvedTheme}` };\n}\n","\"use client\";\n\nimport MapLibreGL, { type MarkerOptions, type PopupOptions } from \"maplibre-gl\";\nimport { createContext, use, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\ntype MarkerContextValue = {\n marker: MapLibreGL.Marker;\n map: MapLibreGL.Map | null;\n};\n\nconst MarkerContext = createContext<MarkerContextValue | null>(null);\n\nfunction useMarkerContext() {\n const context = use(MarkerContext);\n if (!context) {\n throw new Error(\"MapMarker sub-components must be used within <MapMarker>\");\n }\n return context;\n}\n\nexport type MapMarkerProps = {\n /** Longitude coordinate for the marker position. */\n longitude: number;\n /** Latitude coordinate for the marker position. */\n latitude: number;\n /** Marker sub-components (MapMarkerContent, MapMarkerPopup, MapMarkerTooltip, MapMarkerLabel). */\n children: ReactNode;\n /** Callback when the marker is clicked. */\n onClick?: (e: MouseEvent) => void;\n /** Callback when the mouse enters the marker. */\n onMouseEnter?: (e: MouseEvent) => void;\n /** Callback when the mouse leaves the marker. */\n onMouseLeave?: (e: MouseEvent) => void;\n /** Callback when a drag starts (requires `draggable`). */\n onDragStart?: (lngLat: { lng: number; lat: number }) => void;\n /** Callback during a drag (requires `draggable`). */\n onDrag?: (lngLat: { lng: number; lat: number }) => void;\n /** Callback when a drag ends (requires `draggable`). */\n onDragEnd?: (lngLat: { lng: number; lat: number }) => void;\n} & Omit<MarkerOptions, \"element\">;\n\n/**\n * A marker anchored at a lng/lat. Compose the pieces you need:\n * `MapMarkerContent` (the visual), `MapMarkerLabel`, `MapMarkerPopup` (opens\n * on click) and `MapMarkerTooltip` (shows on hover).\n */\nexport function MapMarker({\n longitude,\n latitude,\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n draggable = false,\n ...markerOptions\n}: MapMarkerProps) {\n const { map } = useMap();\n const [marker, setMarker] = useState<MapLibreGL.Marker | null>(null);\n\n const callbacksRef = useRef({\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n });\n callbacksRef.current = {\n onClick,\n onMouseEnter,\n onMouseLeave,\n onDragStart,\n onDrag,\n onDragEnd,\n };\n\n const { anchor, className, offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;\n\n // `longitude`/`latitude`/`draggable` at construction time only — the sync\n // effect below corrects them on the very next commit, and are read from a\n // ref here so they don't force a rebuild on every position change (see the\n // `anchor`/`className` note below for what SHOULD force one).\n const initialRef = useRef({ longitude, latitude, draggable });\n initialRef.current = { longitude, latitude, draggable };\n\n // Builds (and tears down) the actual MapLibre `Marker` instance. This is a\n // real `useEffect`, never `useMemo`: `useMemo`'s initializer runs during\n // RENDER — including on the server, where `document` doesn't exist — and\n // React offers no guarantee it runs only once (it may discard and recompute\n // a memoized value), which would silently leak marker instances/listeners.\n //\n // `anchor` and `className` have no MapLibre setter (`Marker` bakes both into\n // its constructor), so they are the only options that must REBUILD the\n // instance; every other option (position, draggable, offset, rotation,\n // alignment) has a live setter and is kept in sync by the effect further\n // below without ever recreating the marker.\n useEffect(() => {\n const markerInstance = new MapLibreGL.Marker({\n anchor,\n className,\n element: document.createElement(\"div\"),\n draggable: initialRef.current.draggable,\n }).setLngLat([initialRef.current.longitude, initialRef.current.latitude]);\n\n const handleClick = (e: MouseEvent) => callbacksRef.current.onClick?.(e);\n const handleMouseEnter = (e: MouseEvent) => callbacksRef.current.onMouseEnter?.(e);\n const handleMouseLeave = (e: MouseEvent) => callbacksRef.current.onMouseLeave?.(e);\n\n markerInstance.getElement()?.addEventListener(\"click\", handleClick);\n markerInstance.getElement()?.addEventListener(\"mouseenter\", handleMouseEnter);\n markerInstance.getElement()?.addEventListener(\"mouseleave\", handleMouseLeave);\n\n const handleDragStart = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDragStart?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n const handleDrag = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDrag?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n const handleDragEnd = () => {\n const lngLat = markerInstance.getLngLat();\n callbacksRef.current.onDragEnd?.({ lng: lngLat.lng, lat: lngLat.lat });\n };\n\n markerInstance.on(\"dragstart\", handleDragStart);\n markerInstance.on(\"drag\", handleDrag);\n markerInstance.on(\"dragend\", handleDragEnd);\n\n setMarker(markerInstance);\n }, [anchor, className]);\n\n // Attaches/detaches the CURRENT marker instance to the map — its own effect\n // so both a `map` change and an `anchor`/`className`-driven rebuild above\n // are handled the same way, without a double `remove()`.\n useEffect(() => {\n if (!map || !marker) return;\n\n marker.addTo(map);\n\n return () => {\n marker.remove();\n };\n }, [map, marker]);\n\n useEffect(() => {\n if (!marker) return;\n\n const current = marker.getLngLat();\n if (current.lng !== longitude || current.lat !== latitude) {\n marker.setLngLat([longitude, latitude]);\n }\n\n if (marker.isDraggable() !== draggable) {\n marker.setDraggable(draggable);\n }\n\n const currentOffset = marker.getOffset();\n const newOffset = offset ?? [0, 0];\n const [newOffsetX, newOffsetY] = Array.isArray(newOffset)\n ? newOffset\n : [newOffset.x, newOffset.y];\n if (currentOffset.x !== newOffsetX || currentOffset.y !== newOffsetY) {\n marker.setOffset(newOffset);\n }\n\n if (marker.getRotation() !== (rotation ?? 0)) {\n marker.setRotation(rotation ?? 0);\n }\n if (marker.getRotationAlignment() !== (rotationAlignment ?? \"auto\")) {\n marker.setRotationAlignment(rotationAlignment ?? \"auto\");\n }\n if (marker.getPitchAlignment() !== (pitchAlignment ?? \"auto\")) {\n marker.setPitchAlignment(pitchAlignment ?? \"auto\");\n }\n }, [marker, longitude, latitude, draggable, offset, rotation, rotationAlignment, pitchAlignment]);\n\n // No marker yet (first client tick after mount, or the moment an\n // `anchor`/`className` rebuild is in flight) — render nothing rather than\n // a sub-component (`MapMarkerContent` et al.) reaching into a null marker,\n // and nothing at all on the server.\n if (!marker) {\n return null;\n }\n\n return <MarkerContext.Provider value={{ marker, map }}>{children}</MarkerContext.Provider>;\n}\n\nexport interface MapMarkerContentProps {\n /** Custom marker content. Defaults to a primary-colored dot. */\n children?: ReactNode;\n /** Additional CSS classes for the marker container. */\n className?: string;\n}\n\n/** The marker's visual, portaled into the MapLibre marker element. */\nexport function MapMarkerContent({ children, className }: MapMarkerContentProps) {\n const { marker } = useMarkerContext();\n\n return createPortal(\n <div className={cn(\"relative cursor-pointer\", className)}>\n {children || <DefaultMarkerIcon />}\n </div>,\n marker.getElement(),\n );\n}\n\nfunction DefaultMarkerIcon() {\n return (\n <div className=\"relative size-4 rounded-full border-2 border-background bg-primary shadow-sm\" />\n );\n}\n\nfunction PopupCloseButton({ onClick }: { onClick: () => void }) {\n const { t } = useLocale();\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={t(\"maps.popup.close\")}\n className=\"absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n );\n}\n\nexport type MapMarkerPopupProps = {\n /** Popup content. */\n children: ReactNode;\n /** Additional CSS classes for the popup container. */\n className?: string;\n /** Show a close button in the popup (default: false). */\n closeButton?: boolean;\n} & Omit<PopupOptions, \"className\" | \"closeButton\">;\n\n/** A popup attached to the marker — MapLibre toggles it on marker click. */\nexport function MapMarkerPopup({\n children,\n className,\n closeButton = false,\n ...popupOptions\n}: MapMarkerPopupProps) {\n const { marker, map } = useMarkerContext();\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const popup = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeButton: false,\n })\n .setMaxWidth(\"none\")\n .setDOMContent(container);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n popup.setDOMContent(container);\n marker.setPopup(popup);\n\n return () => {\n marker.setPopup(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- popup/marker/container are stable\n }, [map]);\n\n // Sync popup options when they change.\n useEffect(() => {\n popup.setOffset(offset ?? 16);\n if (maxWidth) {\n popup.setMaxWidth(maxWidth);\n }\n }, [popup, offset, maxWidth]);\n\n const handleClose = () => popup.remove();\n\n return createPortal(\n <div\n className={cn(\n \"relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {closeButton && <PopupCloseButton onClick={handleClose} />}\n {children}\n </div>,\n container,\n );\n}\n\nexport type MapMarkerTooltipProps = {\n /** Tooltip content. */\n children: ReactNode;\n /** Additional CSS classes for the tooltip container. */\n className?: string;\n} & Omit<PopupOptions, \"className\" | \"closeButton\" | \"closeOnClick\">;\n\n/** A hover tooltip attached to the marker. */\nexport function MapMarkerTooltip({ children, className, ...popupOptions }: MapMarkerTooltipProps) {\n const { marker, map } = useMarkerContext();\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const tooltip = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeOnClick: true,\n closeButton: false,\n }).setMaxWidth(\"none\");\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n tooltip.setDOMContent(container);\n\n const handleMouseEnter = () => {\n tooltip.setLngLat(marker.getLngLat()).addTo(map);\n };\n const handleMouseLeave = () => tooltip.remove();\n\n marker.getElement()?.addEventListener(\"mouseenter\", handleMouseEnter);\n marker.getElement()?.addEventListener(\"mouseleave\", handleMouseLeave);\n\n return () => {\n marker.getElement()?.removeEventListener(\"mouseenter\", handleMouseEnter);\n marker.getElement()?.removeEventListener(\"mouseleave\", handleMouseLeave);\n tooltip.remove();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- tooltip/marker/container are stable\n }, [map]);\n\n // Sync tooltip options when they change.\n useEffect(() => {\n tooltip.setOffset(offset ?? 16);\n if (maxWidth) {\n tooltip.setMaxWidth(maxWidth);\n }\n }, [tooltip, offset, maxWidth]);\n\n return createPortal(\n <div\n className={cn(\n \"pointer-events-none rounded-md bg-foreground px-2 py-1 text-meta text-balance text-background shadow-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {children}\n </div>,\n container,\n );\n}\n\nexport interface MapMarkerLabelProps {\n /** Label text content. */\n children: ReactNode;\n /** Additional CSS classes for the label. */\n className?: string;\n /** Position of the label relative to the marker (default: \"top\"). */\n position?: \"top\" | \"bottom\";\n}\n\n/**\n * A small always-visible text label above or below the marker. Portaled into\n * the marker element so it anchors to the marker whether composed as a sibling\n * of `MapMarkerContent` or nested inside it (a cross-theme sweep caught the\n * sibling composition rendering the label against the map container instead).\n */\nexport function MapMarkerLabel({ children, className, position = \"top\" }: MapMarkerLabelProps) {\n const { marker } = useMarkerContext();\n const positionClasses = {\n top: \"bottom-full mb-1\",\n bottom: \"top-full mt-1\",\n };\n\n return createPortal(\n <div\n className={cn(\n \"absolute left-1/2 -translate-x-1/2 whitespace-nowrap\",\n \"text-meta font-medium text-foreground\",\n positionClasses[position],\n className,\n )}\n >\n {children}\n </div>,\n marker.getElement(),\n );\n}\n","\"use client\";\n\nimport MapLibreGL, { type PopupOptions } from \"maplibre-gl\";\nimport { useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\nexport type MapPopupProps = {\n /** Longitude coordinate for the popup position. */\n longitude: number;\n /** Latitude coordinate for the popup position. */\n latitude: number;\n /** Callback when the popup is closed. */\n onClose?: () => void;\n /** Popup content. */\n children: ReactNode;\n /** Additional CSS classes for the popup container. */\n className?: string;\n /** Show a close button in the popup (default: false). */\n closeButton?: boolean;\n} & Omit<PopupOptions, \"className\" | \"closeButton\">;\n\n/**\n * A standalone popup anchored at a lng/lat (not attached to a marker) —\n * typically rendered conditionally from app state (e.g. after a layer click).\n */\nexport function MapPopup({\n longitude,\n latitude,\n onClose,\n children,\n className,\n closeButton = false,\n ...popupOptions\n}: MapPopupProps) {\n const { map } = useMap();\n const { t } = useLocale();\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n const container = useMemo(() => document.createElement(\"div\"), []);\n const { offset, maxWidth } = popupOptions;\n\n const popup = useMemo(() => {\n return new MapLibreGL.Popup({\n offset: 16,\n ...popupOptions,\n closeButton: false,\n })\n .setMaxWidth(\"none\")\n .setLngLat([longitude, latitude]);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- instance created once; position/options are synced by the effect below\n }, []);\n\n useEffect(() => {\n if (!map) return;\n\n const onCloseProp = () => onCloseRef.current?.();\n\n popup.on(\"close\", onCloseProp);\n\n popup.setDOMContent(container);\n popup.addTo(map);\n\n return () => {\n popup.off(\"close\", onCloseProp);\n if (popup.isOpen()) {\n popup.remove();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- popup/container are stable\n }, [map]);\n\n // Sync popup position and options when they change.\n useEffect(() => {\n const current = popup.getLngLat();\n if (!current || current.lng !== longitude || current.lat !== latitude) {\n popup.setLngLat([longitude, latitude]);\n }\n popup.setOffset(offset ?? 16);\n if (maxWidth) {\n popup.setMaxWidth(maxWidth);\n }\n }, [popup, longitude, latitude, offset, maxWidth]);\n\n const handleClose = () => {\n popup.remove();\n };\n\n return createPortal(\n <div\n className={cn(\n \"relative max-w-62 rounded-md bg-popover p-3 text-popover-foreground shadow-ring-md\",\n \"animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\",\n className,\n )}\n >\n {closeButton && (\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label={t(\"maps.popup.close\")}\n className=\"absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n )}\n {children}\n </div>,\n container,\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { Locate, Maximize, Minus, Plus } from \"lucide-react\";\nimport { Spinner } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\nexport interface MapControlsProps {\n /** Position of the controls on the map (default: \"bottom-right\"). */\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n /** Show zoom in/out buttons (default: true). */\n showZoom?: boolean;\n /** Show a compass button to reset bearing/pitch (default: false). */\n showCompass?: boolean;\n /** Show a locate button to fly to the user's location (default: false). */\n showLocate?: boolean;\n /** Show a fullscreen toggle button (default: false). */\n showFullscreen?: boolean;\n /** Additional CSS classes for the controls container. */\n className?: string;\n /** Callback with user coordinates when located. */\n onLocate?: (coords: { longitude: number; latitude: number }) => void;\n /** Callback when geolocation fails or is denied. */\n onLocateError?: (error: GeolocationPositionError) => void;\n}\n\n// bottom-right sits above MapLibre's attribution control — keep it visible.\nconst positionClasses = {\n \"top-left\": \"top-2 left-2\",\n \"top-right\": \"top-2 right-2\",\n \"bottom-left\": \"bottom-2 left-2\",\n \"bottom-right\": \"bottom-10 right-2\",\n};\n\nfunction ControlGroup({ children }: { children: ReactNode }) {\n return (\n <div className=\"flex flex-col divide-y overflow-hidden rounded-md bg-surface-elevated shadow-ring-sm\">\n {children}\n </div>\n );\n}\n\nfunction ControlButton({\n onClick,\n label,\n children,\n disabled = false,\n}: {\n onClick: () => void;\n label: string;\n children: ReactNode;\n disabled?: boolean;\n}) {\n return (\n <button\n onClick={onClick}\n aria-label={label}\n type=\"button\"\n className={cn(\n \"flex size-8 items-center justify-center text-foreground transition-colors duration-fast\",\n \"hover:bg-surface-muted\",\n \"focus-ring-inset\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n )}\n disabled={disabled}\n >\n {children}\n </button>\n );\n}\n\n/** Branded zoom / compass / locate / fullscreen controls. Render inside `<MapCanvas>`. */\nexport function MapControls({\n position = \"bottom-right\",\n showZoom = true,\n showCompass = false,\n showLocate = false,\n showFullscreen = false,\n className,\n onLocate,\n onLocateError,\n}: MapControlsProps) {\n const { map } = useMap();\n const [waitingForLocation, setWaitingForLocation] = useState(false);\n\n const handleZoomIn = useCallback(() => {\n map?.zoomTo(map.getZoom() + 1, { duration: 300 });\n }, [map]);\n\n const handleZoomOut = useCallback(() => {\n map?.zoomTo(map.getZoom() - 1, { duration: 300 });\n }, [map]);\n\n const handleResetBearing = useCallback(() => {\n map?.resetNorthPitch({ duration: 300 });\n }, [map]);\n\n const handleLocate = useCallback(() => {\n if (!(\"geolocation\" in navigator)) return;\n setWaitingForLocation(true);\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n const coords = {\n longitude: pos.coords.longitude,\n latitude: pos.coords.latitude,\n };\n map?.flyTo({\n center: [coords.longitude, coords.latitude],\n zoom: 14,\n duration: 1500,\n });\n onLocate?.(coords);\n setWaitingForLocation(false);\n },\n (error) => {\n onLocateError?.(error);\n setWaitingForLocation(false);\n },\n );\n }, [map, onLocate, onLocateError]);\n\n const handleFullscreen = useCallback(() => {\n const container = map?.getContainer();\n if (!container) return;\n if (document.fullscreenElement) {\n void document.exitFullscreen();\n } else {\n void container.requestFullscreen();\n }\n }, [map]);\n\n return (\n <div\n className={cn(\"absolute z-10 flex flex-col gap-1.5\", positionClasses[position], className)}\n >\n {showZoom && (\n <ControlGroup>\n <ControlButton onClick={handleZoomIn} label=\"Zoom in\">\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n <ControlButton onClick={handleZoomOut} label=\"Zoom out\">\n <Minus className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n </ControlGroup>\n )}\n {showCompass && (\n <ControlGroup>\n <CompassButton onClick={handleResetBearing} />\n </ControlGroup>\n )}\n {showLocate && (\n <ControlGroup>\n <ControlButton\n onClick={handleLocate}\n label=\"Find my location\"\n disabled={waitingForLocation}\n >\n {waitingForLocation ? (\n <Spinner label=\"Locating\" className=\"size-4 text-foreground\" />\n ) : (\n <Locate className=\"size-4\" aria-hidden=\"true\" />\n )}\n </ControlButton>\n </ControlGroup>\n )}\n {showFullscreen && (\n <ControlGroup>\n <ControlButton onClick={handleFullscreen} label=\"Toggle fullscreen\">\n <Maximize className=\"size-4\" aria-hidden=\"true\" />\n </ControlButton>\n </ControlGroup>\n )}\n </div>\n );\n}\n\nfunction CompassButton({ onClick }: { onClick: () => void }) {\n const { map } = useMap();\n const compassRef = useRef<SVGSVGElement>(null);\n\n useEffect(() => {\n if (!map || !compassRef.current) return;\n\n const compass = compassRef.current;\n\n const updateRotation = () => {\n const bearing = map.getBearing();\n const pitch = map.getPitch();\n compass.style.transform = `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`;\n };\n\n map.on(\"rotate\", updateRotation);\n map.on(\"pitch\", updateRotation);\n updateRotation();\n\n return () => {\n map.off(\"rotate\", updateRotation);\n map.off(\"pitch\", updateRotation);\n };\n }, [map]);\n\n return (\n <ControlButton onClick={onClick} label=\"Reset bearing to north\">\n <svg\n ref={compassRef}\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n className=\"size-5 transition-transform duration-fast\"\n style={{ transformStyle: \"preserve-3d\" }}\n >\n <path d=\"M12 2L16 12H12V2Z\" className=\"fill-destructive\" />\n <path d=\"M12 2L8 12H12V2Z\" className=\"fill-destructive/50\" />\n <path d=\"M12 22L16 12H12V22Z\" className=\"fill-muted-foreground/60\" />\n <path d=\"M12 22L8 12H12V22Z\" className=\"fill-muted-foreground/30\" />\n </svg>\n </ControlButton>\n );\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport interface MapRouteProps {\n /** Optional unique identifier for the route layer. */\n id?: string;\n /** Array of [longitude, latitude] coordinate pairs defining the route. */\n coordinates: [number, number][];\n /** Line color as a CSS color value. Defaults to the theme's `--primary` token. */\n color?: string;\n /** Line width in pixels (default: 3). */\n width?: number;\n /** Line opacity from 0 to 1 (default: 0.8). */\n opacity?: number;\n /** Dash pattern [dash length, gap length] for dashed lines. */\n dashArray?: [number, number];\n /** Callback when the route line is clicked. */\n onClick?: () => void;\n /** Callback when the mouse enters the route line. */\n onMouseEnter?: () => void;\n /** Callback when the mouse leaves the route line. */\n onMouseLeave?: () => void;\n /** Whether the route is interactive — shows a pointer cursor on hover (default: true). */\n interactive?: boolean;\n}\n\n/** A GeoJSON line layer for routes/paths. Renders nothing itself — it draws on the map. */\nexport function MapRoute({\n id: propId,\n coordinates,\n color,\n width = 3,\n opacity = 0.8,\n dashArray,\n onClick,\n onMouseEnter,\n onMouseLeave,\n interactive = true,\n}: MapRouteProps) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `route-source-${id}`;\n const layerId = `route-layer-${id}`;\n\n const primary = useTokenColor(\"--primary\");\n const lineColor = color ?? primary;\n\n // Add source and layer on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data: {\n type: \"Feature\",\n properties: {},\n geometry: { type: \"LineString\", coordinates: [] },\n },\n });\n\n map.addLayer({\n id: layerId,\n type: \"line\",\n source: sourceId,\n layout: { \"line-join\": \"round\", \"line-cap\": \"round\" },\n paint: {\n \"line-color\": lineColor,\n \"line-width\": width,\n \"line-opacity\": opacity,\n ...(dashArray && { \"line-dasharray\": dashArray }),\n },\n });\n\n return () => {\n try {\n if (map.getLayer(layerId)) map.removeLayer(layerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layer created once per map; data + paint are synced by the effects below\n }, [isLoaded, map]);\n\n // When coordinates change, update the source data.\n useEffect(() => {\n if (!isLoaded || !map || coordinates.length < 2) return;\n\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n if (source) {\n source.setData({\n type: \"Feature\",\n properties: {},\n geometry: { type: \"LineString\", coordinates },\n });\n }\n }, [isLoaded, map, coordinates, sourceId]);\n\n // Sync paint when styling (or the resolved theme color) changes.\n useEffect(() => {\n if (!isLoaded || !map || !map.getLayer(layerId)) return;\n\n map.setPaintProperty(layerId, \"line-color\", lineColor);\n map.setPaintProperty(layerId, \"line-width\", width);\n map.setPaintProperty(layerId, \"line-opacity\", opacity);\n map.setPaintProperty(layerId, \"line-dasharray\", dashArray);\n }, [isLoaded, map, layerId, lineColor, width, opacity, dashArray]);\n\n // Handle click and hover events.\n useEffect(() => {\n if (!isLoaded || !map || !interactive) return;\n\n const handleClick = () => {\n onClick?.();\n };\n const handleMouseEnter = () => {\n map.getCanvas().style.cursor = \"pointer\";\n onMouseEnter?.();\n };\n const handleMouseLeave = () => {\n map.getCanvas().style.cursor = \"\";\n onMouseLeave?.();\n };\n\n map.on(\"click\", layerId, handleClick);\n map.on(\"mouseenter\", layerId, handleMouseEnter);\n map.on(\"mouseleave\", layerId, handleMouseLeave);\n\n return () => {\n map.off(\"click\", layerId, handleClick);\n map.off(\"mouseenter\", layerId, handleMouseEnter);\n map.off(\"mouseleave\", layerId, handleMouseLeave);\n };\n }, [isLoaded, map, layerId, onClick, onMouseEnter, onMouseLeave, interactive]);\n\n return null;\n}\n","\"use client\";\n\nimport { resolveTokenColor } from \"@elabs-ai/components-tokens\";\nimport { useLayoutEffect, useState } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\n\n/**\n * Resolve a semantic token (e.g. `\"--primary\"`) to a concrete color MapLibre's\n * WebGL paint can consume, re-resolving whenever the brand theme changes.\n * This is how default layer paints stay token-driven: WebGL can't read CSS\n * custom properties, so we read them at runtime off the map container.\n */\nexport function useTokenColor(name: string, fallback = \"#000000\"): string {\n const { map, themeKey } = useMap();\n const [color, setColor] = useState(fallback);\n\n useLayoutEffect(() => {\n setColor(resolveTokenColor(name, { el: map?.getContainer(), fallback }));\n }, [name, fallback, map, themeKey]);\n\n return color;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { buildArcCoordinates } from \"../lib/arc-math\";\nimport { mergeHoverPaint } from \"../lib/merge-hover-paint\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\n/** A single arc to render inside `<MapArc data={...}>`. */\nexport type MapArcDatum = {\n /** Unique identifier for this arc. Required for hover state tracking and event payloads. */\n id: string | number;\n /** Start coordinate as [longitude, latitude]. */\n from: [number, number];\n /** End coordinate as [longitude, latitude]. */\n to: [number, number];\n};\n\n/** Event payload passed to MapArc interaction callbacks. */\nexport type MapArcEvent<T extends MapArcDatum = MapArcDatum> = {\n /** The arc datum that was hovered or clicked. */\n arc: T;\n /** Longitude of the cursor at the time of the event. */\n longitude: number;\n /** Latitude of the cursor at the time of the event. */\n latitude: number;\n /** The underlying MapLibre mouse event for advanced use cases. */\n originalEvent: MapLibreGL.MapMouseEvent;\n};\n\nexport type MapArcLinePaint = NonNullable<MapLibreGL.LineLayerSpecification[\"paint\"]>;\nexport type MapArcLineLayout = NonNullable<MapLibreGL.LineLayerSpecification[\"layout\"]>;\n\nexport type MapArcProps<T extends MapArcDatum = MapArcDatum> = {\n /** Array of arcs to render. Each arc must have a unique `id`. */\n data: T[];\n /** Optional unique identifier prefix for the arc source/layers. Auto-generated if not provided. */\n id?: string;\n /**\n * How far each arc bows away from a straight line. `0` renders straight\n * lines; higher values bend further; negative values bend to the opposite\n * side. Arcs cross the antimeridian via the shorter direction. (default: 0.2)\n */\n curvature?: number;\n /** Number of samples used to render each curve. Higher = smoother. (default: 64) */\n samples?: number;\n /**\n * MapLibre paint properties for the arc layer. Merged on top of theme-aware\n * defaults (`line-color` = the `--primary` token, `line-width: 2`,\n * `line-opacity: 0.85`). Any value can be a MapLibre expression for\n * per-feature styling; every field on each arc datum (besides `from`/`to`)\n * is exposed via `[\"get\", ...]`.\n */\n paint?: MapArcLinePaint;\n /** MapLibre layout properties for the arc layer. Defaults to rounded joins/caps. */\n layout?: MapArcLineLayout;\n /**\n * Paint properties applied to the arc currently under the cursor. Each key\n * is merged into `paint` as a `case` expression keyed on per-feature hover\n * state, so only the hovered arc changes appearance.\n */\n hoverPaint?: MapArcLinePaint;\n /** Callback when an arc is clicked. */\n onClick?: (e: MapArcEvent<T>) => void;\n /**\n * Callback fired when the hovered arc changes. Receives the cursor's\n * lng/lat at the moment of entry, and `null` when the cursor leaves the\n * last hovered arc.\n */\n onHover?: (e: MapArcEvent<T> | null) => void;\n /** Whether arcs respond to mouse events (default: true). */\n interactive?: boolean;\n /** Optional MapLibre layer id to insert the arc layers before (z-order control). */\n beforeId?: string;\n};\n\nconst DEFAULT_ARC_CURVATURE = 0.2;\nconst DEFAULT_ARC_SAMPLES = 64;\nconst ARC_HIT_MIN_WIDTH = 12;\nconst ARC_HIT_PADDING = 6;\n\nconst DEFAULT_ARC_LAYOUT: MapArcLineLayout = {\n \"line-join\": \"round\",\n \"line-cap\": \"round\",\n};\n\n/**\n * Curved great-circle-style arcs between coordinate pairs (flight paths,\n * network links). Pairs well with `<MapCanvas blank projection={{ type: \"globe\" }}>`.\n */\nexport function MapArc<T extends MapArcDatum = MapArcDatum>({\n data,\n id: propId,\n curvature = DEFAULT_ARC_CURVATURE,\n samples = DEFAULT_ARC_SAMPLES,\n paint,\n layout,\n hoverPaint,\n onClick,\n onHover,\n interactive = true,\n beforeId,\n}: MapArcProps<T>) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `arc-source-${id}`;\n const layerId = `arc-layer-${id}`;\n const hitLayerId = `arc-hit-layer-${id}`;\n\n const primary = useTokenColor(\"--primary\");\n\n const mergedPaint = useMemo(\n () =>\n mergeHoverPaint(\n { \"line-color\": primary, \"line-width\": 2, \"line-opacity\": 0.85, ...paint },\n hoverPaint,\n ),\n [primary, paint, hoverPaint],\n );\n const mergedLayout = useMemo(() => ({ ...DEFAULT_ARC_LAYOUT, ...layout }), [layout]);\n\n const hitWidth = useMemo(() => {\n const w = paint?.[\"line-width\"] ?? 2;\n const base = typeof w === \"number\" ? w : ARC_HIT_MIN_WIDTH;\n return Math.max(base + ARC_HIT_PADDING, ARC_HIT_MIN_WIDTH);\n }, [paint]);\n\n const geoJSON = useMemo<GeoJSON.FeatureCollection<GeoJSON.LineString>>(\n () => ({\n type: \"FeatureCollection\",\n features: data.map((arc) => {\n const { from, to, ...properties } = arc;\n return {\n type: \"Feature\",\n properties,\n geometry: {\n type: \"LineString\",\n coordinates: buildArcCoordinates(from, to, curvature, samples),\n },\n };\n }),\n }),\n [data, curvature, samples],\n );\n\n const latestRef = useRef({ data, onClick, onHover });\n latestRef.current = { data, onClick, onHover };\n\n // Add source and layers on mount. The invisible hit layer widens the\n // pointer target so thin arcs stay hoverable/clickable.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data: geoJSON,\n promoteId: \"id\",\n });\n\n map.addLayer(\n {\n id: hitLayerId,\n type: \"line\",\n source: sourceId,\n layout: DEFAULT_ARC_LAYOUT,\n paint: {\n \"line-color\": \"transparent\",\n \"line-width\": hitWidth,\n \"line-opacity\": 1,\n },\n },\n beforeId,\n );\n\n map.addLayer(\n {\n id: layerId,\n type: \"line\",\n source: sourceId,\n layout: mergedLayout,\n paint: mergedPaint,\n },\n beforeId,\n );\n\n return () => {\n try {\n if (map.getLayer(layerId)) map.removeLayer(layerId);\n if (map.getLayer(hitLayerId)) map.removeLayer(hitLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layers created once per map; data + paint are synced by the effects below\n }, [isLoaded, map]);\n\n // Sync features when data / curvature / samples change.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource | undefined;\n source?.setData(geoJSON);\n }, [isLoaded, map, geoJSON, sourceId]);\n\n // Sync paint/layout when they change.\n useEffect(() => {\n if (!isLoaded || !map || !map.getLayer(layerId)) return;\n for (const [key, value] of Object.entries(mergedPaint)) {\n map.setPaintProperty(layerId, key as keyof MapArcLinePaint, value as never);\n }\n for (const [key, value] of Object.entries(mergedLayout)) {\n map.setLayoutProperty(layerId, key as keyof MapArcLineLayout, value as never);\n }\n if (map.getLayer(hitLayerId)) {\n map.setPaintProperty(hitLayerId, \"line-width\", hitWidth);\n }\n }, [isLoaded, map, layerId, hitLayerId, mergedPaint, mergedLayout, hitWidth]);\n\n // Interaction handlers (bound to the hit layer).\n useEffect(() => {\n if (!isLoaded || !map || !interactive) return;\n\n let hoveredId: string | number | null = null;\n\n const setHover = (next: string | number | null) => {\n if (next === hoveredId) return;\n const sourceExists = !!map.getSource(sourceId);\n if (hoveredId != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });\n }\n hoveredId = next;\n if (next != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: next }, { hover: true });\n }\n };\n\n const findArc = (featureId: string | number | undefined) =>\n featureId == null\n ? undefined\n : latestRef.current.data.find((arc) => String(arc.id) === String(featureId));\n\n const handleMouseMove = (e: MapLibreGL.MapLayerMouseEvent) => {\n const featureId = e.features?.[0]?.id as string | number | undefined;\n if (featureId == null || featureId === hoveredId) return;\n\n setHover(featureId);\n map.getCanvas().style.cursor = \"pointer\";\n\n const arc = findArc(featureId);\n if (arc) {\n latestRef.current.onHover?.({\n arc: arc as T,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n }\n };\n\n const handleMouseLeave = () => {\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n latestRef.current.onHover?.(null);\n };\n\n const handleClick = (e: MapLibreGL.MapLayerMouseEvent) => {\n const arc = findArc(e.features?.[0]?.id as string | number | undefined);\n if (!arc) return;\n latestRef.current.onClick?.({\n arc: arc as T,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n map.on(\"mousemove\", hitLayerId, handleMouseMove);\n map.on(\"mouseleave\", hitLayerId, handleMouseLeave);\n map.on(\"click\", hitLayerId, handleClick);\n\n return () => {\n map.off(\"mousemove\", hitLayerId, handleMouseMove);\n map.off(\"mouseleave\", hitLayerId, handleMouseLeave);\n map.off(\"click\", hitLayerId, handleClick);\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n };\n }, [isLoaded, map, hitLayerId, sourceId, interactive]);\n\n return null;\n}\n","/** Pure geometry for `<MapArc>` — kept engine-free so it can be unit-tested. */\n\n/**\n * Sample a quadratic Bézier between `from` and `to` in lng/lat space.\n * `curvature` is how far the arc bows away from a straight line (0 = straight;\n * negative bends to the opposite side). The destination longitude is unwrapped\n * relative to the origin so arcs cross the antimeridian via the shorter\n * great-circle direction — resulting longitudes may fall outside [-180, 180],\n * which MapLibre renders correctly on the globe projection.\n */\nexport function buildArcCoordinates(\n from: [number, number],\n to: [number, number],\n curvature: number,\n samples: number,\n): [number, number][] {\n const [x0, y0] = from;\n const [xTo, y2] = to;\n const rawDx = xTo - x0;\n const x2 = rawDx > 180 ? xTo - 360 : rawDx < -180 ? xTo + 360 : xTo;\n const dx = x2 - x0;\n const dy = y2 - y0;\n const distance = Math.hypot(dx, dy);\n\n if (distance === 0 || curvature === 0) return [from, [x2, y2]];\n\n const mx = (x0 + x2) / 2;\n const my = (y0 + y2) / 2;\n const nx = -dy / distance;\n const ny = dx / distance;\n const offset = distance * curvature;\n const cx = mx + nx * offset;\n const cy = my + ny * offset;\n\n const points: [number, number][] = [];\n const segments = Math.max(2, Math.floor(samples));\n for (let i = 0; i <= segments; i += 1) {\n const t = i / segments;\n const inv = 1 - t;\n const x = inv * inv * x0 + 2 * inv * t * cx + t * t * x2;\n const y = inv * inv * y0 + 2 * inv * t * cy + t * t * y2;\n points.push([x, y]);\n }\n return points;\n}\n","/**\n * Merge hover paint into base paint as MapLibre `case` expressions keyed on\n * the per-feature `hover` feature-state, so only the hovered feature changes\n * appearance.\n */\nexport function mergeHoverPaint<T extends Record<string, unknown>>(\n paint: T,\n hoverPaint: T | undefined,\n): T {\n if (!hoverPaint) return paint;\n const merged: Record<string, unknown> = { ...paint };\n for (const [key, hoverValue] of Object.entries(hoverPaint)) {\n if (hoverValue === undefined) continue;\n const baseValue = merged[key];\n merged[key] =\n baseValue === undefined\n ? hoverValue\n : [\"case\", [\"boolean\", [\"feature-state\", \"hover\"], false], hoverValue, baseValue];\n }\n return merged as T;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { mergeHoverPaint } from \"../lib/merge-hover-paint\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport type MapGeoJSONData<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n | GeoJSON.FeatureCollection<GeoJSON.Geometry, P>\n | GeoJSON.Feature<GeoJSON.Geometry, P>\n | GeoJSON.Geometry\n | string;\n\nexport type MapFillPaint = NonNullable<MapLibreGL.FillLayerSpecification[\"paint\"]>;\nexport type MapLinePaint = NonNullable<MapLibreGL.LineLayerSpecification[\"paint\"]>;\n\n/** A rendered feature with strongly-typed `properties`. */\nexport type MapGeoJSONFeature<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n Omit<MapLibreGL.MapGeoJSONFeature, \"properties\"> & { properties: P };\n\n/** Event payload passed to MapGeoJSON interaction callbacks. */\nexport type MapGeoJSONEvent<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {\n /** The feature under the cursor, with its typed GeoJSON properties. */\n feature: MapGeoJSONFeature<P>;\n /** Longitude of the cursor at the time of the event. */\n longitude: number;\n /** Latitude of the cursor at the time of the event. */\n latitude: number;\n /** The underlying MapLibre mouse event for advanced use cases. */\n originalEvent: MapLibreGL.MapLayerMouseEvent;\n};\n\nexport type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {\n /** GeoJSON data (FeatureCollection, Feature, Geometry) or a URL to fetch it from. */\n data: MapGeoJSONData<P>;\n /** Optional unique identifier prefix for the source/layers. Auto-generated if not provided. */\n id?: string;\n /**\n * Feature property to promote to the feature `id`. Required for hover\n * feature-state (`fillHoverPaint`) and stable `onHover`/`onClick` payloads.\n */\n promoteId?: string;\n /**\n * Paint for the polygon fill layer. Merged on top of a theme-aware neutral\n * default (the `--border` token — a mid neutral that reads on the page\n * surface in every theme). Pass `false` to omit the fill layer entirely\n * (e.g. outlines only).\n */\n fillPaint?: MapFillPaint | false;\n /**\n * Paint for the outline layer. Merged on top of a hairline default\n * (`line-color` = the `--background` token, `line-width` = 0.5) for thin\n * separators. Override `line-color` if your container differs, or pass\n * `false` to omit the layer.\n */\n linePaint?: MapLinePaint | false;\n /**\n * Paint merged onto the fill layer for the feature under the cursor, applied\n * as a `case` expression keyed on hover feature-state. Requires `promoteId`.\n */\n fillHoverPaint?: MapFillPaint;\n /** Callback when a feature is clicked. */\n onClick?: (e: MapGeoJSONEvent<P>) => void;\n /** Callback fired when the hovered feature changes; `null` when the cursor leaves. */\n onHover?: (e: MapGeoJSONEvent<P> | null) => void;\n /** Whether features respond to mouse events (default: false). */\n interactive?: boolean;\n /** Optional MapLibre layer id to insert the layers before (z-order control). */\n beforeId?: string;\n};\n\n/**\n * Renders arbitrary GeoJSON as fill + outline layers on the map. Composes like\n * `MapRoute` / `MapArc` — drop it inside `<MapCanvas>` (typically with `blank`)\n * for choropleths and region/data maps. For full control over expressions and\n * multiple layers, manage layers directly via `useMap()` instead.\n */\nexport function MapGeoJSON<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({\n data,\n id: propId,\n promoteId,\n fillPaint,\n linePaint,\n fillHoverPaint,\n onClick,\n onHover,\n interactive = false,\n beforeId,\n}: MapGeoJSONProps<P>) {\n const { map, isLoaded } = useMap();\n const autoId = useId();\n const id = propId ?? autoId;\n const sourceId = `geojson-source-${id}`;\n const fillLayerId = `geojson-fill-${id}`;\n const lineLayerId = `geojson-line-${id}`;\n\n // Theme-driven neutral defaults: landmass = the mid-neutral `--border` rung,\n // separators = the page surface. Both re-resolve on theme change.\n const defaultFill = useTokenColor(\"--border\");\n const defaultLine = useTokenColor(\"--background\");\n\n const showFill = fillPaint !== false;\n const showLine = linePaint !== false;\n\n const mergedFillPaint = useMemo(\n () => mergeHoverPaint({ \"fill-color\": defaultFill, ...(fillPaint || {}) }, fillHoverPaint),\n [defaultFill, fillPaint, fillHoverPaint],\n );\n const mergedLinePaint = useMemo(\n () => ({\n \"line-color\": defaultLine,\n \"line-width\": 0.5,\n ...(linePaint || {}),\n }),\n [defaultLine, linePaint],\n );\n const latestRef = useRef({ onClick, onHover });\n latestRef.current = { onClick, onHover };\n\n // Add source on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data,\n ...(promoteId ? { promoteId } : {}),\n });\n\n return () => {\n try {\n if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);\n if (map.getLayer(fillLayerId)) map.removeLayer(fillLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source created once per map; data/layers are synced by the effects below\n }, [isLoaded, map]);\n\n // Sync data when it changes.\n useEffect(() => {\n if (!isLoaded || !map) return;\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource | undefined;\n source?.setData(data as never);\n }, [isLoaded, map, data, sourceId]);\n\n // Sync layers and paint when visibility or styling changes.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n const source = map.getSource(sourceId);\n if (!source) return;\n\n if (showFill && !map.getLayer(fillLayerId)) {\n map.addLayer(\n {\n id: fillLayerId,\n type: \"fill\",\n source: sourceId,\n paint: mergedFillPaint,\n },\n beforeId,\n );\n } else if (!showFill && map.getLayer(fillLayerId)) {\n map.removeLayer(fillLayerId);\n }\n\n if (showLine && !map.getLayer(lineLayerId)) {\n map.addLayer(\n {\n id: lineLayerId,\n type: \"line\",\n source: sourceId,\n paint: mergedLinePaint,\n },\n beforeId,\n );\n } else if (!showLine && map.getLayer(lineLayerId)) {\n map.removeLayer(lineLayerId);\n }\n\n if (showFill && map.getLayer(fillLayerId)) {\n for (const [key, value] of Object.entries(mergedFillPaint)) {\n map.setPaintProperty(fillLayerId, key as keyof MapFillPaint, value as never);\n }\n }\n if (showLine && map.getLayer(lineLayerId)) {\n for (const [key, value] of Object.entries(mergedLinePaint)) {\n map.setPaintProperty(lineLayerId, key as keyof MapLinePaint, value as never);\n }\n }\n }, [\n isLoaded,\n map,\n sourceId,\n fillLayerId,\n lineLayerId,\n showFill,\n showLine,\n mergedFillPaint,\n mergedLinePaint,\n beforeId,\n ]);\n\n // Interaction handlers (bound to the fill layer).\n useEffect(() => {\n if (!isLoaded || !map || !interactive || !showFill) return;\n\n let hoveredId: string | number | null = null;\n\n const setHover = (next: string | number | null) => {\n if (next === hoveredId) return;\n const sourceExists = !!map.getSource(sourceId);\n if (hoveredId != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: false });\n }\n hoveredId = next;\n if (next != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: next }, { hover: true });\n }\n };\n\n const handleMouseMove = (e: MapLibreGL.MapLayerMouseEvent) => {\n const feature = e.features?.[0];\n if (!feature) return;\n map.getCanvas().style.cursor = \"pointer\";\n\n const featureId = feature.id;\n if (featureId === hoveredId) return;\n setHover(featureId ?? null);\n latestRef.current.onHover?.({\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n const handleMouseLeave = () => {\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n latestRef.current.onHover?.(null);\n };\n\n const handleClick = (e: MapLibreGL.MapLayerMouseEvent) => {\n const feature = e.features?.[0];\n if (!feature) return;\n latestRef.current.onClick?.({\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n originalEvent: e,\n });\n };\n\n map.on(\"mousemove\", fillLayerId, handleMouseMove);\n map.on(\"mouseleave\", fillLayerId, handleMouseLeave);\n map.on(\"click\", fillLayerId, handleClick);\n\n return () => {\n map.off(\"mousemove\", fillLayerId, handleMouseMove);\n map.off(\"mouseleave\", fillLayerId, handleMouseLeave);\n map.off(\"click\", fillLayerId, handleClick);\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n };\n }, [isLoaded, map, fillLayerId, sourceId, interactive, showFill]);\n\n return null;\n}\n","\"use client\";\n\nimport type MapLibreGL from \"maplibre-gl\";\nimport { useEffect, useId, useMemo } from \"react\";\n\nimport { useMap } from \"../map-canvas/map-context\";\nimport { useTokenColor } from \"../lib/use-token-color\";\n\nexport type MapClusterLayerProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> =\n {\n /** GeoJSON FeatureCollection data or a URL to fetch GeoJSON from. */\n data: string | GeoJSON.FeatureCollection<GeoJSON.Point, P>;\n /** Maximum zoom level to cluster points on (default: 14). */\n clusterMaxZoom?: number;\n /** Radius of each cluster when clustering points, in pixels (default: 50). */\n clusterRadius?: number;\n /**\n * Colors for cluster circles: [small, medium, large] based on point count.\n * Defaults to the theme's `--success`/`--warning`/`--destructive` tokens.\n */\n clusterColors?: [string, string, string];\n /** Point-count thresholds for the color/size steps: [medium, large] (default: [100, 750]). */\n clusterThresholds?: [number, number];\n /** Color for unclustered individual points. Defaults to the theme's `--primary` token. */\n pointColor?: string;\n /** Callback when an unclustered point is clicked. */\n onPointClick?: (\n feature: GeoJSON.Feature<GeoJSON.Point, P>,\n coordinates: [number, number],\n ) => void;\n /** Callback when a cluster is clicked. If not provided, zooms into the cluster. */\n onClusterClick?: (clusterId: number, coordinates: [number, number], pointCount: number) => void;\n };\n\nconst DEFAULT_CLUSTER_THRESHOLDS: [number, number] = [100, 750];\n\n/**\n * Clustered point rendering for large point datasets. Cluster circles step\n * through the status tokens (success → warning → destructive) as the point\n * count grows; strokes and count labels use the page surface for contrast.\n */\nexport function MapClusterLayer<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({\n data,\n clusterMaxZoom = 14,\n clusterRadius = 50,\n clusterColors,\n clusterThresholds = DEFAULT_CLUSTER_THRESHOLDS,\n pointColor,\n onPointClick,\n onClusterClick,\n}: MapClusterLayerProps<P>) {\n const { map, isLoaded } = useMap();\n const id = useId();\n const sourceId = `cluster-source-${id}`;\n const clusterLayerId = `clusters-${id}`;\n const clusterCountLayerId = `cluster-count-${id}`;\n const unclusteredLayerId = `unclustered-point-${id}`;\n\n const success = useTokenColor(\"--success\");\n const warning = useTokenColor(\"--warning\");\n const destructive = useTokenColor(\"--destructive\");\n const primary = useTokenColor(\"--primary\");\n const surface = useTokenColor(\"--background\");\n\n const resolvedClusterColors = useMemo<[string, string, string]>(\n () => clusterColors ?? [success, warning, destructive],\n [clusterColors, success, warning, destructive],\n );\n const resolvedPointColor = pointColor ?? primary;\n\n // Add source and layers on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data,\n cluster: true,\n clusterMaxZoom,\n clusterRadius,\n });\n\n map.addLayer({\n id: clusterLayerId,\n type: \"circle\",\n source: sourceId,\n filter: [\"has\", \"point_count\"],\n paint: {\n \"circle-color\": [\n \"step\",\n [\"get\", \"point_count\"],\n resolvedClusterColors[0],\n clusterThresholds[0],\n resolvedClusterColors[1],\n clusterThresholds[1],\n resolvedClusterColors[2],\n ],\n \"circle-radius\": [\n \"step\",\n [\"get\", \"point_count\"],\n 20,\n clusterThresholds[0],\n 30,\n clusterThresholds[1],\n 40,\n ],\n \"circle-stroke-width\": 1,\n \"circle-stroke-color\": surface,\n \"circle-opacity\": 0.85,\n },\n });\n\n map.addLayer({\n id: clusterCountLayerId,\n type: \"symbol\",\n source: sourceId,\n filter: [\"has\", \"point_count\"],\n layout: {\n \"text-field\": \"{point_count_abbreviated}\",\n \"text-font\": [\"Open Sans\"],\n \"text-size\": 12,\n },\n paint: {\n \"text-color\": surface,\n },\n });\n\n map.addLayer({\n id: unclusteredLayerId,\n type: \"circle\",\n source: sourceId,\n filter: [\"!\", [\"has\", \"point_count\"]],\n paint: {\n \"circle-color\": resolvedPointColor,\n \"circle-radius\": 5,\n \"circle-stroke-width\": 2,\n \"circle-stroke-color\": surface,\n },\n });\n\n return () => {\n try {\n if (map.getLayer(clusterCountLayerId)) map.removeLayer(clusterCountLayerId);\n if (map.getLayer(unclusteredLayerId)) map.removeLayer(unclusteredLayerId);\n if (map.getLayer(clusterLayerId)) map.removeLayer(clusterLayerId);\n if (map.getSource(sourceId)) map.removeSource(sourceId);\n } catch {\n // style may be mid-reload\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- source/layers created once per map; data + paint are synced by the effects below\n }, [isLoaded, map, sourceId]);\n\n // Update source data when the data prop changes (only for non-URL data).\n useEffect(() => {\n if (!isLoaded || !map || typeof data === \"string\") return;\n\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n if (source) {\n source.setData(data);\n }\n }, [isLoaded, map, data, sourceId]);\n\n // Sync layer styles when props (or the resolved theme colors) change.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n if (map.getLayer(clusterLayerId)) {\n map.setPaintProperty(clusterLayerId, \"circle-color\", [\n \"step\",\n [\"get\", \"point_count\"],\n resolvedClusterColors[0],\n clusterThresholds[0],\n resolvedClusterColors[1],\n clusterThresholds[1],\n resolvedClusterColors[2],\n ]);\n map.setPaintProperty(clusterLayerId, \"circle-radius\", [\n \"step\",\n [\"get\", \"point_count\"],\n 20,\n clusterThresholds[0],\n 30,\n clusterThresholds[1],\n 40,\n ]);\n map.setPaintProperty(clusterLayerId, \"circle-stroke-color\", surface);\n }\n\n if (map.getLayer(clusterCountLayerId)) {\n map.setPaintProperty(clusterCountLayerId, \"text-color\", surface);\n }\n\n if (map.getLayer(unclusteredLayerId)) {\n map.setPaintProperty(unclusteredLayerId, \"circle-color\", resolvedPointColor);\n map.setPaintProperty(unclusteredLayerId, \"circle-stroke-color\", surface);\n }\n }, [\n isLoaded,\n map,\n clusterLayerId,\n clusterCountLayerId,\n unclusteredLayerId,\n resolvedClusterColors,\n clusterThresholds,\n resolvedPointColor,\n surface,\n ]);\n\n // Handle click events.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n // Cluster click handler — zoom into the cluster by default.\n const handleClusterClick = async (e: MapLibreGL.MapMouseEvent) => {\n const features = map.queryRenderedFeatures(e.point, {\n layers: [clusterLayerId],\n });\n const feature = features[0];\n if (!feature) return;\n const clusterId = feature.properties?.cluster_id as number;\n const pointCount = feature.properties?.point_count as number;\n const coordinates = (feature.geometry as GeoJSON.Point).coordinates as [number, number];\n\n if (onClusterClick) {\n onClusterClick(clusterId, coordinates, pointCount);\n } else {\n const source = map.getSource(sourceId) as MapLibreGL.GeoJSONSource;\n const zoom = await source.getClusterExpansionZoom(clusterId);\n map.easeTo({\n center: coordinates,\n zoom,\n });\n }\n };\n\n // Unclustered point click handler.\n const handlePointClick = (\n e: MapLibreGL.MapMouseEvent & {\n features?: MapLibreGL.MapGeoJSONFeature[];\n },\n ) => {\n const feature = e.features?.[0];\n if (!onPointClick || !feature) return;\n const coordinates = (feature.geometry as GeoJSON.Point).coordinates.slice() as [\n number,\n number,\n ];\n\n // Handle world copies.\n while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {\n coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;\n }\n\n onPointClick(feature as unknown as GeoJSON.Feature<GeoJSON.Point, P>, coordinates);\n };\n\n // Cursor style handlers.\n const handleMouseEnterCluster = () => {\n map.getCanvas().style.cursor = \"pointer\";\n };\n const handleMouseLeaveCluster = () => {\n map.getCanvas().style.cursor = \"\";\n };\n const handleMouseEnterPoint = () => {\n if (onPointClick) {\n map.getCanvas().style.cursor = \"pointer\";\n }\n };\n const handleMouseLeavePoint = () => {\n map.getCanvas().style.cursor = \"\";\n };\n\n map.on(\"click\", clusterLayerId, handleClusterClick);\n map.on(\"click\", unclusteredLayerId, handlePointClick);\n map.on(\"mouseenter\", clusterLayerId, handleMouseEnterCluster);\n map.on(\"mouseleave\", clusterLayerId, handleMouseLeaveCluster);\n map.on(\"mouseenter\", unclusteredLayerId, handleMouseEnterPoint);\n map.on(\"mouseleave\", unclusteredLayerId, handleMouseLeavePoint);\n\n return () => {\n map.off(\"click\", clusterLayerId, handleClusterClick);\n map.off(\"click\", unclusteredLayerId, handlePointClick);\n map.off(\"mouseenter\", clusterLayerId, handleMouseEnterCluster);\n map.off(\"mouseleave\", clusterLayerId, handleMouseLeaveCluster);\n map.off(\"mouseenter\", unclusteredLayerId, handleMouseEnterPoint);\n map.off(\"mouseleave\", unclusteredLayerId, handleMouseLeavePoint);\n };\n }, [isLoaded, map, clusterLayerId, unclusteredLayerId, sourceId, onClusterClick, onPointClick]);\n\n return null;\n}\n"],"mappings":"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","names":["useEffect","useState","MapCanvas","useState","useEffect","MapLibreGL","createContext","use","useEffect","useMemo","useRef","useState","cn","useLocale","jsx","jsxs","createContext","use","useState","useRef","useEffect","MapLibreGL","cn","useLocale","useMemo","positionClasses","MapLibreGL","useEffect","useMemo","useRef","createPortal","X","cn","useLocale","jsx","jsxs","useLocale","useRef","useMemo","MapLibreGL","useEffect","createPortal","cn","X","useCallback","useEffect","useRef","useState","Spinner","cn","jsx","jsxs","cn","useState","useCallback","Spinner","useRef","useEffect","useEffect","useState","useState","useEffect","useEffect","useId","useMemo","useRef","useId","useMemo","useRef","useEffect","useEffect","useId","useMemo","useRef","useId","useMemo","useRef","useEffect","useEffect","useId","useMemo","useId","useMemo","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elabs-ai/components-maps",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"maplibre-gl": "^5.15.0",
|
|
33
33
|
"react": "^18.2.0 || ^19.0.0",
|
|
34
34
|
"react-dom": "^18.2.0 || ^19.0.0",
|
|
35
|
-
"@elabs-ai/components-
|
|
36
|
-
"@elabs-ai/components-
|
|
35
|
+
"@elabs-ai/components-tokens": "4.2.0",
|
|
36
|
+
"@elabs-ai/components-ui": "4.2.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"tsup": "^8.3.5",
|
|
51
51
|
"typescript": "^5.7.3",
|
|
52
52
|
"vitest": "^3.0.2",
|
|
53
|
-
"@elabs-ai/components-typescript-config": "0.1.0",
|
|
54
53
|
"@elabs-ai/components-eslint-config": "0.1.0",
|
|
55
|
-
"@elabs-ai/components-
|
|
56
|
-
"@elabs-ai/components-
|
|
54
|
+
"@elabs-ai/components-tokens": "4.2.0",
|
|
55
|
+
"@elabs-ai/components-typescript-config": "0.1.0",
|
|
56
|
+
"@elabs-ai/components-ui": "4.2.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsup && node ../../scripts/link-dist-css.mjs",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
1
|
+
import { act, cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
4
|
// maplibre-gl requires WebGL — mock the engine and assert the brand wrapper's
|
|
@@ -90,4 +90,35 @@ describe("MapCanvas", () => {
|
|
|
90
90
|
}
|
|
91
91
|
expect(() => render(<Bare />)).toThrow(/within a <MapCanvas>/);
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
it("applies the CURRENT projection prop on a later styledata event, not the value from mount", async () => {
|
|
95
|
+
const globe = { type: "globe" } as const;
|
|
96
|
+
const mercator = { type: "mercator" } as const;
|
|
97
|
+
const { rerender } = render(<MapCanvas projection={globe} />);
|
|
98
|
+
const map = MockMap.instances[0]!;
|
|
99
|
+
// Let the mount-time styledata's own 100ms timeout settle first.
|
|
100
|
+
await act(async () => {
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const setProjectionSpy = vi.spyOn(map, "setProjection");
|
|
105
|
+
rerender(<MapCanvas projection={mercator} />);
|
|
106
|
+
// The dedicated "sync projection on prop change" effect also fires here —
|
|
107
|
+
// clear it so the assertion below isolates the `styledata` handler alone.
|
|
108
|
+
await act(async () => {
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
110
|
+
});
|
|
111
|
+
setProjectionSpy.mockClear();
|
|
112
|
+
|
|
113
|
+
// A style reload (theme swap, `setStyle`) re-fires `styledata` on the SAME
|
|
114
|
+
// mount-only handler — it must read the current prop via a ref, not the
|
|
115
|
+
// value closed over when the handler was registered.
|
|
116
|
+
await act(async () => {
|
|
117
|
+
map.emit("styledata");
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(setProjectionSpy).toHaveBeenCalledWith(mercator);
|
|
122
|
+
expect(setProjectionSpy).not.toHaveBeenCalledWith(globe);
|
|
123
|
+
});
|
|
93
124
|
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useState,
|
|
14
14
|
type ReactNode,
|
|
15
15
|
} from "react";
|
|
16
|
-
import { Spinner, StatePanel } from "@elabs-ai/components-ui";
|
|
16
|
+
import { Spinner, StatePanel, useLocale } from "@elabs-ai/components-ui";
|
|
17
17
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
18
|
|
|
19
19
|
import { MapContext, type BasemapTheme } from "./map-context";
|
|
@@ -142,6 +142,7 @@ export const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCa
|
|
|
142
142
|
},
|
|
143
143
|
ref,
|
|
144
144
|
) {
|
|
145
|
+
const { t } = useLocale();
|
|
145
146
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
146
147
|
const [mapInstance, setMapInstance] = useState<MapLibreGL.Map | null>(null);
|
|
147
148
|
const [initFailed, setInitFailed] = useState(false);
|
|
@@ -157,6 +158,11 @@ export const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCa
|
|
|
157
158
|
const onViewportChangeRef = useRef(onViewportChange);
|
|
158
159
|
onViewportChangeRef.current = onViewportChange;
|
|
159
160
|
|
|
161
|
+
// Read from the mount-only `styledata` handler below so a `projection` prop
|
|
162
|
+
// change after mount isn't reapplied with the value captured at mount time.
|
|
163
|
+
const projectionRef = useRef(projection);
|
|
164
|
+
projectionRef.current = projection;
|
|
165
|
+
|
|
160
166
|
const mapStyles = useMemo(() => {
|
|
161
167
|
// Explicit styles win. Otherwise `blank` opts into the transparent
|
|
162
168
|
// tile-less basemap; with neither, fall back to the Carto defaults.
|
|
@@ -221,8 +227,8 @@ export const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCa
|
|
|
221
227
|
// race conditions on setStyle without force-updating every layer.
|
|
222
228
|
styleTimeoutRef.current = setTimeout(() => {
|
|
223
229
|
setIsStyleLoaded(true);
|
|
224
|
-
if (
|
|
225
|
-
map.setProjection(
|
|
230
|
+
if (projectionRef.current) {
|
|
231
|
+
map.setProjection(projectionRef.current);
|
|
226
232
|
}
|
|
227
233
|
}, 100);
|
|
228
234
|
};
|
|
@@ -329,8 +335,8 @@ export const MapCanvas = forwardRef<MapCanvasRef, MapCanvasProps>(function MapCa
|
|
|
329
335
|
<div className={cn("relative h-full w-full", className)}>
|
|
330
336
|
<StatePanel
|
|
331
337
|
kind="error"
|
|
332
|
-
title="
|
|
333
|
-
description="
|
|
338
|
+
title={t("maps.canvas.unavailableTitle")}
|
|
339
|
+
description={t("maps.canvas.unavailableDescription")}
|
|
334
340
|
className="h-full"
|
|
335
341
|
/>
|
|
336
342
|
</div>
|
|
@@ -61,7 +61,7 @@ function ControlButton({
|
|
|
61
61
|
className={cn(
|
|
62
62
|
"flex size-8 items-center justify-center text-foreground transition-colors duration-fast",
|
|
63
63
|
"hover:bg-surface-muted",
|
|
64
|
-
"focus-
|
|
64
|
+
"focus-ring-inset",
|
|
65
65
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
66
66
|
)}
|
|
67
67
|
disabled={disabled}
|
|
@@ -78,4 +78,59 @@ describe("MapMarker", () => {
|
|
|
78
78
|
unmount();
|
|
79
79
|
expect(MockMarker.instances[0]!.addedTo).toBeNull();
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
it("rebuilds the marker instance when `anchor` changes — MapLibre has no live setter for it", async () => {
|
|
83
|
+
const { rerender } = render(
|
|
84
|
+
<MapCanvas>
|
|
85
|
+
<MapMarker longitude={0} latitude={0} anchor="top">
|
|
86
|
+
<MapMarkerContent />
|
|
87
|
+
</MapMarker>
|
|
88
|
+
</MapCanvas>,
|
|
89
|
+
);
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(MockMarker.instances).toHaveLength(1);
|
|
92
|
+
expect(MockMarker.instances[0]!.options.anchor).toBe("top");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
rerender(
|
|
96
|
+
<MapCanvas>
|
|
97
|
+
<MapMarker longitude={0} latitude={0} anchor="bottom">
|
|
98
|
+
<MapMarkerContent />
|
|
99
|
+
</MapMarker>
|
|
100
|
+
</MapCanvas>,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(MockMarker.instances).toHaveLength(2);
|
|
105
|
+
expect(MockMarker.instances[1]!.options.anchor).toBe("bottom");
|
|
106
|
+
});
|
|
107
|
+
// The old instance is detached, not mutated in place.
|
|
108
|
+
expect(MockMarker.instances[0]!.addedTo).toBeNull();
|
|
109
|
+
expect(MockMarker.instances[1]!.addedTo).not.toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("never touches `document` during the render phase (SSR-safe)", async () => {
|
|
113
|
+
// `renderToStaticMarkup` never runs effects — only the plain render
|
|
114
|
+
// functions — which is exactly the environment a real SSR host runs in.
|
|
115
|
+
// Deleting `document` reproduces that host having none: the old
|
|
116
|
+
// `useMemo`-based marker construction called `document.createElement`
|
|
117
|
+
// straight out of the render function and would throw here.
|
|
118
|
+
const { renderToStaticMarkup } = await import("react-dom/server");
|
|
119
|
+
const originalDocument = globalThis.document;
|
|
120
|
+
// @ts-expect-error -- simulating a server environment with no DOM
|
|
121
|
+
delete globalThis.document;
|
|
122
|
+
try {
|
|
123
|
+
expect(() =>
|
|
124
|
+
renderToStaticMarkup(
|
|
125
|
+
<MapCanvas>
|
|
126
|
+
<MapMarker longitude={0} latitude={0}>
|
|
127
|
+
<MapMarkerContent />
|
|
128
|
+
</MapMarker>
|
|
129
|
+
</MapCanvas>,
|
|
130
|
+
),
|
|
131
|
+
).not.toThrow();
|
|
132
|
+
} finally {
|
|
133
|
+
globalThis.document = originalDocument;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
81
136
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import MapLibreGL, { type MarkerOptions, type PopupOptions } from "maplibre-gl";
|
|
4
|
-
import { createContext, use, useEffect, useMemo, useRef, type ReactNode } from "react";
|
|
4
|
+
import { createContext, use, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import { X } from "lucide-react";
|
|
7
7
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
8
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
8
9
|
|
|
9
10
|
import { useMap } from "../map-canvas/map-context";
|
|
10
11
|
|
|
@@ -63,6 +64,7 @@ export function MapMarker({
|
|
|
63
64
|
...markerOptions
|
|
64
65
|
}: MapMarkerProps) {
|
|
65
66
|
const { map } = useMap();
|
|
67
|
+
const [marker, setMarker] = useState<MapLibreGL.Marker | null>(null);
|
|
66
68
|
|
|
67
69
|
const callbacksRef = useRef({
|
|
68
70
|
onClick,
|
|
@@ -81,12 +83,33 @@ export function MapMarker({
|
|
|
81
83
|
onDragEnd,
|
|
82
84
|
};
|
|
83
85
|
|
|
84
|
-
const
|
|
86
|
+
const { anchor, className, offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;
|
|
87
|
+
|
|
88
|
+
// `longitude`/`latitude`/`draggable` at construction time only — the sync
|
|
89
|
+
// effect below corrects them on the very next commit, and are read from a
|
|
90
|
+
// ref here so they don't force a rebuild on every position change (see the
|
|
91
|
+
// `anchor`/`className` note below for what SHOULD force one).
|
|
92
|
+
const initialRef = useRef({ longitude, latitude, draggable });
|
|
93
|
+
initialRef.current = { longitude, latitude, draggable };
|
|
94
|
+
|
|
95
|
+
// Builds (and tears down) the actual MapLibre `Marker` instance. This is a
|
|
96
|
+
// real `useEffect`, never `useMemo`: `useMemo`'s initializer runs during
|
|
97
|
+
// RENDER — including on the server, where `document` doesn't exist — and
|
|
98
|
+
// React offers no guarantee it runs only once (it may discard and recompute
|
|
99
|
+
// a memoized value), which would silently leak marker instances/listeners.
|
|
100
|
+
//
|
|
101
|
+
// `anchor` and `className` have no MapLibre setter (`Marker` bakes both into
|
|
102
|
+
// its constructor), so they are the only options that must REBUILD the
|
|
103
|
+
// instance; every other option (position, draggable, offset, rotation,
|
|
104
|
+
// alignment) has a live setter and is kept in sync by the effect further
|
|
105
|
+
// below without ever recreating the marker.
|
|
106
|
+
useEffect(() => {
|
|
85
107
|
const markerInstance = new MapLibreGL.Marker({
|
|
86
|
-
|
|
108
|
+
anchor,
|
|
109
|
+
className,
|
|
87
110
|
element: document.createElement("div"),
|
|
88
|
-
draggable,
|
|
89
|
-
}).setLngLat([longitude, latitude]);
|
|
111
|
+
draggable: initialRef.current.draggable,
|
|
112
|
+
}).setLngLat([initialRef.current.longitude, initialRef.current.latitude]);
|
|
90
113
|
|
|
91
114
|
const handleClick = (e: MouseEvent) => callbacksRef.current.onClick?.(e);
|
|
92
115
|
const handleMouseEnter = (e: MouseEvent) => callbacksRef.current.onMouseEnter?.(e);
|
|
@@ -113,24 +136,25 @@ export function MapMarker({
|
|
|
113
136
|
markerInstance.on("drag", handleDrag);
|
|
114
137
|
markerInstance.on("dragend", handleDragEnd);
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}, []);
|
|
139
|
+
setMarker(markerInstance);
|
|
140
|
+
}, [anchor, className]);
|
|
119
141
|
|
|
142
|
+
// Attaches/detaches the CURRENT marker instance to the map — its own effect
|
|
143
|
+
// so both a `map` change and an `anchor`/`className`-driven rebuild above
|
|
144
|
+
// are handled the same way, without a double `remove()`.
|
|
120
145
|
useEffect(() => {
|
|
121
|
-
if (!map) return;
|
|
146
|
+
if (!map || !marker) return;
|
|
122
147
|
|
|
123
148
|
marker.addTo(map);
|
|
124
149
|
|
|
125
150
|
return () => {
|
|
126
151
|
marker.remove();
|
|
127
152
|
};
|
|
128
|
-
|
|
129
|
-
}, [map]);
|
|
130
|
-
|
|
131
|
-
const { offset, rotation, rotationAlignment, pitchAlignment } = markerOptions;
|
|
153
|
+
}, [map, marker]);
|
|
132
154
|
|
|
133
155
|
useEffect(() => {
|
|
156
|
+
if (!marker) return;
|
|
157
|
+
|
|
134
158
|
const current = marker.getLngLat();
|
|
135
159
|
if (current.lng !== longitude || current.lat !== latitude) {
|
|
136
160
|
marker.setLngLat([longitude, latitude]);
|
|
@@ -160,6 +184,14 @@ export function MapMarker({
|
|
|
160
184
|
}
|
|
161
185
|
}, [marker, longitude, latitude, draggable, offset, rotation, rotationAlignment, pitchAlignment]);
|
|
162
186
|
|
|
187
|
+
// No marker yet (first client tick after mount, or the moment an
|
|
188
|
+
// `anchor`/`className` rebuild is in flight) — render nothing rather than
|
|
189
|
+
// a sub-component (`MapMarkerContent` et al.) reaching into a null marker,
|
|
190
|
+
// and nothing at all on the server.
|
|
191
|
+
if (!marker) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
163
195
|
return <MarkerContext.Provider value={{ marker, map }}>{children}</MarkerContext.Provider>;
|
|
164
196
|
}
|
|
165
197
|
|
|
@@ -189,12 +221,13 @@ function DefaultMarkerIcon() {
|
|
|
189
221
|
}
|
|
190
222
|
|
|
191
223
|
function PopupCloseButton({ onClick }: { onClick: () => void }) {
|
|
224
|
+
const { t } = useLocale();
|
|
192
225
|
return (
|
|
193
226
|
<button
|
|
194
227
|
type="button"
|
|
195
228
|
onClick={onClick}
|
|
196
|
-
aria-label="
|
|
197
|
-
className="absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus
|
|
229
|
+
aria-label={t("maps.popup.close")}
|
|
230
|
+
className="absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset"
|
|
198
231
|
>
|
|
199
232
|
<X className="size-3.5" aria-hidden="true" />
|
|
200
233
|
</button>
|
|
@@ -347,7 +380,7 @@ export interface MapMarkerLabelProps {
|
|
|
347
380
|
/**
|
|
348
381
|
* A small always-visible text label above or below the marker. Portaled into
|
|
349
382
|
* the marker element so it anchors to the marker whether composed as a sibling
|
|
350
|
-
* of `MapMarkerContent` or nested inside it (
|
|
383
|
+
* of `MapMarkerContent` or nested inside it (a cross-theme sweep caught the
|
|
351
384
|
* sibling composition rendering the label against the map container instead).
|
|
352
385
|
*/
|
|
353
386
|
export function MapMarkerLabel({ children, className, position = "top" }: MapMarkerLabelProps) {
|
|
@@ -5,6 +5,7 @@ import { useEffect, useMemo, useRef, type ReactNode } from "react";
|
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import { X } from "lucide-react";
|
|
7
7
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
8
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
8
9
|
|
|
9
10
|
import { useMap } from "../map-canvas/map-context";
|
|
10
11
|
|
|
@@ -37,6 +38,7 @@ export function MapPopup({
|
|
|
37
38
|
...popupOptions
|
|
38
39
|
}: MapPopupProps) {
|
|
39
40
|
const { map } = useMap();
|
|
41
|
+
const { t } = useLocale();
|
|
40
42
|
const onCloseRef = useRef(onClose);
|
|
41
43
|
onCloseRef.current = onClose;
|
|
42
44
|
const container = useMemo(() => document.createElement("div"), []);
|
|
@@ -100,8 +102,8 @@ export function MapPopup({
|
|
|
100
102
|
<button
|
|
101
103
|
type="button"
|
|
102
104
|
onClick={handleClose}
|
|
103
|
-
aria-label="
|
|
104
|
-
className="absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus
|
|
105
|
+
aria-label={t("maps.popup.close")}
|
|
106
|
+
className="absolute top-1 right-1 z-10 inline-flex size-5 items-center justify-center rounded-sm text-foreground transition-colors duration-fast hover:bg-muted focus-ring-inset"
|
|
105
107
|
>
|
|
106
108
|
<X className="size-3.5" aria-hidden="true" />
|
|
107
109
|
</button>
|
|
@@ -55,6 +55,13 @@ export class MockMap {
|
|
|
55
55
|
return this;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/** Manually re-fire an event's handlers — e.g. a second `styledata` after a style reload. */
|
|
59
|
+
emit(event: string, layerId?: string, ...args: unknown[]) {
|
|
60
|
+
this.handlers.get(this.key(event, layerId))?.forEach((handler) => {
|
|
61
|
+
handler(...args);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
remove() {
|
|
59
66
|
this.removed = true;
|
|
60
67
|
}
|
|
@@ -124,9 +131,12 @@ export class MockMarker {
|
|
|
124
131
|
addedTo: MockMap | null = null;
|
|
125
132
|
popup: unknown = null;
|
|
126
133
|
lngLat = { lng: 0, lat: 0 };
|
|
134
|
+
/** The full options object the component constructed the marker with. */
|
|
135
|
+
options: Record<string, any>;
|
|
127
136
|
|
|
128
|
-
constructor(options: { element?: HTMLElement } = {}) {
|
|
137
|
+
constructor(options: { element?: HTMLElement } & Record<string, any> = {}) {
|
|
129
138
|
this.element = options.element ?? document.createElement("div");
|
|
139
|
+
this.options = options;
|
|
130
140
|
MockMarker.instances.push(this);
|
|
131
141
|
}
|
|
132
142
|
|