react-leaflet
Version:
React components for Leaflet maps
987 lines (875 loc) • 29.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('leaflet'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'leaflet', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ReactLeaflet = {}, global.React, global.L, global.ReactDOM));
}(this, (function (exports, React, leaflet, reactDom) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
function useAttribution(map, attribution) {
const attributionRef = React.useRef(attribution);
React.useEffect(function updateAttribution() {
if (attribution !== attributionRef.current && map.attributionControl != null) {
if (attributionRef.current != null) {
map.attributionControl.removeAttribution(attributionRef.current);
}
if (attribution != null) {
map.attributionControl.addAttribution(attribution);
}
}
attributionRef.current = attribution;
}, [map, attribution]);
}
function updateCircle(layer, props, prevProps) {
if (props.center !== prevProps.center) {
layer.setLatLng(props.center);
}
if (props.radius != null && props.radius !== prevProps.radius) {
layer.setRadius(props.radius);
}
}
const CONTEXT_VERSION = 1;
const LeafletContext = /*#__PURE__*/React.createContext(null);
const LeafletProvider = LeafletContext.Provider;
function useLeafletContext() {
const context = React.useContext(LeafletContext);
if (context == null) {
throw new Error('No context provided: useLeafletContext() can only be used in a descendant of <MapContainer>');
}
return context;
}
function createContainerComponent(useElement) {
function ContainerComponent(props, ref) {
const {
instance,
context
} = useElement(props).current;
React.useImperativeHandle(ref, () => instance);
return props.children == null ? null : /*#__PURE__*/React__default['default'].createElement(LeafletProvider, {
value: context
}, props.children);
}
return /*#__PURE__*/React.forwardRef(ContainerComponent);
}
function createDivOverlayComponent(useElement) {
function OverlayComponent(props, ref) {
const [isOpen, setOpen] = React.useState(false);
const {
instance
} = useElement(props, setOpen).current;
React.useImperativeHandle(ref, () => instance);
React.useEffect(function updateOverlay() {
if (isOpen) {
instance.update();
}
}, [instance, isOpen, props.children]); // @ts-ignore _contentNode missing in type definition
const contentNode = instance._contentNode;
return contentNode ? /*#__PURE__*/reactDom.createPortal(props.children, contentNode) : null;
}
return /*#__PURE__*/React.forwardRef(OverlayComponent);
}
function createLeafComponent(useElement) {
function LeafComponent(props, ref) {
const {
instance
} = useElement(props).current;
React.useImperativeHandle(ref, () => instance);
return null;
}
return /*#__PURE__*/React.forwardRef(LeafComponent);
}
function createControlHook(useElement) {
return function useLeafletControl(props) {
const context = useLeafletContext();
const elementRef = useElement(props, context);
const {
instance
} = elementRef.current;
const positionRef = React.useRef(props.position);
const {
position
} = props;
React.useEffect(function addControl() {
instance.addTo(context.map);
return function removeControl() {
instance.remove();
};
}, [context.map, instance]);
React.useEffect(function updateControl() {
if (position != null && position !== positionRef.current) {
instance.setPosition(position);
positionRef.current = position;
}
}, [instance, position]);
return elementRef;
};
}
function useEventHandlers(element, eventHandlers) {
const eventHandlersRef = React.useRef();
React.useEffect(function addEventHandlers() {
if (eventHandlers != null) {
element.instance.on(eventHandlers);
}
eventHandlersRef.current = eventHandlers;
return function removeEventHandlers() {
if (eventHandlersRef.current != null) {
element.instance.off(eventHandlersRef.current);
}
eventHandlersRef.current = null;
};
}, [element, eventHandlers]);
}
function withPane(props, context) {
const pane = props.pane ?? context.pane;
return pane ? { ...props,
pane
} : props;
}
function createDivOverlayHook(useElement, useLifecycle) {
return function useDivOverlay(props, setOpen) {
const context = useLeafletContext();
const elementRef = useElement(withPane(props, context), context);
useAttribution(context.map, props.attribution);
useEventHandlers(elementRef.current, props.eventHandlers);
useLifecycle(elementRef.current, context, props, setOpen);
return elementRef;
};
}
function splitClassName(className) {
return className.split(' ').filter(Boolean);
}
function addClassName(element, className) {
splitClassName(className).forEach(cls => {
leaflet.DomUtil.addClass(element, cls);
});
}
function createElementHook(createElement, updateElement) {
if (updateElement == null) {
return function useImmutableLeafletElement(props, context) {
return React.useRef(createElement(props, context));
};
}
return function useMutableLeafletElement(props, context) {
const elementRef = React.useRef(createElement(props, context));
const propsRef = React.useRef(props);
const {
instance
} = elementRef.current;
React.useEffect(function updateElementProps() {
if (propsRef.current !== props) {
updateElement(instance, props, propsRef.current);
propsRef.current = props;
}
}, [instance, props, context]);
return elementRef;
};
}
function useLayerLifecycle(element, context) {
React.useEffect(function addLayer() {
const container = context.layerContainer ?? context.map;
container.addLayer(element.instance);
return function removeLayer() {
var _context$layersContro;
(_context$layersContro = context.layersControl) == null ? void 0 : _context$layersContro.removeLayer(element.instance);
context.map.removeLayer(element.instance);
};
}, [context, element]);
}
function createLayerHook(useElement) {
return function useLayer(props) {
const context = useLeafletContext();
const elementRef = useElement(withPane(props, context), context);
useAttribution(context.map, props.attribution);
useEventHandlers(elementRef.current, props.eventHandlers);
useLayerLifecycle(elementRef.current, context);
return elementRef;
};
}
function usePathOptions(element, props) {
const optionsRef = React.useRef();
React.useEffect(function updatePathOptions() {
if (props.pathOptions !== optionsRef.current) {
const options = props.pathOptions ?? {};
element.instance.setStyle(options);
optionsRef.current = options;
}
}, [element, props]);
}
function createPathHook(useElement) {
return function usePath(props) {
const context = useLeafletContext();
const elementRef = useElement(withPane(props, context), context);
useEventHandlers(elementRef.current, props.eventHandlers);
useLayerLifecycle(elementRef.current, context);
usePathOptions(elementRef.current, props);
return elementRef;
};
}
function createControlComponent(createInstance) {
function createElement(props, context) {
return {
instance: createInstance(props),
context
};
}
const useElement = createElementHook(createElement);
const useControl = createControlHook(useElement);
return createLeafComponent(useControl);
}
function createLayerComponent(createElement, updateElement) {
const useElement = createElementHook(createElement, updateElement);
const useLayer = createLayerHook(useElement);
return createContainerComponent(useLayer);
}
function createOverlayComponent(createElement, useLifecycle) {
const useElement = createElementHook(createElement);
const useOverlay = createDivOverlayHook(useElement, useLifecycle);
return createDivOverlayComponent(useOverlay);
}
function createPathComponent(createElement, updateElement) {
const useElement = createElementHook(createElement, updateElement);
const usePath = createPathHook(useElement);
return createContainerComponent(usePath);
}
function createTileLayerComponent(createElement, updateElement) {
const useElement = createElementHook(createElement, updateElement);
const useLayer = createLayerHook(useElement);
return createLeafComponent(useLayer);
}
function updateGridLayer(layer, props, prevProps) {
const {
opacity,
zIndex
} = props;
if (opacity != null && opacity !== prevProps.opacity) {
layer.setOpacity(opacity);
}
if (zIndex != null && zIndex !== prevProps.zIndex) {
layer.setZIndex(zIndex);
}
}
function updateMediaOverlay(overlay, props, prevProps) {
if (props.bounds instanceof leaflet.LatLngBounds && props.bounds !== prevProps.bounds) {
overlay.setBounds(props.bounds);
}
if (props.opacity != null && props.opacity !== prevProps.opacity) {
overlay.setOpacity(props.opacity);
}
if (props.zIndex != null && props.zIndex !== prevProps.zIndex) {
// @ts-ignore missing in definition but inherited from ImageOverlay
overlay.setZIndex(props.zIndex);
}
}
function useMap() {
return useLeafletContext().map;
}
function useMapEvent(type, handler) {
const map = useMap();
React.useEffect(function addMapEventHandler() {
// @ts-ignore event type
map.on(type, handler);
return function removeMapEventHandler() {
// @ts-ignore event type
map.off(type, handler);
};
}, [map, type, handler]);
return map;
}
function useMapEvents(handlers) {
const map = useMap();
React.useEffect(function addMapEventHandlers() {
map.on(handlers);
return function removeMapEventHandlers() {
map.off(handlers);
};
}, [map, handlers]);
return map;
}
const AttributionControl = createControlComponent(function createAttributionControl(props) {
return new leaflet.Control.Attribution(props);
});
const Circle = createPathComponent(function createCircle({
center,
children: _c,
...options
}, ctx) {
const instance = new leaflet.Circle(center, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, updateCircle);
const CircleMarker = createPathComponent(function createCircleMarker({
center,
children: _c,
...options
}, ctx) {
const instance = new leaflet.CircleMarker(center, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, updateCircle);
const FeatureGroup = createPathComponent(function createFeatureGroup({
children: _c,
...options
}, ctx) {
const instance = new leaflet.FeatureGroup([], options);
const context = { ...ctx,
layerContainer: instance,
overlayContainer: instance
};
return {
instance,
context
};
});
const GeoJSON = createPathComponent(function createGeoJSON({
data,
...options
}, ctx) {
const instance = new leaflet.GeoJSON(data, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateGeoJSON(layer, props, prevProps) {
if (props.style !== prevProps.style) {
if (props.style == null) {
layer.resetStyle();
} else {
layer.setStyle(props.style);
}
}
});
const ImageOverlay = createLayerComponent(function createImageOveraly({
bounds,
url,
...options
}, ctx) {
const instance = new leaflet.ImageOverlay(url, bounds, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateImageOverlay(overlay, props, prevProps) {
updateMediaOverlay(overlay, props, prevProps);
if (props.url !== prevProps.url) {
overlay.setUrl(props.url);
}
});
const LayerGroup = createLayerComponent(function createLayerGroup({
children: _c,
...options
}, ctx) {
const instance = new leaflet.LayerGroup([], options);
return {
instance,
context: { ...ctx,
layerContainer: instance
}
};
});
const useLayersControlElement = createElementHook(function createLayersControl({
children: _c,
...options
}, ctx) {
const instance = new leaflet.Control.Layers(undefined, undefined, options);
return {
instance,
context: { ...ctx,
layersControl: instance
}
};
}, function updateLayersControl(control, props, prevProps) {
if (props.collapsed !== prevProps.collapsed) {
if (props.collapsed === true) {
control.collapse();
} else {
control.expand();
}
}
});
const useLayersControl = createControlHook(useLayersControlElement);
// @ts-ignore
const LayersControl = createContainerComponent(useLayersControl);
function createControlledLayer(addLayerToControl) {
return function ControlledLayer(props) {
const parentContext = useLeafletContext();
const propsRef = React.useRef(props);
const [layer, setLayer] = React.useState(null);
const {
layersControl,
map
} = parentContext;
const addLayer = React.useCallback(layerToAdd => {
if (layersControl != null) {
if (propsRef.current.checked) {
map.addLayer(layerToAdd);
}
addLayerToControl(layersControl, layerToAdd, propsRef.current.name);
setLayer(layerToAdd);
}
}, [layersControl, map]);
const removeLayer = React.useCallback(layerToRemove => {
layersControl == null ? void 0 : layersControl.removeLayer(layerToRemove);
setLayer(null);
}, [layersControl]);
const context = React.useMemo(() => ({ ...parentContext,
layerContainer: {
addLayer,
removeLayer
}
}), [parentContext, addLayer, removeLayer]);
React.useEffect(() => {
if (layer !== null && propsRef.current !== props) {
if (props.checked === true && (propsRef.current.checked == null || propsRef.current.checked === false)) {
map.addLayer(layer);
} else if (propsRef.current.checked === true && (props.checked == null || props.checked === false)) {
map.removeLayer(layer);
}
propsRef.current = props;
}
});
return props.children ? /*#__PURE__*/React__default['default'].createElement(LeafletProvider, {
value: context
}, props.children) : null;
};
}
LayersControl.BaseLayer = createControlledLayer(function addBaseLayer(layersControl, layer, name) {
layersControl.addBaseLayer(layer, name);
});
LayersControl.Overlay = createControlledLayer(function addOverlay(layersControl, layer, name) {
layersControl.addOverlay(layer, name);
});
function MapConsumer({
children
}) {
return children(useMap());
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function useMapElement(mapRef, props) {
const [map, setMap] = React.useState(null);
React.useEffect(() => {
if (mapRef.current !== null && map === null) {
const instance = new leaflet.Map(mapRef.current, props);
if (props.center != null && props.zoom != null) {
instance.setView(props.center, props.zoom);
} else if (props.bounds != null) {
instance.fitBounds(props.bounds, props.boundsOptions);
}
if (props.whenReady != null) {
instance.whenReady(props.whenReady);
}
setMap(instance);
}
}, [mapRef, map, props]);
return map;
}
function MapContainer({
children,
className,
id,
placeholder,
style,
whenCreated,
...options
}) {
const mapRef = React.useRef(null);
const map = useMapElement(mapRef, options);
const createdRef = React.useRef(false);
React.useEffect(() => {
if (map != null && createdRef.current === false && whenCreated != null) {
createdRef.current = true;
whenCreated(map);
}
}, [map, whenCreated]);
const [props] = React.useState({
className,
id,
style
});
const context = React.useMemo(() => map ? {
__version: CONTEXT_VERSION,
map
} : null, [map]);
const contents = context ? /*#__PURE__*/React__default['default'].createElement(LeafletProvider, {
value: context
}, children) : placeholder ?? null;
return /*#__PURE__*/React__default['default'].createElement("div", _extends({}, props, {
ref: mapRef
}), contents);
}
const Marker = createLayerComponent(function createMarker({
position,
...options
}, ctx) {
const instance = new leaflet.Marker(position, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateMarker(marker, props, prevProps) {
if (props.position !== prevProps.position) {
marker.setLatLng(props.position);
}
if (props.icon != null && props.icon !== prevProps.icon) {
marker.setIcon(props.icon);
}
if (props.zIndexOffset != null && props.zIndexOffset !== prevProps.zIndexOffset) {
marker.setZIndexOffset(props.zIndexOffset);
}
if (props.opacity != null && props.opacity !== prevProps.opacity) {
marker.setOpacity(props.opacity);
}
if (marker.dragging != null && props.draggable !== prevProps.draggable) {
if (props.draggable === true) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
}
});
const DEFAULT_PANES = ['mapPane', 'markerPane', 'overlayPane', 'popupPane', 'shadowPane', 'tilePane', 'tooltipPane'];
function omitPane(obj, pane) {
const {
[pane]: _p,
...others
} = obj;
return others;
}
function createPane(props, context) {
const name = props.name;
if (DEFAULT_PANES.indexOf(name) !== -1) {
throw new Error(`You must use a unique name for a pane that is not a default Leaflet pane: ${name}`);
}
if (context.map.getPane(name) != null) {
throw new Error(`A pane with this name already exists: ${name}`);
}
const parentPaneName = props.pane ?? context.pane;
const parentPane = parentPaneName ? context.map.getPane(parentPaneName) : undefined;
const element = context.map.createPane(name, parentPane);
if (props.className != null) {
addClassName(element, props.className);
}
if (props.style != null) {
Object.keys(props.style).forEach(key => {
// @ts-ignore
element.style[key] = props.style[key];
});
}
return element;
}
function Pane(props) {
const [paneElement, setPaneElement] = React.useState();
const context = useLeafletContext();
const newContext = React.useMemo(() => ({ ...context,
pane: props.name
}), [context]);
React.useEffect(() => {
setPaneElement(createPane(props, context));
return function removeCreatedPane() {
const pane = context.map.getPane(props.name);
pane == null ? void 0 : pane.remove == null ? void 0 : pane.remove(); // @ts-ignore map internals
if (context.map._panes != null) {
// @ts-ignore map internals
context.map._panes = omitPane(context.map._panes, props.name); // @ts-ignore map internals
context.map._paneRenderers = omitPane( // @ts-ignore map internals
context.map._paneRenderers, props.name);
}
}; // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return props.children != null && paneElement != null ? /*#__PURE__*/reactDom.createPortal( /*#__PURE__*/React__default['default'].createElement(LeafletProvider, {
value: newContext
}, props.children), paneElement) : null;
}
const Polygon = createPathComponent(function createPolygon({
positions,
...options
}, ctx) {
const instance = new leaflet.Polygon(positions, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updatePolygon(layer, props, prevProps) {
if (props.positions !== prevProps.positions) {
layer.setLatLngs(props.positions);
}
});
const Polyline = createPathComponent(function createPolyline({
positions,
...options
}, ctx) {
const instance = new leaflet.Polyline(positions, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updatePolyline(layer, props, prevProps) {
if (props.positions !== prevProps.positions) {
layer.setLatLngs(props.positions);
}
});
const Popup = createOverlayComponent(function createPopup(props, context) {
return {
instance: new leaflet.Popup(props, context.overlayContainer),
context
};
}, function usePopupLifecycle(element, context, props, setOpen) {
const {
onClose,
onOpen,
position
} = props;
React.useEffect(function addPopup() {
const {
instance
} = element;
function onPopupOpen(event) {
if (event.popup === instance) {
instance.update();
setOpen(true);
onOpen == null ? void 0 : onOpen();
}
}
function onPopupClose(event) {
if (event.popup === instance) {
setOpen(false);
onClose == null ? void 0 : onClose();
}
}
context.map.on({
popupopen: onPopupOpen,
popupclose: onPopupClose
});
if (context.overlayContainer == null) {
// Attach to a Map
if (position != null) {
instance.setLatLng(position);
}
instance.openOn(context.map);
} else {
// Attach to container component
context.overlayContainer.bindPopup(instance);
}
return function removePopup() {
context.map.off({
popupopen: onPopupOpen,
popupclose: onPopupClose
});
context.map.removeLayer(instance);
};
}, [element, context, setOpen, onClose, onOpen, position]);
});
const Rectangle = createPathComponent(function createRectangle({
bounds,
...options
}, ctx) {
const instance = new leaflet.Rectangle(bounds, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateRectangle(layer, props, prevProps) {
if (props.bounds !== prevProps.bounds) {
layer.setBounds(props.bounds);
}
});
const ScaleControl = createControlComponent(function createScaleControl(props) {
return new leaflet.Control.Scale(props);
});
const useSVGOverlayElement = createElementHook(function createSVGOverlay(props, context) {
const {
attributes,
bounds,
...options
} = props;
const container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
container.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
if (attributes != null) {
Object.keys(attributes).forEach(name => {
container.setAttribute(name, attributes[name]);
});
}
return {
instance: new leaflet.SVGOverlay(container, bounds, options),
container,
context
};
}, updateMediaOverlay);
const useSVGOverlay = createLayerHook(useSVGOverlayElement);
function SVGOverlayComponent({
children,
...options
}, ref) {
const {
instance,
container
} = useSVGOverlay(options).current;
React.useImperativeHandle(ref, () => instance);
return container == null || children == null ? null : /*#__PURE__*/reactDom.createPortal(children, container);
}
const SVGOverlay = /*#__PURE__*/React.forwardRef(SVGOverlayComponent);
const TileLayer = createTileLayerComponent(function createTileLayer({
url,
...options
}, context) {
return {
instance: new leaflet.TileLayer(url, withPane(options, context)),
context
};
}, updateGridLayer);
const Tooltip = createOverlayComponent(function createTooltip(props, context) {
return {
instance: new leaflet.Tooltip(props, context.overlayContainer),
context
};
}, function useTooltipLifecycle(element, context, props, setOpen) {
const {
onClose,
onOpen
} = props;
React.useEffect(function addTooltip() {
const container = context.overlayContainer;
if (container == null) {
return;
}
const {
instance
} = element;
const onTooltipOpen = event => {
if (event.tooltip === instance) {
instance.update();
setOpen(true);
onOpen == null ? void 0 : onOpen();
}
};
const onTooltipClose = event => {
if (event.tooltip === instance) {
setOpen(false);
onClose == null ? void 0 : onClose();
}
};
container.on({
tooltipopen: onTooltipOpen,
tooltipclose: onTooltipClose
});
container.bindTooltip(instance);
return function removeTooltip() {
container.off({
tooltipopen: onTooltipOpen,
tooltipclose: onTooltipClose
}); // @ts-ignore protected property
if (container._map != null) {
container.unbindTooltip();
}
};
}, [element, context, setOpen, onClose, onOpen]);
});
const VideoOverlay = createLayerComponent(function createVideoOverlay({
bounds,
url,
...options
}, ctx) {
const instance = new leaflet.VideoOverlay(url, bounds, options);
if (options.play === true) {
var _instance$getElement;
(_instance$getElement = instance.getElement()) == null ? void 0 : _instance$getElement.play();
}
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateVideoOverlay(overlay, props, prevProps) {
updateMediaOverlay(overlay, props, prevProps);
if (typeof props.url === 'string' && props.url !== prevProps.url) {
overlay.setUrl(props.url);
}
const video = overlay.getElement();
if (video != null) {
if (props.play === true && !prevProps.play) {
video.play();
} else if (!props.play && prevProps.play === true) {
video.pause();
}
}
});
const WMSTileLayer = createTileLayerComponent(function createWMSTileLayer({
params = {},
url,
...options
}, context) {
return {
instance: new leaflet.TileLayer.WMS(url, { ...params,
...withPane(options, context)
}),
context
};
}, function updateWMSTileLayer(layer, props, prevProps) {
updateGridLayer(layer, props, prevProps);
if (props.params != null && props.params !== prevProps.params) {
layer.setParams(props.params);
}
});
const ZoomControl = createControlComponent(function createZoomControl(props) {
return new leaflet.Control.Zoom(props);
});
exports.AttributionControl = AttributionControl;
exports.Circle = Circle;
exports.CircleMarker = CircleMarker;
exports.FeatureGroup = FeatureGroup;
exports.GeoJSON = GeoJSON;
exports.ImageOverlay = ImageOverlay;
exports.LayerGroup = LayerGroup;
exports.LayersControl = LayersControl;
exports.MapConsumer = MapConsumer;
exports.MapContainer = MapContainer;
exports.Marker = Marker;
exports.Pane = Pane;
exports.Polygon = Polygon;
exports.Polyline = Polyline;
exports.Popup = Popup;
exports.Rectangle = Rectangle;
exports.SVGOverlay = SVGOverlay;
exports.ScaleControl = ScaleControl;
exports.TileLayer = TileLayer;
exports.Tooltip = Tooltip;
exports.VideoOverlay = VideoOverlay;
exports.WMSTileLayer = WMSTileLayer;
exports.ZoomControl = ZoomControl;
exports.useMap = useMap;
exports.useMapEvent = useMapEvent;
exports.useMapEvents = useMapEvents;
Object.defineProperty(exports, '__esModule', { value: true });
})));