mirror of
https://github.com/visgl/react-map-gl.git
synced 2026-01-18 15:54:22 +00:00
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
/* 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 {MapboxEvent, Anchor, PointLike} from '../types';
|
|
|
|
import {MapContext} from './map';
|
|
import {deepEqual} from '../utils/deep-equal';
|
|
|
|
export type PopupProps = {
|
|
/** Longitude of the anchor location */
|
|
longitude: number;
|
|
/** Latitude of the anchor location */
|
|
latitude: number;
|
|
/**
|
|
* A string indicating the part of the popup that should be positioned closest to the coordinate.
|
|
* Options are `'center'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`,
|
|
* and `'bottom-right'`. If unset, the anchor will be dynamically set to ensure the popup falls within the map
|
|
* container with a preference for `'bottom'`.
|
|
*/
|
|
anchor?: Anchor;
|
|
/**
|
|
* If `true`, a close button will appear in the top right corner of the popup.
|
|
* @default true
|
|
*/
|
|
closeButton?: boolean;
|
|
/**
|
|
* If `true`, the popup will close when the map is clicked.
|
|
* @default true
|
|
*/
|
|
closeOnClick?: boolean;
|
|
/**
|
|
* If `true`, the popup will closed when the map moves.
|
|
* @default false
|
|
*/
|
|
closeOnMove?: boolean;
|
|
/**
|
|
* If `true`, the popup will try to focus the first focusable element inside the popup.
|
|
* @default true
|
|
*/
|
|
focusAfterOpen?: boolean;
|
|
/**
|
|
* A pixel offset applied to the popup's location specified as:
|
|
* - a single number specifying a distance from the popup's location
|
|
* - a PointLike specifying a constant offset
|
|
* - an object of Points specifing an offset for each anchor position.
|
|
*/
|
|
offset?: number | PointLike | Partial<{[anchor in Anchor]: PointLike}>;
|
|
/** Space-separated CSS class names to add to popup container. */
|
|
className?: string;
|
|
/**
|
|
* A string that sets the CSS property of the popup's maximum width (for example, `'300px'`).
|
|
* To ensure the popup resizes to fit its content, set this property to `'none'`
|
|
* @default "240px"
|
|
*/
|
|
maxWidth?: string;
|
|
|
|
onOpen?: (e: MapboxEvent) => void;
|
|
onClose?: (e: MapboxEvent) => void;
|
|
children?: React.ReactNode;
|
|
};
|
|
|
|
function Popup(props: PopupProps) {
|
|
const map = useContext(MapContext);
|
|
const [container] = useState(() => {
|
|
return document.createElement('div');
|
|
});
|
|
const [popup] = useState(() => {
|
|
const options = {...props};
|
|
return new mapboxgl.Popup(options)
|
|
.setLngLat([props.longitude, props.latitude])
|
|
.setDOMContent(container)
|
|
.addTo(map);
|
|
});
|
|
const thisRef = useRef({props});
|
|
thisRef.current.props = props;
|
|
|
|
useEffect(() => {
|
|
popup.on('open', e => {
|
|
thisRef.current.props.onOpen?.(e as MapboxEvent);
|
|
});
|
|
popup.on('close', e => {
|
|
thisRef.current.props.onClose?.(e as MapboxEvent);
|
|
});
|
|
|
|
return () => {
|
|
popup.remove();
|
|
};
|
|
}, []);
|
|
|
|
if (popup.getLngLat().lng !== props.longitude || popup.getLngLat().lat !== props.latitude) {
|
|
popup.setLngLat([props.longitude, props.latitude]);
|
|
}
|
|
// @ts-ignore
|
|
if (props.offset && deepEqual(popup.options.offset, props.offset)) {
|
|
popup.setOffset(props.offset);
|
|
}
|
|
// @ts-ignore
|
|
if (popup.options.anchor !== props.anchor || popup.options.maxWidth !== props.maxWidth) {
|
|
// @ts-ignore
|
|
popup.options.anchor = props.anchor;
|
|
popup.setMaxWidth(props.maxWidth);
|
|
}
|
|
// Adapted from https://github.com/mapbox/mapbox-gl-js/blob/main/src/ui/popup.js
|
|
// @ts-ignore
|
|
if (popup.options.className !== props.className) {
|
|
// @ts-ignore
|
|
popup.options.className = props.className;
|
|
// @ts-ignore
|
|
popup._classList = new Set(props.className ? props.className.trim().split(/\s+/) : []);
|
|
// @ts-ignore
|
|
popup._updateClassList();
|
|
}
|
|
|
|
return createPortal(props.children, container);
|
|
}
|
|
|
|
// @ts-ignore
|
|
export default React.memo(Popup);
|