UNPKG

@vis.gl/react-google-maps

Version:

React components and hooks for the Google Maps JavaScript API

268 lines (238 loc) 6.87 kB
import {useEffect} from 'react'; /** * Base event type for all Map3D events. */ export interface Map3DEvent { type: string; map3d: google.maps.maps3d.Map3DElement; } /** * Event fired when a camera property changes. */ export interface Map3DCameraChangedEvent extends Map3DEvent { detail: { center: google.maps.LatLngAltitudeLiteral; range: number; heading: number; tilt: number; roll: number; }; } /** * Event fired when the map is clicked. */ export interface Map3DClickEvent extends Map3DEvent { detail: { position: google.maps.LatLngAltitude | null; placeId?: string; }; } /** * Event fired when the map's steady state changes. */ export interface Map3DSteadyChangeEvent extends Map3DEvent { detail: { isSteady: boolean; }; } /** * Props for Map3D event handlers. */ export interface Map3DEventProps { /** Called when the center property changes. */ onCenterChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when the heading property changes. */ onHeadingChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when the tilt property changes. */ onTiltChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when the range property changes. */ onRangeChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when the roll property changes. */ onRollChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when any camera property changes (aggregated). */ onCameraChanged?: (event: Map3DCameraChangedEvent) => void; /** Called when the map is clicked. */ onClick?: (event: Map3DClickEvent) => void; /** Called when the map's steady state changes. */ onSteadyChange?: (event: Map3DSteadyChangeEvent) => void; /** Called when a fly animation ends. */ onAnimationEnd?: (event: Map3DEvent) => void; /** Called when a map error occurs. */ onError?: (event: Map3DEvent) => void; } /** * Camera-related event types for the aggregated onCameraChanged handler. */ const CAMERA_EVENTS = [ 'gmp-centerchange', 'gmp-headingchange', 'gmp-tiltchange', 'gmp-rangechange', 'gmp-rollchange' ]; /** * Creates a camera changed event with current camera state. */ function createCameraEvent( map3d: google.maps.maps3d.Map3DElement, type: string ): Map3DCameraChangedEvent { const center = map3d.center; // Normalize center to LatLngAltitudeLiteral // If center is a LatLngAltitude class instance, it has a toJSON method // Otherwise it's already a literal object let centerLiteral: google.maps.LatLngAltitudeLiteral; if (center && 'toJSON' in center && typeof center.toJSON === 'function') { centerLiteral = (center as google.maps.LatLngAltitude).toJSON(); } else if (center) { centerLiteral = center as google.maps.LatLngAltitudeLiteral; } else { centerLiteral = {lat: 0, lng: 0, altitude: 0}; } return { type, map3d, detail: { center: centerLiteral, range: map3d.range || 0, heading: map3d.heading || 0, tilt: map3d.tilt || 0, roll: map3d.roll || 0 } }; } /** * Creates a click event from a LocationClickEvent or PlaceClickEvent. */ function createClickEvent( map3d: google.maps.maps3d.Map3DElement, srcEvent: | google.maps.maps3d.LocationClickEvent | google.maps.maps3d.PlaceClickEvent ): Map3DClickEvent { const placeClickEvent = srcEvent as google.maps.maps3d.PlaceClickEvent; return { type: 'gmp-click', map3d, detail: { position: srcEvent.position || null, placeId: placeClickEvent.placeId } }; } /** * Creates a steady change event. */ function createSteadyChangeEvent( map3d: google.maps.maps3d.Map3DElement, srcEvent: google.maps.maps3d.SteadyChangeEvent ): Map3DSteadyChangeEvent { return { type: 'gmp-steadychange', map3d, detail: { isSteady: srcEvent.isSteady } }; } /** * Hook to set up event handlers for Map3D events. * * @internal */ export function useMap3DEvents( map3d: google.maps.maps3d.Map3DElement | null, props: Map3DEventProps ) { const { onCenterChanged, onHeadingChanged, onTiltChanged, onRangeChanged, onRollChanged, onCameraChanged, onClick, onSteadyChange, onAnimationEnd, onError } = props; useMap3DEvent(map3d, 'gmp-centerchange', onCenterChanged, createCameraEvent); useMap3DEvent( map3d, 'gmp-headingchange', onHeadingChanged, createCameraEvent ); useMap3DEvent(map3d, 'gmp-tiltchange', onTiltChanged, createCameraEvent); useMap3DEvent(map3d, 'gmp-rangechange', onRangeChanged, createCameraEvent); useMap3DEvent(map3d, 'gmp-rollchange', onRollChanged, createCameraEvent); // onCameraChanged aggregates all camera property change events into one handler useEffect(() => { if (!map3d || !onCameraChanged) return; const handler = () => { onCameraChanged(createCameraEvent(map3d, 'camerachange')); }; for (const eventName of CAMERA_EVENTS) { map3d.addEventListener(eventName, handler); } return () => { for (const eventName of CAMERA_EVENTS) { map3d.removeEventListener(eventName, handler); } }; }, [map3d, onCameraChanged]); useEffect(() => { if (!map3d || !onClick) return; const handler = (ev: Event) => { onClick( createClickEvent( map3d, ev as | google.maps.maps3d.LocationClickEvent | google.maps.maps3d.PlaceClickEvent ) ); }; map3d.addEventListener('gmp-click', handler); return () => map3d.removeEventListener('gmp-click', handler); }, [map3d, onClick]); useEffect(() => { if (!map3d || !onSteadyChange) return; const handler = (ev: Event) => { onSteadyChange( createSteadyChangeEvent( map3d, ev as google.maps.maps3d.SteadyChangeEvent ) ); }; map3d.addEventListener('gmp-steadychange', handler); return () => map3d.removeEventListener('gmp-steadychange', handler); }, [map3d, onSteadyChange]); useMap3DEvent(map3d, 'gmp-animationend', onAnimationEnd, (map3d, type) => ({ type, map3d })); useMap3DEvent(map3d, 'gmp-error', onError, (map3d, type) => ({ type, map3d })); } /** * Helper hook for individual events. */ function useMap3DEvent<T extends Map3DEvent>( map3d: google.maps.maps3d.Map3DElement | null, eventName: string, handler: ((event: T) => void) | undefined, createEvent: (map3d: google.maps.maps3d.Map3DElement, type: string) => T ) { useEffect(() => { if (!map3d || !handler) return; const listener = () => { handler(createEvent(map3d, eventName)); }; map3d.addEventListener(eventName, listener); return () => map3d.removeEventListener(eventName, listener); }, [map3d, eventName, handler, createEvent]); }