import { cn } from "helpers/cn"; import { RefAttributes, useEffect, useImperativeHandle } from "react"; import { createContext, Ref, useContext, useState } from "react"; import { Popup, PopupProps, useMap } from "react-leaflet"; import { Popup as LPopup, popup } from "leaflet"; const PopupContext = createContext({ anchor: "topleft", }); export const useSmartPopup = () => { const context = useContext(PopupContext); if (!context) { throw new Error("usePopup must be used within a PopupProvider"); } return context; }; export const useConflict = (id: string, mode: "popup" | "marker") => { const otherMarkers = document.querySelectorAll(".map-collision"); // get markers and check if they are overlapping const ownMarker = mode === "popup" ? document.querySelector(`#popup-domain-${id}`) : document.querySelector(`#marker-domain-${id}`); if (!otherMarkers || !ownMarker) return "topleft"; const marksersInCluster = Array.from(otherMarkers).filter((marker) => { if (mode === "popup" && marker.id === `marker-${id}`) return false; const rect1 = (marker as HTMLElement).getBoundingClientRect(); const rect2 = (ownMarker as HTMLElement).getBoundingClientRect(); return !( rect1.right < rect2.left || rect1.left > rect2.right || rect1.bottom < rect2.top || rect1.top > rect2.bottom ); }); // get the center of all overlapping markers const markersPosition = marksersInCluster.map((marker) => { const rect = (marker as HTMLElement).getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2, }; }); const ownMarkerBounds = (ownMarker as HTMLElement).getBoundingClientRect(); const ownMarkerPosition = { x: ownMarkerBounds.left + ownMarkerBounds.width / 2, y: ownMarkerBounds.top + ownMarkerBounds.height / 2, }; const centerOfOverlappingMarkers = markersPosition.reduce( (acc, pos) => ({ x: acc.x + pos.x, y: acc.y + pos.y, }), { x: 0, y: 0 }, ); centerOfOverlappingMarkers.x /= markersPosition.length; centerOfOverlappingMarkers.y /= markersPosition.length; if (marksersInCluster.length > 1) { if (centerOfOverlappingMarkers.y < ownMarkerPosition.y) { if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { return "topright"; } else { return "topleft"; } } else { if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { return "bottomright"; } else { return "bottomleft"; } } } else { // Default return "topleft"; } }; export interface SmartPopupRef { handleConflict: () => void; } export const SmartPopup = ( props: PopupProps & RefAttributes & { smartPopupRef?: Ref; id: string; wrapperClassName?: string; }, ) => { const [showContent, setShowContent] = useState(false); const { smartPopupRef, id, className, wrapperClassName } = props; const [anchor, setAnchor] = useState< "topleft" | "topright" | "bottomleft" | "bottomright" >("topleft"); const handleConflict = () => { const newAnchor = useConflict(id, "popup"); setAnchor(newAnchor); }; useImperativeHandle(smartPopupRef, () => ({ handleConflict, })); const map = useMap(); useEffect(() => { setTimeout(() => { handleConflict(); setShowContent(true); }, 50); // wait for marker to be positioned by leaflet, then check for conflicts, for now no better solution map.on("zoom", handleConflict); return () => { map.off("zoom", handleConflict); }; }, [map, anchor]); return (
{props.children}
); };