import { Marker, type Map as MapLibreMap } from "maplibre-gl"; import type { EventGpsCoordinate } from "./event-location-coordinate"; const arrowDirections: Record = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }; export function createEventLocationMarker({ map, element, coordinate, disabled, onChange, onDragChange }: { map: MapLibreMap; element: HTMLButtonElement; coordinate: EventGpsCoordinate; disabled: boolean; onChange: (coordinate: EventGpsCoordinate) => void; onDragChange: (dragging: boolean) => void; }) { const marker = new Marker({ element, anchor: "bottom", draggable: !disabled }) .setLngLat(coordinate) .addTo(map); element.setAttribute("aria-label", "事件位置标记,可拖拽调整"); element.setAttribute("title", "拖拽调整事件位置,方向键微调,Shift 加速"); element.setAttribute("aria-keyshortcuts", "ArrowUp ArrowDown ArrowLeft ArrowRight"); marker.on("dragstart", () => { element.dataset.dragging = "true"; onDragChange(true); }); marker.on("dragend", () => { element.dataset.dragging = "false"; onDragChange(false); const position = marker.getLngLat(); onChange([normalizeLongitude(position.lng), clampLatitude(position.lat)]); }); // Custom MapLibre markers own their keyboard interaction. Move in screen // pixels and consume arrows so adjusting the pin cannot also pan the map. element.onkeydown = (event) => { if (!marker.isDraggable() || event.altKey || event.ctrlKey || event.metaKey) return; const direction = arrowDirections[event.key]; if (!direction) return; event.preventDefault(); event.stopPropagation(); const point = map.project(marker.getLngLat()); const step = event.shiftKey ? 10 : 1; const position = map.unproject([point.x + direction[0] * step, point.y + direction[1] * step]); const next: EventGpsCoordinate = [normalizeLongitude(position.lng), clampLatitude(position.lat)]; marker.setLngLat(next); onChange(next); }; return marker; } export function normalizeLongitude(longitude: number) { return ((longitude + 180) % 360 + 360) % 360 - 180; } export function clampLatitude(latitude: number) { return Math.max(-90, Math.min(90, latitude)); }