Skip to content

Commit

Permalink
feat: implement props for all map-events
Browse files Browse the repository at this point in the history
  • Loading branch information
usefulthink committed Nov 8, 2023
1 parent 6221bb6 commit 1d111b4
Showing 1 changed file with 131 additions and 30 deletions.
161 changes: 131 additions & 30 deletions src/components/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,112 @@ import {useCallbackRef} from '../libraries/use-callback-ref';
export interface GoogleMapsContextValue {
map: google.maps.Map | null;
}

export const GoogleMapsContext =
React.createContext<GoogleMapsContextValue | null>(null);

/**
* Props for the Google Maps Map Component
* Handlers for all events that could be emitted by map-instances.ß
*/
export type MapProps = google.maps.MapOptions & {
style?: CSSProperties;
/**
* Adds custom style to the map by passing a css class.
*/
className?: string;
/**
* Adds initial bounds to the map as an alternative to specifying the center/zoom of the map.
* Calls the fitBounds method internally https://developers.google.com/maps/documentation/javascript/reference/map?hl=en#Map-Methods
*/
initialBounds?: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral;
/**
* An id that is added to the map. Needed when using more than one Map component.
* This is also needed to reference the map inside the useMap hook.
*/
id?: string;
/**
* Viewport from deck.gl
*/
viewport?: unknown;
/**
* View state from deck.gl
*/
viewState?: Record<string, unknown>;
/**
* Initial View State from deck.gl
*/
initialViewState?: Record<string, unknown>;
type MapEventProps = {
// map view state events
onBoundsChanged: () => void;
onCenterChanged: () => void;
onHeadingChanged: () => void;
onTiltChanged: () => void;
onZoomChanged: () => void;
onProjectionChanged: () => void;

// mouse / touch / pointer events
onClick: (
event: google.maps.MapMouseEvent | google.maps.IconMouseEvent
) => void;
onDblclick: (event: google.maps.MapMouseEvent) => void;
onContextmenu: (event: google.maps.MapMouseEvent) => void;
onMousemove: (event: google.maps.MapMouseEvent) => void;
onMouseover: (event: google.maps.MapMouseEvent) => void;
onMouseout: (event: google.maps.MapMouseEvent) => void;
onDrag: () => void;
onDragend: () => void;
onDragstart: () => void;

// loading events
onTilesloaded: () => void;
onIdle: () => void;

// configuration events
onIsFractionalZoomEnabledChanged: () => void;
onMapCapabilitiesChanged: () => void;
onMapTypeIdChanged: () => void;
onRenderingtypeChanged: () => void;
};

/**
* Maps the camelCased names of event-props to the corresponding event-types
* used in the maps API.
*/
const propNameToEventType: {[prop in keyof MapEventProps]: string} = {
onBoundsChanged: 'bounds_changed',
onCenterChanged: 'center_changed',
onClick: 'click',
onContextmenu: 'contextmenu',
onDblclick: 'dblclick',
onDrag: 'drag',
onDragend: 'dragend',
onDragstart: 'dragstart',
onHeadingChanged: 'heading_changed',
onIdle: 'idle',
onIsFractionalZoomEnabledChanged: 'isfractionalzoomenabled_changed',
onMapCapabilitiesChanged: 'mapcapabilities_changed',
onMapTypeIdChanged: 'maptypeid_changed',
onMousemove: 'mousemove',
onMouseout: 'mouseout',
onMouseover: 'mouseover',
onProjectionChanged: 'projection_changed',
onRenderingtypeChanged: 'renderingtype_changed',
onTilesloaded: 'tilesloaded',
onTiltChanged: 'tilt_changed',
onZoomChanged: 'zoom_changed'
} as const;

type MapEventPropName = keyof MapEventProps;
const propNames = Object.freeze(
Object.keys(propNameToEventType) as MapEventPropName[]
);

/**
* Props for the Google Maps Map Component
*/
export type MapProps = google.maps.MapOptions &
MapEventProps & {
style?: CSSProperties;
/**
* Adds custom style to the map by passing a css class.
*/
className?: string;
/**
* Adds initial bounds to the map as an alternative to specifying the center/zoom of the map.
* Calls the fitBounds method internally https://developers.google.com/maps/documentation/javascript/reference/map?hl=en#Map-Methods
*/
initialBounds?: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral;
/**
* An id that is added to the map. Needed when using more than one Map component.
* This is also needed to reference the map inside the useMap hook.
*/
id?: string;
/**
* Viewport from deck.gl
*/
viewport?: unknown;
/**
* View state from deck.gl
*/
viewState?: Record<string, unknown>;
/**
* Initial View State from deck.gl
*/
initialViewState?: Record<string, unknown>;
};

/**
* Component to render a Google Maps map
*/
Expand All @@ -72,6 +141,7 @@ export const Map = (props: PropsWithChildren<MapProps>) => {

const [map, mapRef] = useMapInstanceHandlerEffects(props, context);
useMapOptionsEffects(map, props);
useMapEvents(map, props);
useDeckGLCameraUpdateEffect(map, viewState);

const isViewportSet = useMemo(() => Boolean(viewport), [viewport]);
Expand Down Expand Up @@ -188,6 +258,7 @@ function useMapOptionsEffects(map: google.maps.Map | null, mapProps: MapProps) {

// All of these effects aren't triggered when the map is changed.
// In that case, the values have already been passed to the map constructor.
/* eslint-disable react-hooks/exhaustive-deps */

// update the map options when mapOptions is changed
useEffect(() => {
Expand Down Expand Up @@ -223,6 +294,36 @@ function useMapOptionsEffects(map: google.maps.Map | null, mapProps: MapProps) {

map.setTilt(tilt as number);
}, [tilt]);

/* eslint-enable react-hooks/exhaustive-deps */
}

function useMapEvents(map: google.maps.Map | null, props: MapEventProps) {
// register effects for every event-type.

// note: calling a useEffect hook from within a loop is prohibited by the
// rules of hooks, but it's ok here since it's unconditional and the number
// and order of iterations is always strictly the same.
// (see https://legacy.reactjs.org/docs/hooks-rules.html)

for (const propName of propNames) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(
() => {
if (!map) return;
if (!props[propName]) return;

const listener = map.addListener(
propNameToEventType[propName],
props[propName]
);

return () => listener.remove();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[props[propName]]
);
}
}

/**
Expand Down

0 comments on commit 1d111b4

Please sign in to comment.