@page-speed/maps 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +120 -0
  2. package/dist/components/geo-map.cjs +1228 -0
  3. package/dist/components/geo-map.cjs.map +1 -0
  4. package/dist/components/geo-map.d.cts +138 -0
  5. package/dist/components/geo-map.d.ts +138 -0
  6. package/dist/components/geo-map.js +1207 -0
  7. package/dist/components/geo-map.js.map +1 -0
  8. package/dist/components/index.cjs +1350 -0
  9. package/dist/components/index.cjs.map +1 -0
  10. package/dist/components/index.d.cts +5 -0
  11. package/dist/components/index.d.ts +5 -0
  12. package/dist/components/index.js +1326 -0
  13. package/dist/components/index.js.map +1 -0
  14. package/dist/components/map-marker.cjs +137 -0
  15. package/dist/components/map-marker.cjs.map +1 -0
  16. package/dist/components/map-marker.d.cts +76 -0
  17. package/dist/components/map-marker.d.ts +76 -0
  18. package/dist/components/map-marker.js +130 -0
  19. package/dist/components/map-marker.js.map +1 -0
  20. package/dist/core/MapLibre.cjs +46 -20
  21. package/dist/core/MapLibre.cjs.map +1 -1
  22. package/dist/core/MapLibre.js +46 -20
  23. package/dist/core/MapLibre.js.map +1 -1
  24. package/dist/core/index.cjs +46 -20
  25. package/dist/core/index.cjs.map +1 -1
  26. package/dist/core/index.js +46 -20
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/index.cjs +964 -39
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +2 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +945 -39
  33. package/dist/index.js.map +1 -1
  34. package/dist/types/index.d.cts +5 -5
  35. package/dist/types/index.d.ts +5 -5
  36. package/dist/utils/cn.cjs +13 -0
  37. package/dist/utils/cn.cjs.map +1 -0
  38. package/dist/utils/cn.d.cts +16 -0
  39. package/dist/utils/cn.d.ts +16 -0
  40. package/dist/utils/cn.js +11 -0
  41. package/dist/utils/cn.js.map +1 -0
  42. package/dist/utils/index.cjs +63 -0
  43. package/dist/utils/index.cjs.map +1 -1
  44. package/dist/utils/index.d.cts +4 -0
  45. package/dist/utils/index.d.ts +4 -0
  46. package/dist/utils/index.js +42 -1
  47. package/dist/utils/index.js.map +1 -1
  48. package/dist/utils/simple-pressable.cjs +63 -0
  49. package/dist/utils/simple-pressable.cjs.map +1 -0
  50. package/dist/utils/simple-pressable.d.cts +20 -0
  51. package/dist/utils/simple-pressable.d.ts +20 -0
  52. package/dist/utils/simple-pressable.js +41 -0
  53. package/dist/utils/simple-pressable.js.map +1 -0
  54. package/package.json +29 -2
package/dist/index.cjs CHANGED
@@ -1,12 +1,30 @@
1
1
  'use strict';
2
2
 
3
- var React = require('react');
3
+ var React3 = require('react');
4
4
  var maplibre = require('react-map-gl/maplibre');
5
+ var clsx = require('clsx');
6
+ var tailwindMerge = require('tailwind-merge');
5
7
  var jsxRuntime = require('react/jsx-runtime');
6
8
 
7
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
8
26
 
9
- var React__default = /*#__PURE__*/_interopDefault(React);
27
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
10
28
 
11
29
  // src/core/MapLibre.tsx
12
30
 
@@ -85,11 +103,54 @@ function generateGoogleMapLink(latitude, longitude, zoom = 15) {
85
103
  function generateGoogleDirectionsLink(latitude, longitude) {
86
104
  return `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`;
87
105
  }
106
+ function cn(...inputs) {
107
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
108
+ }
109
+ var SimplePressable = React3__namespace.forwardRef(({ children, className, href, onClick, ...props }, ref) => {
110
+ if (href) {
111
+ const isExternal = href.startsWith("http://") || href.startsWith("https://");
112
+ return /* @__PURE__ */ jsxRuntime.jsx(
113
+ "a",
114
+ {
115
+ ref,
116
+ href,
117
+ className,
118
+ target: isExternal ? "_blank" : props.target,
119
+ rel: isExternal ? "noopener noreferrer" : props.rel,
120
+ onClick,
121
+ ...props,
122
+ children
123
+ }
124
+ );
125
+ }
126
+ if (onClick) {
127
+ return /* @__PURE__ */ jsxRuntime.jsx(
128
+ "button",
129
+ {
130
+ ref,
131
+ type: "button",
132
+ className,
133
+ onClick,
134
+ ...props,
135
+ children
136
+ }
137
+ );
138
+ }
139
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children });
140
+ });
141
+ SimplePressable.displayName = "SimplePressable";
88
142
  var DEFAULT_MAPLIBRE_CSS_HREF = "https://cdn.jsdelivr.net/npm/maplibre-gl@5.18.0/dist/maplibre-gl.css";
89
143
  var MAPLIBRE_STYLESHEET_ID = "page-speed-maplibre-gl-css";
144
+ var DEFAULT_FLY_TO_OPTIONS = Object.freeze({});
145
+ var VIEW_STATE_COORDINATE_EPSILON = 1e-6;
146
+ var VIEW_STATE_ZOOM_EPSILON = 0.01;
147
+ var DEFAULT_FLY_TO_EASING = (t) => 1 - Math.pow(1 - t, 3);
90
148
  function joinClassNames(...classNames) {
91
149
  return classNames.filter(Boolean).join(" ");
92
150
  }
