import L from 'leaflet'; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { Marker, Tooltip, useMap } from 'react-leaflet'; import { MAP_LABEL_BADGE_CLASS } from './mapHoverLabel'; import { plannerPinShape, type PlannerPinPoint } from './plannerPinShape'; import { wirePlannerWaypointDrag } from './plannerWaypointDrag'; /** The pin's inline style for a size and selection; the renderer owns the colour. */ export type PinCss = (size: number, selected: boolean) => string; function plannerWaypointIcon( pinCss: PinCss, point: PlannerPinPoint, index: number, selected: boolean, draggable: boolean ): L.DivIcon { const { size, text } = plannerPinShape(point, index, selected); return L.divIcon({ className: 'tour-planner-waypoint-marker', iconSize: [size, size], iconAnchor: [size / 2, size / 2], html: `${text}`, }); } export default function PlannerWaypointMarker({ point, index, selected, onClick, onMove, pinCss, }: { point: { id: string; lat: number; lng: number; name?: string; detail?: string } & PlannerPinPoint; index: number; selected: boolean; onClick?: (id: string) => void; onMove?: (id: string, lat: number, lng: number) => boolean; pinCss: PinCss; }) { const map = useMap(); const [marker, setMarker] = useState(null); const handlersRef = useRef({ onClick, onMove }); useLayoutEffect(() => { handlersRef.current = { onClick, onMove }; }, [onClick, onMove]); const position = useMemo(() => [point.lat, point.lng] as [number, number], [point.lat, point.lng]); const { label, shaping } = point; const icon = useMemo( () => plannerWaypointIcon(pinCss, { label, shaping }, index, selected, !!onMove), [index, label, onMove, pinCss, selected, shaping] ); const eventHandlers = useMemo( () => ({ click: (event: { originalEvent: MouseEvent }) => { event.originalEvent.stopPropagation(); handlersRef.current.onClick?.(point.id); }, }), [point.id] ); useEffect(() => { if (!marker || !onMove) return; const element = marker.getElement(); if (!element) return; return wirePlannerWaypointDrag({ element, initial: { lat: point.lat, lng: point.lng }, coordinateAt: (clientX, clientY) => { const rect = map.getContainer().getBoundingClientRect(); const at = map.containerPointToLatLng([clientX - rect.left, clientY - rect.top]); return { lat: at.lat, lng: at.lng }; }, setPosition: ({ lat, lng }) => { marker.setLatLng([lat, lng]); }, onCommit: ({ lat, lng }) => handlersRef.current.onMove?.(point.id, lat, lng) ?? false, }); }, [map, marker, onMove, point.id, point.lat, point.lng]); return ( {point.name ?? index + 1} {point.detail && {point.detail}} ); }