@page-speed/maps 0.1.9 → 0.2.1
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/dist/components/geo-map.cjs +166 -31
- package/dist/components/geo-map.cjs.map +1 -1
- package/dist/components/geo-map.js +167 -32
- package/dist/components/geo-map.js.map +1 -1
- package/dist/components/index.cjs +166 -31
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +167 -32
- package/dist/components/index.js.map +1 -1
- package/dist/index.cjs +98 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +98 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/components/index.js
CHANGED
|
@@ -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,12 +637,93 @@ 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",
|
|
643
711
|
"bottom-left": "bottom-4 left-4",
|
|
644
712
|
"bottom-right": "bottom-4 right-4"
|
|
645
713
|
};
|
|
714
|
+
function getOptimalPanelPosition(markerLat, markerLng, mapCenter) {
|
|
715
|
+
const isNorth = markerLat >= mapCenter.latitude;
|
|
716
|
+
const isEast = markerLng >= mapCenter.longitude;
|
|
717
|
+
if (isNorth && isEast) {
|
|
718
|
+
return "bottom-left";
|
|
719
|
+
} else if (isNorth && !isEast) {
|
|
720
|
+
return "bottom-right";
|
|
721
|
+
} else if (!isNorth && isEast) {
|
|
722
|
+
return "top-left";
|
|
723
|
+
} else {
|
|
724
|
+
return "top-right";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
646
727
|
var DEFAULT_VIEW_STATE = {
|
|
647
728
|
latitude: 39.5,
|
|
648
729
|
longitude: -98.35,
|
|
@@ -885,6 +966,13 @@ function GeoMap({
|
|
|
885
966
|
IconComponent = FallbackIcon,
|
|
886
967
|
ImgComponent = FallbackImg
|
|
887
968
|
}) {
|
|
969
|
+
const containerRef = React3.useRef(null);
|
|
970
|
+
const [containerDimensions, setContainerDimensions] = React3.useState({
|
|
971
|
+
width: 800,
|
|
972
|
+
// Default width
|
|
973
|
+
height: 520
|
|
974
|
+
// Default height
|
|
975
|
+
});
|
|
888
976
|
const [isMobile, setIsMobile] = React3.useState(false);
|
|
889
977
|
React3.useEffect(() => {
|
|
890
978
|
const checkMobile = () => {
|
|
@@ -900,6 +988,24 @@ function GeoMap({
|
|
|
900
988
|
}
|
|
901
989
|
return isMobile ? 420 : 520;
|
|
902
990
|
}, [mapSize, isMobile]);
|
|
991
|
+
React3.useEffect(() => {
|
|
992
|
+
if (!containerRef.current) return;
|
|
993
|
+
const updateDimensions = () => {
|
|
994
|
+
if (containerRef.current) {
|
|
995
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
996
|
+
setContainerDimensions({
|
|
997
|
+
width: rect.width || 800,
|
|
998
|
+
height: rect.height || calculatedHeight
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
updateDimensions();
|
|
1003
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1004
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
1005
|
+
resizeObserver.observe(containerRef.current);
|
|
1006
|
+
return () => resizeObserver.disconnect();
|
|
1007
|
+
}
|
|
1008
|
+
}, [calculatedHeight]);
|
|
903
1009
|
const normalizedStandaloneMarkers = React3.useMemo(
|
|
904
1010
|
() => markers.map((marker, index) => ({
|
|
905
1011
|
...marker,
|
|
@@ -983,47 +1089,49 @@ function GeoMap({
|
|
|
983
1089
|
longitude: DEFAULT_VIEW_STATE.longitude
|
|
984
1090
|
};
|
|
985
1091
|
}, [normalizedClusters, normalizedStandaloneMarkers]);
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
return markerFocusZoom;
|
|
989
|
-
}
|
|
990
|
-
const allCoords = [];
|
|
1092
|
+
const zoomCoordinates = React3.useMemo(() => {
|
|
1093
|
+
const coords = [];
|
|
991
1094
|
normalizedStandaloneMarkers.forEach((marker) => {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1095
|
+
coords.push({
|
|
1096
|
+
lat: marker.latitude,
|
|
1097
|
+
lng: marker.longitude
|
|
995
1098
|
});
|
|
996
1099
|
});
|
|
997
1100
|
normalizedClusters.forEach((cluster) => {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1101
|
+
coords.push({
|
|
1102
|
+
lat: cluster.latitude,
|
|
1103
|
+
lng: cluster.longitude
|
|
1001
1104
|
});
|
|
1002
1105
|
});
|
|
1003
|
-
|
|
1106
|
+
return coords;
|
|
1107
|
+
}, [normalizedStandaloneMarkers, normalizedClusters]);
|
|
1108
|
+
const properZoom = useDefaultZoom({
|
|
1109
|
+
coordinates: zoomCoordinates,
|
|
1110
|
+
mapWidth: containerDimensions.width,
|
|
1111
|
+
mapHeight: containerDimensions.height,
|
|
1112
|
+
padding: 80,
|
|
1113
|
+
// Increased padding for better framing
|
|
1114
|
+
maxZoom: 18,
|
|
1115
|
+
minZoom: 1
|
|
1116
|
+
});
|
|
1117
|
+
const calculatedZoom = React3.useMemo(() => {
|
|
1118
|
+
if (zoomCoordinates.length === 1) {
|
|
1119
|
+
return markerFocusZoom;
|
|
1120
|
+
}
|
|
1121
|
+
if (zoomCoordinates.length === 0) {
|
|
1004
1122
|
return DEFAULT_VIEW_STATE.zoom;
|
|
1005
1123
|
}
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const lngDiff = Math.max(...lngs) - Math.min(...lngs);
|
|
1010
|
-
const maxDiff = Math.max(latDiff, lngDiff);
|
|
1011
|
-
const heightFactor = calculatedHeight / 520;
|
|
1012
|
-
const paddingFactor = 0.85;
|
|
1013
|
-
if (maxDiff > 10) return Math.max(2, 3 * heightFactor * paddingFactor);
|
|
1014
|
-
if (maxDiff > 5) return Math.max(4, 5 * heightFactor * paddingFactor);
|
|
1015
|
-
if (maxDiff > 2) return Math.max(6, 7 * heightFactor * paddingFactor);
|
|
1016
|
-
if (maxDiff > 1) return Math.max(8, 9 * heightFactor * paddingFactor);
|
|
1017
|
-
if (maxDiff > 0.5) return Math.max(9, 10 * heightFactor * paddingFactor);
|
|
1018
|
-
if (maxDiff > 0.1) return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
1019
|
-
if (maxDiff > 0.01) return Math.max(12, 13 * heightFactor * paddingFactor);
|
|
1020
|
-
return Math.max(11, 12 * heightFactor * paddingFactor);
|
|
1021
|
-
}, [normalizedClusters, normalizedStandaloneMarkers, markerFocusZoom, calculatedHeight]);
|
|
1124
|
+
const adjustedZoom = properZoom ? properZoom - 0.5 : DEFAULT_VIEW_STATE.zoom;
|
|
1125
|
+
return Math.min(adjustedZoom, 15);
|
|
1126
|
+
}, [properZoom, zoomCoordinates.length, markerFocusZoom]);
|
|
1022
1127
|
const [uncontrolledViewState, setUncontrolledViewState] = React3.useState({
|
|
1023
1128
|
latitude: defaultViewState?.latitude ?? firstCoordinate.latitude,
|
|
1024
1129
|
longitude: defaultViewState?.longitude ?? firstCoordinate.longitude,
|
|
1025
1130
|
zoom: defaultViewState?.zoom ?? calculatedZoom
|
|
1026
1131
|
});
|
|
1132
|
+
const [dynamicPanelPosition, setDynamicPanelPosition] = React3.useState(
|
|
1133
|
+
panelPosition
|
|
1134
|
+
);
|
|
1027
1135
|
React3.useEffect(() => {
|
|
1028
1136
|
if (!viewState && !defaultViewState) {
|
|
1029
1137
|
setUncontrolledViewState({
|
|
@@ -1103,6 +1211,19 @@ function GeoMap({
|
|
|
1103
1211
|
markerId: marker.id,
|
|
1104
1212
|
clusterId: marker.clusterId
|
|
1105
1213
|
});
|
|
1214
|
+
const currentCenter = resolvedViewState || {
|
|
1215
|
+
latitude: firstCoordinate.latitude,
|
|
1216
|
+
longitude: firstCoordinate.longitude
|
|
1217
|
+
};
|
|
1218
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1219
|
+
marker.latitude,
|
|
1220
|
+
marker.longitude,
|
|
1221
|
+
{
|
|
1222
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1223
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1106
1227
|
applyViewState({
|
|
1107
1228
|
latitude: marker.latitude,
|
|
1108
1229
|
longitude: marker.longitude,
|
|
@@ -1110,7 +1231,7 @@ function GeoMap({
|
|
|
1110
1231
|
});
|
|
1111
1232
|
emitSelectionChange({ type: "marker", marker });
|
|
1112
1233
|
},
|
|
1113
|
-
[applyViewState, emitSelectionChange, markerFocusZoom]
|
|
1234
|
+
[applyViewState, emitSelectionChange, markerFocusZoom, resolvedViewState, firstCoordinate]
|
|
1114
1235
|
);
|
|
1115
1236
|
const selectCluster = React3.useCallback(
|
|
1116
1237
|
(cluster) => {
|
|
@@ -1118,6 +1239,19 @@ function GeoMap({
|
|
|
1118
1239
|
type: "cluster",
|
|
1119
1240
|
clusterId: cluster.id
|
|
1120
1241
|
});
|
|
1242
|
+
const currentCenter = resolvedViewState || {
|
|
1243
|
+
latitude: firstCoordinate.latitude,
|
|
1244
|
+
longitude: firstCoordinate.longitude
|
|
1245
|
+
};
|
|
1246
|
+
const optimalPosition = getOptimalPanelPosition(
|
|
1247
|
+
cluster.latitude,
|
|
1248
|
+
cluster.longitude,
|
|
1249
|
+
{
|
|
1250
|
+
latitude: currentCenter.latitude ?? firstCoordinate.latitude,
|
|
1251
|
+
longitude: currentCenter.longitude ?? firstCoordinate.longitude
|
|
1252
|
+
}
|
|
1253
|
+
);
|
|
1254
|
+
setDynamicPanelPosition(optimalPosition);
|
|
1121
1255
|
applyViewState({
|
|
1122
1256
|
latitude: cluster.latitude,
|
|
1123
1257
|
longitude: cluster.longitude,
|
|
@@ -1125,7 +1259,7 @@ function GeoMap({
|
|
|
1125
1259
|
});
|
|
1126
1260
|
emitSelectionChange({ type: "cluster", cluster });
|
|
1127
1261
|
},
|
|
1128
|
-
[applyViewState, clusterFocusZoom, emitSelectionChange]
|
|
1262
|
+
[applyViewState, clusterFocusZoom, emitSelectionChange, resolvedViewState, firstCoordinate]
|
|
1129
1263
|
);
|
|
1130
1264
|
const clearSelection = React3.useCallback(() => {
|
|
1131
1265
|
setSelection({ type: "none" });
|
|
@@ -1344,6 +1478,7 @@ function GeoMap({
|
|
|
1344
1478
|
return /* @__PURE__ */ jsxs(
|
|
1345
1479
|
"div",
|
|
1346
1480
|
{
|
|
1481
|
+
ref: containerRef,
|
|
1347
1482
|
className: cn(
|
|
1348
1483
|
"relative rounded-2xl border border-border bg-background",
|
|
1349
1484
|
// Remove overflow-hidden from outer container to allow panel to overflow
|
|
@@ -1411,7 +1546,7 @@ function GeoMap({
|
|
|
1411
1546
|
{
|
|
1412
1547
|
className: cn(
|
|
1413
1548
|
"pointer-events-none absolute z-30",
|
|
1414
|
-
PANEL_POSITION_CLASS[
|
|
1549
|
+
PANEL_POSITION_CLASS[dynamicPanelPosition]
|
|
1415
1550
|
),
|
|
1416
1551
|
style: {
|
|
1417
1552
|
// Ensure panel can overflow and has higher z-index
|