151
+ function hasMeaningfulViewStateDelta(previous, next) {
152
+ return Math.abs(previous.latitude - next.latitude) > VIEW_STATE_COORDINATE_EPSILON || Math.abs(previous.longitude - next.longitude) > VIEW_STATE_COORDINATE_EPSILON || Math.abs(previous.zoom - next.zoom) > VIEW_STATE_ZOOM_EPSILON;
153
+ }
93
154
  function ensureMapLibreStylesheet(href) {
94
155
  if (typeof document === "undefined") {
95
156
  return;
@@ -202,22 +263,37 @@ function MapLibre({
202
263
  showGeolocateControl = false,
203
264
  navigationControlPosition = "bottom-right",
204
265
  geolocateControlPosition = "top-left",
205
- flyToOptions = {}
266
+ flyToOptions = DEFAULT_FLY_TO_OPTIONS
206
267
  }) {
207
- const mapRef = React__default.default.useRef(null);
268
+ const mapRef = React3__namespace.default.useRef(null);
208
269
  const resolvedMapLibreCssHref = mapLibreCssHref && mapLibreCssHref.trim().length > 0 ? mapLibreCssHref : DEFAULT_MAPLIBRE_CSS_HREF;
209
- const [internalViewState, setInternalViewState] = React__default.default.useState({
270
+ const [internalViewState, setInternalViewState] = React3__namespace.default.useState({
210
271
  latitude: viewState?.latitude ?? center.lat,
211
272
  longitude: viewState?.longitude ?? center.lng,
212
273
  zoom: viewState?.zoom ?? zoom
213
274
  });
214
- const isUserInteracting = React__default.default.useRef(false);
215
- const isMarkerDragging = React__default.default.useRef(false);
216
- const dragAnimationFrame = React__default.default.useRef(null);
217
- React__default.default.useEffect(() => {
275
+ const isUserInteracting = React3__namespace.default.useRef(false);
276
+ const isMarkerDragging = React3__namespace.default.useRef(false);
277
+ const dragAnimationFrame = React3__namespace.default.useRef(null);
278
+ const lastReportedViewState = React3__namespace.default.useRef(null);
279
+ const resolvedFlyToOptions = React3__namespace.default.useMemo(
280
+ () => ({
281
+ speed: flyToOptions.speed ?? 0.8,
282
+ curve: flyToOptions.curve ?? 1.2,
283
+ bearing: flyToOptions.bearing ?? 0,
284
+ easing: flyToOptions.easing ?? DEFAULT_FLY_TO_EASING
285
+ }),
286
+ [
287
+ flyToOptions.bearing,
288
+ flyToOptions.curve,
289
+ flyToOptions.easing,
290
+ flyToOptions.speed
291
+ ]
292
+ );
293
+ React3__namespace.default.useEffect(() => {
218
294
  ensureMapLibreStylesheet(resolvedMapLibreCssHref);
219
295
  }, [resolvedMapLibreCssHref]);
220
- React__default.default.useEffect(() => {
296
+ React3__namespace.default.useEffect(() => {
221
297
  if (!mapRef.current || !viewState || isUserInteracting.current || isMarkerDragging.current) {
222
298
  return;
223
299
  }
@@ -227,32 +303,34 @@ function MapLibre({
227
303
  longitude: viewState.longitude ?? previous.longitude,
228
304
  zoom: viewState.zoom ?? previous.zoom
229
305
  };
230
- const hasChanged = previous.latitude !== next.latitude || previous.longitude !== next.longitude || previous.zoom !== next.zoom;
306
+ const hasChanged = hasMeaningfulViewStateDelta(previous, next);
231
307
  if (!hasChanged) {
232
308
  return previous;
233
309
  }
234
- const {
235
- speed = 0.8,
236
- curve = 1.2,
237
- bearing = 0,
238
- easing = (t) => 1 - Math.pow(1 - t, 3)
239
- } = flyToOptions;
240
- mapRef.current?.flyTo({
241
- center: [next.longitude, next.latitude],
242
- zoom: next.zoom,
243
- speed,
244
- curve,
245
- bearing,
246
- easing,
247
- essential: true
248
- });
310
+ const isEchoedMoveState = !!lastReportedViewState.current && !hasMeaningfulViewStateDelta(lastReportedViewState.current, next);
311
+ if (!isEchoedMoveState) {
312
+ mapRef.current?.flyTo({
313
+ center: [next.longitude, next.latitude],
314
+ zoom: next.zoom,
315
+ speed: resolvedFlyToOptions.speed,
316
+ curve: resolvedFlyToOptions.curve,
317
+ bearing: resolvedFlyToOptions.bearing,
318
+ easing: resolvedFlyToOptions.easing,
319
+ essential: true
320
+ });
321
+ }
249
322
  return next;
250
323
  });
251
- }, [flyToOptions, viewState?.latitude, viewState?.longitude, viewState?.zoom]);
252
- const handleMoveStart = React__default.default.useCallback(() => {
324
+ }, [
325
+ resolvedFlyToOptions,
326
+ viewState?.latitude,
327
+ viewState?.longitude,
328
+ viewState?.zoom
329
+ ]);
330
+ const handleMoveStart = React3__namespace.default.useCallback(() => {
253
331
  isUserInteracting.current = true;
254
332
  }, []);
255
- const handleMove = React__default.default.useCallback(
333
+ const handleMove = React3__namespace.default.useCallback(
256
334
  (event) => {
257
335
  const nextViewState = event.viewState;
258
336
  setInternalViewState({
@@ -260,15 +338,17 @@ function MapLibre({
260
338
  longitude: nextViewState.longitude,
261
339
  zoom: nextViewState.zoom
262
340
  });
263
- onViewStateChange?.({
341
+ const roundedViewState = {
264
342
  latitude: Number(nextViewState.latitude.toFixed(6)),
265
343
  longitude: Number(nextViewState.longitude.toFixed(6)),
266
344
  zoom: Number(nextViewState.zoom.toFixed(2))
267
- });
345
+ };
346
+ lastReportedViewState.current = roundedViewState;
347
+ onViewStateChange?.(roundedViewState);
268
348
  },
269
349
  [onViewStateChange]
270
350
  );
271
- const handleMoveEnd = React__default.default.useCallback(
351
+ const handleMoveEnd = React3__namespace.default.useCallback(
272
352
  (event) => {
273
353
  isUserInteracting.current = false;
274
354
  if (!onMoveEnd) {
@@ -289,7 +369,7 @@ function MapLibre({
289
369
  },
290
370
  [onMoveEnd]
291
371
  );
292
- const handleMapClick = React__default.default.useCallback(
372
+ const handleMapClick = React3__namespace.default.useCallback(
293
373
  (event) => {
294
374
  if (!onClick) {
295
375
  return;
@@ -298,11 +378,11 @@ function MapLibre({
298
378
  },
299
379
  [onClick]
300
380
  );
301
- const normalizedMarkers = React__default.default.useMemo(
381
+ const normalizedMarkers = React3__namespace.default.useMemo(
302
382
  () => normalizeMarkers(markers),
303
383
  [markers]
304
384
  );
305
- const markerElements = React__default.default.useMemo(
385
+ const markerElements = React3__namespace.default.useMemo(
306
386
  () => normalizedMarkers.map((marker) => /* @__PURE__ */ jsxRuntime.jsx(
307
387
  maplibre.Marker,
308
388
  {
@@ -389,7 +469,7 @@ function MapLibre({
389
469
  )),
390
470
  [normalizedMarkers, onMarkerDrag]
391
471
  );
392
- const resolvedMapStyleUrl = React__default.default.useMemo(() => {
472
+ const resolvedMapStyleUrl = React3__namespace.default.useMemo(() => {
393
473
  if (styleUrl) {
394
474
  return appendStadiaApiKey(styleUrl, stadiaApiKey);
395
475
  }
@@ -456,7 +536,7 @@ function computeGeoCenter(coordinates) {
456
536
  return { lat, lng };
457
537
  }
458
538
  function useGeoCenter(coordinates) {
459
- return React.useMemo(() => computeGeoCenter(coordinates), [coordinates]);
539
+ return React3.useMemo(() => computeGeoCenter(coordinates), [coordinates]);
460
540
  }
461
541
  var TILE_SIZE = 512;
462
542
  function latToMercatorY(lat) {
@@ -514,7 +594,7 @@ function computeDefaultZoom(options) {
514
594
  }
515
595
  function useDefaultZoom(options) {
516
596
  const { coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom } = options;
517
- return React.useMemo(
597
+ return React3.useMemo(
518
598
  () => computeDefaultZoom({
519
599
  coordinates,
520
600
  mapWidth,
@@ -526,12 +606,857 @@ function useDefaultZoom(options) {
526
606
  [coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom]
527
607
  );
528
608
  }
609
+ var SIZE_CONFIG = {
610
+ sm: {
611
+ outer: "size-10",
612
+ middle: "size-7",
613
+ inner: "size-5",
614
+ dot: "size-2"
615
+ },
616
+ md: {
617
+ outer: "size-14",
618
+ middle: "size-10",
619
+ inner: "size-7",
620
+ dot: "size-2.5"
621
+ },
622
+ lg: {
623
+ outer: "size-20",
624
+ middle: "size-14",
625
+ inner: "size-10",
626
+ dot: "size-3.5"
627
+ }
628
+ };
629
+ function MapMarker({
630
+ size = "md",
631
+ isSelected = false,
632
+ dotColor,
633
+ innerRingColor,
634
+ middleRingColor,
635
+ outerRingColor,
636
+ className,
637
+ onClick,
638
+ interactive = true,
639
+ "aria-label": ariaLabel = "Map location marker"
640
+ }) {
641
+ const sizeConfig = SIZE_CONFIG[size];
642
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(
643
+ "div",
644
+ {
645
+ className: cn(
646
+ "relative flex items-center justify-center rounded-full transition-transform duration-200",
647
+ sizeConfig.outer,
648
+ isSelected && "scale-110",
649
+ className
650
+ ),
651
+ style: { backgroundColor: outerRingColor },
652
+ children: [
653
+ /* @__PURE__ */ jsxRuntime.jsx(
654
+ "div",
655
+ {
656
+ className: cn(
657
+ "absolute rounded-full transition-all duration-200",
658
+ sizeConfig.middle
659
+ ),
660
+ style: { backgroundColor: middleRingColor }
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsxRuntime.jsx(
664
+ "div",
665
+ {
666
+ className: cn(
667
+ "absolute rounded-full transition-all duration-200",
668
+ sizeConfig.inner
669
+ ),
670
+ style: { backgroundColor: innerRingColor }
671
+ }
672
+ ),
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ "div",
675
+ {
676
+ className: cn(
677
+ "absolute rounded-full transition-all duration-200",
678
+ sizeConfig.dot
679
+ ),
680
+ style: { backgroundColor: dotColor }
681
+ }
682
+ )
683
+ ]
684
+ }
685
+ );
686
+ if (!interactive) {
687
+ return content;
688
+ }
689
+ return /* @__PURE__ */ jsxRuntime.jsx(
690
+ "button",
691
+ {
692
+ type: "button",
693
+ className: "group cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-full",
694
+ onClick,
695
+ "aria-label": ariaLabel,
696
+ children: /* @__PURE__ */ jsxRuntime.jsx(
697
+ "div",
698
+ {
699
+ className: cn(
700
+ "transition-transform duration-200 group-hover:scale-110",
701
+ isSelected && "scale-110"
702
+ ),
703
+ children: content
704
+ }
705
+ )
706
+ }
707
+ );
708
+ }
709
+ function NeutralMapMarker(props) {
710
+ return /* @__PURE__ */ jsxRuntime.jsx(
711
+ MapMarker,
712
+ {
713
+ ...props,
714
+ dotColor: "hsl(var(--neutral-900, 0 0% 9%))",
715
+ innerRingColor: "hsl(var(--neutral-400, 0 0% 64%))",
716
+ middleRingColor: "hsl(var(--neutral-300, 0 0% 78%))",
717
+ outerRingColor: "hsl(var(--neutral-200, 0 0% 88%))"
718
+ }
719
+ );
720
+ }
721
+ function createMapMarkerElement(config) {
722
+ return function MarkerElement({ isSelected }) {
723
+ return /* @__PURE__ */ jsxRuntime.jsx(MapMarker, { ...config, isSelected, interactive: false });
724
+ };
725
+ }
726
+ var PANEL_POSITION_CLASS = {
727
+ "top-left": "left-4 top-4",
728
+ "top-right": "right-4 top-4",
729
+ "bottom-left": "bottom-4 left-4",
730
+ "bottom-right": "bottom-4 right-4"
731
+ };
732
+ var DEFAULT_VIEW_STATE = {
733
+ latitude: 39.5,
734
+ longitude: -98.35,
735
+ zoom: 3
736
+ };
737
+ var VIDEO_FILE_EXTENSION_REGEX = /\.(mp4|webm|ogg|mov|m4v|m3u8)(\?.*)?$/i;
738
+ function resolveMediaType(item) {
739
+ if (item.type) {
740
+ return item.type;
741
+ }
742
+ return VIDEO_FILE_EXTENSION_REGEX.test(item.src) ? "video" : "image";
743
+ }
744
+ function normalizeId(value, fallback) {
745
+ if (value === null || value === void 0 || value === "") {
746
+ return fallback;
747
+ }
748
+ return String(value);
749
+ }
750
+ function buildClusterCenter(markers) {
751
+ if (!markers.length) {
752
+ return null;
753
+ }
754
+ const total = markers.reduce(
755
+ (accumulator, marker) => ({
756
+ latitude: accumulator.latitude + marker.latitude,
757
+ longitude: accumulator.longitude + marker.longitude
758
+ }),
759
+ { latitude: 0, longitude: 0 }
760
+ );
761
+ return {
762
+ latitude: total.latitude / markers.length,
763
+ longitude: total.longitude / markers.length
764
+ };
765
+ }
766
+ function resolveActionKey(action, index) {
767
+ if (typeof action.label === "string" && action.label.trim().length > 0) {
768
+ return `label:${action.label}:${index}`;
769
+ }
770
+ if (action.href) {
771
+ return `href:${action.href}:${index}`;
772
+ }
773
+ return `action:${index}`;
774
+ }
775
+ var FallbackIcon = ({
776
+ size = 20,
777
+ className
778
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
779
+ "svg",
780
+ {
781
+ width: size,
782
+ height: size,
783
+ viewBox: "0 0 24 24",
784
+ fill: "none",
785
+ stroke: "currentColor",
786
+ strokeWidth: "2",
787
+ strokeLinecap: "round",
788
+ strokeLinejoin: "round",
789
+ className,
790
+ children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" })
791
+ }
792
+ );
793
+ var FallbackImg = ({ src, alt, className, loading }) => /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className, loading });
794
+ function MarkerActions({ actions }) {
795
+ if (!actions || actions.length === 0) {
796
+ return null;
797
+ }
798
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex flex-wrap gap-2", children: actions.map((action, index) => {
799
+ const {
800
+ label,
801
+ icon,
802
+ iconAfter,
803
+ children,
804
+ href,
805
+ onClick,
806
+ className: actionClassName,
807
+ variant,
808
+ size,
809
+ asButton,
810
+ ...rest
811
+ } = action;
812
+ const buttonStyles = cn(
813
+ "inline-flex items-center gap-2 px-4 py-2 rounded-md font-medium transition-colors",
814
+ variant === "outline" ? "border border-border bg-background hover:bg-muted" : "bg-primary text-primary-foreground hover:bg-primary/90",
815
+ size === "sm" && "text-sm px-3 py-1.5",
816
+ size === "icon" && "p-2",
817
+ actionClassName
818
+ );
819
+ return /* @__PURE__ */ jsxRuntime.jsx(
820
+ SimplePressable,
821
+ {
822
+ href,
823
+ onClick,
824
+ className: buttonStyles,
825
+ ...rest,
826
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
827
+ icon,
828
+ label,
829
+ iconAfter
830
+ ] })
831
+ },
832
+ resolveActionKey(action, index)
833
+ );
834
+ }) });
835
+ }
836
+ function MarkerMediaCarousel({
837
+ mediaItems,
838
+ optixFlowConfig,
839
+ IconComponent = FallbackIcon,
840
+ ImgComponent = FallbackImg
841
+ }) {
842
+ const [activeIndex, setActiveIndex] = React3__namespace.useState(0);
843
+ const totalItems = mediaItems.length;
844
+ const mediaResetKey = React3__namespace.useMemo(
845
+ () => mediaItems.map((item, index) => {
846
+ const itemId = normalizeId(item.id, `media-${index}`);
847
+ return `${itemId}:${item.src}:${item.type ?? ""}:${item.poster ?? ""}`;
848
+ }).join("|"),
849
+ [mediaItems]
850
+ );
851
+ const activeItemIndex = Math.min(activeIndex, Math.max(0, totalItems - 1));
852
+ React3__namespace.useEffect(() => {
853
+ setActiveIndex(0);
854
+ }, [mediaResetKey]);
855
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative border-b border-border/60 bg-muted/40", children: [
856
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative aspect-video w-full overflow-hidden", children: mediaItems.map((item, index) => {
857
+ const isActive = index === activeItemIndex;
858
+ const mediaType = resolveMediaType(item);
859
+ return /* @__PURE__ */ jsxRuntime.jsx(
860
+ "div",
861
+ {
862
+ "aria-hidden": !isActive,
863
+ className: cn(
864
+ "absolute inset-0 transition-opacity duration-500 ease-in-out",
865
+ isActive ? "opacity-100 z-1" : "opacity-0 z-0 pointer-events-none"
866
+ ),
867
+ children: mediaType === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
868
+ "video",
869
+ {
870
+ className: "h-full w-full object-cover",
871
+ controls: isActive,
872
+ preload: "metadata",
873
+ poster: item.poster,
874
+ tabIndex: isActive ? 0 : -1,
875
+ children: /* @__PURE__ */ jsxRuntime.jsx("source", { src: item.src })
876
+ }
877
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
878
+ ImgComponent,
879
+ {
880
+ src: item.src,
881
+ alt: item.alt ?? "Map marker media",
882
+ className: "h-full w-full object-cover",
883
+ loading: "eager",
884
+ optixFlowConfig
885
+ }
886
+ )
887
+ },
888
+ normalizeId(item.id, `media-slide-${index}`)
889
+ );
890
+ }) }),
891
+ totalItems > 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
892
+ /* @__PURE__ */ jsxRuntime.jsx(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ "aria-label": "Show previous media",
897
+ className: "absolute left-4 top-1/2 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-2xl bg-card text-card-foreground shadow-lg border-4 border-black hover:border-white hover:bg-black hover:text-white transition-all duration-500 z-[2]",
898
+ onClick: () => {
899
+ setActiveIndex(
900
+ (current) => (current - 1 + totalItems) % totalItems
901
+ );
902
+ },
903
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { name: "lucide/arrow-left", size: 18 })
904
+ }
905
+ ),
906
+ /* @__PURE__ */ jsxRuntime.jsx(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ "aria-label": "Show next media",
911
+ className: "absolute right-4 top-1/2 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-2xl bg-card text-card-foreground shadow-lg border-4 border-black hover:border-white hover:bg-black hover:text-white transition-all duration-500 z-2",
912
+ onClick: () => {
913
+ setActiveIndex((current) => (current + 1) % totalItems);
914
+ },
915
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { name: "lucide/arrow-right", size: 18 })
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-2 left-1/2 flex -translate-x-1/2 items-center gap-1.5 z-[2]", children: mediaItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
919
+ "button",
920
+ {
921
+ type: "button",
922
+ "aria-label": `Show media item ${index + 1}`,
923
+ className: cn(
924
+ "h-2 rounded-full transition-all duration-300",
925
+ index === activeItemIndex ? "w-6 bg-card" : "w-2 bg-card opacity-50 hover:opacity-100"
926
+ ),
927
+ onClick: () => setActiveIndex(index)
928
+ },
929
+ normalizeId(item.id, `media-dot-${index}`)
930
+ )) })
931
+ ] }) : null
932
+ ] });
933
+ }
934
+ function getMarkerTitle(marker, markerIndex) {
935
+ if (marker.title !== void 0 && marker.title !== null) {
936
+ return marker.title;
937
+ }
938
+ if (marker.label !== void 0 && marker.label !== null) {
939
+ return marker.label;
940
+ }
941
+ return `Location ${markerIndex + 1}`;
942
+ }
943
+ function GeoMap({
944
+ className,
945
+ mapWrapperClassName,
946
+ mapClassName,
947
+ panelClassName,
948
+ panelPosition = "top-left",
949
+ stadiaApiKey = "",
950
+ mapStyle = "osm-bright",
951
+ styleUrl,
952
+ mapLibreCssHref,
953
+ markers = [],
954
+ clusters = [],
955
+ viewState,
956
+ defaultViewState,
957
+ onViewStateChange,
958
+ onMapClick,
959
+ onMarkerDrag,
960
+ showNavigationControl = true,
961
+ showGeolocateControl = false,
962
+ navigationControlPosition = "top-right",
963
+ geolocateControlPosition = "top-left",
964
+ flyToOptions,
965
+ markerFocusZoom = 14,
966
+ clusterFocusZoom = 5,
967
+ selectedMarkerId,
968
+ initialSelectedMarkerId,
969
+ onSelectionChange,
970
+ clearSelectionOnMapClick = true,
971
+ mapChildren,
972
+ optixFlowConfig,
973
+ IconComponent = FallbackIcon,
974
+ ImgComponent = FallbackImg
975
+ }) {
976
+ const normalizedStandaloneMarkers = React3__namespace.useMemo(
977
+ () => markers.map((marker, index) => ({
978
+ ...marker,
979
+ id: normalizeId(marker.id, `marker-${index}`)
980
+ })),
981
+ [markers]
982
+ );
983
+ const normalizedClusters = React3__namespace.useMemo(() => {
984
+ const results = [];
985
+ clusters.forEach((cluster, clusterIndex) => {
986
+ const clusterId = normalizeId(cluster.id, `cluster-${clusterIndex}`);
987
+ const normalizedClusterMarkers = cluster.markers.map(
988
+ (marker, markerIndex) => ({
989
+ ...marker,
990
+ id: normalizeId(marker.id, `${clusterId}-marker-${markerIndex}`),
991
+ clusterId
992
+ })
993
+ );
994
+ const clusterCenter = cluster.latitude !== void 0 && cluster.longitude !== void 0 ? { latitude: cluster.latitude, longitude: cluster.longitude } : buildClusterCenter(normalizedClusterMarkers);
995
+ if (!clusterCenter) {
996
+ return;
997
+ }
998
+ results.push({
999
+ ...cluster,
1000
+ id: clusterId,
1001
+ latitude: clusterCenter.latitude,
1002
+ longitude: clusterCenter.longitude,
1003
+ markers: normalizedClusterMarkers
1004
+ });
1005
+ });
1006
+ return results;
1007
+ }, [clusters]);
1008
+ const markerLookup = React3__namespace.useMemo(() => {
1009
+ const lookup = /* @__PURE__ */ new Map();
1010
+ normalizedStandaloneMarkers.forEach((marker) => {
1011
+ lookup.set(marker.id, marker);
1012
+ });
1013
+ normalizedClusters.forEach((cluster) => {
1014
+ cluster.markers.forEach((marker) => {
1015
+ lookup.set(marker.id, marker);
1016
+ });
1017
+ });
1018
+ return lookup;
1019
+ }, [normalizedClusters, normalizedStandaloneMarkers]);
1020
+ const clusterLookup = React3__namespace.useMemo(() => {
1021
+ const lookup = /* @__PURE__ */ new Map();
1022
+ normalizedClusters.forEach((cluster) => {
1023
+ lookup.set(cluster.id, cluster);
1024
+ });
1025
+ return lookup;
1026
+ }, [normalizedClusters]);
1027
+ const firstCoordinate = React3__namespace.useMemo(() => {
1028
+ const allCoords = [];
1029
+ normalizedStandaloneMarkers.forEach((marker) => {
1030
+ allCoords.push({ latitude: marker.latitude, longitude: marker.longitude });
1031
+ });
1032
+ normalizedClusters.forEach((cluster) => {
1033
+ allCoords.push({ latitude: cluster.latitude, longitude: cluster.longitude });
1034
+ });
1035
+ if (allCoords.length > 0) {
1036
+ const sum = allCoords.reduce(
1037
+ (acc, coord) => ({
1038
+ latitude: acc.latitude + coord.latitude,
1039
+ longitude: acc.longitude + coord.longitude
1040
+ }),
1041
+ { latitude: 0, longitude: 0 }
1042
+ );
1043
+ return {
1044
+ latitude: sum.latitude / allCoords.length,
1045
+ longitude: sum.longitude / allCoords.length
1046
+ };
1047
+ }
1048
+ return {
1049
+ latitude: DEFAULT_VIEW_STATE.latitude,
1050
+ longitude: DEFAULT_VIEW_STATE.longitude
1051
+ };
1052
+ }, [normalizedClusters, normalizedStandaloneMarkers]);
1053
+ const calculatedZoom = React3__namespace.useMemo(() => {
1054
+ if (normalizedStandaloneMarkers.length + normalizedClusters.length <= 1) {
1055
+ return markerFocusZoom;
1056
+ }
1057
+ const allCoords = [];
1058
+ normalizedStandaloneMarkers.forEach((marker) => {
1059
+ allCoords.push({ latitude: marker.latitude, longitude: marker.longitude });
1060
+ });
1061
+ normalizedClusters.forEach((cluster) => {
1062
+ allCoords.push({ latitude: cluster.latitude, longitude: cluster.longitude });
1063
+ });
1064
+ if (allCoords.length === 0) {
1065
+ return DEFAULT_VIEW_STATE.zoom;
1066
+ }
1067
+ const lats = allCoords.map((c) => c.latitude);
1068
+ const lngs = allCoords.map((c) => c.longitude);
1069
+ const latDiff = Math.max(...lats) - Math.min(...lats);
1070
+ const lngDiff = Math.max(...lngs) - Math.min(...lngs);
1071
+ const maxDiff = Math.max(latDiff, lngDiff);
1072
+ if (maxDiff > 10) return 3;
1073
+ if (maxDiff > 5) return 5;
1074
+ if (maxDiff > 2) return 7;
1075
+ if (maxDiff > 1) return 9;
1076
+ if (maxDiff > 0.5) return 10;
1077
+ if (maxDiff > 0.1) return 12;
1078
+ return 13;
1079
+ }, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom]);
1080
+ const [uncontrolledViewState, setUncontrolledViewState] = React3__namespace.useState({
1081
+ latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
1082
+ longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
1083
+ zoom: defaultViewState?.zoom ?? calculatedZoom
1084
+ });
1085
+ React3__namespace.useEffect(() => {
1086
+ if (!viewState && !defaultViewState) {
1087
+ setUncontrolledViewState({
1088
+ latitude: firstCoordinate.latitude,
1089
+ longitude: firstCoordinate.longitude,
1090
+ zoom: calculatedZoom
1091
+ });
1092
+ }
1093
+ }, [firstCoordinate, calculatedZoom, viewState, defaultViewState]);
1094
+ const isControlledViewState = viewState !== void 0;
1095
+ const resolvedViewState = isControlledViewState ? viewState : uncontrolledViewState;
1096
+ const applyViewState = React3__namespace.useCallback(
1097
+ (nextState) => {
1098
+ if (!isControlledViewState) {
1099
+ setUncontrolledViewState((current) => {
1100
+ const next = { ...current, ...nextState };
1101
+ const hasChanged = current.latitude !== next.latitude || current.longitude !== next.longitude || current.zoom !== next.zoom;
1102
+ return hasChanged ? next : current;
1103
+ });
1104
+ }
1105
+ onViewStateChange?.(nextState);
1106
+ },
1107
+ [isControlledViewState, onViewStateChange]
1108
+ );
1109
+ const [selection, setSelection] = React3__namespace.useState(() => {
1110
+ if (initialSelectedMarkerId !== void 0 && initialSelectedMarkerId !== null) {
1111
+ return {
1112
+ type: "marker",
1113
+ markerId: String(initialSelectedMarkerId)
1114
+ };
1115
+ }
1116
+ return { type: "none" };
1117
+ });
1118
+ React3__namespace.useEffect(() => {
1119
+ if (selectedMarkerId === void 0 || selectedMarkerId === null) {
1120
+ return;
1121
+ }
1122
+ setSelection({
1123
+ type: "marker",
1124
+ markerId: String(selectedMarkerId)
1125
+ });
1126
+ }, [selectedMarkerId]);
1127
+ const selectedMarker = selection.markerId ? markerLookup.get(selection.markerId) : void 0;
1128
+ const selectedCluster = selection.clusterId ? clusterLookup.get(selection.clusterId) : void 0;
1129
+ React3__namespace.useEffect(() => {
1130
+ if (selection.type === "marker" && selection.markerId && !selectedMarker) {
1131
+ setSelection({ type: "none" });
1132
+ onSelectionChange?.({ type: "none" });
1133
+ }
1134
+ }, [onSelectionChange, selectedMarker, selection]);
1135
+ const emitSelectionChange = React3__namespace.useCallback(
1136
+ (nextSelection) => {
1137
+ if (nextSelection.type === "none") {
1138
+ onSelectionChange?.({ type: "none" });
1139
+ return;
1140
+ }
1141
+ if (nextSelection.type === "marker") {
1142
+ const parentCluster = nextSelection.marker.clusterId ? clusterLookup.get(nextSelection.marker.clusterId) : void 0;
1143
+ onSelectionChange?.({
1144
+ type: "marker",
1145
+ marker: nextSelection.marker,
1146
+ cluster: parentCluster
1147
+ });
1148
+ return;
1149
+ }
1150
+ onSelectionChange?.({
1151
+ type: "cluster",
1152
+ cluster: nextSelection.cluster
1153
+ });
1154
+ },
1155
+ [clusterLookup, onSelectionChange]
1156
+ );
1157
+ const selectMarker = React3__namespace.useCallback(
1158
+ (marker) => {
1159
+ setSelection({
1160
+ type: "marker",
1161
+ markerId: marker.id,
1162
+ clusterId: marker.clusterId
1163
+ });
1164
+ applyViewState({
1165
+ latitude: marker.latitude,
1166
+ longitude: marker.longitude,
1167
+ zoom: markerFocusZoom
1168
+ });
1169
+ emitSelectionChange({ type: "marker", marker });
1170
+ },
1171
+ [applyViewState, emitSelectionChange, markerFocusZoom]
1172
+ );
1173
+ const selectCluster = React3__namespace.useCallback(
1174
+ (cluster) => {
1175
+ setSelection({
1176
+ type: "cluster",
1177
+ clusterId: cluster.id
1178
+ });
1179
+ applyViewState({
1180
+ latitude: cluster.latitude,
1181
+ longitude: cluster.longitude,
1182
+ zoom: clusterFocusZoom
1183
+ });
1184
+ emitSelectionChange({ type: "cluster", cluster });
1185
+ },
1186
+ [applyViewState, clusterFocusZoom, emitSelectionChange]
1187
+ );
1188
+ const clearSelection = React3__namespace.useCallback(() => {
1189
+ setSelection({ type: "none" });
1190
+ emitSelectionChange({ type: "none" });
1191
+ }, [emitSelectionChange]);
1192
+ const mapMarkers = React3__namespace.useMemo(() => {
1193
+ const resolvedMarkers = [];
1194
+ normalizedClusters.forEach((cluster) => {
1195
+ const isSelected = selection.type === "cluster" && selection.clusterId === cluster.id;
1196
+ resolvedMarkers.push({
1197
+ id: `cluster-pin:${cluster.id}`,
1198
+ latitude: cluster.latitude,
1199
+ longitude: cluster.longitude,
1200
+ element: () => {
1201
+ const customMarkerElement = cluster.markerElement;
1202
+ const markerBody = typeof customMarkerElement === "function" ? customMarkerElement({
1203
+ isSelected,
1204
+ count: cluster.markers.length
1205
+ }) : customMarkerElement;
1206
+ return /* @__PURE__ */ jsxRuntime.jsx(
1207
+ "button",
1208
+ {
1209
+ type: "button",
1210
+ className: "group cursor-pointer",
1211
+ onClick: (event) => {
1212
+ event.preventDefault();
1213
+ event.stopPropagation();
1214
+ selectCluster(cluster);
1215
+ },
1216
+ "aria-label": `View ${cluster.markers.length} clustered locations`,
1217
+ children: markerBody ?? /* @__PURE__ */ jsxRuntime.jsx(
1218
+ "span",
1219
+ {
1220
+ className: cn(
1221
+ "inline-flex min-h-10 min-w-10 items-center justify-center rounded-full border-2 border-white px-2 text-xs font-semibold text-white shadow-lg transition-transform duration-200 group-hover:scale-105",
1222
+ isSelected && "ring-4 ring-primary/30",
1223
+ cluster.pinClassName
1224
+ ),
1225
+ style: {
1226
+ backgroundColor: cluster.pinColor ?? "var(--foreground)"
1227
+ },
1228
+ children: cluster.markers.length
1229
+ }
1230
+ )
1231
+ }
1232
+ );
1233
+ }
1234
+ });
1235
+ });
1236
+ normalizedStandaloneMarkers.forEach((marker) => {
1237
+ const isSelected = selection.type === "marker" && selection.markerId === marker.id;
1238
+ const customMarkerElement = marker.markerElement;
1239
+ resolvedMarkers.push({
1240
+ id: marker.id,
1241
+ latitude: marker.latitude,
1242
+ longitude: marker.longitude,
1243
+ draggable: marker.draggable,
1244
+ element: () => {
1245
+ const markerBody = typeof customMarkerElement === "function" ? customMarkerElement({ isSelected }) : customMarkerElement;
1246
+ return /* @__PURE__ */ jsxRuntime.jsx(
1247
+ "button",
1248
+ {
1249
+ type: "button",
1250
+ className: "group cursor-pointer",
1251
+ onClick: (event) => {
1252
+ event.preventDefault();
1253
+ event.stopPropagation();
1254
+ selectMarker(marker);
1255
+ },
1256
+ "aria-label": typeof marker.title === "string" ? `View ${marker.title}` : "View location details",
1257
+ children: markerBody ?? /* @__PURE__ */ jsxRuntime.jsx(
1258
+ "span",
1259
+ {
1260
+ className: cn(
1261
+ "inline-flex h-4 w-4 rounded-full border-2 border-white shadow-md transition-transform duration-200 group-hover:scale-110",
1262
+ isSelected && "h-5 w-5 ring-4 ring-primary/30",
1263
+ marker.pinClassName
1264
+ ),
1265
+ style: {
1266
+ backgroundColor: marker.pinColor ?? "#f43f5e"
1267
+ }
1268
+ }
1269
+ )
1270
+ }
1271
+ );
1272
+ }
1273
+ });
1274
+ });
1275
+ return resolvedMarkers;
1276
+ }, [
1277
+ normalizedClusters,
1278
+ normalizedStandaloneMarkers,
1279
+ selectCluster,
1280
+ selectMarker,
1281
+ selection
1282
+ ]);
1283
+ const renderMarkerPanel = () => {
1284
+ if (selectedMarker) {
1285
+ const markerMediaItems = selectedMarker.mediaItems ?? [];
1286
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1287
+ "div",
1288
+ {
1289
+ className: cn(
1290
+ "relative w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-2xl",
1291
+ panelClassName
1292
+ ),
1293
+ children: [
1294
+ /* @__PURE__ */ jsxRuntime.jsx(
1295
+ "button",
1296
+ {
1297
+ type: "button",
1298
+ "aria-label": "Close marker details",
1299
+ className: "flex size-12 items-center justify-center rounded-bl-lg rounded-br-0 rounded-t-0 bg-black text-white transition-all duration-500 absolute top-0 right-0 z-10 cursor-pointer ring-4 ring-white",
1300
+ onClick: clearSelection,
1301
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { name: "lucide/x", size: 20 })
1302
+ }
1303
+ ),
1304
+ markerMediaItems.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1305
+ MarkerMediaCarousel,
1306
+ {
1307
+ mediaItems: markerMediaItems,
1308
+ optixFlowConfig,
1309
+ IconComponent,
1310
+ ImgComponent
1311
+ }
1312
+ ) : null,
1313
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 p-4", children: [
1314
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-1", children: [
1315
+ selectedMarker.eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide", children: selectedMarker.eyebrow }) : null,
1316
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-base font-semibold leading-tight", children: selectedMarker.title ?? selectedMarker.label ?? "Location" })
1317
+ ] }) }),
1318
+ selectedMarker.summary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm leading-relaxed", children: selectedMarker.summary }) : null,
1319
+ selectedMarker.locationLine ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-start text-sm gap-2", children: [
1320
+ /* @__PURE__ */ jsxRuntime.jsx(
1321
+ IconComponent,
1322
+ {
1323
+ name: "lucide:map-pin",
1324
+ className: "opacity-50",
1325
+ size: 18
1326
+ }
1327
+ ),
1328
+ typeof selectedMarker.locationLine === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
1329
+ SimplePressable,
1330
+ {
1331
+ href: selectedMarker.locationUrl,
1332
+ className: cn(
1333
+ "transition-all duration-500",
1334
+ "font-medium opacity-75 hover:opacity-100",
1335
+ selectedMarker.locationUrl ? "underline underline-offset-4" : ""
1336
+ ),
1337
+ children: selectedMarker.locationLine
1338
+ }
1339
+ ) : selectedMarker.locationLine
1340
+ ] }) : null,
1341
+ selectedMarker.hoursLine ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-start text-sm gap-2", children: [
1342
+ /* @__PURE__ */ jsxRuntime.jsx(
1343
+ IconComponent,
1344
+ {
1345
+ name: "lucide:clock",
1346
+ className: "opacity-50",
1347
+ size: 18
1348
+ }
1349
+ ),
1350
+ typeof selectedMarker.hoursLine === "string" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: selectedMarker.hoursLine }) : selectedMarker.hoursLine
1351
+ ] }) : null,
1352
+ selectedMarker.markerContentComponent ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: selectedMarker.markerContentComponent }) : null,
1353
+ /* @__PURE__ */ jsxRuntime.jsx(MarkerActions, { actions: selectedMarker.actions })
1354
+ ] })
1355
+ ]
1356
+ }
1357
+ );
1358
+ }
1359
+ if (selectedCluster) {
1360
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1361
+ "div",
1362
+ {
1363
+ className: cn(
1364
+ "relative w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-xl border border-border bg-card text-card-foreground p-4 shadow-2xl",
1365
+ panelClassName
1366
+ ),
1367
+ children: [
1368
+ /* @__PURE__ */ jsxRuntime.jsx(
1369
+ "button",
1370
+ {
1371
+ type: "button",
1372
+ "aria-label": "Close cluster details",
1373
+ className: "flex size-8 items-center justify-center rounded-full border border-border bg-card text-card-foreground transition hover:bg-muted hover:text-foreground absolute top-2 right-2 z-10",
1374
+ onClick: clearSelection,
1375
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { name: "lucide/x", size: 20 })
1376
+ }
1377
+ ),
1378
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 flex items-start justify-between gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1379
+ selectedCluster.label ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: selectedCluster.label }) : null,
1380
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-base font-semibold leading-tight text-foreground", children: selectedCluster.title ?? "Clustered Locations" }),
1381
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: selectedCluster.summary ?? `${selectedCluster.markers.length} location${selectedCluster.markers.length === 1 ? "" : "s"} in this cluster.` })
1382
+ ] }) }),
1383
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 space-y-2 overflow-y-auto pr-1", children: selectedCluster.markers.map((marker, markerIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
1384
+ "button",
1385
+ {
1386
+ type: "button",
1387
+ className: "w-full rounded-lg border border-border/60 p-3 text-left transition hover:border-border hover:bg-muted/50",
1388
+ onClick: () => selectMarker(marker),
1389
+ children: [
1390
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "line-clamp-1 text-sm font-semibold text-foreground", children: getMarkerTitle(marker, markerIndex) }),
1391
+ marker.summary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-muted-foreground", children: marker.summary }) : null
1392
+ ]
1393
+ },
1394
+ marker.id
1395
+ )) })
1396
+ ]
1397
+ }
1398
+ );
1399
+ }
1400
+ return null;
1401
+ };
1402
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1403
+ "div",
1404
+ {
1405
+ className: cn(
1406
+ "relative overflow-hidden rounded-2xl border border-border bg-background",
1407
+ className
1408
+ ),
1409
+ children: [
1410
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("h-[520px] w-full", mapWrapperClassName), children: /* @__PURE__ */ jsxRuntime.jsx(
1411
+ MapLibre,
1412
+ {
1413
+ stadiaApiKey,
1414
+ mapStyle,
1415
+ styleUrl,
1416
+ mapLibreCssHref,
1417
+ viewState: resolvedViewState,
1418
+ onViewStateChange: applyViewState,
1419
+ markers: mapMarkers,
1420
+ onClick: (coord) => {
1421
+ onMapClick?.(coord);
1422
+ if (clearSelectionOnMapClick) {
1423
+ clearSelection();
1424
+ }
1425
+ },
1426
+ onMarkerDrag,
1427
+ showNavigationControl,
1428
+ showGeolocateControl,
1429
+ navigationControlPosition,
1430
+ geolocateControlPosition,
1431
+ flyToOptions,
1432
+ className: cn("h-full w-full", mapClassName),
1433
+ children: mapChildren
1434
+ }
1435
+ ) }),
1436
+ selection.type !== "none" ? /* @__PURE__ */ jsxRuntime.jsx(
1437
+ "div",
1438
+ {
1439
+ className: cn(
1440
+ "pointer-events-none absolute z-20",
1441
+ PANEL_POSITION_CLASS[panelPosition]
1442
+ ),
1443
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-auto", children: renderMarkerPanel() })
1444
+ }
1445
+ ) : null
1446
+ ]
1447
+ }
1448
+ );
1449
+ }
529
1450
 
530
1451
  exports.DTMapLibreMap = DTMapLibreMap;
1452
+ exports.GeoMap = GeoMap;
531
1453
  exports.MapLibre = MapLibre;
1454
+ exports.MapMarker = MapMarker;
1455
+ exports.NeutralMapMarker = NeutralMapMarker;
532
1456
  exports.appendStadiaApiKey = appendStadiaApiKey;
533
1457
  exports.computeDefaultZoom = computeDefaultZoom;
534
1458
  exports.computeGeoCenter = computeGeoCenter;
1459
+ exports.createMapMarkerElement = createMapMarkerElement;
535
1460
  exports.generateGoogleDirectionsLink = generateGoogleDirectionsLink;
536
1461
  exports.generateGoogleMapLink = generateGoogleMapLink;
537
1462
  exports.getMapLibreStyleUrl = getMapLibreStyleUrl;