@page-speed/maps 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as React3 from 'react';
5
- import React3__default from 'react';
5
+ import React3__default, { useMemo } from 'react';
6
6
  import { Marker, Map as Map$1, GeolocateControl, NavigationControl } from 'react-map-gl/maplibre';
7
7
 
8
8
  // src/utils/cn.ts
@@ -637,6 +637,74 @@ function MapLibre({
637
637
  }
638
638
  );
639
639
  }
640
+ var TILE_SIZE = 512;
641
+ function latToMercatorY(lat) {
642
+ const latRad = lat * Math.PI / 180;
643
+ const mercN = Math.log(Math.tan(Math.PI / 4 + latRad / 2));
644
+ return TILE_SIZE / (2 * Math.PI) * (Math.PI - mercN);
645
+ }
646
+ function lngToMercatorX(lng) {
647
+ return TILE_SIZE / (2 * Math.PI) * ((lng + 180) / 360 * 2 * Math.PI);
648
+ }
649
+ function computeDefaultZoom(options) {
650
+ const {
651
+ coordinates,
652
+ mapWidth,
653
+ mapHeight,
654
+ padding = 50,
655
+ maxZoom = 18,
656
+ minZoom = 1
657
+ } = options;
658
+ if (coordinates.length === 0) return null;
659
+ if (coordinates.length === 1) return maxZoom;
660
+ if (mapWidth <= 0 || mapHeight <= 0) return null;
661
+ let minLat = Infinity;
662
+ let maxLat = -Infinity;
663
+ let minLng = Infinity;
664
+ let maxLng = -Infinity;
665
+ for (const coord of coordinates) {
666
+ if (coord.lat < minLat) minLat = coord.lat;
667
+ if (coord.lat > maxLat) maxLat = coord.lat;
668
+ if (coord.lng < minLng) minLng = coord.lng;
669
+ if (coord.lng > maxLng) maxLng = coord.lng;
670
+ }
671
+ const pixelXMin = lngToMercatorX(minLng);
672
+ const pixelXMax = lngToMercatorX(maxLng);
673
+ const pixelYMin = latToMercatorY(maxLat);
674
+ const pixelYMax = latToMercatorY(minLat);
675
+ const dx = Math.abs(pixelXMax - pixelXMin);
676
+ const dy = Math.abs(pixelYMax - pixelYMin);
677
+ const availableWidth = mapWidth - padding * 2;
678
+ const availableHeight = mapHeight - padding * 2;
679
+ if (availableWidth <= 0 || availableHeight <= 0) return minZoom;
680
+ let zoom;
681
+ if (dx === 0 && dy === 0) {
682
+ return maxZoom;
683
+ } else if (dx === 0) {
684
+ zoom = Math.log2(availableHeight / dy);
685
+ } else if (dy === 0) {
686
+ zoom = Math.log2(availableWidth / dx);
687
+ } else {
688
+ const zoomX = Math.log2(availableWidth / dx);
689
+ const zoomY = Math.log2(availableHeight / dy);
690
+ zoom = Math.min(zoomX, zoomY);
691
+ }
692
+ return Math.max(minZoom, Math.min(maxZoom, Math.floor(zoom * 100) / 100));
693
+ }
694
+ function useDefaultZoom(options) {
695
+ const { coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom } = options;
696
+ return useMemo(
697
+ () => computeDefaultZoom({
698
+ coordinates,
699
+ mapWidth,
700
+ mapHeight,
701
+ padding,
702
+ maxZoom,
703
+ minZoom
704
+ }),
705
+ [coordinates, mapWidth, mapHeight, padding, maxZoom, minZoom]
706
+ );
707
+ }
640
708
  var PANEL_POSITION_CLASS = {
641
709
  "top-left": "left-4 top-4",
642
710
  "top-right": "right-4 top-4",
@@ -881,9 +949,50 @@ function GeoMap({
881
949
  clearSelectionOnMapClick = true,
882
950
  mapChildren,
883
951
  optixFlowConfig,
952
+ mapSize,
884
953
  IconComponent = FallbackIcon,
885
954
  ImgComponent = FallbackImg
886
955
  }) {
956
+ const containerRef = React3.useRef(null);
957
+ const [containerDimensions, setContainerDimensions] = React3.useState({
958
+ width: 800,
959
+ // Default width
960
+ height: 520
961
+ // Default height
962
+ });
963
+ const [isMobile, setIsMobile] = React3.useState(false);
964
+ React3.useEffect(() => {
965
+ const checkMobile = () => {
966
+ setIsMobile(window.innerWidth < 768);
967
+ };
968
+ checkMobile();
969
+ window.addEventListener("resize", checkMobile);
970
+ return () => window.removeEventListener("resize", checkMobile);
971
+ }, []);
972
+ const calculatedHeight = React3.useMemo(() => {
973
+ if (mapSize) {
974
+ return isMobile ? mapSize.mobile : mapSize.desktop;
975
+ }
976
+ return isMobile ? 420 : 520;
977
+ }, [mapSize, isMobile]);
978
+ React3.useEffect(() => {
979
+ if (!containerRef.current) return;
980
+ const updateDimensions = () => {
981
+ if (containerRef.current) {
982
+ const rect = containerRef.current.getBoundingClientRect();
983
+ setContainerDimensions({
984
+ width: rect.width || 800,
985
+ height: rect.height || calculatedHeight
986
+ });
987
+ }
988
+ };
989
+ updateDimensions();
990
+ if (typeof ResizeObserver !== "undefined") {
991
+ const resizeObserver = new ResizeObserver(updateDimensions);
992
+ resizeObserver.observe(containerRef.current);
993
+ return () => resizeObserver.disconnect();
994
+ }
995
+ }, [calculatedHeight]);
887
996
  const normalizedStandaloneMarkers = React3.useMemo(
888
997
  () => markers.map((marker, index) => ({
889
998
  ...marker,
@@ -967,39 +1076,41 @@ function GeoMap({
967
1076
  longitude: DEFAULT_VIEW_STATE.longitude
968
1077
  };
969
1078
  }, [normalizedClusters, normalizedStandaloneMarkers]);
970
- const calculatedZoom = React3.useMemo(() => {
971
- if (normalizedStandaloneMarkers.length + normalizedClusters.length <= 1) {
972
- return markerFocusZoom;
973
- }
974
- const allCoords = [];
1079
+ const zoomCoordinates = React3.useMemo(() => {
1080
+ const coords = [];
975
1081
  normalizedStandaloneMarkers.forEach((marker) => {
976
- allCoords.push({
977
- latitude: marker.latitude,
978
- longitude: marker.longitude
1082
+ coords.push({
1083
+ lat: marker.latitude,
1084
+ lng: marker.longitude
979
1085
  });
980
1086
  });
981
1087
  normalizedClusters.forEach((cluster) => {
982
- allCoords.push({
983
- latitude: cluster.latitude,
984
- longitude: cluster.longitude
1088
+ coords.push({
1089
+ lat: cluster.latitude,
1090
+ lng: cluster.longitude
985
1091
  });
986
1092
  });
987
- if (allCoords.length === 0) {
1093
+ return coords;
1094
+ }, [normalizedStandaloneMarkers, normalizedClusters]);
1095
+ const properZoom = useDefaultZoom({
1096
+ coordinates: zoomCoordinates,
1097
+ mapWidth: containerDimensions.width,
1098
+ mapHeight: containerDimensions.height,
1099
+ padding: 80,
1100
+ // Increased padding for better framing
1101
+ maxZoom: 18,
1102
+ minZoom: 1
1103
+ });
1104
+ const calculatedZoom = React3.useMemo(() => {
1105
+ if (zoomCoordinates.length === 1) {
1106
+ return markerFocusZoom;
1107
+ }
1108
+ if (zoomCoordinates.length === 0) {
988
1109
  return DEFAULT_VIEW_STATE.zoom;
989
1110
  }
990
- const lats = allCoords.map((c) => c.latitude);
991
- const lngs = allCoords.map((c) => c.longitude);
992
- const latDiff = Math.max(...lats) - Math.min(...lats);
993
- const lngDiff = Math.max(...lngs) - Math.min(...lngs);
994
- const maxDiff = Math.max(latDiff, lngDiff);
995
- if (maxDiff > 10) return 3;
996
- if (maxDiff > 5) return 5;
997
- if (maxDiff > 2) return 7;
998
- if (maxDiff > 1) return 9;
999
- if (maxDiff > 0.5) return 10;
1000
- if (maxDiff > 0.1) return 12;
1001
- return 13;
1002
- }, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom]);
1111
+ const adjustedZoom = properZoom ? properZoom - 0.5 : DEFAULT_VIEW_STATE.zoom;
1112
+ return Math.min(adjustedZoom, 15);
1113
+ }, [properZoom, zoomCoordinates.length, markerFocusZoom]);
1003
1114
  const [uncontrolledViewState, setUncontrolledViewState] = React3.useState({
1004
1115
  latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
1005
1116
  longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
@@ -1325,25 +1436,35 @@ function GeoMap({
1325
1436
  return /* @__PURE__ */ jsxs(
1326
1437
  "div",
1327
1438
  {
1439
+ ref: containerRef,
1328
1440
  className: cn(
1329
- "relative overflow-hidden rounded-2xl border border-border bg-background",
1441
+ "relative rounded-2xl border border-border bg-background",
1442
+ // Remove overflow-hidden from outer container to allow panel to overflow
1330
1443
  className
1331
1444
  ),
1445
+ style: {
1446
+ // If className includes height settings, they'll override via CSS specificity
1447
+ height: className?.includes("h-[") || className?.includes("min-h-[") || className?.includes("max-h-[") ? void 0 : `${calculatedHeight}px`,
1448
+ // Explicitly allow overflow for marker panels
1449
+ overflow: "visible"
1450
+ },
1332
1451
  children: [
1333
1452
  /* @__PURE__ */ jsx(
1334
1453
  "div",
1335
1454
  {
1336
1455
  className: cn(
1337
- "w-full",
1338
- // Default height, can be overridden by mapWrapperClassName
1339
- mapWrapperClassName || "h-[520px]"
1456
+ "w-full rounded-2xl",
1457
+ // Only apply default height class if mapWrapperClassName not provided
1458
+ !mapWrapperClassName && `h-[${calculatedHeight}px]`,
1459
+ mapWrapperClassName
1340
1460
  ),
1341
1461
  style: {
1342
- // CRITICAL: Always use explicit height to prevent MapLibre canvas expansion
1343
- height: mapWrapperClassName ? void 0 : "520px",
1462
+ // If mapWrapperClassName includes height, let it handle the height
1463
+ height: mapWrapperClassName?.includes("h-[") || mapWrapperClassName?.includes("min-h-[") || mapWrapperClassName?.includes("max-h-[") ? void 0 : `${calculatedHeight}px`,
1344
1464
  maxHeight: "100vh",
1345
1465
  // Prevent excessive growth
1346
1466
  position: "relative",
1467
+ // Keep overflow hidden only on the map wrapper to contain the canvas
1347
1468
  overflow: "hidden"
1348
1469
  },
1349
1470
  children: /* @__PURE__ */ jsx(
@@ -1369,6 +1490,10 @@ function GeoMap({
1369
1490
  geolocateControlPosition,
1370
1491
  flyToOptions,
1371
1492
  className: cn("h-full w-full", mapClassName),
1493
+ style: {
1494
+ // Pass the calculated height to MapLibre for better zoom calculations
1495
+ height: mapClassName?.includes("h-[") || mapClassName?.includes("min-h-[") || mapClassName?.includes("max-h-[") ? void 0 : `${calculatedHeight}px`
1496
+ },
1372
1497
  children: mapChildren
1373
1498
  }
1374
1499
  )
@@ -1378,9 +1503,13 @@ function GeoMap({
1378
1503
  "div",
1379
1504
  {
1380
1505
  className: cn(
1381
- "pointer-events-none absolute z-20",
1506
+ "pointer-events-none absolute z-30",
1382
1507
  PANEL_POSITION_CLASS[panelPosition]
1383
1508
  ),
1509
+ style: {
1510
+ // Ensure panel can overflow and has higher z-index
1511
+ zIndex: 30
1512
+ },
1384
1513
  children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: renderMarkerPanel() })
1385
1514
  }
1386
1515
  ) : null