UNPKG

react-bkoi-gl

Version:

A React library for Barikoi Maps with interactive components, markers, popups, and controls.

1 lines 1.1 MB
{"version":3,"sources":["../src/index.ts","../src/components/map.tsx","../src/components/use-map.tsx","../src/utils/deep-equal.ts","../src/utils/transform.ts","../src/utils/style-utils.ts","../src/utils/logger.ts","../src/maplibre/maplibre.ts","../src/maplibre/create-ref.ts","../src/utils/use-isomorphic-layout-effect.ts","../src/utils/set-globals.ts","../src/components/logo-control.ts","../src/utils/apply-react-style.ts","../src/components/use-control.ts","../src/components/attribution-control.ts","../src/components/marker.ts","../src/utils/compare-class-names.ts","../src/components/popup.ts","../src/components/fullscreen-control.ts","../src/components/geolocate-control.ts","../src/components/navigation-control.ts","../src/components/scale-control.ts","../src/components/terrain-control.ts","../src/components/source.ts","../src/utils/assert.ts","../src/utils/warn.ts","../src/components/canvas-source.ts","../src/components/layer.ts","../src/components/draw-control.ts","../node_modules/maplibre-gl-draw/src/lib/mode_handler.js","../node_modules/maplibre-gl-draw/node_modules/wgs84/index.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/geojson-area/index.js","../node_modules/maplibre-gl-draw/src/constants.js","../node_modules/maplibre-gl-draw/src/lib/sort_features.js","../node_modules/maplibre-gl-draw/src/lib/map_event_to_bounding_box.js","../node_modules/maplibre-gl-draw/src/lib/string_set.js","../node_modules/maplibre-gl-draw/src/lib/features_at.js","../node_modules/maplibre-gl-draw/src/lib/get_features_and_set_cursor.js","../node_modules/maplibre-gl-draw/src/lib/euclidean_distance.js","../node_modules/maplibre-gl-draw/src/lib/is_click.js","../node_modules/maplibre-gl-draw/src/lib/is_tap.js","../node_modules/maplibre-gl-draw/node_modules/hat/index.js","../node_modules/maplibre-gl-draw/src/feature_types/feature.js","../node_modules/maplibre-gl-draw/src/feature_types/point.js","../node_modules/maplibre-gl-draw/src/feature_types/line_string.js","../node_modules/maplibre-gl-draw/src/feature_types/polygon.js","../node_modules/maplibre-gl-draw/src/feature_types/multi_feature.js","../node_modules/maplibre-gl-draw/src/modes/mode_interface_accessors.js","../node_modules/maplibre-gl-draw/src/modes/mode_interface.js","../node_modules/maplibre-gl-draw/src/modes/object_to_mode.js","../node_modules/maplibre-gl-draw/src/lib/SRCenter.js","../node_modules/maplibre-gl-draw/src/events.js","../node_modules/maplibre-gl-draw/src/lib/to_dense_array.js","../node_modules/maplibre-gl-draw/src/render.js","../node_modules/maplibre-gl-draw/src/store.js","../node_modules/maplibre-gl-draw/node_modules/xtend/immutable.js","../node_modules/maplibre-gl-draw/src/ui.js","../node_modules/maplibre-gl-draw/src/setup.js","../node_modules/maplibre-gl-draw/src/lib/theme.js","../node_modules/maplibre-gl-draw/src/lib/common_selectors.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/point-geometry/index.js","../node_modules/maplibre-gl-draw/src/lib/mouse_event_point.js","../node_modules/maplibre-gl-draw/src/lib/create_vertex.js","../node_modules/maplibre-gl-draw/src/lib/create_midpoint.js","../node_modules/maplibre-gl-draw/src/lib/create_supplementary_points.js","../node_modules/maplibre-gl-draw/src/lib/double_click_zoom.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/geojson-normalize/index.js","../node_modules/maplibre-gl-draw/node_modules/geojson-flatten/dist/index.es.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/geojson-coords/flatten.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/geojson-coords/index.js","../node_modules/maplibre-gl-draw/node_modules/traverse/index.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/extent/index.js","../node_modules/maplibre-gl-draw/node_modules/@mapbox/geojson-extent/index.js","../node_modules/maplibre-gl-draw/src/lib/constrain_feature_movement.js","../node_modules/maplibre-gl-draw/src/lib/move_features.js","../node_modules/maplibre-gl-draw/src/modes/simple_select.js","../node_modules/maplibre-gl-draw/src/modes/direct_select.js","../node_modules/maplibre-gl-draw/src/modes/draw_point.js","../node_modules/maplibre-gl-draw/src/lib/is_event_at_coordinates.js","../node_modules/maplibre-gl-draw/src/modes/draw_polygon.js","../node_modules/maplibre-gl-draw/src/modes/draw_line_string.js","../node_modules/maplibre-gl-draw/node_modules/@turf/helpers/dist/es/index.js","../node_modules/maplibre-gl-draw/node_modules/@turf/bearing/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/bearing/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/bearing/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/bbox/node_modules/@turf/meta/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/bbox/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/center/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/center/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/destination/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/destination/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/destination/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/distance/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/distance/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/distance/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/midpoint/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-rotate/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-rotate/node_modules/@turf/meta/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-rotate/node_modules/@turf/centroid/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-bearing/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-bearing/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-bearing/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-distance/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-distance/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-distance/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-destination/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-destination/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/rhumb-destination/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/clone/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-rotate/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-rotate/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-scale/node_modules/@turf/helpers/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-scale/node_modules/@turf/meta/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-scale/node_modules/@turf/centroid/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-scale/node_modules/@turf/invariant/main.es.js","../node_modules/maplibre-gl-draw/node_modules/@turf/transform-scale/main.es.js","../node_modules/maplibre-gl-draw/src/modes/img/rotate.png","../node_modules/maplibre-gl-draw/src/modes/img/scale.png","../node_modules/maplibre-gl-draw/src/modes/SRMode.js","../node_modules/maplibre-gl-draw/src/modes/index.js","../node_modules/maplibre-gl-draw/src/lib/SRStyle.js","../node_modules/maplibre-gl-draw/src/options.js","../node_modules/maplibre-gl-draw/node_modules/lodash.isequal/index.js","../node_modules/maplibre-gl-draw/src/lib/string_sets_are_equal.js","../node_modules/maplibre-gl-draw/src/api.js","../node_modules/maplibre-gl-draw/index.js","../src/components/globe-control.ts","../src/components/minimap-control.ts","../src/exports-maplibre-gl.ts"],"sourcesContent":["export * from './exports-maplibre-gl.js'\nexport { default as default } from './exports-maplibre-gl.js'\n","import * as React from 'react'\nimport { useState, useRef, useEffect, useContext, useMemo, useImperativeHandle } from 'react'\n\nimport { MountedMapsContext } from './use-map'\nimport Maplibre, { MaplibreProps } from '../maplibre/maplibre'\nimport createRef, { MapRef } from '../maplibre/create-ref'\n\nimport type { CSSProperties } from 'react'\nimport useIsomorphicLayoutEffect from '../utils/use-isomorphic-layout-effect'\nimport setGlobals, { GlobalSettings } from '../utils/set-globals'\nimport type { MapLib, MapOptions } from '../types/lib'\nimport type { MapOptionsInternal } from '../types/internal'\nimport type { ErrorEvent } from '../types/events'\nimport { LogoControl } from './logo-control'\nimport { AttributionControl } from './attribution-control'\nimport { logger } from '../utils/logger'\n\nexport type MapContextValue = {\n mapLib: MapLib\n map: MapRef\n /** Consumer-supplied warning handler, propagated to child components so they\n * can surface non-fatal warnings without touching console directly. */\n onWarning?: (e: ErrorEvent) => void\n}\n\nexport const MapContext = React.createContext<MapContextValue>(null)\n\ntype MapInitOptions = Omit<\n MapOptions,\n 'style' | 'container' | 'bounds' | 'fitBoundsOptions' | 'center'\n>\n\nexport type MapProps = MapInitOptions &\n MaplibreProps &\n GlobalSettings & {\n mapLib?: MapLib | Promise<MapLib>\n reuseMaps?: boolean\n /** Map container id */\n id?: string\n /** Map container CSS style */\n style?: CSSProperties\n children?: React.ReactNode\n /** Whether to render the Barikoi logo control.\n * @default true */\n showBarikoiLogo?: boolean\n /** Whether to render the map attribution control.\n * @default true\n * @remarks Attribution is required by the OpenStreetMap and MapLibre\n * license terms; keep this enabled unless you provide attribution elsewhere. */\n showAttribution?: boolean\n } & React.RefAttributes<MapRef>\n\nfunction _Map(props: MapProps, ref: React.Ref<MapRef>) {\n const mountedMapsContext = useContext(MountedMapsContext)\n const [mapInstance, setMapInstance] = useState<Maplibre>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n\n const propsRef = useRef(props)\n propsRef.current = props\n\n const prevPropsRef = useRef<MapProps>(props)\n\n const { current: contextValue } = useRef<MapContextValue>({\n mapLib: null,\n map: null,\n })\n\n useEffect(() => {\n const initialMapLib = propsRef.current.mapLib\n const initialId = propsRef.current.id\n const initialReuseMaps = propsRef.current.reuseMaps\n\n let isMounted = true\n let maplibre: Maplibre | null = null\n\n Promise.resolve(initialMapLib || import('maplibre-gl'))\n .then((module: MapLib | { default: MapLib }) => {\n if (!isMounted) {\n return\n }\n if (!module) {\n throw new Error('Invalid mapLib')\n }\n const mapboxgl = 'Map' in module ? module : module.default\n if (!mapboxgl.Map) {\n throw new Error('Invalid mapLib')\n }\n\n setGlobals(mapboxgl, propsRef.current)\n if (initialReuseMaps) {\n maplibre = Maplibre.reuse(propsRef.current, containerRef.current)\n }\n if (!maplibre) {\n maplibre = new Maplibre(\n mapboxgl.Map as any,\n {\n ...propsRef.current,\n attributionControl: false,\n } as MapOptions & MapOptionsInternal & MaplibreProps,\n containerRef.current\n )\n }\n if (maplibre) {\n contextValue.map = createRef(maplibre)\n contextValue.mapLib = mapboxgl\n setMapInstance(maplibre)\n }\n mountedMapsContext?.onMapMount(contextValue.map, propsRef.current.id || initialId)\n })\n .catch(error => {\n const { onError } = propsRef.current\n if (onError) {\n onError({\n type: 'error',\n target: null,\n originalEvent: null,\n error,\n })\n } else {\n logger.error(error)\n }\n })\n\n return () => {\n isMounted = false\n if (maplibre) {\n mountedMapsContext?.onMapUnmount(propsRef.current.id || initialId)\n if (initialReuseMaps) {\n maplibre.recycle()\n } else {\n maplibre.destroy()\n }\n }\n }\n }, [])\n\n // Keep latest props in a ref so this effect doesn't re-subscribe every render.\n const latestPropsRef = useRef(props)\n latestPropsRef.current = props\n\n useIsomorphicLayoutEffect(() => {\n if (!mapInstance) return\n const prevProps = prevPropsRef.current\n const currentProps = latestPropsRef.current\n if (!prevProps) {\n prevPropsRef.current = currentProps\n return\n }\n\n let propsChanged = false\n for (const key in currentProps) {\n if (key !== 'children' && key !== 'mapLib') {\n if (currentProps[key] !== prevProps[key]) {\n propsChanged = true\n break\n }\n }\n }\n if (!propsChanged) {\n for (const key in prevProps) {\n if (key !== 'children' && key !== 'mapLib') {\n if (!(key in currentProps)) {\n propsChanged = true\n break\n }\n }\n }\n }\n\n if (propsChanged) {\n mapInstance.setProps(currentProps)\n }\n prevPropsRef.current = currentProps\n }, [mapInstance])\n\n useImperativeHandle(ref, () => contextValue.map, [mapInstance])\n\n const style: CSSProperties = useMemo(\n () => ({\n position: 'relative',\n width: '100%',\n height: '100%',\n ...props.style,\n }),\n [props.style]\n )\n\n const CHILD_CONTAINER_STYLE = {\n height: '100%',\n }\n\n // Propagate the warning handler to children via the (stable) context value.\n contextValue.onWarning = props.onWarning\n\n // The outer container holds the MapLibre map. MapLibre automatically assigns\n // role=\"region\" and aria-label=\"Map\" to the internal canvas element.\n // Note: For keyboard accessibility (arrow keys for panning, +/- for zooming),\n // the `keyboard` option must remain enabled (defaults to true).\n return (\n <div id={props.id} ref={containerRef} style={style}>\n {mapInstance && (\n <MapContext.Provider value={contextValue}>\n <div style={CHILD_CONTAINER_STYLE}>\n {/* Automatically include Barikoi Logo and Attribution controls.\n Both default to true; set showBarikoiLogo / showAttribution to\n false to opt out. */}\n {props.showBarikoiLogo !== false && <LogoControl position='bottom-left' />}\n {props.showAttribution !== false && <AttributionControl position='bottom-right' />}\n {props.children}\n </div>\n </MapContext.Provider>\n )}\n </div>\n )\n}\n\n/**\n * Interactive Map component using MapLibre GL.\n *\n * @remarks\n * For keyboard navigation and accessibility, the `keyboard` prop must be set to `true` (which is default).\n * If explicitly disabled (e.g. `keyboard={false}`), keyboard access (such as panning/zooming via arrow keys)\n * will be unavailable to screen reader and keyboard-only users.\n */\nexport const Map: React.FC<MapProps> = React.forwardRef(_Map)\n","import * as React from 'react'\nimport { useState, useCallback, useMemo, useContext } from 'react'\n\nimport { MapRef } from '../maplibre/create-ref'\nimport { MapContext } from './map'\n\ntype MountedMapsContextValue = {\n maps: { [id: string]: MapRef }\n onMapMount: (map: MapRef, id: string) => void\n onMapUnmount: (id: string) => void\n}\n\nexport const MountedMapsContext = React.createContext<MountedMapsContextValue>(null)\n\nexport const MapProvider: React.FC<{ children?: React.ReactNode }> = props => {\n const [maps, setMaps] = useState<{ [id: string]: MapRef }>({})\n\n const onMapMount = useCallback((map: MapRef, id: string = 'default') => {\n setMaps(currMaps => {\n if (id === 'current') {\n throw new Error(\"'current' cannot be used as map id\")\n }\n if (currMaps[id]) {\n throw new Error(`Multiple maps with the same id: ${id}`)\n }\n return { ...currMaps, [id]: map }\n })\n }, [])\n\n const onMapUnmount = useCallback((id: string = 'default') => {\n setMaps(currMaps => {\n if (currMaps[id]) {\n const nextMaps = { ...currMaps }\n delete nextMaps[id]\n return nextMaps\n }\n return currMaps\n })\n }, [])\n\n return (\n <MountedMapsContext.Provider\n value={{\n maps,\n onMapMount,\n onMapUnmount,\n }}\n >\n {props.children}\n </MountedMapsContext.Provider>\n )\n}\n\nexport type MapCollection = {\n [id: string]: MapRef | undefined\n current?: MapRef\n}\n\nexport function useMap(): MapCollection {\n const maps = useContext(MountedMapsContext)?.maps\n const currentMap = useContext(MapContext)\n\n const mapsWithCurrent = useMemo(() => {\n // If currentMap is available from MapContext, use it\n // Otherwise, fall back to the first registered map from MountedMapsContext\n const current = currentMap?.map || (maps ? Object.values(maps)[0] : undefined)\n return { ...maps, current }\n }, [maps, currentMap])\n\n return mapsWithCurrent as MapCollection\n}\n","import type { PointLike } from '../types/common'\n\n/**\n * Compare two points\n * @param a\n * @param b\n * @returns true if the points are equal\n */\nexport function arePointsEqual(a?: PointLike, b?: PointLike): boolean {\n const ax = Array.isArray(a) ? a[0] : a ? a.x : 0\n const ay = Array.isArray(a) ? a[1] : a ? a.y : 0\n const bx = Array.isArray(b) ? b[0] : b ? b.x : 0\n const by = Array.isArray(b) ? b[1] : b ? b.y : 0\n return ax === bx && ay === by\n}\n\n/**\n * Compare any two objects\n * @param a\n * @param b\n * @returns true if the objects are deep equal\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true\n }\n if (!a || !b) {\n return false\n }\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) {\n return false\n }\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) {\n return false\n }\n }\n return true\n } else if (Array.isArray(b)) {\n return false\n }\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) {\n return false\n }\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) {\n return false\n }\n if (!deepEqual(a[key], b[key])) {\n return false\n }\n }\n return true\n }\n return false\n}\n","import type { MaplibreProps } from '../maplibre/maplibre'\nimport type { ViewState } from '../types/common'\nimport type { TransformLike } from '../types/internal'\nimport { deepEqual } from './deep-equal'\n\n/**\n * Capture a transform's current state\n * @param transform\n * @returns descriptor of the view state\n */\nexport function transformToViewState(tr: TransformLike): ViewState {\n return {\n longitude: tr.center.lng,\n latitude: tr.center.lat,\n zoom: tr.zoom,\n pitch: tr.pitch,\n bearing: tr.bearing,\n padding: tr.padding,\n }\n}\n\n/**\n * Applies requested view state to a transform\n * @returns an object containing detected changes\n */\nexport function applyViewStateToTransform(\n /** An object that describes Maplibre's camera state */\n tr: TransformLike,\n /** Props from Map component */\n props: MaplibreProps\n): Partial<TransformLike> {\n const v: Partial<ViewState> = props.viewState || props\n const changes: Partial<TransformLike> = {}\n\n if (\n 'longitude' in v &&\n 'latitude' in v &&\n (v.longitude !== tr.center.lng || v.latitude !== tr.center.lat)\n ) {\n const LngLat = tr.center.constructor\n // @ts-expect-error we should not import LngLat class from maplibre-gl because we don't know the source of mapLib\n changes.center = new LngLat(v.longitude, v.latitude)\n }\n if ('zoom' in v && v.zoom !== tr.zoom) {\n changes.zoom = v.zoom\n }\n if ('bearing' in v && v.bearing !== tr.bearing) {\n changes.bearing = v.bearing\n }\n if ('pitch' in v && v.pitch !== tr.pitch) {\n changes.pitch = v.pitch\n }\n if (v.padding && tr.padding && !deepEqual(v.padding, tr.padding)) {\n changes.padding = v.padding\n }\n return changes\n}\n","import type { StyleSpecification } from '../types/style-spec'\nimport type { ImmutableLike } from '../types/common'\n\nconst refProps = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout']\n\n// Legacy layer type with optional fields not in current type definitions\ninterface LegacyLayer {\n id: string\n interactive?: boolean\n ref?: string\n [key: string]: unknown\n}\n\n// Prepare a map style object for diffing\n// If immutable - convert to plain object\n// Work around some issues in older styles that would fail Mapbox's diffing\nexport function normalizeStyle(\n style: string | StyleSpecification | ImmutableLike<StyleSpecification>\n): string | StyleSpecification | null {\n if (!style) {\n return null\n }\n if (typeof style === 'string') {\n return style\n }\n if ('toJS' in style) {\n style = style.toJS()\n }\n if (!style.layers) {\n return style\n }\n const layerIndex: Record<string, LegacyLayer> = {}\n\n for (const layer of style.layers) {\n layerIndex[layer.id] = layer as LegacyLayer\n }\n\n const layers = style.layers.map(layer => {\n const legacyLayer = layer as LegacyLayer\n let normalizedLayer: LegacyLayer | null = null\n\n if ('interactive' in legacyLayer) {\n normalizedLayer = Object.assign({}, legacyLayer)\n // Breaks style diffing :(\n delete normalizedLayer.interactive\n }\n\n // Style diffing doesn't work with refs so expand them out manually before diffing.\n const layerRef = layerIndex[legacyLayer.ref as string]\n if (layerRef) {\n normalizedLayer = normalizedLayer || Object.assign({}, legacyLayer)\n delete normalizedLayer.ref\n // https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/deref.js\n for (const propName of refProps) {\n if (propName in layerRef) {\n normalizedLayer[propName] = layerRef[propName]\n }\n }\n }\n\n return (normalizedLayer || layer) as typeof layer\n })\n\n // Do not mutate the style object provided by the user\n return { ...style, layers }\n}\n","export interface Logger {\n warn: (message: any, ...args: any[]) => void\n error: (message: any, ...args: any[]) => void\n}\n\nlet activeLogger: Logger = {\n warn: (message, ...args) => console.warn(message, ...args),\n error: (message, ...args) => console.error(message, ...args),\n}\n\nexport const logger = {\n warn(message: any, ...args: any[]) {\n activeLogger.warn(message, ...args)\n },\n error(message: any, ...args: any[]) {\n activeLogger.error(message, ...args)\n },\n}\n\nexport function setLogger(customLogger: Partial<Logger>) {\n activeLogger = {\n warn:\n typeof customLogger.warn === 'function'\n ? customLogger.warn\n : (m, ...a) => console.warn(m, ...a),\n error:\n typeof customLogger.error === 'function'\n ? customLogger.error\n : (m, ...a) => console.error(m, ...a),\n }\n}\n","import { transformToViewState, applyViewStateToTransform } from '../utils/transform'\nimport { normalizeStyle } from '../utils/style-utils'\nimport { deepEqual } from '../utils/deep-equal'\nimport { logger } from '../utils/logger'\n\nimport type { TransformLike, MapInternalProperties, MapWithProjection } from '../types/internal'\nimport type {\n ViewState,\n Point,\n PointLike,\n PaddingOptions,\n ImmutableLike,\n LngLatBoundsLike,\n MapGeoJSONFeature,\n} from '../types/common'\nimport type {\n StyleSpecification,\n SkySpecification,\n LightSpecification,\n TerrainSpecification,\n ProjectionSpecification,\n} from '../types/style-spec'\nimport type { Map as MapInstance } from '../types/lib'\nimport type {\n MapCallbacks,\n ViewStateChangeEvent,\n MapEvent,\n ErrorEvent,\n MapMouseEvent,\n} from '../types/events'\n\nexport type MaplibreProps = Partial<ViewState> &\n MapCallbacks & {\n /** Camera options used when constructing the Map instance */\n initialViewState?: Partial<ViewState> & {\n /** The initial bounds of the map. If bounds is specified, it overrides longitude, latitude and zoom options. */\n bounds?: LngLatBoundsLike\n /** A fitBounds options object to use only when setting the bounds option. */\n fitBoundsOptions?: {\n offset?: PointLike\n minZoom?: number\n maxZoom?: number\n padding?: number | PaddingOptions\n }\n }\n\n /** If provided, render into an external WebGL context */\n gl?: WebGLRenderingContext\n\n /** For external controller to override the camera state */\n viewState?: ViewState & {\n width: number\n height: number\n }\n\n // Styling\n\n /** Mapbox style */\n mapStyle?: string | StyleSpecification | ImmutableLike<StyleSpecification>\n /** Enable diffing when the map style changes\n * @default true\n */\n styleDiffing?: boolean\n /** The projection property of the style. Must conform to the Projection Style Specification.\n * @default 'mercator'\n */\n projection?: ProjectionSpecification | 'mercator' | 'globe'\n /** Light properties of the map. */\n light?: LightSpecification\n /** Terrain property of the style. Must conform to the Terrain Style Specification.\n * If `undefined` is provided, removes terrain from the map. */\n terrain?: TerrainSpecification\n /** Sky properties of the map. Must conform to the Sky Style Specification. */\n sky?: SkySpecification\n\n /** Default layers to query on pointer events */\n interactiveLayerIds?: string[]\n /** CSS cursor */\n cursor?: string\n\n /** Called with non-fatal warnings (e.g. transient errors before the style\n * has finished loading). When omitted, warnings fall back to `console.warn`. */\n onWarning?: (e: ErrorEvent) => void\n }\n\nconst DEFAULT_STYLE = {\n version: 8,\n sources: {},\n layers: [],\n} as StyleSpecification\n\nconst pointerEvents = {\n mousedown: 'onMouseDown',\n mouseup: 'onMouseUp',\n mouseover: 'onMouseOver',\n mousemove: 'onMouseMove',\n click: 'onClick',\n dblclick: 'onDblClick',\n mouseenter: 'onMouseEnter',\n mouseleave: 'onMouseLeave',\n mouseout: 'onMouseOut',\n contextmenu: 'onContextMenu',\n touchstart: 'onTouchStart',\n touchend: 'onTouchEnd',\n touchmove: 'onTouchMove',\n touchcancel: 'onTouchCancel',\n}\nconst cameraEvents = {\n movestart: 'onMoveStart',\n move: 'onMove',\n moveend: 'onMoveEnd',\n dragstart: 'onDragStart',\n drag: 'onDrag',\n dragend: 'onDragEnd',\n zoomstart: 'onZoomStart',\n zoom: 'onZoom',\n zoomend: 'onZoomEnd',\n rotatestart: 'onRotateStart',\n rotate: 'onRotate',\n rotateend: 'onRotateEnd',\n pitchstart: 'onPitchStart',\n pitch: 'onPitch',\n pitchend: 'onPitchEnd',\n}\nconst otherEvents = {\n wheel: 'onWheel',\n boxzoomstart: 'onBoxZoomStart',\n boxzoomend: 'onBoxZoomEnd',\n boxzoomcancel: 'onBoxZoomCancel',\n resize: 'onResize',\n load: 'onLoad',\n render: 'onRender',\n idle: 'onIdle',\n remove: 'onRemove',\n data: 'onData',\n styledata: 'onStyleData',\n sourcedata: 'onSourceData',\n error: 'onError',\n}\nconst settingNames = [\n 'minZoom',\n 'maxZoom',\n 'minPitch',\n 'maxPitch',\n 'maxBounds',\n 'projection',\n 'renderWorldCopies',\n]\nconst handlerNames = [\n 'scrollZoom',\n 'boxZoom',\n 'dragRotate',\n 'dragPan',\n 'keyboard',\n 'doubleClickZoom',\n 'touchZoomRotate',\n 'touchPitch',\n]\n\n/**\n * A wrapper for mapbox-gl's Map class\n */\n\nexport default class Maplibre {\n private _MapClass: { new (options: unknown): MapInstance }\n // mapboxgl.Map instance\n private _map: MapInstance = null\n // User-supplied props\n props: MaplibreProps\n\n // Internal states\n private _internalUpdate: boolean = false\n private _hoveredFeatures: MapGeoJSONFeature[] = null\n private _propsedCameraUpdate: ViewState | null = null\n // Suppresses repeated queryRenderedFeatures warnings while the style loads\n private _warnedQueryFeatures: boolean = false\n private _styleComponents: {\n light?: LightSpecification\n sky?: SkySpecification\n projection?: ProjectionSpecification\n terrain?: TerrainSpecification | null\n } = {}\n\n static savedMaps: Maplibre[] = []\n\n constructor(\n MapClass: { new (options: unknown): MapInstance },\n props: MaplibreProps,\n container: HTMLDivElement\n ) {\n this._MapClass = MapClass\n this.props = props\n this._initialize(container)\n }\n\n get map(): MapInstance {\n return this._map\n }\n\n setProps(props: MaplibreProps) {\n const oldProps = this.props\n this.props = props\n\n const settingsChanged = this._updateSettings(props, oldProps)\n const sizeChanged = this._updateSize(props)\n const viewStateChanged = this._updateViewState(props)\n this._updateStyle(props, oldProps)\n this._updateStyleComponents(props)\n this._updateHandlers(props, oldProps)\n\n // If 1) view state has changed to match props and\n // 2) the props change is not triggered by map events,\n // it's driven by an external state change. Redraw immediately\n if (settingsChanged || sizeChanged || (viewStateChanged && !this._map.isMoving())) {\n this.redraw()\n }\n }\n\n static reuse(props: MaplibreProps, container: HTMLDivElement): Maplibre {\n const that = Maplibre.savedMaps.pop()\n if (!that) {\n return null\n }\n\n const map = that.map\n // When reusing the saved map, we need to reparent the map(canvas) and other child nodes\n // intoto the new container from the props.\n // Step 1: reparenting child nodes from old container to new container\n const oldContainer = map.getContainer()\n container.className = oldContainer.className\n while (oldContainer.childNodes.length > 0) {\n container.appendChild(oldContainer.childNodes[0])\n }\n // Step 2: replace the internal container with new container from the react component\n const mapInternal = map as unknown as MapInternalProperties\n if (mapInternal && '_container' in mapInternal) {\n mapInternal._container = container\n }\n\n // With maplibre-gl as mapLib, map uses ResizeObserver to observe when its container resizes.\n // When reusing the saved map, we need to disconnect the observer and observe the new container.\n // Step 3: telling the ResizeObserver to disconnect and observe the new container\n const resizeObserver = mapInternal?._resizeObserver\n if (\n resizeObserver &&\n typeof resizeObserver.disconnect === 'function' &&\n typeof resizeObserver.observe === 'function'\n ) {\n resizeObserver.disconnect()\n resizeObserver.observe(container)\n }\n\n // Step 4: apply new props\n that.setProps({ ...props, styleDiffing: false })\n map.resize()\n const { initialViewState } = props\n if (initialViewState) {\n if (initialViewState.bounds) {\n map.fitBounds(initialViewState.bounds, {\n ...initialViewState.fitBoundsOptions,\n duration: 0,\n })\n } else {\n that._updateViewState(initialViewState)\n }\n }\n\n // Simulate load event. isStyleLoaded() throws if no style is set\n // (deferred or errored), so guard with a style check first.\n if (map.style && map.isStyleLoaded()) {\n map.fire('load')\n } else {\n map.once('style.load', () => map.fire('load'))\n }\n\n // Force reload\n const mapInternalForUpdate = map as unknown as MapInternalProperties\n if (mapInternalForUpdate && typeof mapInternalForUpdate._update === 'function') {\n mapInternalForUpdate._update()\n }\n return that\n }\n\n private _initialize(container: HTMLDivElement) {\n const { props } = this\n const { mapStyle = DEFAULT_STYLE } = props\n const mapOptions = {\n ...props,\n ...props.initialViewState,\n container,\n style: normalizeStyle(mapStyle),\n }\n\n const viewState = mapOptions.initialViewState || mapOptions.viewState || mapOptions\n Object.assign(mapOptions, {\n center: [viewState.longitude || 0, viewState.latitude || 0],\n zoom: viewState.zoom || 0,\n pitch: viewState.pitch || 0,\n bearing: viewState.bearing || 0,\n })\n\n let savedGetContext: typeof HTMLCanvasElement.prototype.getContext | null = null\n if (props.gl) {\n savedGetContext = HTMLCanvasElement.prototype.getContext\n // Hijack canvas.getContext to return our own WebGLContext.\n // This will be called inside the mapboxgl.Map constructor.\n // @ts-expect-error - temporarily overriding getContext to inject custom WebGL context\n HTMLCanvasElement.prototype.getContext = () => props.gl\n }\n\n let map: MapInstance\n try {\n map = new this._MapClass(mapOptions)\n } finally {\n // Always restore the original getContext — if the constructor threw before\n // calling getContext, every other <canvas> on the page would otherwise stay\n // permanently hijacked.\n if (savedGetContext) {\n HTMLCanvasElement.prototype.getContext = savedGetContext\n }\n }\n // Props that are not part of constructor options\n if (viewState.padding) {\n map.setPadding(viewState.padding)\n }\n if (props.cursor) {\n map.getCanvas().style.cursor = props.cursor\n }\n\n // add listeners\n map.transformCameraUpdate = this._onCameraUpdate\n map.on('style.load', () => {\n // Map style has changed, this would have wiped out all settings from props\n const mapWithProjection = map as unknown as MapWithProjection\n this._styleComponents = {\n light: map.getLight(),\n sky: map.getSky(),\n projection: mapWithProjection.getProjection?.(),\n terrain: map.getTerrain(),\n }\n this._updateStyleComponents(this.props)\n })\n map.on('sourcedata', () => {\n // Some sources have loaded, we may need them to attach terrain\n this._updateStyleComponents(this.props)\n })\n for (const eventName in pointerEvents) {\n map.on(eventName, this._onPointerEvent)\n }\n for (const eventName in cameraEvents) {\n map.on(eventName, this._onCameraEvent)\n }\n for (const eventName in otherEvents) {\n map.on(eventName, this._onEvent)\n }\n this._map = map\n }\n\n recycle() {\n // Clean up unnecessary elements before storing for reuse.\n const container = this.map.getContainer()\n const children = container.querySelector('[mapboxgl-children]')\n children?.remove()\n\n Maplibre.savedMaps.push(this)\n }\n\n destroy() {\n this._map.remove()\n }\n\n // Force redraw the map now. Typically resize() and jumpTo() is reflected in the next\n // render cycle, which is managed by Mapbox's animation loop.\n // This removes the synchronization issue caused by requestAnimationFrame.\n redraw() {\n const map = this._map as unknown as MapInternalProperties\n // map._render will throw error if style does not exist\n // https://github.com/mapbox/mapbox-gl-js/blob/fb9fc316da14e99ff4368f3e4faa3888fb43c513\n // /src/ui/map.js#L1834\n if (map && map.style) {\n if (typeof map._render === 'function') {\n // cancel the scheduled update\n if (map._frame && typeof map._frame.cancel === 'function') {\n map._frame.cancel()\n map._frame = null\n }\n // the order is important - render() may schedule another update\n map._render()\n } else if (typeof this._map.triggerRepaint === 'function') {\n this._map.triggerRepaint()\n }\n }\n }\n\n /* Trigger map resize if size is controlled\n @param {object} nextProps\n @returns {bool} true if size has changed\n */\n private _updateSize(nextProps: MaplibreProps): boolean {\n // Check if size is controlled\n const { viewState } = nextProps\n if (viewState) {\n const map = this._map\n if (viewState.width !== map.transform.width || viewState.height !== map.transform.height) {\n map.resize()\n return true\n }\n }\n return false\n }\n\n // Adapted from map.jumpTo\n /* Update camera to match props\n @param {object} nextProps\n @param {bool} triggerEvents - should fire camera events\n @returns {bool} true if anything is changed\n */\n private _updateViewState(nextProps: MaplibreProps): boolean {\n const map = this._map\n const tr = map.transform\n const isMoving = map.isMoving()\n\n // Avoid manipulating the real transform when interaction/animation is ongoing\n // as it would interfere with Mapbox's handlers\n if (!isMoving) {\n const changes: Record<string, unknown> = applyViewStateToTransform(tr, nextProps)\n if (Object.keys(changes).length > 0) {\n this._internalUpdate = true\n map.jumpTo(changes)\n this._internalUpdate = false\n return true\n }\n }\n\n return false\n }\n\n /* Update camera constraints and projection settings to match props\n @param {object} nextProps\n @param {object} currProps\n @returns {bool} true if anything is changed\n */\n private _updateSettings(nextProps: MaplibreProps, currProps: MaplibreProps): boolean {\n const map = this._map\n let changed = false\n for (const propName of settingNames) {\n if (propName in nextProps && !deepEqual(nextProps[propName], currProps[propName])) {\n changed = true\n const setter = map[`set${propName[0].toUpperCase()}${propName.slice(1)}`]\n setter?.call(map, nextProps[propName])\n }\n }\n return changed\n }\n\n /* Update map style to match props */\n private _updateStyle(nextProps: MaplibreProps, currProps: MaplibreProps): void {\n if (nextProps.cursor !== currProps.cursor) {\n this._map.getCanvas().style.cursor = nextProps.cursor || ''\n }\n if (nextProps.mapStyle !== currProps.mapStyle) {\n const { mapStyle = DEFAULT_STYLE, styleDiffing = true } = nextProps\n const options: Record<string, unknown> = {\n diff: styleDiffing,\n }\n if ('localIdeographFontFamily' in nextProps) {\n // Mapbox specific prop not in maplibre types\n options.localIdeographFontFamily = nextProps.localIdeographFontFamily\n }\n this._map.setStyle(normalizeStyle(mapStyle), options)\n }\n }\n\n /* Update fog, light, projection and terrain to match props\n * These props are special because\n * 1. They can not be applied right away. Certain conditions (style loaded, source loaded, etc.) must be met\n * 2. They can be overwritten by mapStyle\n */\n private _updateStyleComponents({ light, projection, sky, terrain }: MaplibreProps): void {\n const map = this._map\n const currProps = this._styleComponents\n const mapWithProjection = map as unknown as MapWithProjection\n // We can safely manipulate map style once it's loaded.\n // isStyleLoaded() throws when no style is set, so guard explicitly.\n if (map.style && map.isStyleLoaded()) {\n if (light && !deepEqual(light, currProps.light)) {\n currProps.light = light\n map.setLight(light)\n }\n if (\n projection &&\n !deepEqual(projection, currProps.projection) &&\n projection !== currProps.projection?.type\n ) {\n currProps.projection = typeof projection === 'string' ? { type: projection } : projection\n mapWithProjection.setProjection?.(currProps.projection)\n }\n if (sky && !deepEqual(sky, currProps.sky)) {\n currProps.sky = sky\n map.setSky(sky)\n }\n if (terrain !== undefined && !deepEqual(terrain, currProps.terrain)) {\n if (!terrain || map.getSource(terrain.source)) {\n currProps.terrain = terrain\n map.setTerrain(terrain)\n }\n }\n }\n }\n\n /* Update interaction handlers to match props */\n private _updateHandlers(nextProps: MaplibreProps, currProps: MaplibreProps): void {\n const map = this._map\n for (const propName of handlerNames) {\n const newValue = nextProps[propName] ?? true\n const oldValue = currProps[propName] ?? true\n if (!deepEqual(newValue, oldValue)) {\n if (newValue) {\n map[propName].enable(newValue)\n } else {\n map[propName].disable()\n }\n }\n }\n }\n\n private _onEvent = (e: MapEvent) => {\n const handlerName = otherEvents[e.type] as keyof MapCallbacks\n const cb = this.props[handlerName] as ((e: MapEvent) => void) | undefined\n if (cb) {\n cb(e)\n } else if (e.type === 'error') {\n logger.error((e as ErrorEvent).error)\n }\n }\n\n private _onCameraEvent = (e: ViewStateChangeEvent) => {\n if (this._internalUpdate) {\n return\n }\n e.viewState = this._propsedCameraUpdate || transformToViewState(this._map.transform)\n const handlerName = cameraEvents[e.type] as keyof MapCallbacks\n const cb = this.props[handlerName] as ((e: ViewStateChangeEvent) => void) | undefined\n if (cb) {\n cb(e)\n }\n }\n\n private _onCameraUpdate = (tr: TransformLike) => {\n if (this._internalUpdate) {\n return tr\n }\n this._propsedCameraUpdate = transformToViewState(tr)\n return applyViewStateToTransform(tr, this.props) as TransformLike\n }\n\n /** Surface a non-fatal warning via the consumer's onWarning callback,\n * falling back to console.warn when none is provided. */\n private _warn(error: Error) {\n const cb = this.props.onWarning\n if (cb) {\n cb({ type: 'error', target: this._map, originalEvent: null, error })\n } else {\n logger.warn(error)\n }\n }\n\n private _queryRenderedFeatures(point: Point) {\n const map = this._map\n const { interactiveLayerIds = [] } = this.props\n try {\n return map.queryRenderedFeatures(point, {\n layers: interactiveLayerIds.filter(map.getLayer.bind(map)),\n })\n } catch (err) {\n // queryRenderedFeatures runs on the hover hot-path and throws\n // transiently before the style finishes loading. Warn once per\n // instance to avoid console spam, and surface via onWarning if set.\n if (!this._warnedQueryFeatures) {\n this._warnedQueryFeatures = true\n this._warn(err instanceof Error ? err : new Error(String(err)))\n }\n return []\n }\n }\n\n private _updateHover(e: MapMouseEvent) {\n const { props } = this\n const shouldTrackHoveredFeatures =\n props.interactiveLayerIds && (props.onMouseMove || props.onMouseEnter || props.onMouseLeave)\n\n if (shouldTrackHoveredFeatures) {\n const eventType = e.type\n const wasHovering = this._hoveredFeatures?.length > 0\n const features = this._queryRenderedFeatures(e.point)\n const isHovering = features.length > 0\n\n if (!isHovering && wasHovering) {\n e.type = 'mouseleave'\n this._onPointerEvent(e)\n }\n this._hoveredFeatures = features\n if (isHovering && !wasHovering) {\n e.type = 'mouseenter'\n this._onPointerEvent(e)\n }\n e.type = eventType\n } else {\n this._hoveredFeatures = null\n }\n }\n\n private _onPointerEvent = (e: MapMouseEvent) => {\n if (e.type === 'mousemove' || e.type === 'mouseout') {\n this._updateHover(e)\n }\n\n const handlerName = pointerEvents[e.type as keyof typeof pointerEvents] as keyof MapCallbacks\n const cb = this.props[handlerName] as ((e: MapMouseEvent) => void) | undefined\n if (cb) {\n if (this.props.interactiveLayerIds && e.type !== 'mouseover' && e.type !== 'mouseout') {\n e.features = this._hoveredFeatures || this._queryRenderedFeatures(e.point)\n }\n cb(e)\n delete e.features\n }\n }\n}\n","import type { Map as MapInstance } from '../types/lib'\nimport type Maplibre from './maplibre'\n\n/** These methods may break the react binding if called directly */\nconst skipMethods = [\n 'setMaxBounds',\n 'setMinZoom',\n 'setMaxZoom',\n 'setMinPitch',\n 'setMaxPitch',\n 'setRenderWorldCopies',\n 'setProjection',\n 'setStyle',\n 'addSource',\n 'removeSource',\n 'addLayer',\n 'removeLayer',\n 'setLayerZoomRange',\n 'setFilter',\n 'setPaintProperty',\n 'setLayoutProperty',\n 'setLight',\n 'setTerrain',\n 'setFog',\n 'remove',\n] as const\n\nexport type MapRef = {\n getMap(): MapInstance\n} & Omit<MapInstance, (typeof skipMethods)[number]>\n\nexport default function createRef(mapInstance: Maplibre): MapRef | null {\n if (!mapInstance) {\n return null\n }\n\n const map = mapInstance.map\n\n const result: any = {\n getMap: () => map,\n }\n\n for (const key of getMethodNames(map)) {\n // @ts-expect-error - dynamically binding map methods to result object\n if (!(key in result) && !skipMethods.includes(key)) {\n result[key] = map[key].bind(map)\n }\n }\n\n return result\n}\n\nfunction getMethodNames(obj: object) {\n const result = new Set<string>()\n\n let proto = obj\n while (proto) {\n for (const key of Object.getOwnPropertyNames(proto)) {\n if (\n key[0] !== '_' &&\n typeof obj[key] === 'function' &&\n key !== 'fire' &&\n key !== 'setEventedParent'\n ) {\n result.add(key)\n }\n }\n proto = Object.getPrototypeOf(proto)\n }\n return Array.from(result)\n}\n","// From https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts\n// useLayoutEffect but does not trigger warning in server-side rendering\nimport { useEffect, useLayoutEffect } from 'react'\n\nconst useIsomorphicLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nexport default useIsomorphicLayoutEffect\n","import type { MapLib } from '../types/lib'\nimport { logger } from './logger'\n\nexport type GlobalSettings = {\n /** The maximum number of images (raster tiles, sprites, icons) to load in parallel.\n * @default 16\n */\n maxParallelImageRequests?: number\n /** The map's RTL text plugin. Necessary for supporting the Arabic and Hebrew languages, which are written right-to-left. */\n RTLTextPlugin?: string | { pluginUrl: string; lazy?: boolean }\n /** The number of web workers instantiated on a page with maplibre-gl maps.\n * @default 2\n */\n workerCount?: number\n /** Provides an interface for loading maplibre-gl's WebWorker bundle from a self-hosted URL.\n * This is useful if your site needs to operate in a strict CSP (Content Security Policy) environment\n * wherein you are not allowed to load JavaScript code from a Blob URL, which is default behavior. */\n workerUrl?: string\n}\n\n/**\n * Validates that a URL uses a safe protocol (http/https)\n */\nconst validateUrl = (url: string, settingName: string): boolean => {\n try {\n const parsed = new URL(url)\n if (!['http:', 'https:'].includes(parsed.protocol)) {\n logger.warn(`${settingName}: Only http/https protocols are allowed, got: ${parsed.protocol}`)\n return false\n }\n return true\n } catch {\n logger.warn(`${settingName}: Invalid URL format: ${url}`)\n return false\n }\n}\n\n/**\n * The `mapLib` argument augmented with the module-level global setters it uses.\n * These exist on both mapbox-gl and maplibre-gl but are intentionally omitted\n * from the shared `MapLib` interface, so they are declared optional here and\n * invoked with optional chaining at the call sites.\n */\ntype GlobalSettingsMapLib = MapLib & {\n getRTLTextPluginStatus?: () => string\n setRTLTextPlugin?: (pluginUrl: string, callback: (error?: Error) => void, lazy: boolean) => void\n setMaxParallelImageRequests?: (count: number) => void\n setWorkerCount?: (count: number) => void\n setWorkerUrl?: (url: string) => void\n}\n\nexport default function setGlobals(mapLib: GlobalSettingsMapLib, props: GlobalSettings) {\n const { RTLTextPlugin, maxParallelImageRequests, workerCount, workerUrl } = props\n if (\n RTLTextPlugin &&\n mapLib.getRTLTextPluginStatus &&\n mapLib.getRTLTextPluginStatus() === 'unavailable'\n ) {\n const { pluginUrl, lazy = true } =\n typeof RTLTextPlugin === 'string' ? { pluginUrl: RTLTextPlugin } : RTLTextPlugin\n\n if (validateUrl(pluginUrl, 'RTLTextPlugin')) {\n if (typeof mapLib.setRTLTextPlugin !== 'function') {\n logger.warn(\n `RTLTextPlugin was configured but the provided mapLib does not expose setRTLTextPlugin. Right-to-left scripts (Arabic, Hebrew) will not render correctly.`\n )\n } else {\n mapLib.setRTLTextPlugin(\n pluginUrl,\n (error?: Error) => {\n if (error) {\n logger.error(error)\n }\n },\n lazy\n )\n }\n }\n }\n if (maxParallelImageRequests !== undefined) {\n mapLib.setMaxParallelImageRequests?.(maxParallelImageRequests)\n }\n if (workerCount !== undefined) {\n mapLib.setWorkerCount?.(workerCount)\n }\n if (workerUrl !== undefine