UNPKG

maplibre-react-components

Version:
1,635 lines (1,613 loc) 70.2 kB
"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