import type { ViewHook } from "phoenix_live_view" interface LocationMapHook extends ViewHook { map: L.Map marker: L.CircleMarker visibilityHandler: (() => void) | null } export const LocationMap: Partial = { mounted(this: LocationMapHook) { const lat = parseFloat(this.el.dataset.lat || "0") const lon = parseFloat(this.el.dataset.lon || "0") const osm = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 }) const satellite = L.tileLayer( "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", { maxZoom: 21, maxNativeZoom: 19 } ) const topo = L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", { maxZoom: 17 }) const map = L.map(this.el, { center: [lat, lon], zoom: 16, zoomControl: true, attributionControl: false }) this.map = map satellite.addTo(map) L.control.layers( { "Satellite": satellite, "OSM": osm, "Topo": topo }, {}, { position: "topright", collapsed: false } ).addTo(map) this.marker = L.circleMarker([lat, lon], { radius: 8, color: "#ffffff", weight: 2, fillColor: "#ef4444", fillOpacity: 0.95, pane: "markerPane" }).addTo(map) this.marker.bindPopup(`${lat.toFixed(6)}, ${lon.toFixed(6)}`) // LiveView reconnect / tab visibility — Leaflet needs invalidateSize. this.visibilityHandler = () => { if (document.visibilityState === "visible") this.map.invalidateSize() } document.addEventListener("visibilitychange", this.visibilityHandler) setTimeout(() => map.invalidateSize(), 50) }, reconnected(this: LocationMapHook) { this.map?.invalidateSize() }, destroyed(this: LocationMapHook) { if (this.visibilityHandler) { document.removeEventListener("visibilitychange", this.visibilityHandler) this.visibilityHandler = null } this.map?.remove() } }