@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.
- package/README.md +120 -0
- package/dist/components/geo-map.cjs +1228 -0
- package/dist/components/geo-map.cjs.map +1 -0
- package/dist/components/geo-map.d.cts +138 -0
- package/dist/components/geo-map.d.ts +138 -0
- package/dist/components/geo-map.js +1207 -0
- package/dist/components/geo-map.js.map +1 -0
- package/dist/components/index.cjs +1350 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +5 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +1326 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/map-marker.cjs +137 -0
- package/dist/components/map-marker.cjs.map +1 -0
- package/dist/components/map-marker.d.cts +76 -0
- package/dist/components/map-marker.d.ts +76 -0
- package/dist/components/map-marker.js +130 -0
- package/dist/components/map-marker.js.map +1 -0
- package/dist/core/MapLibre.cjs +46 -20
- package/dist/core/MapLibre.cjs.map +1 -1
- package/dist/core/MapLibre.js +46 -20
- package/dist/core/MapLibre.js.map +1 -1
- package/dist/core/index.cjs +46 -20
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +46 -20
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +964 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +945 -39
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.cts +5 -5
- package/dist/types/index.d.ts +5 -5
- package/dist/utils/cn.cjs +13 -0
- package/dist/utils/cn.cjs.map +1 -0
- package/dist/utils/cn.d.cts +16 -0
- package/dist/utils/cn.d.ts +16 -0
- package/dist/utils/cn.js +11 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/index.cjs +63 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +4 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +42 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/simple-pressable.cjs +63 -0
- package/dist/utils/simple-pressable.cjs.map +1 -0
- package/dist/utils/simple-pressable.d.cts +20 -0
- package/dist/utils/simple-pressable.d.ts +20 -0
- package/dist/utils/simple-pressable.js +41 -0
- package/dist/utils/simple-pressable.js.map +1 -0
- package/package.json +29 -2
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
|
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
|
|
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 =
|
|
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] =
|
|
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 =
|
|
215
|
-
const isMarkerDragging =
|
|
216
|
-
const dragAnimationFrame =
|
|
217
|
-
|
|
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
|
-
|
|
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
|
|
306
|
+
const hasChanged = hasMeaningfulViewStateDelta(previous, next);
|
|
231
307
|
if (!hasChanged) {
|
|
232
308
|
return previous;
|
|
233
309
|
}
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
}, [
|
|
252
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
381
|
+
const normalizedMarkers = React3__namespace.default.useMemo(
|
|
302
382
|
() => normalizeMarkers(markers),
|
|
303
383
|
[markers]
|
|
304
384
|
);
|
|
305
|
-
const markerElements =
|
|
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 =
|
|
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
|
|
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
|
|
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;
|