@elabs-ai/components-maps 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-2FPB53NG.js +36 -0
- package/dist/chunk-2FPB53NG.js.map +1 -0
- package/dist/chunk-2VVJRT66.js +37 -0
- package/dist/chunk-2VVJRT66.js.map +1 -0
- package/dist/chunk-3HXFONRP.js +2 -0
- package/dist/chunk-3HXFONRP.js.map +1 -0
- package/dist/chunk-3SJN3IYT.js +2 -0
- package/dist/chunk-3SJN3IYT.js.map +1 -0
- package/dist/chunk-4DANBD5B.js +55 -0
- package/dist/chunk-4DANBD5B.js.map +1 -0
- package/dist/chunk-5PDVG53B.js +2 -0
- package/dist/chunk-5PDVG53B.js.map +1 -0
- package/dist/chunk-643R7IJC.js +2 -0
- package/dist/chunk-643R7IJC.js.map +1 -0
- package/dist/chunk-B3ZDBQ6I.js +164 -0
- package/dist/chunk-B3ZDBQ6I.js.map +1 -0
- package/dist/chunk-BGCE3RR5.js +35 -0
- package/dist/chunk-BGCE3RR5.js.map +1 -0
- package/dist/chunk-C4VI37TD.js +485 -0
- package/dist/chunk-C4VI37TD.js.map +1 -0
- package/dist/chunk-CJJT6AEI.js +179 -0
- package/dist/chunk-CJJT6AEI.js.map +1 -0
- package/dist/chunk-D2RCPTRO.js +2 -0
- package/dist/chunk-D2RCPTRO.js.map +1 -0
- package/dist/chunk-DTV7JZVN.js +2 -0
- package/dist/chunk-DTV7JZVN.js.map +1 -0
- package/dist/chunk-E65Z3MSV.js +80 -0
- package/dist/chunk-E65Z3MSV.js.map +1 -0
- package/dist/chunk-E7VPNINZ.js +70 -0
- package/dist/chunk-E7VPNINZ.js.map +1 -0
- package/dist/chunk-FHV5IA6N.js +100 -0
- package/dist/chunk-FHV5IA6N.js.map +1 -0
- package/dist/chunk-FQ6V7BFZ.js +347 -0
- package/dist/chunk-FQ6V7BFZ.js.map +1 -0
- package/dist/chunk-G7JDXDJN.js +19 -0
- package/dist/chunk-G7JDXDJN.js.map +1 -0
- package/dist/chunk-GWULZOMW.js +21 -0
- package/dist/chunk-GWULZOMW.js.map +1 -0
- package/dist/chunk-GYDTTAVD.js +120 -0
- package/dist/chunk-GYDTTAVD.js.map +1 -0
- package/dist/chunk-HCOBWIM4.js +48 -0
- package/dist/chunk-HCOBWIM4.js.map +1 -0
- package/dist/chunk-IAHK5MVT.js +2 -0
- package/dist/chunk-IAHK5MVT.js.map +1 -0
- package/dist/chunk-JGJ6442I.js +235 -0
- package/dist/chunk-JGJ6442I.js.map +1 -0
- package/dist/chunk-JYFERXZT.js +18 -0
- package/dist/chunk-JYFERXZT.js.map +1 -0
- package/dist/chunk-LU2UEKFM.js +53 -0
- package/dist/chunk-LU2UEKFM.js.map +1 -0
- package/dist/chunk-LVYM6C5C.js +2 -0
- package/dist/chunk-LVYM6C5C.js.map +1 -0
- package/dist/chunk-MIMDP3EU.js +104 -0
- package/dist/chunk-MIMDP3EU.js.map +1 -0
- package/dist/chunk-NNBURJHY.js +2 -0
- package/dist/chunk-NNBURJHY.js.map +1 -0
- package/dist/chunk-NPUC3V37.js +2 -0
- package/dist/chunk-NPUC3V37.js.map +1 -0
- package/dist/chunk-OEVVNPN3.js +201 -0
- package/dist/chunk-OEVVNPN3.js.map +1 -0
- package/dist/chunk-PF3R2BXM.js +200 -0
- package/dist/chunk-PF3R2BXM.js.map +1 -0
- package/dist/chunk-Q2I5YODW.js +2 -0
- package/dist/chunk-Q2I5YODW.js.map +1 -0
- package/dist/chunk-QDWWP3N7.js +247 -0
- package/dist/chunk-QDWWP3N7.js.map +1 -0
- package/dist/chunk-QU33KPMP.js +2 -0
- package/dist/chunk-QU33KPMP.js.map +1 -0
- package/dist/chunk-RK4BUMHS.js +2 -0
- package/dist/chunk-RK4BUMHS.js.map +1 -0
- package/dist/chunk-RNSLG64F.js +26 -0
- package/dist/chunk-RNSLG64F.js.map +1 -0
- package/dist/chunk-RV673TB4.js +68 -0
- package/dist/chunk-RV673TB4.js.map +1 -0
- package/dist/chunk-S7N2WMVT.js +180 -0
- package/dist/chunk-S7N2WMVT.js.map +1 -0
- package/dist/chunk-T3Z6TCDD.js +2 -0
- package/dist/chunk-T3Z6TCDD.js.map +1 -0
- package/dist/chunk-T4TJTVVA.js +105 -0
- package/dist/chunk-T4TJTVVA.js.map +1 -0
- package/dist/chunk-UMOYRCDX.js +103 -0
- package/dist/chunk-UMOYRCDX.js.map +1 -0
- package/dist/chunk-V2REM6UT.js +185 -0
- package/dist/chunk-V2REM6UT.js.map +1 -0
- package/dist/chunk-VFBRFGID.js +89 -0
- package/dist/chunk-VFBRFGID.js.map +1 -0
- package/dist/chunk-W76NLFER.js +103 -0
- package/dist/chunk-W76NLFER.js.map +1 -0
- package/dist/chunk-WNU4IOGL.js +116 -0
- package/dist/chunk-WNU4IOGL.js.map +1 -0
- package/dist/chunk-WS4Z7ENS.js +494 -0
- package/dist/chunk-WS4Z7ENS.js.map +1 -0
- package/dist/chunk-XQT7YLJY.js +497 -0
- package/dist/chunk-XQT7YLJY.js.map +1 -0
- package/dist/chunk-Y44MEKJV.js +21 -0
- package/dist/chunk-Y44MEKJV.js.map +1 -0
- package/dist/chunk-Z4EPSOB4.js +53 -0
- package/dist/chunk-Z4EPSOB4.js.map +1 -0
- package/dist/chunk-Z5OPDBDC.js +2 -0
- package/dist/chunk-Z5OPDBDC.js.map +1 -0
- package/dist/index.d.ts +1241 -46
- package/dist/index.js +189 -1471
- package/dist/index.js.map +1 -1
- package/dist/lib/arc-math.js +8 -0
- package/dist/lib/arc-math.js.map +1 -0
- package/dist/lib/map-position.js +11 -0
- package/dist/lib/map-position.js.map +1 -0
- package/dist/lib/merge-hover-paint.js +10 -0
- package/dist/lib/merge-hover-paint.js.map +1 -0
- package/dist/lib/plan-crs.js +14 -0
- package/dist/lib/plan-crs.js.map +1 -0
- package/dist/lib/plan-patterns.js +14 -0
- package/dist/lib/plan-patterns.js.map +1 -0
- package/dist/lib/plan-regions.js +18 -0
- package/dist/lib/plan-regions.js.map +1 -0
- package/dist/lib/plan-status.js +14 -0
- package/dist/lib/plan-status.js.map +1 -0
- package/dist/lib/route-arrow.js +12 -0
- package/dist/lib/route-arrow.js.map +1 -0
- package/dist/lib/use-map-breakpoint.js +33 -0
- package/dist/lib/use-map-breakpoint.js.map +1 -0
- package/dist/lib/use-plan-projection.js +9 -0
- package/dist/lib/use-plan-projection.js.map +1 -0
- package/dist/lib/use-token-color.js +10 -0
- package/dist/lib/use-token-color.js.map +1 -0
- package/dist/lib/warn-once.js +10 -0
- package/dist/lib/warn-once.js.map +1 -0
- package/dist/map-annotation/anchor.js +14 -0
- package/dist/map-annotation/anchor.js.map +1 -0
- package/dist/map-annotation/index.js +21 -0
- package/dist/map-annotation/index.js.map +1 -0
- package/dist/map-annotation/map-annotation.js +18 -0
- package/dist/map-annotation/map-annotation.js.map +1 -0
- package/dist/map-arc/index.js +16 -0
- package/dist/map-arc/index.js.map +1 -0
- package/dist/map-arc/map-arc.js +13 -0
- package/dist/map-arc/map-arc.js.map +1 -0
- package/dist/map-canvas/index.css +11 -0
- package/dist/map-canvas/index.css.map +1 -0
- package/dist/map-canvas/index.js +20 -0
- package/dist/map-canvas/index.js.map +1 -0
- package/dist/map-canvas/map-canvas.css +11 -0
- package/dist/map-canvas/map-canvas.css.map +1 -0
- package/dist/map-canvas/map-canvas.js +17 -0
- package/dist/map-canvas/map-canvas.js.map +1 -0
- package/dist/map-canvas/map-context.js +11 -0
- package/dist/map-canvas/map-context.js.map +1 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js +9 -0
- package/dist/map-canvas/use-resolved-basemap-theme.js.map +1 -0
- package/dist/map-cluster-layer/index.js +12 -0
- package/dist/map-cluster-layer/index.js.map +1 -0
- package/dist/map-cluster-layer/map-cluster-layer.js +12 -0
- package/dist/map-cluster-layer/map-cluster-layer.js.map +1 -0
- package/dist/map-controls/index.js +11 -0
- package/dist/map-controls/index.js.map +1 -0
- package/dist/map-controls/map-controls.js +11 -0
- package/dist/map-controls/map-controls.js.map +1 -0
- package/dist/map-geojson/index.js +13 -0
- package/dist/map-geojson/index.js.map +1 -0
- package/dist/map-geojson/map-geojson.js +13 -0
- package/dist/map-geojson/map-geojson.js.map +1 -0
- package/dist/map-inset/index.css +11 -0
- package/dist/map-inset/index.css.map +1 -0
- package/dist/map-inset/index.js +20 -0
- package/dist/map-inset/index.js.map +1 -0
- package/dist/map-inset/map-inset.css +11 -0
- package/dist/map-inset/map-inset.css.map +1 -0
- package/dist/map-inset/map-inset.js +20 -0
- package/dist/map-inset/map-inset.js.map +1 -0
- package/dist/map-legend/index.js +14 -0
- package/dist/map-legend/index.js.map +1 -0
- package/dist/map-legend/map-corner.js +10 -0
- package/dist/map-legend/map-corner.js.map +1 -0
- package/dist/map-legend/map-legend.js +11 -0
- package/dist/map-legend/map-legend.js.map +1 -0
- package/dist/map-marker/index.js +22 -0
- package/dist/map-marker/index.js.map +1 -0
- package/dist/map-marker/map-marker.js +22 -0
- package/dist/map-marker/map-marker.js.map +1 -0
- package/dist/map-north-arrow/index.js +11 -0
- package/dist/map-north-arrow/index.js.map +1 -0
- package/dist/map-north-arrow/map-north-arrow.js +11 -0
- package/dist/map-north-arrow/map-north-arrow.js.map +1 -0
- package/dist/map-plan/index.js +30 -0
- package/dist/map-plan/index.js.map +1 -0
- package/dist/map-plan/map-plan-legend.js +10 -0
- package/dist/map-plan/map-plan-legend.js.map +1 -0
- package/dist/map-plan/map-plan-overlay.js +13 -0
- package/dist/map-plan/map-plan-overlay.js.map +1 -0
- package/dist/map-plan/map-plan-status.js +9 -0
- package/dist/map-plan/map-plan-status.js.map +1 -0
- package/dist/map-plan/map-plan-table.js +10 -0
- package/dist/map-plan/map-plan-table.js.map +1 -0
- package/dist/map-plan/use-plan-selection.js +9 -0
- package/dist/map-plan/use-plan-selection.js.map +1 -0
- package/dist/map-plan-image/index.js +11 -0
- package/dist/map-plan-image/index.js.map +1 -0
- package/dist/map-plan-image/map-plan-image.js +11 -0
- package/dist/map-plan-image/map-plan-image.js.map +1 -0
- package/dist/map-popup/index.js +12 -0
- package/dist/map-popup/index.js.map +1 -0
- package/dist/map-popup/map-popup.js +12 -0
- package/dist/map-popup/map-popup.js.map +1 -0
- package/dist/map-route/index.js +12 -0
- package/dist/map-route/index.js.map +1 -0
- package/dist/map-route/map-route.js +12 -0
- package/dist/map-route/map-route.js.map +1 -0
- package/dist/map-scale-bar/index.js +18 -0
- package/dist/map-scale-bar/index.js.map +1 -0
- package/dist/map-scale-bar/map-scale-bar.js +18 -0
- package/dist/map-scale-bar/map-scale-bar.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +83 -0
- package/src/lib/arc-math.test.ts +19 -0
- package/src/lib/arc-math.ts +6 -1
- package/src/lib/map-position.ts +77 -0
- package/src/lib/merge-hover-paint.ts +44 -13
- package/src/lib/plan-crs.parity.test.ts +39 -0
- package/src/lib/plan-crs.test.ts +248 -0
- package/src/lib/plan-crs.ts +252 -0
- package/src/lib/plan-patterns.test.ts +255 -0
- package/src/lib/plan-patterns.ts +160 -0
- package/src/lib/plan-regions.test.ts +186 -0
- package/src/lib/plan-regions.ts +209 -0
- package/src/lib/plan-status.test.ts +87 -0
- package/src/lib/plan-status.ts +147 -0
- package/src/lib/route-arrow.test.ts +88 -0
- package/src/lib/route-arrow.ts +59 -0
- package/src/lib/use-map-breakpoint.test.ts +83 -0
- package/src/lib/use-map-breakpoint.ts +238 -0
- package/src/lib/use-plan-projection.ts +157 -0
- package/src/lib/warn-once.ts +21 -0
- package/src/map-annotation/anchor.ts +92 -0
- package/src/map-annotation/index.ts +15 -0
- package/src/map-annotation/map-annotation.stories.tsx +113 -0
- package/src/map-annotation/map-annotation.test.tsx +106 -0
- package/src/map-annotation/map-annotation.tsx +356 -0
- package/src/map-arc/map-arc.tsx +26 -5
- package/src/map-canvas/map-canvas.stories.tsx +211 -0
- package/src/map-canvas/map-canvas.test.tsx +389 -0
- package/src/map-canvas/map-canvas.tsx +457 -17
- package/src/map-canvas/map-context.ts +11 -0
- package/src/map-cluster-layer/map-cluster-layer.tsx +14 -1
- package/src/map-controls/map-controls.stories.tsx +16 -0
- package/src/map-controls/map-controls.test.tsx +145 -1
- package/src/map-controls/map-controls.tsx +57 -12
- package/src/map-geojson/map-geojson.stories.tsx +153 -3
- package/src/map-geojson/map-geojson.test.tsx +453 -0
- package/src/map-geojson/map-geojson.tsx +591 -51
- package/src/map-inset/index.ts +1 -0
- package/src/map-inset/map-inset.stories.tsx +61 -0
- package/src/map-inset/map-inset.test.tsx +117 -0
- package/src/map-inset/map-inset.tsx +264 -0
- package/src/map-legend/index.ts +10 -0
- package/src/map-legend/map-corner.ts +23 -0
- package/src/map-legend/map-legend.stories.tsx +171 -0
- package/src/map-legend/map-legend.test.tsx +136 -0
- package/src/map-legend/map-legend.tsx +264 -0
- package/src/map-marker/map-marker.stories.tsx +136 -4
- package/src/map-marker/map-marker.test.tsx +328 -3
- package/src/map-marker/map-marker.tsx +370 -33
- package/src/map-north-arrow/index.ts +1 -0
- package/src/map-north-arrow/map-north-arrow.stories.tsx +65 -0
- package/src/map-north-arrow/map-north-arrow.test.tsx +39 -0
- package/src/map-north-arrow/map-north-arrow.tsx +82 -0
- package/src/map-plan/index.ts +13 -0
- package/src/map-plan/map-plan-legend.test.tsx +61 -0
- package/src/map-plan/map-plan-legend.tsx +83 -0
- package/src/map-plan/map-plan-overlay.stories.tsx +260 -0
- package/src/map-plan/map-plan-overlay.test.tsx +302 -0
- package/src/map-plan/map-plan-overlay.tsx +531 -0
- package/src/map-plan/map-plan-status.test.tsx +71 -0
- package/src/map-plan/map-plan-status.tsx +77 -0
- package/src/map-plan/map-plan-table.test.tsx +86 -0
- package/src/map-plan/map-plan-table.tsx +153 -0
- package/src/map-plan/use-plan-selection.ts +78 -0
- package/src/map-plan-image/index.ts +1 -0
- package/src/map-plan-image/map-plan-image.stories.tsx +301 -0
- package/src/map-plan-image/map-plan-image.test.tsx +176 -0
- package/src/map-plan-image/map-plan-image.tsx +279 -0
- package/src/map-popup/map-popup.tsx +15 -10
- package/src/map-route/index.ts +1 -1
- package/src/map-route/map-route.test.tsx +216 -0
- package/src/map-route/map-route.tsx +105 -6
- package/src/map-scale-bar/index.ts +9 -0
- package/src/map-scale-bar/map-scale-bar.stories.tsx +63 -0
- package/src/map-scale-bar/map-scale-bar.test.tsx +65 -0
- package/src/map-scale-bar/map-scale-bar.tsx +145 -0
- package/src/test-utils/locator-fixture.ts +75 -0
- package/src/test-utils/maplibre-mock.ts +256 -19
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/map-geojson/map-geojson.tsx"],"sourcesContent":["\"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 { mergeFeatureStatePaint } from \"../lib/merge-hover-paint\";\nimport type { PlanPoint } from \"../lib/plan-crs\";\nimport { useTokenColor } from \"../lib/use-token-color\";\nimport { warnMapOnce } from \"../lib/warn-once\";\n\n/** Invisible line width, in px, that makes a hairline outline or a wall touchable. */\nconst DEFAULT_HIT_WIDTH = 16;\n/** Half-size, in px, of the box a plan click is allowed to miss by. */\nconst DEFAULT_PLAN_PICK_PADDING = 8;\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/**\n * A hatch drawn over the fill — the locator-map \"area marker\" look. `width` is\n * a stripe's thickness and `gap` the space between stripes, both in CSS px\n * measured across the 45° stripes. Stripes take the fill colour (a plain\n * `fill-color`; an expression falls back to the `--foreground` token).\n */\nexport interface MapGeoJSONPattern {\n kind: \"stripes\";\n /** Stripe thickness in px (default 2). */\n width?: number;\n /** Space between stripes in px (default 4). */\n gap?: number;\n}\n\n/**\n * A soft glow around the area's edge — a second, blurred line layer under the\n * fill, in the fill colour. `width` is how far the glow reaches out, in px.\n */\nexport interface MapGeoJSONVignette {\n /** Glow reach in px (default 12). */\n width?: number;\n /** Glow opacity, 0–1 (default 0.35). */\n opacity?: number;\n}\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 /**\n * The cursor in PLAN units, when the canvas declares a `plan` extent —\n * otherwise `null`. What a plan consumer actually wants: metres along the\n * hall, not a synthesized longitude.\n */\n plan: PlanPoint | null;\n /**\n * The underlying MapLibre mouse event for advanced use cases. `null` when\n * the feature was reached from the KEYBOARD rather than a pointer — there is\n * no mouse event behind that (c-4/c-2, WCAG 2.1.1).\n */\n originalEvent: MapLibreGL.MapLayerMouseEvent | null;\n};\n\nexport type MapGeoJSONProps<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties> = {\n /**\n * GeoJSON data (FeatureCollection, Feature, Geometry) or a URL to fetch it\n * from. On a canvas with a `plan` extent the coordinates are PLAN units —\n * `[x, y]` in the plan's own drawing units — and are converted for you.\n */\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 and\n * selected feature-state (`fillHoverPaint`, `selectedId`) and stable\n * `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 /**\n * The fill layer's opacity, 0–1 — shorthand for `fillPaint[\"fill-opacity\"]`\n * (which wins when both are set). Default 1, or 0.25 with a `pattern`, so\n * the stripes read over a tint of the same colour.\n */\n fillOpacity?: number;\n /** Hatch the areas with stripes (see {@link MapGeoJSONPattern}). */\n pattern?: MapGeoJSONPattern;\n /** Glow around the areas' edges (see {@link MapGeoJSONVignette}). */\n vignette?: MapGeoJSONVignette;\n /** The outline equivalent of `fillHoverPaint`. Requires `promoteId`. */\n lineHoverPaint?: MapLinePaint;\n /**\n * Paint merged onto the fill layer for the feature(s) named by `selectedId`.\n * Applied outside hover, so a selected feature still looks selected while the\n * cursor is over it. Requires `promoteId`.\n */\n fillSelectedPaint?: MapFillPaint;\n /** The outline equivalent of `fillSelectedPaint`. Requires `promoteId`. */\n lineSelectedPaint?: MapLinePaint;\n /**\n * The selected feature id(s) — the promoted `promoteId` values. Selection is\n * the consumer's state; this only paints it.\n */\n selectedId?: string | number | readonly (string | number)[] | null;\n /**\n * Take over the highlight: when set, the hover paint follows THIS id instead\n * of the pointer. That is how `MapPlanOverlay` makes a keyboard-focused room\n * light up exactly as a hovered one does — one channel, one appearance.\n */\n hoveredId?: string | number | null;\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 /**\n * Whether features respond to mouse events. Defaults to `false` on a\n * geographic canvas and `true` on a plan canvas, where the shapes ARE the\n * subject rather than a backdrop.\n */\n interactive?: boolean;\n /**\n * The text of a feature in the keyboard list an `interactive` map renders —\n * one visually-hidden button per feature, so the values a pointer reveals\n * on hover are reachable by Tab and readable by a screen reader (WCAG 2.1.1\n * and 1.3.1). Say what the region is AND what it is worth — the same\n * sentence a hover readout prints. Defaults to the promoted id.\n */\n featureLabel?: (feature: MapGeoJSONFeature<P>, index: number) => string;\n /**\n * How far, in px, a click may miss a shape and still land on it. Defaults to\n * 8 px on a plan (so a 20 px seat is a 36 px touch target) and 0 elsewhere.\n * An exact hit always wins over a padded one.\n */\n pickPadding?: number;\n /**\n * Width, in px, of the invisible line that makes an outline-only shape\n * touchable (default 16). Only used when `fillPaint` is `false`.\n */\n hitWidth?: number;\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 *\n * On a canvas that declares a plan extent it is also the shape layer of a\n * custom (non-geographic) map: rooms, machine cells, aisles and seats written\n * in the plan's own units.\n *\n * ```tsx\n * <MapCanvas blank plan={{ width: 2400, height: 1600, unit: \"cm\" }}>\n * <MapGeoJSON data={roomsInCentimetres} promoteId=\"id\" selectedId={roomId} />\n * </MapCanvas>\n * ```\n */\nexport function MapGeoJSON<P extends GeoJSON.GeoJsonProperties = GeoJSON.GeoJsonProperties>({\n data,\n id: propId,\n promoteId,\n fillPaint,\n linePaint,\n fillHoverPaint,\n fillOpacity,\n pattern,\n vignette,\n lineHoverPaint,\n fillSelectedPaint,\n lineSelectedPaint,\n selectedId,\n hoveredId,\n onClick,\n onHover,\n interactive: interactiveProp,\n featureLabel,\n pickPadding,\n hitWidth = DEFAULT_HIT_WIDTH,\n beforeId,\n}: MapGeoJSONProps<P>) {\n const { map, isLoaded, plan } = 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 const patternLayerId = `geojson-pattern-${id}`;\n const vignetteLayerId = `geojson-vignette-${id}`;\n const patternImageId = `geojson-pattern-image-${id}`;\n const hitLayerId = `geojson-hit-${id}`;\n\n const interactive = interactiveProp ?? plan != null;\n const pickPad = pickPadding ?? (plan ? DEFAULT_PLAN_PICK_PADDING : 0);\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 const defaultInk = useTokenColor(\"--foreground\");\n\n const showFill = fillPaint !== false;\n const showLine = linePaint !== false;\n // Without a fill there is nothing to click: a hairline outline is a 1 px\n // target and a wall or conveyor has no interior at all.\n const showHit = interactive && !showFill;\n\n // Plan units in, Mercator out. A URL cannot be converted — the fetch happens\n // inside the engine — so say so once instead of drawing the plan in the\n // Atlantic.\n if (plan && typeof data === \"string\") {\n warnMapOnce(\n \"geojson-plan-url\",\n \"<MapGeoJSON data={url}> cannot be converted to plan units. Fetch the GeoJSON yourself and pass the object, or pass coordinates already in lng/lat.\",\n );\n }\n if (interactive && !promoteId) {\n warnMapOnce(\n \"geojson-promote-id\",\n \"<MapGeoJSON interactive> needs `promoteId` to tell features apart: without it hover and selection paint nothing.\",\n );\n }\n\n const resolvedData = useMemo(\n () => (plan && typeof data !== \"string\" ? plan.toGeoJSON(data) : data),\n [plan, data],\n );\n\n const baseOpacity = fillOpacity ?? (pattern ? 0.25 : undefined);\n const mergedFillPaint = useMemo(\n () =>\n mergeFeatureStatePaint(\n {\n \"fill-color\": defaultFill,\n ...(baseOpacity !== undefined ? { \"fill-opacity\": baseOpacity } : {}),\n ...(fillPaint || {}),\n },\n { hover: fillHoverPaint, selected: fillSelectedPaint },\n ),\n [defaultFill, baseOpacity, fillPaint, fillHoverPaint, fillSelectedPaint],\n );\n // The colour stripes and the glow take: the fill's own plain colour, or ink.\n const configuredFill = fillPaint ? fillPaint[\"fill-color\"] : undefined;\n const areaColor =\n configuredFill === undefined\n ? defaultFill\n : typeof configuredFill === \"string\"\n ? configuredFill\n : defaultInk;\n const stripeWidth = Math.max(1, pattern?.width ?? 2);\n const stripeGap = Math.max(0, pattern?.gap ?? 4);\n const vignetteWidth = Math.max(0, vignette?.width ?? 12);\n const vignetteOpacity = Math.min(1, Math.max(0, vignette?.opacity ?? 0.35));\n const hasPattern = pattern !== undefined;\n const hasVignette = vignette !== undefined;\n // The tile drawn last; a new one is drawn only when colour or size change.\n const patternDrawnRef = useRef<string | null>(null);\n const mergedLinePaint = useMemo(\n () =>\n mergeFeatureStatePaint(\n {\n \"line-color\": defaultLine,\n \"line-width\": 0.5,\n ...(linePaint || {}),\n },\n { hover: lineHoverPaint, selected: lineSelectedPaint },\n ),\n [defaultLine, linePaint, lineHoverPaint, lineSelectedPaint],\n );\n const latestRef = useRef({ onClick, onHover, plan });\n latestRef.current = { onClick, onHover, plan };\n // c-2: the pointer path owns the hover feature-state inside its effect; the\n // keyboard list below drives the SAME highlight through this handle, so a\n // focused region lights up exactly as a hovered one does.\n const setHoverRef = useRef<((next: string | number | null) => void) | null>(null);\n\n // Add source on mount.\n useEffect(() => {\n if (!isLoaded || !map) return;\n\n map.addSource(sourceId, {\n type: \"geojson\",\n data: resolvedData as never,\n ...(promoteId ? { promoteId } : {}),\n });\n\n return () => {\n try {\n if (map.getLayer(hitLayerId)) map.removeLayer(hitLayerId);\n if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);\n if (map.getLayer(patternLayerId)) map.removeLayer(patternLayerId);\n if (map.getLayer(fillLayerId)) map.removeLayer(fillLayerId);\n if (map.getLayer(vignetteLayerId)) map.removeLayer(vignetteLayerId);\n if (map.hasImage(patternImageId)) map.removeImage(patternImageId);\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(resolvedData as never);\n }, [isLoaded, map, resolvedData, 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 // Invisible, but still picked: MapLibre hit-tests geometry, not opacity.\n if (showHit && !map.getLayer(hitLayerId)) {\n map.addLayer(\n {\n id: hitLayerId,\n type: \"line\",\n source: sourceId,\n paint: { \"line-color\": defaultLine, \"line-opacity\": 0, \"line-width\": hitWidth },\n },\n beforeId,\n );\n } else if (!showHit && map.getLayer(hitLayerId)) {\n map.removeLayer(hitLayerId);\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 // Vignette: a blurred line UNDER the fill, so the glow reads outside it.\n const vignettePaint: MapLinePaint = {\n \"line-color\": areaColor,\n \"line-width\": vignetteWidth * 2,\n \"line-blur\": vignetteWidth,\n \"line-opacity\": vignetteOpacity,\n };\n if (hasVignette && !map.getLayer(vignetteLayerId)) {\n const before = map.getLayer(fillLayerId)\n ? fillLayerId\n : map.getLayer(lineLayerId)\n ? lineLayerId\n : beforeId;\n map.addLayer(\n { id: vignetteLayerId, type: \"line\", source: sourceId, paint: vignettePaint },\n before,\n );\n } else if (hasVignette) {\n for (const [key, value] of Object.entries(vignettePaint)) {\n map.setPaintProperty(vignetteLayerId, key as keyof MapLinePaint, value as never);\n }\n } else if (map.getLayer(vignetteLayerId)) {\n map.removeLayer(vignetteLayerId);\n }\n\n // Pattern: a stripe tile drawn in the area colour, on its own fill layer\n // between the fill and the outline. Re-drawn when the colour or size changes.\n if (hasPattern) {\n const drawKey = `${areaColor}|${stripeWidth}|${stripeGap}`;\n if (patternDrawnRef.current !== drawKey || !map.hasImage(patternImageId)) {\n const image = stripeImage(areaColor, stripeWidth, stripeGap);\n if (image) {\n if (map.hasImage(patternImageId)) map.removeImage(patternImageId);\n map.addImage(patternImageId, image.data, { pixelRatio: image.pixelRatio });\n patternDrawnRef.current = drawKey;\n }\n }\n if (map.hasImage(patternImageId)) {\n if (!map.getLayer(patternLayerId)) {\n map.addLayer(\n {\n id: patternLayerId,\n type: \"fill\",\n source: sourceId,\n paint: { \"fill-pattern\": patternImageId },\n },\n map.getLayer(lineLayerId) ? lineLayerId : beforeId,\n );\n }\n }\n } else {\n if (map.getLayer(patternLayerId)) map.removeLayer(patternLayerId);\n if (map.hasImage(patternImageId)) map.removeImage(patternImageId);\n patternDrawnRef.current = null;\n }\n\n if (showHit && map.getLayer(hitLayerId)) {\n map.setPaintProperty(hitLayerId, \"line-width\", hitWidth);\n }\n }, [\n areaColor,\n hasPattern,\n stripeWidth,\n stripeGap,\n hasVignette,\n vignetteWidth,\n vignetteOpacity,\n patternLayerId,\n vignetteLayerId,\n patternImageId,\n isLoaded,\n map,\n sourceId,\n fillLayerId,\n lineLayerId,\n hitLayerId,\n showFill,\n showLine,\n showHit,\n hitWidth,\n defaultLine,\n mergedFillPaint,\n mergedLinePaint,\n beforeId,\n ]);\n\n // Selection is the consumer's state; mirror it into feature-state and clear\n // whatever fell out of the set, so paint never sticks to a deselected shape.\n const selectedKey = useMemo(() => {\n if (selectedId == null) return \"\";\n return (Array.isArray(selectedId) ? selectedId : [selectedId]).join(\"\u0000\");\n }, [selectedId]);\n const appliedSelectionRef = useRef<(string | number)[]>([]);\n useEffect(() => {\n if (!isLoaded || !map || !map.getSource(sourceId)) return;\n const next =\n selectedId == null\n ? []\n : ((Array.isArray(selectedId) ? [...selectedId] : [selectedId]) as (string | number)[]);\n\n for (const previous of appliedSelectionRef.current) {\n if (!next.includes(previous)) {\n map.setFeatureState({ source: sourceId, id: previous }, { selected: false });\n }\n }\n for (const current of next) {\n map.setFeatureState({ source: sourceId, id: current }, { selected: true });\n }\n appliedSelectionRef.current = next;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on the ids themselves so an inline array literal doesn't re-run this\n }, [isLoaded, map, sourceId, selectedKey]);\n\n // A controlled highlight: the overlay's keyboard focus and the pointer must\n // produce the same appearance, so when `hoveredId` is given the pointer stops\n // writing hover state and this effect owns it instead.\n const hoverIsControlled = hoveredId !== undefined;\n const appliedHoverRef = useRef<string | number | null>(null);\n useEffect(() => {\n if (!hoverIsControlled || !isLoaded || !map || !map.getSource(sourceId)) return;\n const previous = appliedHoverRef.current;\n if (previous != null && previous !== hoveredId) {\n map.setFeatureState({ source: sourceId, id: previous }, { hover: false });\n }\n if (hoveredId != null) {\n map.setFeatureState({ source: sourceId, id: hoveredId }, { hover: true });\n }\n appliedHoverRef.current = hoveredId ?? null;\n }, [hoverIsControlled, hoveredId, isLoaded, map, sourceId]);\n\n // Interaction handlers, bound to whichever layer carries the shape: the fill,\n // or the invisible hit line when there is no fill.\n const pickLayerId = showFill ? fillLayerId : hitLayerId;\n useEffect(() => {\n if (!isLoaded || !map || !interactive) return;\n if (!showFill && !showHit) return;\n\n let pointerHoverId: string | number | null = null;\n\n const setHover = (next: string | number | null) => {\n if (hoverIsControlled) return;\n if (next === pointerHoverId) return;\n const sourceExists = !!map.getSource(sourceId);\n if (pointerHoverId != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: pointerHoverId }, { hover: false });\n }\n pointerHoverId = next;\n if (next != null && sourceExists) {\n map.setFeatureState({ source: sourceId, id: next }, { hover: true });\n }\n };\n\n const payload = (\n feature: MapLibreGL.MapGeoJSONFeature,\n e: MapLibreGL.MapLayerMouseEvent,\n ): MapGeoJSONEvent<P> => {\n const planCrs = latestRef.current.plan;\n return {\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude: e.lngLat.lng,\n latitude: e.lngLat.lat,\n plan: planCrs ? planCrs.toPlan([e.lngLat.lng, e.lngLat.lat]) : null,\n originalEvent: e,\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 === pointerHoverId) return;\n setHover(featureId ?? null);\n latestRef.current.onHover?.(payload(feature, e));\n };\n\n const handleMouseLeave = () => {\n setHover(null);\n map.getCanvas().style.cursor = \"\";\n latestRef.current.onHover?.(null);\n };\n\n /**\n * An exact hit always wins; only when nothing is under the pointer does the\n * padded box get a say. On a plan that turns a 20 px seat into a target a\n * fingertip can actually land on.\n */\n const pickPadded = (e: MapLibreGL.MapLayerMouseEvent) => {\n const exact = map.queryRenderedFeatures(e.point, { layers: [pickLayerId] });\n if (exact.length > 0) return exact[0];\n const { x, y } = e.point;\n const box = [\n [x - pickPad, y - pickPad],\n [x + pickPad, y + pickPad],\n ] as unknown as Parameters<typeof map.queryRenderedFeatures>[0];\n return map.queryRenderedFeatures(box, { layers: [pickLayerId] })[0];\n };\n\n setHoverRef.current = setHover;\n\n const handleClick = (e: MapLibreGL.MapLayerMouseEvent) => {\n const feature = pickPad > 0 ? pickPadded(e) : e.features?.[0];\n if (!feature) return;\n latestRef.current.onClick?.(payload(feature, e));\n };\n\n map.on(\"mousemove\", pickLayerId, handleMouseMove);\n map.on(\"mouseleave\", pickLayerId, handleMouseLeave);\n // A padded click must hear about the misses too, so it listens on the map.\n if (pickPad > 0) {\n map.on(\"click\", handleClick);\n } else {\n map.on(\"click\", pickLayerId, handleClick);\n }\n\n return () => {\n map.off(\"mousemove\", pickLayerId, handleMouseMove);\n map.off(\"mouseleave\", pickLayerId, handleMouseLeave);\n if (pickPad > 0) {\n map.off(\"click\", handleClick);\n } else {\n map.off(\"click\", pickLayerId, handleClick);\n }\n setHover(null);\n setHoverRef.current = null;\n map.getCanvas().style.cursor = \"\";\n };\n }, [\n isLoaded,\n map,\n pickLayerId,\n sourceId,\n interactive,\n showFill,\n showHit,\n pickPad,\n hoverIsControlled,\n ]);\n\n // c-2 (WCAG 2.1.1, 1.3.1): the fill layer lives in WebGL, so an interactive\n // choropleth had no DOM at all — its values were reachable by mouse only,\n // with no tab stop, no text alternative and nothing for a screen reader to\n // read. One visually-hidden button per feature gives every region a tab stop\n // whose name IS its value; focusing it lights the region and fires the same\n // `onHover` the pointer does, so a host readout follows the keyboard for\n // free. Hidden, not absent: the map itself is the picture.\n const keyboardFeatures = useMemo(\n () => (interactive && showFill ? listKeyboardFeatures<P>(data, promoteId) : []),\n [interactive, showFill, data, promoteId],\n );\n\n const reach = (entry: KeyboardFeature<P>) => {\n setHoverRef.current?.(entry.id ?? null);\n latestRef.current.onHover?.({\n feature: entry.feature,\n longitude: entry.longitude,\n latitude: entry.latitude,\n // A plan map's keyboard path is `MapPlanOverlay`'s job, so this list\n // never has plan units to hand back.\n plan: null,\n originalEvent: null,\n });\n };\n\n if (keyboardFeatures.length === 0) {\n return null;\n }\n\n return (\n <div className=\"sr-only\" data-slot=\"map-geojson-keyboard-list\">\n <ul>\n {keyboardFeatures.map((entry, index) => (\n <li key={entry.key}>\n <button\n onBlur={() => {\n setHoverRef.current?.(null);\n latestRef.current.onHover?.(null);\n }}\n onClick={() =>\n latestRef.current.onClick?.({\n feature: entry.feature,\n longitude: entry.longitude,\n latitude: entry.latitude,\n plan: null,\n originalEvent: null,\n })\n }\n onFocus={() => reach(entry)}\n type=\"button\"\n >\n {featureLabel?.(entry.feature, index) ?? entry.fallbackLabel}\n </button>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/** One row of the keyboard list: a feature, where it sits and what it is called. */\ninterface KeyboardFeature<P extends GeoJSON.GeoJsonProperties> {\n key: string;\n id: string | number | undefined;\n feature: MapGeoJSONFeature<P>;\n longitude: number;\n latitude: number;\n fallbackLabel: string;\n}\n\n/** Every coordinate in a geometry, flattened — enough for a bounding box. */\nfunction geometryPositions(geometry: GeoJSON.Geometry): GeoJSON.Position[] {\n if (geometry.type === \"GeometryCollection\") {\n return geometry.geometries.flatMap(geometryPositions);\n }\n if (geometry.type === \"Point\") {\n return [geometry.coordinates];\n }\n // Every remaining geometry nests Positions 1–3 arrays deep.\n const flatten = (value: unknown): GeoJSON.Position[] =>\n Array.isArray(value) && typeof value[0] === \"number\"\n ? [value as GeoJSON.Position]\n : Array.isArray(value)\n ? value.flatMap(flatten)\n : [];\n return flatten(geometry.coordinates);\n}\n\n/** The centre of a geometry's bounding box — where a keyboard \"hover\" lands. */\nfunction geometryCentre(geometry: GeoJSON.Geometry): [number, number] {\n const positions = geometryPositions(geometry);\n if (positions.length === 0) return [0, 0];\n let west = Number.POSITIVE_INFINITY;\n let east = Number.NEGATIVE_INFINITY;\n let south = Number.POSITIVE_INFINITY;\n let north = Number.NEGATIVE_INFINITY;\n for (const [lng, lat] of positions) {\n west = Math.min(west, lng ?? 0);\n east = Math.max(east, lng ?? 0);\n south = Math.min(south, lat ?? 0);\n north = Math.max(north, lat ?? 0);\n }\n return [(west + east) / 2, (south + north) / 2];\n}\n\n/**\n * The features a keyboard list can offer. A `data` URL is fetched by MapLibre\n * itself and never reaches React, and a bare Geometry carries no properties to\n * read — both give an empty list rather than a row that says nothing.\n */\nfunction listKeyboardFeatures<P extends GeoJSON.GeoJsonProperties>(\n data: MapGeoJSONData<P>,\n promoteId: string | undefined,\n): KeyboardFeature<P>[] {\n if (typeof data === \"string\") return [];\n const features =\n data.type === \"FeatureCollection\" ? data.features : data.type === \"Feature\" ? [data] : [];\n return features.map((feature, index) => {\n const promoted = promoteId ? feature.properties?.[promoteId] : undefined;\n const id =\n typeof promoted === \"string\" || typeof promoted === \"number\"\n ? promoted\n : typeof feature.id === \"string\" || typeof feature.id === \"number\"\n ? feature.id\n : undefined;\n const [longitude, latitude] = geometryCentre(feature.geometry);\n return {\n key: `${id ?? index}`,\n id,\n feature: feature as unknown as MapGeoJSONFeature<P>,\n longitude,\n latitude,\n fallbackLabel: `${id ?? index + 1}`,\n };\n });\n}\n\n/**\n * One seamless tile of 45° stripes, `width` thick with `gap` between them\n * (both measured across the stripes), in `color` — any CSS colour, so a\n * resolved token works as-is. `null` where no 2D canvas exists (tests, SSR).\n */\nfunction stripeImage(\n color: string,\n width: number,\n gap: number,\n): { data: ImageData; pixelRatio: number } | null {\n if (typeof document === \"undefined\") return null;\n const pixelRatio = Math.max(1, Math.round(globalThis.devicePixelRatio || 1));\n // A 45° stripe period of (width + gap) across the stripes is √2 × that along x.\n const size = Math.max(2, Math.round((width + gap) * Math.SQRT2 * pixelRatio));\n const canvas = document.createElement(\"canvas\");\n canvas.width = size;\n canvas.height = size;\n let context: CanvasRenderingContext2D | null = null;\n try {\n context = canvas.getContext(\"2d\");\n } catch {\n context = null;\n }\n if (!context) return null;\n context.strokeStyle = color;\n context.lineWidth = width * pixelRatio;\n context.lineCap = \"square\";\n context.beginPath();\n // x + y = k·size for k = 0, 1, 2 covers the tile, corners included.\n for (const k of [0, 1, 2]) {\n context.moveTo(k * size - size, size);\n context.lineTo(k * size, 0);\n }\n context.stroke();\n return { data: context.getImageData(0, 0, size, size), pixelRatio };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAGA,SAAS,WAAW,OAAO,SAAS,cAAc;AAgqBtC;AAvpBZ,IAAM,oBAAoB;AAE1B,IAAM,4BAA4B;AAgL3B,SAAS,WAA4E;AAAA,EAC1F;AAAA,EACA,IAAI;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,GAAuB;AACrB,QAAM,EAAE,KAAK,UAAU,KAAK,IAAI,OAAO;AACvC,QAAM,SAAS,MAAM;AACrB,QAAM,KAAK,UAAU;AACrB,QAAM,WAAW,kBAAkB,EAAE;AACrC,QAAM,cAAc,gBAAgB,EAAE;AACtC,QAAM,cAAc,gBAAgB,EAAE;AACtC,QAAM,iBAAiB,mBAAmB,EAAE;AAC5C,QAAM,kBAAkB,oBAAoB,EAAE;AAC9C,QAAM,iBAAiB,yBAAyB,EAAE;AAClD,QAAM,aAAa,eAAe,EAAE;AAEpC,QAAM,cAAc,mBAAmB,QAAQ;AAC/C,QAAM,UAAU,gBAAgB,OAAO,4BAA4B;AAInE,QAAM,cAAc,cAAc,UAAU;AAC5C,QAAM,cAAc,cAAc,cAAc;AAChD,QAAM,aAAa,cAAc,cAAc;AAE/C,QAAM,WAAW,cAAc;AAC/B,QAAM,WAAW,cAAc;AAG/B,QAAM,UAAU,eAAe,CAAC;AAKhC,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,eAAe,CAAC,WAAW;AAC7B;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe;AAAA,IACnB,MAAO,QAAQ,OAAO,SAAS,WAAW,KAAK,UAAU,IAAI,IAAI;AAAA,IACjE,CAAC,MAAM,IAAI;AAAA,EACb;AAEA,QAAM,cAAc,gBAAgB,UAAU,OAAO;AACrD,QAAM,kBAAkB;AAAA,IACtB,MACE;AAAA,MACE;AAAA,QACE,cAAc;AAAA,QACd,GAAI,gBAAgB,SAAY,EAAE,gBAAgB,YAAY,IAAI,CAAC;AAAA,QACnE,GAAI,aAAa,CAAC;AAAA,MACpB;AAAA,MACA,EAAE,OAAO,gBAAgB,UAAU,kBAAkB;AAAA,IACvD;AAAA,IACF,CAAC,aAAa,aAAa,WAAW,gBAAgB,iBAAiB;AAAA,EACzE;AAEA,QAAM,iBAAiB,YAAY,UAAU,YAAY,IAAI;AAC7D,QAAM,YACJ,mBAAmB,SACf,cACA,OAAO,mBAAmB,WACxB,iBACA;AACR,QAAM,cAAc,KAAK,IAAI,GAAG,SAAS,SAAS,CAAC;AACnD,QAAM,YAAY,KAAK,IAAI,GAAG,SAAS,OAAO,CAAC;AAC/C,QAAM,gBAAgB,KAAK,IAAI,GAAG,UAAU,SAAS,EAAE;AACvD,QAAM,kBAAkB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,WAAW,IAAI,CAAC;AAC1E,QAAM,aAAa,YAAY;AAC/B,QAAM,cAAc,aAAa;AAEjC,QAAM,kBAAkB,OAAsB,IAAI;AAClD,QAAM,kBAAkB;AAAA,IACtB,MACE;AAAA,MACE;AAAA,QACE,cAAc;AAAA,QACd,cAAc;AAAA,QACd,GAAI,aAAa,CAAC;AAAA,MACpB;AAAA,MACA,EAAE,OAAO,gBAAgB,UAAU,kBAAkB;AAAA,IACvD;AAAA,IACF,CAAC,aAAa,WAAW,gBAAgB,iBAAiB;AAAA,EAC5D;AACA,QAAM,YAAY,OAAO,EAAE,SAAS,SAAS,KAAK,CAAC;AACnD,YAAU,UAAU,EAAE,SAAS,SAAS,KAAK;AAI7C,QAAM,cAAc,OAAwD,IAAI;AAGhF,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,IAAK;AAEvB,QAAI,UAAU,UAAU;AAAA,MACtB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACnC,CAAC;AAED,WAAO,MAAM;AACX,UAAI;AACF,YAAI,IAAI,SAAS,UAAU,EAAG,KAAI,YAAY,UAAU;AACxD,YAAI,IAAI,SAAS,WAAW,EAAG,KAAI,YAAY,WAAW;AAC1D,YAAI,IAAI,SAAS,cAAc,EAAG,KAAI,YAAY,cAAc;AAChE,YAAI,IAAI,SAAS,WAAW,EAAG,KAAI,YAAY,WAAW;AAC1D,YAAI,IAAI,SAAS,eAAe,EAAG,KAAI,YAAY,eAAe;AAClE,YAAI,IAAI,SAAS,cAAc,EAAG,KAAI,YAAY,cAAc;AAChE,YAAI,IAAI,UAAU,QAAQ,EAAG,KAAI,aAAa,QAAQ;AAAA,MACxD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EAEF,GAAG,CAAC,UAAU,GAAG,CAAC;AAGlB,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,IAAK;AACvB,UAAM,SAAS,IAAI,UAAU,QAAQ;AACrC,YAAQ,QAAQ,YAAqB;AAAA,EACvC,GAAG,CAAC,UAAU,KAAK,cAAc,QAAQ,CAAC;AAG1C,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,IAAK;AAEvB,UAAM,SAAS,IAAI,UAAU,QAAQ;AACrC,QAAI,CAAC,OAAQ;AAEb,QAAI,YAAY,CAAC,IAAI,SAAS,WAAW,GAAG;AAC1C,UAAI;AAAA,QACF;AAAA,UACE,IAAI;AAAA,UACJ,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,IACF,WAAW,CAAC,YAAY,IAAI,SAAS,WAAW,GAAG;AACjD,UAAI,YAAY,WAAW;AAAA,IAC7B;AAEA,QAAI,YAAY,CAAC,IAAI,SAAS,WAAW,GAAG;AAC1C,UAAI;AAAA,QACF;AAAA,UACE,IAAI;AAAA,UACJ,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,IACF,WAAW,CAAC,YAAY,IAAI,SAAS,WAAW,GAAG;AACjD,UAAI,YAAY,WAAW;AAAA,IAC7B;AAGA,QAAI,WAAW,CAAC,IAAI,SAAS,UAAU,GAAG;AACxC,UAAI;AAAA,QACF;AAAA,UACE,IAAI;AAAA,UACJ,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,OAAO,EAAE,cAAc,aAAa,gBAAgB,GAAG,cAAc,SAAS;AAAA,QAChF;AAAA,QACA;AAAA,MACF;AAAA,IACF,WAAW,CAAC,WAAW,IAAI,SAAS,UAAU,GAAG;AAC/C,UAAI,YAAY,UAAU;AAAA,IAC5B;AAEA,QAAI,YAAY,IAAI,SAAS,WAAW,GAAG;AACzC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,eAAe,GAAG;AAC1D,YAAI,iBAAiB,aAAa,KAA2B,KAAc;AAAA,MAC7E;AAAA,IACF;AACA,QAAI,YAAY,IAAI,SAAS,WAAW,GAAG;AACzC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,eAAe,GAAG;AAC1D,YAAI,iBAAiB,aAAa,KAA2B,KAAc;AAAA,MAC7E;AAAA,IACF;AAGA,UAAM,gBAA8B;AAAA,MAClC,cAAc;AAAA,MACd,cAAc,gBAAgB;AAAA,MAC9B,aAAa;AAAA,MACb,gBAAgB;AAAA,IAClB;AACA,QAAI,eAAe,CAAC,IAAI,SAAS,eAAe,GAAG;AACjD,YAAM,SAAS,IAAI,SAAS,WAAW,IACnC,cACA,IAAI,SAAS,WAAW,IACtB,cACA;AACN,UAAI;AAAA,QACF,EAAE,IAAI,iBAAiB,MAAM,QAAQ,QAAQ,UAAU,OAAO,cAAc;AAAA,QAC5E;AAAA,MACF;AAAA,IACF,WAAW,aAAa;AACtB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,aAAa,GAAG;AACxD,YAAI,iBAAiB,iBAAiB,KAA2B,KAAc;AAAA,MACjF;AAAA,IACF,WAAW,IAAI,SAAS,eAAe,GAAG;AACxC,UAAI,YAAY,eAAe;AAAA,IACjC;AAIA,QAAI,YAAY;AACd,YAAM,UAAU,GAAG,SAAS,IAAI,WAAW,IAAI,SAAS;AACxD,UAAI,gBAAgB,YAAY,WAAW,CAAC,IAAI,SAAS,cAAc,GAAG;AACxE,cAAM,QAAQ,YAAY,WAAW,aAAa,SAAS;AAC3D,YAAI,OAAO;AACT,cAAI,IAAI,SAAS,cAAc,EAAG,KAAI,YAAY,cAAc;AAChE,cAAI,SAAS,gBAAgB,MAAM,MAAM,EAAE,YAAY,MAAM,WAAW,CAAC;AACzE,0BAAgB,UAAU;AAAA,QAC5B;AAAA,MACF;AACA,UAAI,IAAI,SAAS,cAAc,GAAG;AAChC,YAAI,CAAC,IAAI,SAAS,cAAc,GAAG;AACjC,cAAI;AAAA,YACF;AAAA,cACE,IAAI;AAAA,cACJ,MAAM;AAAA,cACN,QAAQ;AAAA,cACR,OAAO,EAAE,gBAAgB,eAAe;AAAA,YAC1C;AAAA,YACA,IAAI,SAAS,WAAW,IAAI,cAAc;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,UAAI,IAAI,SAAS,cAAc,EAAG,KAAI,YAAY,cAAc;AAChE,UAAI,IAAI,SAAS,cAAc,EAAG,KAAI,YAAY,cAAc;AAChE,sBAAgB,UAAU;AAAA,IAC5B;AAEA,QAAI,WAAW,IAAI,SAAS,UAAU,GAAG;AACvC,UAAI,iBAAiB,YAAY,cAAc,QAAQ;AAAA,IACzD;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAID,QAAM,cAAc,QAAQ,MAAM;AAChC,QAAI,cAAc,KAAM,QAAO;AAC/B,YAAQ,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU,GAAG,KAAK,IAAG;AAAA,EACzE,GAAG,CAAC,UAAU,CAAC;AACf,QAAM,sBAAsB,OAA4B,CAAC,CAAC;AAC1D,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,UAAU,QAAQ,EAAG;AACnD,UAAM,OACJ,cAAc,OACV,CAAC,IACC,MAAM,QAAQ,UAAU,IAAI,CAAC,GAAG,UAAU,IAAI,CAAC,UAAU;AAEjE,eAAW,YAAY,oBAAoB,SAAS;AAClD,UAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,YAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,SAAS,GAAG,EAAE,UAAU,MAAM,CAAC;AAAA,MAC7E;AAAA,IACF;AACA,eAAW,WAAW,MAAM;AAC1B,UAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,QAAQ,GAAG,EAAE,UAAU,KAAK,CAAC;AAAA,IAC3E;AACA,wBAAoB,UAAU;AAAA,EAEhC,GAAG,CAAC,UAAU,KAAK,UAAU,WAAW,CAAC;AAKzC,QAAM,oBAAoB,cAAc;AACxC,QAAM,kBAAkB,OAA+B,IAAI;AAC3D,YAAU,MAAM;AACd,QAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,UAAU,QAAQ,EAAG;AACzE,UAAM,WAAW,gBAAgB;AACjC,QAAI,YAAY,QAAQ,aAAa,WAAW;AAC9C,UAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,SAAS,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,IAC1E;AACA,QAAI,aAAa,MAAM;AACrB,UAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,UAAU,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IAC1E;AACA,oBAAgB,UAAU,aAAa;AAAA,EACzC,GAAG,CAAC,mBAAmB,WAAW,UAAU,KAAK,QAAQ,CAAC;AAI1D,QAAM,cAAc,WAAW,cAAc;AAC7C,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,OAAO,CAAC,YAAa;AACvC,QAAI,CAAC,YAAY,CAAC,QAAS;AAE3B,QAAI,iBAAyC;AAE7C,UAAM,WAAW,CAAC,SAAiC;AACjD,UAAI,kBAAmB;AACvB,UAAI,SAAS,eAAgB;AAC7B,YAAM,eAAe,CAAC,CAAC,IAAI,UAAU,QAAQ;AAC7C,UAAI,kBAAkB,QAAQ,cAAc;AAC1C,YAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,eAAe,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,MAChF;AACA,uBAAiB;AACjB,UAAI,QAAQ,QAAQ,cAAc;AAChC,YAAI,gBAAgB,EAAE,QAAQ,UAAU,IAAI,KAAK,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,MACrE;AAAA,IACF;AAEA,UAAM,UAAU,CACd,SACA,MACuB;AACvB,YAAM,UAAU,UAAU,QAAQ;AAClC,aAAO;AAAA,QACL;AAAA,QACA,WAAW,EAAE,OAAO;AAAA,QACpB,UAAU,EAAE,OAAO;AAAA,QACnB,MAAM,UAAU,QAAQ,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,OAAO,GAAG,CAAC,IAAI;AAAA,QAC/D,eAAe;AAAA,MACjB;AAAA,IACF;AAEA,UAAM,kBAAkB,CAAC,MAAqC;AAC5D,YAAM,UAAU,EAAE,WAAW,CAAC;AAC9B,UAAI,CAAC,QAAS;AACd,UAAI,UAAU,EAAE,MAAM,SAAS;AAE/B,YAAM,YAAY,QAAQ;AAC1B,UAAI,cAAc,eAAgB;AAClC,eAAS,aAAa,IAAI;AAC1B,gBAAU,QAAQ,UAAU,QAAQ,SAAS,CAAC,CAAC;AAAA,IACjD;AAEA,UAAM,mBAAmB,MAAM;AAC7B,eAAS,IAAI;AACb,UAAI,UAAU,EAAE,MAAM,SAAS;AAC/B,gBAAU,QAAQ,UAAU,IAAI;AAAA,IAClC;AAOA,UAAM,aAAa,CAAC,MAAqC;AACvD,YAAM,QAAQ,IAAI,sBAAsB,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC;AAC1E,UAAI,MAAM,SAAS,EAAG,QAAO,MAAM,CAAC;AACpC,YAAM,EAAE,GAAG,EAAE,IAAI,EAAE;AACnB,YAAM,MAAM;AAAA,QACV,CAAC,IAAI,SAAS,IAAI,OAAO;AAAA,QACzB,CAAC,IAAI,SAAS,IAAI,OAAO;AAAA,MAC3B;AACA,aAAO,IAAI,sBAAsB,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;AAAA,IACpE;AAEA,gBAAY,UAAU;AAEtB,UAAM,cAAc,CAAC,MAAqC;AACxD,YAAM,UAAU,UAAU,IAAI,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;AAC5D,UAAI,CAAC,QAAS;AACd,gBAAU,QAAQ,UAAU,QAAQ,SAAS,CAAC,CAAC;AAAA,IACjD;AAEA,QAAI,GAAG,aAAa,aAAa,eAAe;AAChD,QAAI,GAAG,cAAc,aAAa,gBAAgB;AAElD,QAAI,UAAU,GAAG;AACf,UAAI,GAAG,SAAS,WAAW;AAAA,IAC7B,OAAO;AACL,UAAI,GAAG,SAAS,aAAa,WAAW;AAAA,IAC1C;AAEA,WAAO,MAAM;AACX,UAAI,IAAI,aAAa,aAAa,eAAe;AACjD,UAAI,IAAI,cAAc,aAAa,gBAAgB;AACnD,UAAI,UAAU,GAAG;AACf,YAAI,IAAI,SAAS,WAAW;AAAA,MAC9B,OAAO;AACL,YAAI,IAAI,SAAS,aAAa,WAAW;AAAA,MAC3C;AACA,eAAS,IAAI;AACb,kBAAY,UAAU;AACtB,UAAI,UAAU,EAAE,MAAM,SAAS;AAAA,IACjC;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AASD,QAAM,mBAAmB;AAAA,IACvB,MAAO,eAAe,WAAW,qBAAwB,MAAM,SAAS,IAAI,CAAC;AAAA,IAC7E,CAAC,aAAa,UAAU,MAAM,SAAS;AAAA,EACzC;AAEA,QAAM,QAAQ,CAAC,UAA8B;AAC3C,gBAAY,UAAU,MAAM,MAAM,IAAI;AACtC,cAAU,QAAQ,UAAU;AAAA,MAC1B,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA;AAAA;AAAA,MAGhB,MAAM;AAAA,MACN,eAAe;AAAA,IACjB,CAAC;AAAA,EACH;AAEA,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,SACE,oBAAC,SAAI,WAAU,WAAU,aAAU,6BACjC,8BAAC,QACE,2BAAiB,IAAI,CAAC,OAAO,UAC5B,oBAAC,QACC;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,MAAM;AACZ,oBAAY,UAAU,IAAI;AAC1B,kBAAU,QAAQ,UAAU,IAAI;AAAA,MAClC;AAAA,MACA,SAAS,MACP,UAAU,QAAQ,UAAU;AAAA,QAC1B,SAAS,MAAM;AAAA,QACf,WAAW,MAAM;AAAA,QACjB,UAAU,MAAM;AAAA,QAChB,MAAM;AAAA,QACN,eAAe;AAAA,MACjB,CAAC;AAAA,MAEH,SAAS,MAAM,MAAM,KAAK;AAAA,MAC1B,MAAK;AAAA,MAEJ,yBAAe,MAAM,SAAS,KAAK,KAAK,MAAM;AAAA;AAAA,EACjD,KAnBO,MAAM,GAoBf,CACD,GACH,GACF;AAEJ;AAaA,SAAS,kBAAkB,UAAgD;AACzE,MAAI,SAAS,SAAS,sBAAsB;AAC1C,WAAO,SAAS,WAAW,QAAQ,iBAAiB;AAAA,EACtD;AACA,MAAI,SAAS,SAAS,SAAS;AAC7B,WAAO,CAAC,SAAS,WAAW;AAAA,EAC9B;AAEA,QAAM,UAAU,CAAC,UACf,MAAM,QAAQ,KAAK,KAAK,OAAO,MAAM,CAAC,MAAM,WACxC,CAAC,KAAyB,IAC1B,MAAM,QAAQ,KAAK,IACjB,MAAM,QAAQ,OAAO,IACrB,CAAC;AACT,SAAO,QAAQ,SAAS,WAAW;AACrC;AAGA,SAAS,eAAe,UAA8C;AACpE,QAAM,YAAY,kBAAkB,QAAQ;AAC5C,MAAI,UAAU,WAAW,EAAG,QAAO,CAAC,GAAG,CAAC;AACxC,MAAI,OAAO,OAAO;AAClB,MAAI,OAAO,OAAO;AAClB,MAAI,QAAQ,OAAO;AACnB,MAAI,QAAQ,OAAO;AACnB,aAAW,CAAC,KAAK,GAAG,KAAK,WAAW;AAClC,WAAO,KAAK,IAAI,MAAM,OAAO,CAAC;AAC9B,WAAO,KAAK,IAAI,MAAM,OAAO,CAAC;AAC9B,YAAQ,KAAK,IAAI,OAAO,OAAO,CAAC;AAChC,YAAQ,KAAK,IAAI,OAAO,OAAO,CAAC;AAAA,EAClC;AACA,SAAO,EAAE,OAAO,QAAQ,IAAI,QAAQ,SAAS,CAAC;AAChD;AAOA,SAAS,qBACP,MACA,WACsB;AACtB,MAAI,OAAO,SAAS,SAAU,QAAO,CAAC;AACtC,QAAM,WACJ,KAAK,SAAS,sBAAsB,KAAK,WAAW,KAAK,SAAS,YAAY,CAAC,IAAI,IAAI,CAAC;AAC1F,SAAO,SAAS,IAAI,CAAC,SAAS,UAAU;AACtC,UAAM,WAAW,YAAY,QAAQ,aAAa,SAAS,IAAI;AAC/D,UAAM,KACJ,OAAO,aAAa,YAAY,OAAO,aAAa,WAChD,WACA,OAAO,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,WACtD,QAAQ,KACR;AACR,UAAM,CAAC,WAAW,QAAQ,IAAI,eAAe,QAAQ,QAAQ;AAC7D,WAAO;AAAA,MACL,KAAK,GAAG,MAAM,KAAK;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,GAAG,MAAM,QAAQ,CAAC;AAAA,IACnC;AAAA,EACF,CAAC;AACH;AAOA,SAAS,YACP,OACA,OACA,KACgD;AAChD,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,oBAAoB,CAAC,CAAC;AAE3E,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,OAAO,QAAQ,OAAO,KAAK,QAAQ,UAAU,CAAC;AAC5E,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,MAAI,UAA2C;AAC/C,MAAI;AACF,cAAU,OAAO,WAAW,IAAI;AAAA,EAClC,QAAQ;AACN,cAAU;AAAA,EACZ;AACA,MAAI,CAAC,QAAS,QAAO;AACrB,UAAQ,cAAc;AACtB,UAAQ,YAAY,QAAQ;AAC5B,UAAQ,UAAU;AAClB,UAAQ,UAAU;AAElB,aAAW,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG;AACzB,YAAQ,OAAO,IAAI,OAAO,MAAM,IAAI;AACpC,YAAQ,OAAO,IAAI,MAAM,CAAC;AAAA,EAC5B;AACA,UAAQ,OAAO;AACf,SAAO,EAAE,MAAM,QAAQ,aAAa,GAAG,GAAG,MAAM,IAAI,GAAG,WAAW;AACpE;","names":[]}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useResolvedBasemapTheme
|
|
4
|
+
} from "./chunk-Z4EPSOB4.js";
|
|
5
|
+
import {
|
|
6
|
+
MapAnnotationKey,
|
|
7
|
+
MapAnnotationRegistryProvider,
|
|
8
|
+
useMapAnnotationRegistry
|
|
9
|
+
} from "./chunk-QDWWP3N7.js";
|
|
10
|
+
import {
|
|
11
|
+
MapFrameContext,
|
|
12
|
+
mapBreakpointForWidth,
|
|
13
|
+
resolveMapHeightStyle,
|
|
14
|
+
useMeasuredMapBreakpoint
|
|
15
|
+
} from "./chunk-W76NLFER.js";
|
|
16
|
+
import {
|
|
17
|
+
MapContext
|
|
18
|
+
} from "./chunk-JYFERXZT.js";
|
|
19
|
+
import {
|
|
20
|
+
warnMapOnce
|
|
21
|
+
} from "./chunk-G7JDXDJN.js";
|
|
22
|
+
import {
|
|
23
|
+
createPlanCrs
|
|
24
|
+
} from "./chunk-GYDTTAVD.js";
|
|
25
|
+
|
|
26
|
+
// src/map-canvas/map-canvas.tsx
|
|
27
|
+
import MapLibreGL from "maplibre-gl";
|
|
28
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
29
|
+
import {
|
|
30
|
+
forwardRef,
|
|
31
|
+
useCallback,
|
|
32
|
+
useEffect,
|
|
33
|
+
useImperativeHandle,
|
|
34
|
+
useMemo,
|
|
35
|
+
useRef,
|
|
36
|
+
useState
|
|
37
|
+
} from "react";
|
|
38
|
+
import { Spinner, StatePanel, useLocale } from "@elabs-ai/components-ui";
|
|
39
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
40
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
41
|
+
var defaultStyles = {
|
|
42
|
+
dark: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
|
|
43
|
+
light: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
|
44
|
+
};
|
|
45
|
+
var PLAN_FIT_PADDING = 24;
|
|
46
|
+
var blankMapStyle = {
|
|
47
|
+
version: 8,
|
|
48
|
+
sources: {},
|
|
49
|
+
layers: [
|
|
50
|
+
{
|
|
51
|
+
id: "background",
|
|
52
|
+
type: "background",
|
|
53
|
+
paint: { "background-color": "transparent" }
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
var GESTURE_HANDLERS = [
|
|
58
|
+
"scrollZoom",
|
|
59
|
+
"boxZoom",
|
|
60
|
+
"dragRotate",
|
|
61
|
+
"dragPan",
|
|
62
|
+
"keyboard",
|
|
63
|
+
"doubleClickZoom",
|
|
64
|
+
"touchZoomRotate",
|
|
65
|
+
"touchPitch"
|
|
66
|
+
];
|
|
67
|
+
var STATIC_HANDLER_OPTIONS = Object.fromEntries(
|
|
68
|
+
GESTURE_HANDLERS.map((key) => [key, false])
|
|
69
|
+
);
|
|
70
|
+
var warnedProjection = false;
|
|
71
|
+
function applyProjection(map, projection) {
|
|
72
|
+
if (typeof map.setProjection !== "function") {
|
|
73
|
+
if (!warnedProjection) {
|
|
74
|
+
warnedProjection = true;
|
|
75
|
+
console.warn(
|
|
76
|
+
"[@elabs-ai/components-maps] This MapLibre build cannot switch projections; `projection` is ignored."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
map.setProjection(projection);
|
|
82
|
+
}
|
|
83
|
+
function applyBasemapLabels(map, visible, hidden) {
|
|
84
|
+
let style;
|
|
85
|
+
try {
|
|
86
|
+
style = map.getStyle?.();
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!style?.layers) return;
|
|
91
|
+
if (visible) {
|
|
92
|
+
for (const id of hidden) {
|
|
93
|
+
if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", "visible");
|
|
94
|
+
}
|
|
95
|
+
hidden.clear();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
for (const layer of style.layers) {
|
|
99
|
+
if (layer.type !== "symbol") continue;
|
|
100
|
+
if (!layer.layout || !("text-field" in layer.layout)) continue;
|
|
101
|
+
if (hidden.has(layer.id)) continue;
|
|
102
|
+
if (map.getLayoutProperty?.(layer.id, "visibility") === "none") continue;
|
|
103
|
+
map.setLayoutProperty(layer.id, "visibility", "none");
|
|
104
|
+
hidden.add(layer.id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function boxSize(node) {
|
|
108
|
+
const { width, height } = node.getBoundingClientRect();
|
|
109
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
110
|
+
return `${Math.round(width)}\xD7${Math.round(height)}`;
|
|
111
|
+
}
|
|
112
|
+
function MapLoadingOverlay() {
|
|
113
|
+
const { t } = useLocale();
|
|
114
|
+
return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center bg-background/50 backdrop-blur-xs", children: /* @__PURE__ */ jsx(Spinner, { label: t("maps.canvas.loading"), className: "size-5" }) });
|
|
115
|
+
}
|
|
116
|
+
function getViewport(map) {
|
|
117
|
+
const center = map.getCenter();
|
|
118
|
+
return {
|
|
119
|
+
center: [center.lng, center.lat],
|
|
120
|
+
zoom: map.getZoom(),
|
|
121
|
+
bearing: map.getBearing(),
|
|
122
|
+
pitch: map.getPitch()
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
var MapCanvas = forwardRef(function MapCanvas2({
|
|
126
|
+
children,
|
|
127
|
+
className,
|
|
128
|
+
theme: themeProp,
|
|
129
|
+
styles,
|
|
130
|
+
blank = false,
|
|
131
|
+
basemapLabels = true,
|
|
132
|
+
projection: projectionProp,
|
|
133
|
+
viewport,
|
|
134
|
+
onViewportChange,
|
|
135
|
+
loading = false,
|
|
136
|
+
interactive,
|
|
137
|
+
height,
|
|
138
|
+
plan: planProp,
|
|
139
|
+
fitPlan = true,
|
|
140
|
+
// Pulled out of `...props` so the constructor and the live plan-limit effect
|
|
141
|
+
// below resolve them the same way.
|
|
142
|
+
minZoom,
|
|
143
|
+
maxZoom,
|
|
144
|
+
maxBounds,
|
|
145
|
+
...props
|
|
146
|
+
}, ref) {
|
|
147
|
+
const { t } = useLocale();
|
|
148
|
+
const containerRef = useRef(null);
|
|
149
|
+
const [containerNode, setContainerNode] = useState(null);
|
|
150
|
+
const setContainer = useCallback((node) => {
|
|
151
|
+
containerRef.current = node;
|
|
152
|
+
setContainerNode(node);
|
|
153
|
+
}, []);
|
|
154
|
+
const breakpoint = useMeasuredMapBreakpoint(containerNode);
|
|
155
|
+
const isStatic = interactive === false;
|
|
156
|
+
const [mapInstance, setMapInstance] = useState(null);
|
|
157
|
+
const [initFailed, setInitFailed] = useState(false);
|
|
158
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
159
|
+
const [isStyleLoaded, setIsStyleLoaded] = useState(false);
|
|
160
|
+
const currentStyleRef = useRef(null);
|
|
161
|
+
const styleTimeoutRef = useRef(null);
|
|
162
|
+
const internalUpdateRef = useRef(false);
|
|
163
|
+
const { resolvedTheme, themeKey } = useResolvedBasemapTheme(themeProp);
|
|
164
|
+
const isControlled = viewport !== void 0 && onViewportChange !== void 0;
|
|
165
|
+
const onViewportChangeRef = useRef(onViewportChange);
|
|
166
|
+
onViewportChangeRef.current = onViewportChange;
|
|
167
|
+
const projection = useMemo(
|
|
168
|
+
() => typeof projectionProp === "string" ? { type: projectionProp } : projectionProp,
|
|
169
|
+
[projectionProp]
|
|
170
|
+
);
|
|
171
|
+
const projectionRef = useRef(projection);
|
|
172
|
+
const mapStyles = useMemo(() => {
|
|
173
|
+
if (styles) {
|
|
174
|
+
return {
|
|
175
|
+
dark: styles.dark ?? defaultStyles.dark,
|
|
176
|
+
light: styles.light ?? defaultStyles.light
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (blank) {
|
|
180
|
+
return { dark: blankMapStyle, light: blankMapStyle };
|
|
181
|
+
}
|
|
182
|
+
return defaultStyles;
|
|
183
|
+
}, [styles, blank]);
|
|
184
|
+
const planIsCrs = typeof planProp?.toLngLat === "function";
|
|
185
|
+
const planExtent = planIsCrs ? void 0 : planProp;
|
|
186
|
+
const planWidth = planExtent?.width;
|
|
187
|
+
const planHeight = planExtent?.height;
|
|
188
|
+
const planOrigin = planExtent?.origin ?? "top-left";
|
|
189
|
+
const planUnit = planExtent?.unit ?? "px";
|
|
190
|
+
const derivedPlan = useMemo(
|
|
191
|
+
() => planWidth !== void 0 && planHeight !== void 0 ? createPlanCrs({
|
|
192
|
+
width: planWidth,
|
|
193
|
+
height: planHeight,
|
|
194
|
+
origin: planOrigin,
|
|
195
|
+
unit: planUnit
|
|
196
|
+
}) : null,
|
|
197
|
+
[planWidth, planHeight, planOrigin, planUnit]
|
|
198
|
+
);
|
|
199
|
+
const planCrs = planIsCrs ? planProp ?? null : derivedPlan;
|
|
200
|
+
const projectionIsGlobe = !!projection && projection.type !== "mercator";
|
|
201
|
+
const effectiveProjection = planCrs && projectionIsGlobe ? void 0 : projection;
|
|
202
|
+
projectionRef.current = effectiveProjection;
|
|
203
|
+
if (planCrs && projectionIsGlobe) {
|
|
204
|
+
warnMapOnce(
|
|
205
|
+
"plan-projection",
|
|
206
|
+
`Ignored projection "${projection?.type}" on a plan map: a plan is flat, and only the mercator projection keeps it undistorted.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const planOptions = useMemo(() => {
|
|
210
|
+
if (!planCrs) return null;
|
|
211
|
+
return {
|
|
212
|
+
minZoom: planCrs.minZoom,
|
|
213
|
+
maxZoom: planCrs.maxZoom,
|
|
214
|
+
maxBounds: planCrs.maxBounds(),
|
|
215
|
+
// A plan is 2D: rotation and tilt only make it harder to read.
|
|
216
|
+
dragRotate: false,
|
|
217
|
+
pitchWithRotate: false,
|
|
218
|
+
touchPitch: false,
|
|
219
|
+
bearing: 0,
|
|
220
|
+
pitch: 0,
|
|
221
|
+
// A plan is usually embedded in a page, so don't swallow the wheel.
|
|
222
|
+
cooperativeGestures: true
|
|
223
|
+
};
|
|
224
|
+
}, [planCrs]);
|
|
225
|
+
useImperativeHandle(ref, () => mapInstance, [mapInstance]);
|
|
226
|
+
const clearStyleTimeout = useCallback(() => {
|
|
227
|
+
if (styleTimeoutRef.current) {
|
|
228
|
+
clearTimeout(styleTimeoutRef.current);
|
|
229
|
+
styleTimeoutRef.current = null;
|
|
230
|
+
}
|
|
231
|
+
}, []);
|
|
232
|
+
const heightRef = useRef(height);
|
|
233
|
+
heightRef.current = height;
|
|
234
|
+
const { bounds, fitBoundsOptions } = props;
|
|
235
|
+
const refitRef = useRef({ bounds, fitBoundsOptions });
|
|
236
|
+
refitRef.current = { bounds, fitBoundsOptions };
|
|
237
|
+
const isControlledRef = useRef(isControlled);
|
|
238
|
+
isControlledRef.current = isControlled;
|
|
239
|
+
const userMovedRef = useRef(false);
|
|
240
|
+
const fittedSizeRef = useRef(null);
|
|
241
|
+
const syncBox = useCallback((map, node) => {
|
|
242
|
+
const size = boxSize(node);
|
|
243
|
+
if (!size || size === fittedSizeRef.current) return;
|
|
244
|
+
fittedSizeRef.current = size;
|
|
245
|
+
map.resize();
|
|
246
|
+
const { bounds: currentBounds, fitBoundsOptions: currentOptions } = refitRef.current;
|
|
247
|
+
if (!currentBounds || userMovedRef.current || isControlledRef.current) return;
|
|
248
|
+
map.fitBounds(currentBounds, { ...currentOptions, duration: 0 });
|
|
249
|
+
}, []);
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!containerRef.current) return;
|
|
252
|
+
Object.assign(
|
|
253
|
+
containerRef.current.style,
|
|
254
|
+
resolveMapHeightStyle(
|
|
255
|
+
heightRef.current,
|
|
256
|
+
mapBreakpointForWidth(containerRef.current.getBoundingClientRect().width)
|
|
257
|
+
)
|
|
258
|
+
);
|
|
259
|
+
const initialStyle = resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light;
|
|
260
|
+
currentStyleRef.current = initialStyle;
|
|
261
|
+
let map;
|
|
262
|
+
try {
|
|
263
|
+
map = new MapLibreGL.Map({
|
|
264
|
+
container: containerRef.current,
|
|
265
|
+
style: initialStyle,
|
|
266
|
+
renderWorldCopies: false,
|
|
267
|
+
// Attribution control OFF by default — a maintainer decision for internal
|
|
268
|
+
// use, taken deliberately and recorded in CHANGELOG.md + the map-components
|
|
269
|
+
// rule. NOTE THE CONSTRAINT: the default Carto basemap serves OpenStreetMap
|
|
270
|
+
// data, which is ODbL-licensed and requires the credit, and Carto's terms
|
|
271
|
+
// require it too — so a surface that ships PUBLICLY on these tiles must turn
|
|
272
|
+
// it back on with `attributionControl={{ compact: true }}` (it wins through
|
|
273
|
+
// `...props` below), or move to tiles licensed without the requirement via
|
|
274
|
+
// `styles` / `blank`.
|
|
275
|
+
attributionControl: false,
|
|
276
|
+
...planOptions ?? {},
|
|
277
|
+
// `bounds` fits the plan in the very first frame, so a plan map never
|
|
278
|
+
// flashes at lng/lat 0,0 before the fit effect below runs. Skipped when
|
|
279
|
+
// the caller drives the camera themselves.
|
|
280
|
+
...planOptions && !viewport?.center ? {
|
|
281
|
+
bounds: planCrs.bounds,
|
|
282
|
+
fitBoundsOptions: { padding: PLAN_FIT_PADDING, animate: false }
|
|
283
|
+
} : {},
|
|
284
|
+
...minZoom !== void 0 ? { minZoom } : {},
|
|
285
|
+
...maxZoom !== void 0 ? { maxZoom } : {},
|
|
286
|
+
...maxBounds !== void 0 ? { maxBounds } : {},
|
|
287
|
+
...props,
|
|
288
|
+
...viewport,
|
|
289
|
+
// Static mode: gestures off, but MapLibre's own `interactive` stays on —
|
|
290
|
+
// it would detach EVERY listener, tooltips and hover included.
|
|
291
|
+
...isStatic ? STATIC_HANDLER_OPTIONS : {}
|
|
292
|
+
});
|
|
293
|
+
} catch {
|
|
294
|
+
setInitFailed(true);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const styleDataHandler = () => {
|
|
298
|
+
clearStyleTimeout();
|
|
299
|
+
styleTimeoutRef.current = setTimeout(() => {
|
|
300
|
+
setIsStyleLoaded(true);
|
|
301
|
+
if (projectionRef.current) {
|
|
302
|
+
applyProjection(map, projectionRef.current);
|
|
303
|
+
}
|
|
304
|
+
}, 100);
|
|
305
|
+
};
|
|
306
|
+
const loadHandler = () => {
|
|
307
|
+
setIsLoaded(true);
|
|
308
|
+
map.getContainer().querySelectorAll("details.maplibregl-ctrl-attrib[open]").forEach((el) => {
|
|
309
|
+
el.open = false;
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
const handleMove = () => {
|
|
313
|
+
if (internalUpdateRef.current) return;
|
|
314
|
+
onViewportChangeRef.current?.(getViewport(map));
|
|
315
|
+
};
|
|
316
|
+
const handleMoveStart = (event) => {
|
|
317
|
+
if (event?.originalEvent) userMovedRef.current = true;
|
|
318
|
+
};
|
|
319
|
+
fittedSizeRef.current = boxSize(containerRef.current);
|
|
320
|
+
map.on("load", loadHandler);
|
|
321
|
+
map.on("styledata", styleDataHandler);
|
|
322
|
+
map.on("move", handleMove);
|
|
323
|
+
map.on("movestart", handleMoveStart);
|
|
324
|
+
setMapInstance(map);
|
|
325
|
+
return () => {
|
|
326
|
+
clearStyleTimeout();
|
|
327
|
+
map.off("load", loadHandler);
|
|
328
|
+
map.off("styledata", styleDataHandler);
|
|
329
|
+
map.off("move", handleMove);
|
|
330
|
+
map.off("movestart", handleMoveStart);
|
|
331
|
+
map.remove();
|
|
332
|
+
setIsLoaded(false);
|
|
333
|
+
setIsStyleLoaded(false);
|
|
334
|
+
setMapInstance(null);
|
|
335
|
+
};
|
|
336
|
+
}, []);
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (!mapInstance || !isControlled || !viewport) return;
|
|
339
|
+
if (mapInstance.isMoving()) return;
|
|
340
|
+
const current = getViewport(mapInstance);
|
|
341
|
+
const next = {
|
|
342
|
+
center: viewport.center ?? current.center,
|
|
343
|
+
zoom: viewport.zoom ?? current.zoom,
|
|
344
|
+
bearing: viewport.bearing ?? current.bearing,
|
|
345
|
+
pitch: viewport.pitch ?? current.pitch
|
|
346
|
+
};
|
|
347
|
+
if (next.center[0] === current.center[0] && next.center[1] === current.center[1] && next.zoom === current.zoom && next.bearing === current.bearing && next.pitch === current.pitch) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
internalUpdateRef.current = true;
|
|
351
|
+
mapInstance.jumpTo(next);
|
|
352
|
+
internalUpdateRef.current = false;
|
|
353
|
+
}, [mapInstance, isControlled, viewport]);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (!mapInstance || !resolvedTheme) return;
|
|
356
|
+
const newStyle = resolvedTheme === "dark" ? mapStyles.dark : mapStyles.light;
|
|
357
|
+
if (currentStyleRef.current === newStyle) return;
|
|
358
|
+
clearStyleTimeout();
|
|
359
|
+
currentStyleRef.current = newStyle;
|
|
360
|
+
setIsStyleLoaded(false);
|
|
361
|
+
mapInstance.setStyle(newStyle, { diff: true });
|
|
362
|
+
}, [mapInstance, resolvedTheme, mapStyles, clearStyleTimeout]);
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
if (!mapInstance || !isStyleLoaded || !effectiveProjection) return;
|
|
365
|
+
applyProjection(mapInstance, effectiveProjection);
|
|
366
|
+
}, [mapInstance, isStyleLoaded, effectiveProjection]);
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
if (!mapInstance || !planCrs) return;
|
|
369
|
+
mapInstance.setMinZoom(minZoom ?? planCrs.minZoom);
|
|
370
|
+
mapInstance.setMaxZoom(maxZoom ?? planCrs.maxZoom);
|
|
371
|
+
mapInstance.setMaxBounds(maxBounds ?? planCrs.maxBounds());
|
|
372
|
+
}, [mapInstance, planCrs, minZoom, maxZoom, maxBounds]);
|
|
373
|
+
const fittedExtentRef = useRef(null);
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
if (!mapInstance || !isLoaded || !planCrs || !fitPlan) return;
|
|
376
|
+
if (isControlled || viewport?.center) return;
|
|
377
|
+
const { width, height: height2, origin } = planCrs.extent;
|
|
378
|
+
const extentKey = `${width}x${height2}:${origin}`;
|
|
379
|
+
if (fittedExtentRef.current === extentKey) return;
|
|
380
|
+
fittedExtentRef.current = extentKey;
|
|
381
|
+
mapInstance.fitBounds(planCrs.bounds, {
|
|
382
|
+
padding: PLAN_FIT_PADDING,
|
|
383
|
+
animate: false
|
|
384
|
+
});
|
|
385
|
+
}, [mapInstance, isLoaded, planCrs, fitPlan, isControlled, viewport?.center]);
|
|
386
|
+
const hiddenLabelLayersRef = useRef(/* @__PURE__ */ new Set());
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (!mapInstance || !isStyleLoaded) return;
|
|
389
|
+
applyBasemapLabels(mapInstance, basemapLabels, hiddenLabelLayersRef.current);
|
|
390
|
+
}, [mapInstance, isStyleLoaded, basemapLabels, themeKey]);
|
|
391
|
+
const handlerOptionsRef = useRef(props);
|
|
392
|
+
handlerOptionsRef.current = props;
|
|
393
|
+
const wasStaticRef = useRef(false);
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (!mapInstance) return;
|
|
396
|
+
if (!isStatic && !wasStaticRef.current) return;
|
|
397
|
+
wasStaticRef.current = true;
|
|
398
|
+
for (const key of GESTURE_HANDLERS) {
|
|
399
|
+
const handler = mapInstance[key];
|
|
400
|
+
if (isStatic) handler?.disable?.();
|
|
401
|
+
else if (handlerOptionsRef.current[key] !== false) handler?.enable?.();
|
|
402
|
+
}
|
|
403
|
+
mapInstance.getCanvasContainer().classList.toggle("maplibregl-interactive", !isStatic);
|
|
404
|
+
mapInstance.getCanvas().tabIndex = isStatic ? -1 : 0;
|
|
405
|
+
}, [mapInstance, isStatic]);
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (!mapInstance) return;
|
|
408
|
+
mapInstance.getCanvas()?.classList.add("focus-ring");
|
|
409
|
+
}, [mapInstance]);
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!mapInstance || !containerNode) return void 0;
|
|
412
|
+
const sync = () => syncBox(mapInstance, containerNode);
|
|
413
|
+
sync();
|
|
414
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
415
|
+
const observer = new ResizeObserver(sync);
|
|
416
|
+
observer.observe(containerNode);
|
|
417
|
+
return () => observer.disconnect();
|
|
418
|
+
}, [mapInstance, containerNode, syncBox]);
|
|
419
|
+
const heightStyle = resolveMapHeightStyle(height, breakpoint);
|
|
420
|
+
const heightKey = `${heightStyle.height ?? ""}|${heightStyle.aspectRatio ?? ""}`;
|
|
421
|
+
const appliedHeightKeyRef = useRef(heightKey);
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (!mapInstance || !containerNode || heightKey === appliedHeightKeyRef.current) return;
|
|
424
|
+
appliedHeightKeyRef.current = heightKey;
|
|
425
|
+
syncBox(mapInstance, containerNode);
|
|
426
|
+
}, [mapInstance, containerNode, heightKey, syncBox]);
|
|
427
|
+
const [slotRequests, setSlotRequests] = useState({
|
|
428
|
+
above: 0,
|
|
429
|
+
below: 0
|
|
430
|
+
});
|
|
431
|
+
const [aboveEl, setAboveEl] = useState(null);
|
|
432
|
+
const [belowEl, setBelowEl] = useState(null);
|
|
433
|
+
const requestSlot = useCallback((side) => {
|
|
434
|
+
setSlotRequests((prev) => ({ ...prev, [side]: prev[side] + 1 }));
|
|
435
|
+
return () => setSlotRequests((prev) => ({ ...prev, [side]: Math.max(0, prev[side] - 1) }));
|
|
436
|
+
}, []);
|
|
437
|
+
const frameValue = useMemo(
|
|
438
|
+
() => ({
|
|
439
|
+
breakpoint,
|
|
440
|
+
interactive: !isStatic,
|
|
441
|
+
slots: { above: aboveEl, below: belowEl },
|
|
442
|
+
requestSlot
|
|
443
|
+
}),
|
|
444
|
+
[breakpoint, isStatic, aboveEl, belowEl, requestSlot]
|
|
445
|
+
);
|
|
446
|
+
const annotations = useMapAnnotationRegistry(breakpoint);
|
|
447
|
+
const showBelow = slotRequests.below > 0 || annotations.rows.length > 0;
|
|
448
|
+
const contextValue = useMemo(
|
|
449
|
+
() => ({
|
|
450
|
+
map: mapInstance,
|
|
451
|
+
isLoaded: isLoaded && isStyleLoaded,
|
|
452
|
+
resolvedTheme,
|
|
453
|
+
themeKey,
|
|
454
|
+
plan: planCrs,
|
|
455
|
+
loading
|
|
456
|
+
}),
|
|
457
|
+
[mapInstance, isLoaded, isStyleLoaded, resolvedTheme, themeKey, planCrs, loading]
|
|
458
|
+
);
|
|
459
|
+
if (initFailed) {
|
|
460
|
+
return /* @__PURE__ */ jsx("div", { className: cn("relative h-full w-full", className), children: /* @__PURE__ */ jsx(
|
|
461
|
+
StatePanel,
|
|
462
|
+
{
|
|
463
|
+
kind: "error",
|
|
464
|
+
title: t("maps.canvas.unavailableTitle"),
|
|
465
|
+
description: t("maps.canvas.unavailableDescription"),
|
|
466
|
+
className: "h-full"
|
|
467
|
+
}
|
|
468
|
+
) });
|
|
469
|
+
}
|
|
470
|
+
return /* @__PURE__ */ jsx(MapContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(MapFrameContext.Provider, { value: frameValue, children: /* @__PURE__ */ jsxs(MapAnnotationRegistryProvider, { value: annotations.value, children: [
|
|
471
|
+
slotRequests.above > 0 && /* @__PURE__ */ jsx("div", { ref: setAboveEl, "data-slot": "map-canvas-above", className: "pb-2" }),
|
|
472
|
+
/* @__PURE__ */ jsxs(
|
|
473
|
+
"div",
|
|
474
|
+
{
|
|
475
|
+
ref: setContainer,
|
|
476
|
+
"data-slot": "map-canvas",
|
|
477
|
+
"data-map-breakpoint": breakpoint,
|
|
478
|
+
"data-interactive": isStatic ? "false" : void 0,
|
|
479
|
+
className: cn("relative h-full w-full", className),
|
|
480
|
+
style: heightStyle,
|
|
481
|
+
children: [
|
|
482
|
+
(!isLoaded || loading) && /* @__PURE__ */ jsx(MapLoadingOverlay, {}),
|
|
483
|
+
mapInstance && children
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
),
|
|
487
|
+
showBelow && /* @__PURE__ */ jsxs("div", { "data-slot": "map-canvas-below", className: "flex flex-col gap-2 pt-2", children: [
|
|
488
|
+
/* @__PURE__ */ jsx("div", { ref: setBelowEl, "data-slot": "map-canvas-below-furniture", className: "contents" }),
|
|
489
|
+
annotations.rows.length > 0 && /* @__PURE__ */ jsx(MapAnnotationKey, { rows: annotations.rows })
|
|
490
|
+
] })
|
|
491
|
+
] }) }) });
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
export {
|
|
495
|
+
MapCanvas
|
|
496
|
+
};
|
|
497
|
+
//# sourceMappingURL=chunk-XQT7YLJY.js.map
|