/* global document */ import * as React from 'react'; import {createPortal} from 'react-dom'; import {useEffect, useState, useRef, useContext} from 'react'; import mapboxgl from '../utils/mapboxgl'; import type {MarkerDragEvent, MapboxPopup, PointLike, Anchor, Alignment} from '../types'; import {MapContext} from './map'; import {arePointsEqual} from '../utils/deep-equal'; export type MarkerProps = { /** Longitude of the anchor location */ longitude: number; /** Latitude of the anchor location */ latitude: number; /** A string indicating the part of the Marker that should be positioned closest to the coordinate set via Marker.setLngLat. * Options are `'center'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. * @default "center" */ anchor?: Anchor; /** * The max number of pixels a user can shift the mouse pointer during a click on the marker for it to be considered a valid click * (as opposed to a marker drag). The default (0) is to inherit map's clickTolerance. */ clickTolerance?: number; /** The color to use for the default marker if options.element is not provided. * @default "#3FB1CE" */ color?: string; /** A boolean indicating whether or not a marker is able to be dragged to a new position on the map. * @default false */ draggable?: boolean; /** The offset in pixels as a PointLike object to apply relative to the element's center. Negatives indicate left and up. */ offset?: PointLike; /** `map` aligns the `Marker` to the plane of the map. * `viewport` aligns the `Marker` to the plane of the viewport. * `auto` automatically matches the value of `rotationAlignment`. * @default "auto" */ pitchAlignment?: Alignment; /** The rotation angle of the marker in degrees, relative to its `rotationAlignment` setting. A positive value will rotate the marker clockwise. * @default 0 */ rotation?: number; /** `map` aligns the `Marker`'s rotation relative to the map, maintaining a bearing as the map rotates. * `viewport` aligns the `Marker`'s rotation relative to the viewport, agnostic to map rotations. * `auto` is equivalent to `viewport`. * @default "auto" */ rotationAlignment?: Alignment; /** The scale to use for the default marker if options.element is not provided. * The default scale (1) corresponds to a height of `41px` and a width of `27px`. * @default 1 */ scale?: number; /** A Popup instance that is bound to the marker */ popup?: MapboxPopup; onDragStart?: (e: MarkerDragEvent) => void; onDrag?: (e: MarkerDragEvent) => void; onDragEnd?: (e: MarkerDragEvent) => void; children?: React.ReactNode; }; const defaultProps: Partial = { draggable: false, popup: null, rotation: 0, rotationAlignment: 'auto', pitchAlignment: 'auto' }; function Marker(props: MarkerProps) { const map = useContext(MapContext); const [marker] = useState(() => { let hasChildren = false; React.Children.forEach(props.children, el => { if (el) { hasChildren = true; } }); const options = { ...props, element: hasChildren ? document.createElement('div') : null }; return new mapboxgl.Marker(options).setLngLat([props.longitude, props.latitude]).addTo(map); }); const thisRef = useRef({props}); thisRef.current.props = props; useEffect(() => { marker.on('dragstart', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDragStart?.(evt); }); marker.on('drag', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDrag?.(evt); }); marker.on('dragend', e => { const evt = e as MarkerDragEvent; evt.lngLat = marker.getLngLat(); thisRef.current.props.onDragEnd?.(evt); }); return () => { marker.remove(); }; }, []); if (marker.getLngLat().lng !== props.longitude || marker.getLngLat().lat !== props.latitude) { marker.setLngLat([props.longitude, props.latitude]); } if (props.offset && !arePointsEqual(marker.getOffset(), props.offset)) { marker.setOffset(props.offset); } if (marker.isDraggable() !== props.draggable) { marker.setDraggable(props.draggable); } if (marker.getRotation() !== props.rotation) { marker.setRotation(props.rotation); } if (marker.getRotationAlignment() !== props.rotationAlignment) { marker.setRotationAlignment(props.rotationAlignment); } if (marker.getPitchAlignment() !== props.pitchAlignment) { marker.setPitchAlignment(props.pitchAlignment); } if (marker.getPopup() !== props.popup) { marker.setPopup(props.popup); } return createPortal(props.children, marker.getElement()); } Marker.defaultProps = defaultProps; // @ts-ignore export default React.memo(Marker);