import React, { useRef, useCallback, useEffect } from 'react'; import { StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; import type { LatLng } from '../../types/location'; import { getLeafletMapHtml } from './LeafletMapHtml'; import type { LeafletMapMarker, LeafletMapPolyline } from './LeafletMapHtml'; export interface LeafletMapProps { /** Center coordinate of the map */ center: LatLng; /** Initial zoom level (default 14) */ zoom?: number; /** Markers to display on the map */ markers?: LeafletMapMarker[]; /** Polylines to draw on the map */ polylines?: LeafletMapPolyline[]; /** Whether to show the pulsing user-location marker */ showCurrentLocation?: boolean; /** Whether the map is interactive (default true) */ interactive?: boolean; /** Fired when a marker is tapped */ onMarkerTap?: (data: { lat: number; lng: number; title?: string; type?: string }) => void; /** Fired once the Leaflet map is ready */ onMapReady?: () => void; /** Fired on map move (center / zoom change) */ onMapMove?: (data: { lat: number; lng: number; zoom: number }) => void; /** Optional style override for the WebView container */ style?: object; } /** * WebView wrapper that renders a Leaflet.js map via injected HTML. * Handles two-way communication (postMessage / onMessage) between the * WebView and React Native. */ export function LeafletMap({ center, zoom = 14, markers = [], polylines = [], showCurrentLocation = false, interactive = true, onMarkerTap, onMapReady, onMapMove, style, }: LeafletMapProps) { const webViewRef = useRef(null); // Regenerate HTML when core options change const html = getLeafletMapHtml({ centerLat: center.lat, centerLng: center.lng, zoom, markers, polylines, showCurrentLocation, interactive, }); const handleMessage = useCallback( (event: { nativeEvent: { data: string } }) => { try { const msg = JSON.parse(event.nativeEvent.data); switch (msg.event) { case 'map_ready': onMapReady?.(); break; case 'marker_tap': onMarkerTap?.(msg.data); break; case 'map_move': onMapMove?.(msg.data); break; } } catch { // ignore malformed messages } }, [onMapReady, onMarkerTap, onMapMove], ); // Public imperative methods exposed via ref-forwarding // These are called by injecting JS into the WebView. const runJS = useCallback((js: string) => { webViewRef.current?.injectJavaScript(js); }, []); // Expose updateCurrentLocation as a side-effect (if needed) // The parent can call `webViewRef.current.injectJavaScript(...)` directly. useEffect(() => { // No-op on mount; the map is fully rendered via the html prop. }, [center.lat, center.lng]); return ( ); } const styles = StyleSheet.create({ webview: { flex: 1, backgroundColor: 'transparent', }, });