maplibre-react-components
Version:
React components for MapLibre GL JS
1,635 lines (1,613 loc) • 70.2 kB
JavaScript
"use client";
// src/components/RMap/RMap.tsx
import {
forwardRef,
useContext,
useImperativeHandle,
useMemo,
useRef as useRef2,
useState
} from "react";
// src/lib/MapManager.ts
import maplibregl2 from "maplibre-gl";
// src/lib/util.ts
import maplibregl from "maplibre-gl";
function filterMapProps(options) {
const callbacks = {};
const mapHandlerOptions = {};
const mapReactiveOptions = {};
for (const key in options) {
if (key.startsWith("on")) {
callbacks[key] = options[key];
} else if (mapHandlerNames.includes(key)) {
mapHandlerOptions[key] = options[key];
} else if (mapReactiveOptionNames.includes(key)) {
mapReactiveOptions[key] = options[key];
} else if (!key.startsWith("initial") && key !== "container" && key !== "style") {
throw Error(`unknown map option key ${key}`);
}
}
return [
mapReactiveOptions,
callbacks,
mapHandlerOptions
];
}
function transformPropsToOptions(props, optionKeyWhiteList) {
const callbacks = {};
const options = {};
for (const key in props) {
if (optionKeyWhiteList?.includes(key)) {
options[key] = props[key];
continue;
}
if (key.startsWith("on")) {
callbacks[key] = props[key];
} else {
const definitiveKey = key.startsWith("initial") ? key[7].toLowerCase() + key.substring(8) : key;
if (options[definitiveKey]) {
throw new Error(`duplicate key ${definitiveKey}`);
} else {
options[definitiveKey] = props[key];
}
}
}
return [options, callbacks];
}
function prepareEventDep(eventNameToCallbackName6, callbacks) {
const activeEvents = Object.keys(eventNameToCallbackName6).filter(
(eventName) => eventNameToCallbackName6[eventName] in callbacks
);
return activeEvents.sort();
}
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) {
return false;
}
}
return true;
} else if (Array.isArray(b)) {
return false;
}
if (typeof a === "object" && typeof b === "object") {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) {
return false;
}
for (const key of aKeys) {
if (!Object.prototype.hasOwnProperty.call(b, key)) {
return false;
}
if (!deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
return false;
}
function areLngLatClose(lngLat1, lngLat2) {
if (!lngLat1 && !lngLat2) {
return true;
}
if (!lngLat1 || !lngLat2) {
return false;
}
return Math.round(lngLat1.lng * 1e5) === Math.round(lngLat2.lng * 1e5) && Math.round(lngLat1.lat * 1e5) === Math.round(lngLat2.lat * 1e5);
}
function areCoordsClose(coords1, coords2) {
if (!coords1 && !coords2) {
return true;
}
if (!coords1 || !coords2) {
return false;
}
const lngLat1 = maplibregl.LngLat.convert(coords1);
const lngLat2 = maplibregl.LngLat.convert(coords2);
return Math.round(lngLat1.lng * 1e5) === Math.round(lngLat2.lng * 1e5) && Math.round(lngLat1.lat * 1e5) === Math.round(lngLat2.lat * 1e5);
}
function lngLatClassToObj(lngLat) {
return {
lng: lngLat.lng,
lat: lngLat.lat
};
}
function arePointsEqual(a, b) {
const ax = Array.isArray(a) ? a[0] : a ? a.x : 0;
const ay = Array.isArray(a) ? a[1] : a ? a.y : 0;
const bx = Array.isArray(b) ? b[0] : b ? b.x : 0;
const by = Array.isArray(b) ? b[1] : b ? b.y : 0;
return ax === bx && ay === by;
}
function updateClassNames(elt, prevClassNames, nextClassNames) {
prevClassNames.forEach((name) => {
if (name === "") {
return;
}
if (nextClassNames.indexOf(name) === -1) {
elt.classList.remove(name);
}
});
nextClassNames.forEach((name) => {
if (name === "") {
return;
}
if (prevClassNames.indexOf(name) === -1 || !elt.classList.contains(name)) {
elt.classList.add(name);
}
});
}
function updateListeners(prevEventTypes, nextEventTypes, onSubscribe, onUnsubscribe) {
prevEventTypes.forEach((eventName) => {
if (eventName !== "" && nextEventTypes.indexOf(eventName) === -1) {
onUnsubscribe(eventName);
}
});
nextEventTypes.forEach((eventName) => {
if (eventName !== "" && prevEventTypes.indexOf(eventName) === -1) {
onSubscribe(eventName);
}
});
}
var markerHeight = 41 - 5.8 / 2;
var markerRadius = 13.5;
var linearOffset = Math.abs(markerRadius) / Math.SQRT2;
var markerPopupOffset = {
top: [0, 0],
"top-left": [0, 0],
"top-right": [0, 0],
bottom: [0, -markerHeight],
"bottom-left": [linearOffset, (markerHeight - markerRadius + linearOffset) * -1],
"bottom-right": [-linearOffset, (markerHeight - markerRadius + linearOffset) * -1],
left: [markerRadius, (markerHeight - markerRadius) * -1],
right: [-markerRadius, (markerHeight - markerRadius) * -1]
};
var gradientMarkerHeight = 50;
var gradientMarkerPopupOffset = {
top: [0, 0],
"top-left": [0, 0],
"top-right": [0, 0],
bottom: [0, -gradientMarkerHeight],
"bottom-left": [linearOffset, (gradientMarkerHeight - markerRadius + linearOffset) * -1],
"bottom-right": [-linearOffset, (gradientMarkerHeight - markerRadius + linearOffset) * -1],
left: [markerRadius, (gradientMarkerHeight - markerRadius) * -1],
right: [-markerRadius, (gradientMarkerHeight - markerRadius) * -1]
};
var emptyStyle = {
version: 8,
name: "Empty",
sources: {},
layers: []
};
function uniqueId() {
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < 8; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters[randomIndex];
}
return result;
}
// src/lib/MapManager.ts
var eventNameToCallbackName = {
mousedown: "onMouseDown",
mouseup: "onMouseUp",
mouseover: "onMouseOver",
mouseout: "onMouseOut",
mousemove: "onMouseMove",
mouseenter: "onMouseEnter",
mouseleave: "onMouseLeave",
click: "onClick",
dblclick: "onDblClick",
contextmenu: "onContextMenu",
touchstart: "onTouchStart",
touchend: "onTouchEnd",
touchcancel: "onTouchCancel",
touchmove: "onTouchMove",
movestart: "onMoveStart",
move: "onMove",
moveend: "onMoveEnd",
dragstart: "onDragStart",
drag: "onDrag",
dragend: "onDragEnd",
zoomstart: "onZoomStart",
zoom: "onZoom",
zoomend: "onZoomEnd",
rotatestart: "onRotateStart",
rotate: "onRotate",
rotateend: "onRotateEnd",
pitchstart: "onPitchStart",
pitch: "onPitch",
pitchend: "onPitchEnd",
wheel: "onWheel",
resize: "onResize",
remove: "onRemove",
boxzoomstart: "onBoxZoomStart",
boxzoomend: "onBoxZoomEnd",
boxzoomcancel: "onBoxZoomCancel",
webglcontextlost: "onWebglContextLost",
webglcontextrestored: "onWebglContextRestored",
load: "onLoad",
render: "onRender",
idle: "onIdle",
error: "onError",
data: "onData",
styledata: "onStyleData",
sourcedata: "onSourceData",
dataloading: "onDataLoading",
styledataloading: "onStyleDataLoading",
sourcedataloading: "onSourceDataLoading",
tiledataloading: "onTileDataLoading",
styleimagemissing: "onStyleImageMissing",
dataabort: "onDataAbort",
sourcedataabort: "onSourceDataAbort",
terrain: "onTerrain"
};
var mapReactiveOptionNames = [
"maxBounds",
"minZoom",
"maxZoom",
"minPitch",
"maxPitch",
"renderWorldCopies",
"pixelRatio",
"centerClampedToGround"
];
var mapHandlerNames = [
"scrollZoom",
"boxZoom",
/**
* DragRotateHandler is a composition of multiple handlers
* MouseRotateHandler
* MousePitchHandler (can be disabled with "pitchWithRotate" option)
*/
"dragRotate",
// right click rotate the map (right click can optionnaly pitch the map)
"dragPan",
// left click pan the map
"keyboard",
"doubleClickZoom",
// shift + dbl-click dezoom
/**
* TwoFingersTouchZoomRotateHandler is a composition of multiple handlers
* touchZoom : TwoFingersTouchZoomHandler
* touchRotate : TwoFingersTouchRotateHandler (can be disabled)
* tapDragZoom : TapDragZoomHandler
*/
"touchZoomRotate",
"touchPitch",
/**
* desktop: ctrl + click to zoom
* touch screen: two fingers to move the map
*/
"cooperativeGestures"
];
var DEFAULT_STYLE = "https://demotiles.maplibre.org/style.json";
var MapManager = class {
reactiveOptions = {};
handlerOptions = {};
eventNames = [];
callbacks;
_map;
padding;
mapStyle;
controlledSources = {};
controlledLayers = {};
controlledTerrain = null;
constructor({ mapStyle = DEFAULT_STYLE, padding }, mapProps, container) {
this.mapStyle = mapStyle;
this.padding = padding;
const [mapBaseOptions, callbacks] = transformPropsToOptions(mapProps);
this.callbacks = callbacks;
const mapOptions = {
...mapBaseOptions,
container,
style: mapStyle
};
const map = new maplibregl2.Map(mapOptions);
map.style.on("error", this._onStyleError);
if (padding) {
map.setPadding(padding);
}
this._map = map;
this._updateCallbacks(callbacks);
}
setProps({ mapStyle = DEFAULT_STYLE, styleDiffing = true, styleTransformStyle, padding }, mapProps) {
const [reactiveOptions, callbacks, handlerOptions] = filterMapProps(mapProps);
this._updateCallbacks(callbacks);
this._updateStyle(mapStyle, {
diff: styleDiffing,
transformStyle: styleTransformStyle
});
this._updateReactiveOptions(reactiveOptions, { padding });
this._updateHandlers(handlerOptions);
}
getControlledTerrain() {
return this.controlledTerrain;
}
setControlledTerrain(terrainProps) {
this.controlledTerrain = terrainProps;
}
getControlledLayer(id) {
return this.controlledLayers[id] ?? null;
}
setControlledLayer(id, layerProps) {
if (!layerProps) {
delete this.controlledLayers[id];
} else {
this.controlledLayers[id] = layerProps;
}
}
getControlledSource(id) {
return this.controlledSources[id] ?? null;
}
setControlledSource(id, layerProps) {
if (!layerProps) {
delete this.controlledSources[id];
} else {
this.controlledSources[id] = layerProps;
}
}
_updateStyle(nextStyle, options) {
const curStyle = this.mapStyle;
if (nextStyle !== curStyle) {
this.mapStyle = nextStyle;
this._map.setStyle(nextStyle, {
diff: options.diff,
transformStyle: (prevStyle, nextStyle2) => {
const prevControlledSources = prevStyle ? Object.fromEntries(
Object.entries(prevStyle?.sources).filter(
([sourceId]) => sourceId in this.controlledSources
)
) : {};
const prevControlledLayers = prevStyle ? prevStyle.layers.filter((layer) => layer.id in this.controlledLayers) : [];
const result = {
...nextStyle2,
sources: {
...nextStyle2.sources,
...prevControlledSources
},
layers: [...nextStyle2.layers, ...prevControlledLayers],
terrain: this.controlledTerrain ? prevStyle?.terrain : nextStyle2.terrain
};
return options.transformStyle ? options.transformStyle(prevStyle, result) : result;
}
});
}
}
_updateReactiveOptions(nextReactiveOptions, { padding }) {
const currReactiveOptions = this.reactiveOptions;
this.reactiveOptions = nextReactiveOptions;
for (const optionName of mapReactiveOptionNames) {
if (optionName in nextReactiveOptions && !deepEqual(currReactiveOptions[optionName], nextReactiveOptions[optionName])) {
const setterName = `set${optionName[0].toUpperCase()}${optionName.substring(1)}`;
this._map[setterName](nextReactiveOptions[optionName]);
}
}
if (padding && !deepEqual(this.padding, padding)) {
this._map.setPadding(padding);
}
this.padding = padding;
}
_updateCallbacks(callbacks = {}) {
this.callbacks = callbacks;
const nextEventNames = prepareEventDep(eventNameToCallbackName, callbacks);
if (this.eventNames.join("-") === nextEventNames.join("-")) {
return;
}
updateListeners(
this.eventNames,
nextEventNames,
(eventName) => this._map.on(eventName, this._onMapEvent),
(eventName) => this._map.off(eventName, this._onMapEvent)
);
this.eventNames = nextEventNames;
}
_updateHandlers(nextHandlers) {
const currHandlers = this.handlerOptions;
this.handlerOptions = nextHandlers;
for (const propName of mapHandlerNames) {
const nextValue = nextHandlers[propName] ?? true;
const currValue = currHandlers[propName] ?? true;
if (!deepEqual(nextValue, currValue)) {
if (nextValue) {
this._map[propName].enable(nextValue);
} else {
this._map[propName].disable();
}
}
}
}
_onStyleError = (event) => {
if (event.error.name !== "AbortError") {
console.error(event.error);
}
};
_onMapEvent = (e) => {
const eventType = e.type;
const callbackName = eventNameToCallbackName[eventType];
if (this.callbacks[callbackName]) {
this.callbacks[callbackName]?.(e);
} else {
console.info("not managed RMap event", eventType, e);
}
};
get map() {
return this._map;
}
destroy() {
this._updateCallbacks();
this._map.remove();
}
};
// src/hooks/useIsomorphicLayoutEffect.ts
import { useEffect, useLayoutEffect } from "react";
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
// src/contexts/RMapContextProvider.tsx
import { createContext, useRef } from "react";
// src/lib/MapManagers.ts
var MapManagers = class {
_maps = {};
_listeners = {};
add(id, mapManager) {
this._maps[id] = mapManager;
this._listeners[id]?.forEach(([mounted, setMounted]) => {
if (!mounted) {
setMounted(true);
}
});
}
remove(id) {
delete this._maps[id];
this._listeners[id]?.forEach(([mounted, setMounted]) => {
if (mounted) {
setMounted(false);
}
});
}
get(id) {
if (!id) {
return null;
}
return this._maps[id] ?? null;
}
addListener(id, mountedState) {
if (this._listeners[id]) {
this._listeners[id].push(mountedState);
} else {
this._listeners[id] = [mountedState];
}
const isMounted = !!this._maps[id];
if (mountedState[0] !== isMounted) {
mountedState[1](isMounted);
}
}
removeListener(id, [, callback]) {
this._listeners[id] = this._listeners[id].filter(([, cb]) => cb !== callback);
}
};
// src/contexts/RMapContextProvider.tsx
import { jsx } from "react/jsx-runtime";
var RMapContext = createContext(null);
var RMapContextProvider = ({ children }) => {
const mapManagersRef = useRef(null);
if (!mapManagersRef.current) {
mapManagersRef.current = new MapManagers();
}
return /* @__PURE__ */ jsx(RMapContext.Provider, { value: mapManagersRef, children });
};
// src/contexts/CurrentMapIdContext.ts
import { createContext as createContext2 } from "react";
var CurrentMapIdContext = createContext2(null);
// src/components/RMap/RMap.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
var childContainerStyle = {
height: "100%"
};
var RMap = forwardRef(function RMap2({
/* RMapProps */
children,
style,
id: propsId,
className,
onMounted,
/* ManagerOptions */
mapStyle,
styleDiffing,
styleTransformStyle,
padding,
/* MapProps */
...mapProps
}, ref) {
const containerRef = useRef2(null);
const needPropsUpdate = useRef2(true);
const idRef = useRef2(propsId ?? uniqueId());
if (propsId && propsId !== idRef.current) {
throw new Error(
`RMap id should not change. "${propsId}" "${idRef.current}". If you defined id as const string add a "key" prop to your RMap component`
);
}
const id = idRef.current;
const externalMapManagersRef = useContext(RMapContext);
const localMapManagersRef = useRef2();
if (!externalMapManagersRef && !localMapManagersRef.current) {
localMapManagersRef.current = new MapManagers();
}
const mapManagers = externalMapManagersRef ? externalMapManagersRef.current : localMapManagersRef.current;
const [, reRender] = useState(0);
useIsomorphicLayoutEffect(() => {
const mapManager = mapManagers.get(id);
if (!mapManager) {
const instance = new MapManager(
{ mapStyle, styleDiffing, padding },
mapProps,
containerRef.current
);
mapManagers.add(id, instance);
onMounted && onMounted(instance.map);
reRender((v) => v + 1);
needPropsUpdate.current = false;
} else {
if (needPropsUpdate.current) {
mapManager.setProps({ mapStyle, padding, styleDiffing, styleTransformStyle }, mapProps);
} else {
needPropsUpdate.current = true;
}
}
});
useIsomorphicLayoutEffect(() => {
return () => {
const mapManager = mapManagers.get(id);
if (mapManager) {
mapManager.destroy();
mapManagers.remove(id);
}
};
}, []);
useImperativeHandle(ref, () => mapManagers.get(id)?.map || null, [id, mapManagers]);
useIsomorphicLayoutEffect(() => {
if (!className) {
return;
}
const container = containerRef.current;
className.split(" ").map((classItem) => container.classList.add(classItem));
return () => void className.split(" ").map((classItem) => container.classList.remove(classItem));
}, [className]);
const completeStyle = useMemo(
() => ({
position: "relative",
width: "100%",
height: "100%",
...style
}),
[style]
);
return /* @__PURE__ */ jsx2("div", { ref: containerRef, id, style: completeStyle, children: mapManagers.get(id) && /* @__PURE__ */ jsx2(CurrentMapIdContext.Provider, { value: id, children: externalMapManagersRef ? /* @__PURE__ */ jsx2("div", { className: "maplibregl-children", style: childContainerStyle, children }) : /* @__PURE__ */ jsx2(RMapContext.Provider, { value: localMapManagersRef, children: /* @__PURE__ */ jsx2("div", { className: "maplibregl-children", style: childContainerStyle, children }) }) }) });
});
// src/components/RMarker/RMarker.tsx
import maplibregl3 from "maplibre-gl";
import {
forwardRef as forwardRef2,
memo,
useEffect as useEffect3,
useImperativeHandle as useImperativeHandle2,
useMemo as useMemo2,
useRef as useRef3
} from "react";
import { createPortal } from "react-dom";
// src/hooks/useMapManager.ts
import { useContext as useContext2, useEffect as useEffect2, useState as useState2 } from "react";
function useMapManager(optionalId) {
const mapManagersRef = useContext2(RMapContext);
const currentMapId = useContext2(CurrentMapIdContext);
const id = optionalId ?? currentMapId;
const mapManager = mapManagersRef?.current.get(id) ?? null;
const mountedState = useState2(mapManager !== null);
if (!mapManagersRef?.current) {
throw new Error(
"use useMapManager in components inside <RMap /> or inside <RMapContextProvider />"
);
}
if (!id) {
throw new Error("provide an id to useMap or use inside <RMap />");
}
useEffect2(() => {
const mapManagers = mapManagersRef.current;
if (!mapManagers) {
throw new Error("mapManagers can't disappear");
}
mapManagers.addListener(id, mountedState);
return () => {
mapManagers.removeListener(id, mountedState);
};
}, [id, mapManagersRef, mountedState, mapManager]);
return mapManager;
}
// src/hooks/useMap.ts
function useMap(optionalId) {
return useMapManager(optionalId)?.map ?? null;
}
// src/components/RMarker/RMarker.tsx
var eventNameToCallbackName2 = {
dragstart: "onDragStart",
drag: "onDrag",
dragend: "onDragEnd",
click: "onClick"
};
var RMarker = memo(
forwardRef2(function RMarker2(props, ref) {
const { longitude, latitude, children, ...markerProps } = props;
const map = useMap();
const [options, callbacks] = transformPropsToOptions(markerProps);
const prevOptionsRef = useRef3(options);
const callbacksRef = useRef3();
callbacksRef.current = callbacks;
const marker = useMemo2(() => {
const completeOptions = {
...options,
element: children ? document.createElement("div") : void 0
};
const mk = new maplibregl3.Marker(completeOptions);
mk.setLngLat([longitude, latitude]);
return mk;
}, []);
const nextEventsStr = prepareEventDep(eventNameToCallbackName2, callbacks).join("-");
useEffect3(() => {
function onMarkerEvent(e) {
const eventType = e.type;
const callbackName = eventNameToCallbackName2[eventType];
if (callbacksRef.current?.[callbackName]) {
callbacksRef.current[callbackName]?.(e);
} else {
console.info("not managed RMarker event", eventType, e);
}
}
const eventNames = nextEventsStr.split("-");
eventNames.forEach((eventName) => {
if (eventName === "click") {
marker.getElement().addEventListener("click", onMarkerEvent);
} else {
marker.on(eventName, onMarkerEvent);
}
});
return () => {
eventNames.forEach((eventName) => {
if (eventName === "click") {
marker.getElement().removeEventListener("click", onMarkerEvent);
} else {
marker.off(eventName, onMarkerEvent);
}
});
};
}, [nextEventsStr, marker]);
useEffect3(() => {
marker.addTo(map);
return () => void marker.remove();
}, []);
const {
className,
offset,
draggable,
clickTolerance = 0,
rotation,
rotationAlignment,
subpixelPositioning = false,
pitchAlignment,
opacity,
opacityWhenCovered
} = options;
useImperativeHandle2(ref, () => marker, [marker]);
if (prevOptionsRef.current.className !== className) {
updateClassNames(
marker._element,
prevOptionsRef.current.className?.split(" ") || [],
className?.split(" ") || []
);
}
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._clickTolerance !== clickTolerance) {
marker._clickTolerance = clickTolerance;
}
if (marker.getRotation() !== rotation) {
marker.setRotation(rotation);
}
if (marker.getRotationAlignment() !== rotationAlignment) {
marker.setRotationAlignment(rotationAlignment);
}
if (marker.getPitchAlignment() !== pitchAlignment) {
marker.setPitchAlignment(pitchAlignment);
}
if (marker._opacity !== opacity || marker._opacityWhenCovered !== opacityWhenCovered) {
marker.setOpacity(opacity, opacityWhenCovered);
}
if (marker.setSubpixelPositioning && marker._subpixelPositioning !== subpixelPositioning) {
marker.setSubpixelPositioning(subpixelPositioning);
}
prevOptionsRef.current = options;
return children ? createPortal(children, marker.getElement()) : null;
})
);
// src/components/RPopup/RPopup.tsx
import maplibregl4 from "maplibre-gl";
import {
forwardRef as forwardRef3,
memo as memo2,
useEffect as useEffect4,
useImperativeHandle as useImperativeHandle3,
useMemo as useMemo3,
useRef as useRef4
} from "react";
import { createPortal as createPortal2 } from "react-dom";
var eventNameToCallbackName3 = {
map_click: "onMapClick",
map_move: "onMapMove"
};
var RPopup = memo2(
forwardRef3(function RPopup2(props, ref) {
const { longitude, latitude, children, ...popupProps } = props;
const map = useMap();
const [options, callbacks] = transformPropsToOptions(popupProps);
const popupRef = useRef4(null);
const prevOptionsRef = useRef4(options);
const currCallbacksRef = useRef4();
currCallbacksRef.current = callbacks;
const container = useMemo3(() => {
return document.createElement("div");
}, []);
if (!popupRef.current) {
popupRef.current = new maplibregl4.Popup({
...options,
closeButton: false,
closeOnClick: false,
closeOnMove: false
});
if (longitude !== void 0 && latitude !== void 0) {
popupRef.current.setLngLat([longitude, latitude]);
}
}
const nextEventsStr = prepareEventDep(eventNameToCallbackName3, callbacks).join("-");
useEffect4(() => {
function onPopupEvent(e) {
const eventType = e.type;
const callbackName = eventNameToCallbackName3[eventType] || // @ts-ignore
eventNameToCallbackName3[`map_${eventType}`];
if (currCallbacksRef.current?.[callbackName]) {
currCallbacksRef.current[callbackName]?.(e);
} else {
console.info("not managed RPopup event", eventType, e);
}
}
if (nextEventsStr === "") {
return;
}
const eventNames = nextEventsStr.split("-");
const popupStable = popupRef.current;
eventNames.forEach((eventName) => {
if (eventName.startsWith("map_")) {
map.on(eventName.substring(4), onPopupEvent);
} else {
popupStable.on(eventName, onPopupEvent);
}
});
return () => {
eventNames.forEach((eventName) => {
if (eventName.startsWith("map_")) {
map.off(eventName.substring(4), onPopupEvent);
} else {
popupStable.off(eventName, onPopupEvent);
}
});
};
}, [nextEventsStr, map]);
useEffect4(() => {
popupRef.current.setDOMContent(container).addTo(map);
return () => void popupRef.current.remove();
}, [container, map]);
const { offset, maxWidth = "240px", className } = options;
useImperativeHandle3(ref, () => popupRef.current, [popupRef]);
if (popupRef.current.isOpen()) {
if (longitude !== void 0 && latitude !== void 0 && (popupRef.current.getLngLat().lng !== longitude || popupRef.current.getLngLat().lat !== latitude)) {
popupRef.current.setLngLat([longitude, latitude]);
}
if (offset && !deepEqual(popupRef.current.options.offset, offset)) {
popupRef.current.setOffset(offset);
}
if (prevOptionsRef.current.className !== className) {
updateClassNames(
container,
prevOptionsRef.current.className?.split(" ") || [],
className?.split(" ") || []
);
}
if (popupRef.current.getMaxWidth() !== maxWidth) {
popupRef.current.setMaxWidth(maxWidth);
}
}
prevOptionsRef.current = options;
return createPortal2(children, container);
})
);
// src/components/RLayer/RLayer.tsx
import {
forwardRef as forwardRef4,
memo as memo3,
useCallback,
useEffect as useEffect5,
useImperativeHandle as useImperativeHandle4,
useRef as useRef5,
useState as useState3
} from "react";
var eventNameToCallbackName4 = {
mousedown: "onMouseDown",
mouseup: "onMouseUp",
mouseover: "onMouseOver",
mouseout: "onMouseOut",
mousemove: "onMouseMove",
mouseenter: "onMouseEnter",
mouseleave: "onMouseLeave",
click: "onClick",
dblclick: "onDblClick",
contextmenu: "onContextMenu",
touchstart: "onTouchStart",
touchend: "onTouchEnd",
touchcancel: "onTouchCancel",
touchmove: "onTouchMove"
};
function createLayer(map, layerOptions, beforeId) {
if (map.style?._loaded) {
if (
// BackgroundLayerSpecification and CustomLayerInterface has no source
layerOptions.type === "background" || layerOptions.type === "custom" || // source exists for LayerSpecification who need one
layerOptions.source && map.getSource(layerOptions.source)
) {
map.addLayer(layerOptions, beforeId && map.getLayer(beforeId) ? beforeId : void 0);
return map.getLayer(layerOptions.id);
}
}
return void 0;
}
function updateLayer(map, { beforeId: nextBeforeId, ...nextOptions }, { beforeId: prevBeforeId, ...prevOptions }) {
if (prevOptions.type === "custom" || nextOptions.type === "custom") {
return;
}
if (prevBeforeId !== nextBeforeId) {
map.moveLayer(nextOptions.id, nextBeforeId);
}
if (nextOptions.type !== "background" && nextOptions.type !== "custom" && prevOptions.filter !== nextOptions.filter) {
map.setFilter(nextOptions.id, nextOptions.filter);
}
const prevO = prevOptions;
const nextO = nextOptions;
if (prevO.layout !== nextO.layout) {
if (nextO.layout) {
for (const key of Object.keys(nextO.layout)) {
if (nextO.layout[key] !== prevO.layout?.[key]) {
map.setLayoutProperty(nextOptions.id, key, nextO.layout[key]);
}
}
}
for (const key in prevO.layout) {
if (!Object.prototype.hasOwnProperty.call(nextO.layout, key)) {
map.setLayoutProperty(nextOptions.id, key, void 0);
}
}
}
if (prevO.paint !== nextO.paint) {
if (nextO.paint) {
for (const key of Object.keys(nextO.paint)) {
if (nextO.paint[key] !== prevO.paint?.[key]) {
map.setPaintProperty(nextOptions.id, key, nextO.paint[key]);
}
}
}
for (const key in prevO.paint) {
if (!Object.prototype.hasOwnProperty.call(nextO.paint, key)) {
map.setPaintProperty(nextOptions.id, key, void 0);
}
}
}
if (prevO.minzoom !== nextO.minzoom || prevO.maxzoom !== nextO.maxzoom) {
if (nextO.minzoom && nextO.maxzoom) {
map.setLayerZoomRange(nextOptions.id, nextO.minzoom, nextO.maxzoom);
}
}
}
var RLayer = memo3(
forwardRef4(function RLayer2(props, ref) {
const { beforeId, ...layerProps } = props;
const [layerOptions, callbacks] = transformPropsToOptions(layerProps, ["onAdd"]);
const id = layerOptions.id;
const mapManager = useMapManager();
if (!mapManager) {
throw new Error("use <RLayer /> component inside <RMap />");
}
const map = mapManager.map;
const initialLayerId = useRef5(id);
if (id !== initialLayerId.current) {
throw new Error(
`RLayer id should not change. "${id}" "${initialLayerId.current}". If you defined id as const string add a "key" prop to your RLayer component`
);
}
const prevProps = mapManager.getControlledLayer(id) ?? props;
const [, setVersion] = useState3(0);
const reRender = useCallback(() => setVersion((v) => v + 1), []);
if (props.type !== prevProps.type) {
throw new Error(`RLayer type should not change. "${props.type}" "${prevProps.type}"`);
}
const callbacksRef = useRef5();
callbacksRef.current = callbacks;
useEffect5(() => {
map.on("styledata", reRender);
if (map.style && map.style._loaded) {
reRender();
}
return () => {
map.off("styledata", reRender);
if (map.style && map.style._loaded && map.getLayer(id)) {
map.removeLayer(id);
}
mapManager?.setControlledLayer(id, null);
};
}, [map, id, mapManager, reRender]);
const nextEventsStr = prepareEventDep(eventNameToCallbackName4, callbacks).join("-");
useEffect5(() => {
function onLayerEvent(e) {
const eventType = e.type;
const callbackName = eventNameToCallbackName4[eventType];
if (callbacksRef.current?.[callbackName]) {
callbacksRef.current[callbackName]?.(e);
} else {
console.info("not managed RLayer event", eventType, e);
}
}
const eventNames = nextEventsStr.split("-");
eventNames.forEach((eventName) => {
map.on(eventName, id, onLayerEvent);
});
return () => {
eventNames.forEach((eventName) => {
map.off(eventName, id, onLayerEvent);
});
};
}, [nextEventsStr, id, map]);
let layer = map.style?._loaded && map.getLayer(id);
if (layer) {
updateLayer(map, props, prevProps);
} else {
layer = createLayer(map, layerOptions, beforeId);
if (layer) {
map.off("styledata", reRender);
}
}
useImperativeHandle4(ref, () => layer || null, [layer]);
mapManager.setControlledLayer(id, props);
return null;
})
);
// src/components/RSource/RSource.tsx
import {
forwardRef as forwardRef5,
useEffect as useEffect6,
useRef as useRef6,
useState as useState4,
useImperativeHandle as useImperativeHandle5,
memo as memo4,
useCallback as useCallback2
} from "react";
function createSource(map, id, sourceOptions) {
if (map.style?._loaded) {
map.addSource(id, sourceOptions);
return map.getSource(id);
}
return void 0;
}
function updateSource(source, nextOptions, prevOptions) {
switch (nextOptions.type) {
case "image": {
const prevO = prevOptions;
const nextO = nextOptions;
if (prevO.url !== nextO.url) {
source.updateImage({
url: nextO.url,
coordinates: nextO.coordinates
});
}
if (prevO.coordinates !== nextO.coordinates) {
source.setCoordinates(nextO.coordinates);
}
break;
}
case "video": {
const prevO = prevOptions;
const nextO = nextOptions;
if (prevO.coordinates !== nextO.coordinates) {
source.setCoordinates(nextO.coordinates);
}
break;
}
case "geojson": {
const prevO = prevOptions;
const nextO = nextOptions;
if (prevO.data !== nextO.data) {
source.setData(nextO.data);
}
if (prevO.cluster !== nextO.cluster || prevO.clusterMaxZoom !== nextO.clusterMaxZoom || prevO.clusterRadius !== nextO.clusterRadius) {
source.setClusterOptions({
cluster: nextO.cluster,
clusterMaxZoom: nextO.clusterMaxZoom,
clusterRadius: nextO.clusterRadius
});
}
break;
}
case "raster":
case "raster-dem":
case "vector": {
const prevO = prevOptions;
const nextO = nextOptions;
if (prevO.tiles !== nextO.tiles && nextO.tiles) {
source.setTiles(nextO.tiles);
}
if (prevO.url !== nextO.url && nextO.url) {
source.setUrl(nextO.url);
}
break;
}
}
}
var RSource = memo4(
forwardRef5(function RSource2(props, ref) {
const { id, ...sourceOptions } = props;
const mapManager = useMapManager();
const map = mapManager.map;
const initialId = useRef6(id);
if (id !== initialId.current) {
throw new Error(
`RSource id should not change. "${id}" "${initialId.current}". If you defined id as const string add a "key" prop to your RSource component`
);
}
const { id: _, ...prevOptions } = mapManager.getControlledSource(id) ?? props;
if (sourceOptions.type !== prevOptions.type) {
throw new Error(
`RSource type should not change. "${sourceOptions.type}" "${prevOptions.type}"`
);
}
const [, setVersion] = useState4(0);
const reRender = useCallback2(() => void setTimeout(() => setVersion((v) => v + 1), 0), []);
useEffect6(() => {
map.on("styledata", reRender);
if (map.style && map.style._loaded) {
reRender();
}
return () => {
map.off("styledata", reRender);
if (map.style && map.getSource(id)) {
const layers = map.getStyle()?.layers;
if (layers) {
for (const layer of layers) {
if (layer.type !== "background" && layer.type !== "custom" && layer.source === id) {
map.removeLayer(layer.id);
}
}
}
map.removeSource(id);
}
mapManager?.setControlledSource(id, null);
};
}, [map, id, mapManager, reRender]);
let source = map.style?._loaded && map.getSource(id);
if (source) {
updateSource(source, sourceOptions, prevOptions);
} else {
source = createSource(map, id, sourceOptions);
if (source) {
map.off("styledata", reRender);
}
}
useImperativeHandle5(ref, () => source || null, [source]);
mapManager.setControlledSource(id, props);
return null;
})
);
// src/components/RTerrain/RTerrain.tsx
import { useCallback as useCallback3, useEffect as useEffect7, useState as useState5 } from "react";
var RTerrain = (props) => {
const terrainOptions = props;
const mapManager = useMapManager();
const map = mapManager.map;
const prevOptions = mapManager.getControlledTerrain() ?? props;
const [, setVersion] = useState5(0);
const reRender = useCallback3(() => setVersion((v) => v + 1), []);
useEffect7(() => {
map.on("styledata", reRender);
if (map.style && map.style._loaded) {
reRender();
}
return () => {
map.off("styledata", reRender);
if (map.style?._loaded && map.getTerrain()) {
map.setTerrain(null);
}
mapManager?.setControlledTerrain(null);
};
}, [map, mapManager, reRender]);
const terrain = map.style?._loaded && (map.getSource(terrainOptions.source) ? map.getTerrain() : false);
if (terrain) {
if (prevOptions.exaggeration !== terrainOptions.exaggeration || prevOptions.source !== terrainOptions.source) {
map.setTerrain(terrainOptions);
}
} else if (map.style?._loaded) {
if (map.getSource(terrainOptions.source)) {
map.setTerrain(terrainOptions);
map.off("styledata", reRender);
}
}
mapManager.setControlledTerrain(props);
return null;
};
// src/components/RGradientMarker/GradientMarker.ts
import maplibregl5 from "maplibre-gl";
// node_modules/.pnpm/@mapbox+point-geometry@1.1.0/node_modules/@mapbox/point-geometry/index.js
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
/**
* Clone this point, returning a new point that can be modified
* without affecting the old one.
* @return {Point} the clone
*/
clone() {
return new Point(this.x, this.y);
},
/**
* Add this point's x & y coordinates to another point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
add(p) {
return this.clone()._add(p);
},
/**
* Subtract this point's x & y coordinates to from point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
sub(p) {
return this.clone()._sub(p);
},
/**
* Multiply this point's x & y coordinates by point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
multByPoint(p) {
return this.clone()._multByPoint(p);
},
/**
* Divide this point's x & y coordinates by point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
divByPoint(p) {
return this.clone()._divByPoint(p);
},
/**
* Multiply this point's x & y coordinates by a factor,
* yielding a new point.
* @param {number} k factor
* @return {Point} output point
*/
mult(k) {
return this.clone()._mult(k);
},
/**
* Divide this point's x & y coordinates by a factor,
* yielding a new point.
* @param {number} k factor
* @return {Point} output point
*/
div(k) {
return this.clone()._div(k);
},
/**
* Rotate this point around the 0, 0 origin by an angle a,
* given in radians
* @param {number} a angle to rotate around, in radians
* @return {Point} output point
*/
rotate(a) {
return this.clone()._rotate(a);
},
/**
* Rotate this point around p point by an angle a,
* given in radians
* @param {number} a angle to rotate around, in radians
* @param {Point} p Point to rotate around
* @return {Point} output point
*/
rotateAround(a, p) {
return this.clone()._rotateAround(a, p);
},
/**
* Multiply this point by a 4x1 transformation matrix
* @param {[number, number, number, number]} m transformation matrix
* @return {Point} output point
*/
matMult(m) {
return this.clone()._matMult(m);
},
/**
* Calculate this point but as a unit vector from 0, 0, meaning
* that the distance from the resulting point to the 0, 0
* coordinate will be equal to 1 and the angle from the resulting
* point to the 0, 0 coordinate will be the same as before.
* @return {Point} unit vector point
*/
unit() {
return this.clone()._unit();
},
/**
* Compute a perpendicular point, where the new y coordinate
* is the old x coordinate and the new x coordinate is the old y
* coordinate multiplied by -1
* @return {Point} perpendicular point
*/
perp() {
return this.clone()._perp();
},
/**
* Return a version of this point with the x & y coordinates
* rounded to integers.
* @return {Point} rounded point
*/
round() {
return this.clone()._round();
},
/**
* Return the magnitude of this point: this is the Euclidean
* distance from the 0, 0 coordinate to this point's x and y
* coordinates.
* @return {number} magnitude
*/
mag() {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
/**
* Judge whether this point is equal to another point, returning
* true or false.
* @param {Point} other the other point
* @return {boolean} whether the points are equal
*/
equals(other) {
return this.x === other.x && this.y === other.y;
},
/**
* Calculate the distance from this point to another point
* @param {Point} p the other point
* @return {number} distance
*/
dist(p) {
return Math.sqrt(this.distSqr(p));
},
/**
* Calculate the distance from this point to another point,
* without the square root step. Useful if you're comparing
* relative distances.
* @param {Point} p the other point
* @return {number} distance
*/
distSqr(p) {
const dx = p.x - this.x, dy = p.y - this.y;
return dx * dx + dy * dy;
},
/**
* Get the angle from the 0, 0 coordinate to this point, in radians
* coordinates.
* @return {number} angle
*/
angle() {
return Math.atan2(this.y, this.x);
},
/**
* Get the angle from this point to another point, in radians
* @param {Point} b the other point
* @return {number} angle
*/
angleTo(b) {
return Math.atan2(this.y - b.y, this.x - b.x);
},
/**
* Get the angle between this point and another point, in radians
* @param {Point} b the other point
* @return {number} angle
*/
angleWith(b) {
return this.angleWithSep(b.x, b.y);
},
/**
* Find the angle of the two vectors, solving the formula for
* the cross product a x b = |a||b|sin(θ) for θ.
* @param {number} x the x-coordinate
* @param {number} y the y-coordinate
* @return {number} the angle in radians
*/
angleWithSep(x, y) {
return Math.atan2(
this.x * y - this.y * x,
this.x * x + this.y * y
);
},
/** @param {[number, number, number, number]} m */
_matMult(m) {
const x = m[0] * this.x + m[1] * this.y, y = m[2] * this.x + m[3] * this.y;
this.x = x;
this.y = y;
return this;
},
/** @param {Point} p */
_add(p) {
this.x += p.x;
this.y += p.y;
return this;
},
/** @param {Point} p */
_sub(p) {
this.x -= p.x;
this.y -= p.y;
return this;
},
/** @param {number} k */
_mult(k) {
this.x *= k;
this.y *= k;
return this;
},
/** @param {number} k */
_div(k) {
this.x /= k;
this.y /= k;
return this;
},
/** @param {Point} p */
_multByPoint(p) {
this.x *= p.x;
this.y *= p.y;
return this;
},
/** @param {Point} p */
_divByPoint(p) {
this.x /= p.x;
this.y /= p.y;
return this;
},
_unit() {
this._div(this.mag());
return this;
},
_perp() {
const y = this.y;
this.y = this.x;
this.x = -y;
return this;
},
/** @param {number} angle */
_rotate(angle) {
const cos = Math.cos(angle), sin = Math.sin(angle), x = cos * this.x - sin * this.y, y = sin * this.x + cos * this.y;
this.x = x;
this.y = y;
return this;
},
/**
* @param {number} angle
* @param {Point} p
*/
_rotateAround(angle, p) {
const cos = Math.cos(angle), sin = Math.sin(angle), x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y), y = p.y + sin * (this.x - p.x) + cos * (this.y - p.y);
this.x = x;
this.y = y;
return this;
},
_round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
constructor: Point
};
Point.convert = function(p) {
if (p instanceof Point) {
return (
/** @type {Point} */
p
);
}
if (Array.isArray(p)) {
return new Point(+p[0], +p[1]);
}
if (p.x !== void 0 && p.y !== void 0) {
return new Point(+p.x, +p.y);
}
throw new Error("Expected [x, y] or {x, y} point format");
};
// src/maplibre-core/util/dom.ts
var DOM = class _DOM {
static docStyle = typeof window !== "undefined" && window.document && window.document.documentElement.style;
static userSelect;
static selectProp = _DOM.testProp([
"userSelect",
"MozUserSelect",
"WebkitUserSelect",
"msUserSelect"
]);
static transformProp = _DOM.testProp(["transform", "WebkitTransform"]);
static testProp(props) {
if (!_DOM.docStyle) return props[0];
for (let i = 0; i < props.length; i++) {
if (props[i] in _DOM.docStyle) {
return props[i];
}
}
return props[0];
}
static create(tagName, className, container) {
const el = window.document.createElement(tagName);
if (className !== void 0) el.className = className;
if (container) container.appendChild(el);
return el;
}
static createNS(namespaceURI, tagName) {
const el = window.document.createElementNS(namespaceURI, tagName);
return el;
}
static disableDrag() {
if (_DOM.docStyle && _DOM.selectProp) {
_DOM.userSelect = _DOM.docStyle[_DOM.selectProp];
_DOM.docStyle[_DOM.selectProp] = "none";
}
}
static enableDrag() {
if (_DOM.docStyle && _DOM.selectProp) {
_DOM.docStyle[_DOM.selectProp] = _DOM.userSelect;
}
}
static setTransform(el, value) {
el.style[_DOM.transformProp] = value;
}
static addEventListener(target, type, callback, options = {}) {
if ("passive" in options) {
target.addEventListener(type, callback, options);
} else {
target.addEventListener(type, callback, options.capture);
}
}
static removeEventListener(target, type, callback, options = {}) {
if ("passive" in options) {
target.removeEventListener(type, callback, options);
} else {
target.removeEventListener(type, callback, options.capture);
}
}
// Suppress the next click, but only if it's immediate.
// @ts-ignore
static suppressClickInternal(e) {
e.preventDefault();
e.stopPropagation();
window.removeEventListener("click", _DOM.suppressClickInternal, true);
}
static suppressClick() {
window.addEventListener("click", _DOM.suppressClickInternal, true);
window.setTimeout(() => {
window.removeEventListener("click", _DOM.suppressClickInternal, true);
}, 0);
}
static getScale(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect
};
}
static getPoint(el, scale, e) {
const rect = scale.boundingClientRect;
return new Point(
// rect.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.clientX - rect.left) / scale.x - el.clientLeft,
(e.clientY - rect.top) / scale.y - el.clientTop
);
}
static mousePos(el, e) {
const scale = _DOM.getScale(el);
return _DOM.getPoint(el, scale, e);
}
static touchPos(el, touches) {
const points = [];
const scale = _DOM.getScale(el);
for (let i = 0; i < touches.length; i++) {
points.push(_DOM.getPoint(el, scale, touches[i]));
}
return points;
}
static mouseButton(e) {
return e.button;
}
static remove(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
};
// src/components/RGradientMarker/GradientMarker.ts
var defaultColor = "#ffe64b";
var defaultHeight = 50;
var GradientMarker = class extends maplibregl5.Marker {
_interactive;
_shape;
_icon;
_height = defaultHeight;
_text;
_circleElement = null;
_iconElement = null;
_textElement = null;
_markerElement;
constructor(options) {
options ??= {};
options.element = DOM.create("div", "maplibregl-gradient-marker");
if (options.className) {
options.element.classList.add(options.className);
}
super(options);
if (this._draggable) {
this._element.classList.add("draggable");
}
this._interactive = options && options.interactive === false ? false : "pending";
this._shape = (options && options.shape) ?? "pin";
this._color = (options && options.color) ?? defaultColor;
this._icon = options && options.icon;
this._text = options && options.text;
this._defaultMarker = true;
this._element.setAttribute("aria-label", "Map marker");
this._element.setAttribute("tabindex", "0");
this.setScale(this._scale);
this.setColor(this._color);
this._markerElement = DOM.create("div", `marker`);
this.setShape(this._shape);
if (this._text) {
this.setText(this._text);
} else if (this._icon) {
this.setIcon(this._icon);
}
const target = DOM.create("div", "target");
this._element.appendChild(this._markerElement);
this._element.appendChild(target);
}
_onActive = () => {
this._map.getContainer().addEventListener("mouseup", this._onInactive, { once: true });
this._map.getContainer().addEventLi