UNPKG

@vis.gl/react-mapbox

Version:

React components for Mapbox GL JS

138 lines (119 loc) 4.02 kB
/* global document */ import * as React from 'react'; import {createPortal} from 'react-dom'; import {useImperativeHandle, useEffect, useMemo, useRef, useContext, forwardRef, memo} from 'react'; import {applyReactStyle} from '../utils/apply-react-style'; import type {PopupInstance, MarkerInstance, MarkerOptions} from '../types/lib'; import type {MarkerEvent, MarkerDragEvent} from '../types/events'; import {MapContext} from './map'; import {arePointsEqual} from '../utils/deep-equal'; import {compareClassNames} from '../utils/compare-class-names'; export type MarkerProps = MarkerOptions & { /** Longitude of the anchor location */ longitude: number; /** Latitude of the anchor location */ latitude: number; popup?: PopupInstance; /** CSS style override, applied to the control's container */ style?: React.CSSProperties; onClick?: (e: MarkerEvent<MouseEvent>) => void; onDragStart?: (e: MarkerDragEvent) => void; onDrag?: (e: MarkerDragEvent) => void; onDragEnd?: (e: MarkerDragEvent) => void; children?: React.ReactNode; }; /* eslint-disable complexity,max-statements */ export const Marker = memo( forwardRef((props: MarkerProps, ref: React.Ref<MarkerInstance>) => { const {map, mapLib} = useContext(MapContext); const thisRef = useRef({props}); const marker: MarkerInstance = useMemo(() => { let hasChildren = false; React.Children.forEach(props.children, el => { if (el) { hasChildren = true; } }); const options = { ...props, element: hasChildren ? document.createElement('div') : null }; const mk = new mapLib.Marker(options); mk.setLngLat([props.longitude, props.latitude]); mk.getElement().addEventListener('click', (e: MouseEvent) => { thisRef.current.props.onClick?.({ type: 'click', target: mk, originalEvent: e }); }); mk.on('dragstart', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDragStart?.(evt); }); mk.on('drag', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDrag?.(evt); }); mk.on('dragend', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDragEnd?.(evt); }); return mk; }, []); useEffect(() => { marker.addTo(map.getMap()); return () => { marker.remove(); }; }, []); const { longitude, latitude, offset, style, draggable = false, popup = null, rotation = 0, rotationAlignment = 'auto', pitchAlignment = 'auto' } = props; useEffect(() => { applyReactStyle(marker.getElement(), style); }, [style]); useImperativeHandle(ref, () => marker, []); const oldProps = thisRef.current.props; if (marker.getLngLat().lng !== longitude || marker.getLngLat().lat !== latitude) { marker.setLngLat([longitude, latitude]); } if (offset && !arePointsEqual(marker.getOffset(), offset)) { marker.setOffset(offset); } if (marker.isDraggable() !== draggable) { marker.setDraggable(draggable); } if (marker.getRotation() !== rotation) { marker.setRotation(rotation); } if (marker.getRotationAlignment() !== rotationAlignment) { marker.setRotationAlignment(rotationAlignment); } if (marker.getPitchAlignment() !== pitchAlignment) { marker.setPitchAlignment(pitchAlignment); } if (marker.getPopup() !== popup) { marker.setPopup(popup); } const classNameDiff = compareClassNames(oldProps.className, props.className); if (classNameDiff) { for (const c of classNameDiff) { marker.toggleClassName(c); } } thisRef.current.props = props; return createPortal(props.children, marker.getElement()); }) );