@orioro/react-maplibre-util 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @orioro/react-maplibre-util
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - update all dependencies and multiple internal deps
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @orioro/react-chart-util@0.3.0
13
+
14
+ ## 0.5.3
15
+
16
+ ### Patch Changes
17
+
18
+ - fix layer ordering sync issue
19
+
3
20
  ## 0.5.2
4
21
 
5
22
  ### Patch Changes
@@ -8,7 +8,7 @@ export type ControlContainerProps = {
8
8
  /** Button content and props */
9
9
  children: React.ReactNode;
10
10
  };
11
- export declare function ControlContainer({ style, position, children, }: ControlContainerProps): any;
11
+ export declare function ControlContainer({ style, position, children, }: ControlContainerProps): React.ReactPortal | null;
12
12
  export declare namespace ControlContainer {
13
13
  var Unstyled: typeof ControlContainerWithStyleReset;
14
14
  }
@@ -1,2 +1,5 @@
1
1
  export * from './DynamicImages';
2
2
  export * from './svgImages';
3
+ export * from './svgPatterns';
4
+ import * as SVG_PATTERNS from './svgPatterns';
5
+ export { SVG_PATTERNS };
@@ -0,0 +1,9 @@
1
+ export type IconPathToSvgOptions = {
2
+ size?: number;
3
+ fill?: string;
4
+ stroke?: string;
5
+ strokeWidth?: string | number;
6
+ viewBox?: string;
7
+ style?: string;
8
+ };
9
+ export declare function iconPathToSvg(path: string, { size, fill, stroke, strokeWidth, viewBox, style, }?: IconPathToSvgOptions): string;
@@ -1,30 +1,8 @@
1
- import { StyleImageMetadata } from 'maplibre-gl';
2
- type IconPathToSvgOptions = {
3
- size?: number;
4
- fill?: string;
5
- stroke?: string;
6
- strokeWidth?: string | number;
7
- viewBox?: string;
8
- style?: string;
9
- };
10
- export declare function iconPathToSvg(path: string, { size, fill, stroke, strokeWidth, viewBox, style, }?: IconPathToSvgOptions): string;
11
- /**
12
- * Renders an SVG string onto a canvas and returns an object compatible with maplibre `addImage`.
13
- *
14
- * @param svgString - The SVG markup as a string
15
- * @param pixelRatio - (Optional) device pixel ratio, defaults to window.devicePixelRatio
16
- * @returns Promise resolving to { width, height, data, pixelRatio }
17
- */
18
- export declare function svgToMaplibreImage(svgString: string, pixelRatio?: number): Promise<[
19
- {
20
- width: number;
21
- height: number;
22
- data: Uint8ClampedArray;
23
- },
24
- Partial<StyleImageMetadata>
25
- ]>;
26
- export declare function svgIconId(iconId: string, options?: IconPathToSvgOptions): string;
27
- type SvgIconGeneratorReturn<T extends Record<string, string>> = {
1
+ import { type IconPathToSvgOptions } from './iconPathToSvg';
2
+ import type { StyleImageMetadata } from 'maplibre-gl';
3
+ export declare function svgImageId(imageId: string, options?: IconPathToSvgOptions): string;
4
+ type SvgIconSpecsById = Record<string, string | ((options: Record<string, any>) => string)>;
5
+ type SvgIconGeneratorReturn<T extends SvgIconSpecsById> = {
28
6
  (imageId: string): Promise<[
29
7
  {
30
8
  width: number;
@@ -36,5 +14,6 @@ type SvgIconGeneratorReturn<T extends Record<string, string>> = {
36
14
  } & {
37
15
  [K in keyof T]: (options?: IconPathToSvgOptions) => string;
38
16
  };
39
- export declare function svgIconGenerator<T extends Record<string, string>>(iconPathsById: T): SvgIconGeneratorReturn<T>;
17
+ export declare function svgImageGenerator<T extends SvgIconSpecsById>(svgImageSpecsById: T): SvgIconGeneratorReturn<T>;
18
+ export declare function svgIconGenerator(...args: any[]): SvgIconGeneratorReturn<any>;
40
19
  export {};
@@ -0,0 +1,16 @@
1
+ import type { StyleImageMetadata } from 'maplibre-gl';
2
+ /**
3
+ * Renders an SVG string onto a canvas and returns an object compatible with maplibre `addImage`.
4
+ *
5
+ * @param svgString - The SVG markup as a string
6
+ * @param pixelRatio - (Optional) device pixel ratio, defaults to window.devicePixelRatio
7
+ * @returns Promise resolving to { width, height, data, pixelRatio }
8
+ */
9
+ export declare function svgToMaplibreImage(svgString: string, inputPixelRatio?: number): Promise<[
10
+ {
11
+ width: number;
12
+ height: number;
13
+ data: Uint8ClampedArray;
14
+ },
15
+ Partial<StyleImageMetadata>
16
+ ]>;
@@ -0,0 +1,8 @@
1
+ export declare const squares_1: (data: Record<string, any>) => string;
2
+ export declare const triangles_1: (data: Record<string, any>) => string;
3
+ export declare const diamonds_1: (data: Record<string, any>) => string;
4
+ export declare const cross_1: (data: Record<string, any>) => string;
5
+ export declare const mosaic_1: (data: Record<string, any>) => string;
6
+ export declare const waves_1: (data: Record<string, any>) => string;
7
+ export declare const circles_1: (data: Record<string, any>) => string;
8
+ export declare const lines_1: (data: Record<string, any>) => string;
@@ -3,7 +3,7 @@ import { Merge } from 'type-fest';
3
3
  type ClickableFeature = Merge<MapGeoJSONFeature, {
4
4
  layer: {
5
5
  id: string;
6
- onClick: (feature: MapGeoJSONFeature, event: AugmentedMouseEvent) => any;
6
+ onClick: (feature: MapGeoJSONFeature, event: AugmentedMouseEvent, context: Record<string, any>) => any;
7
7
  };
8
8
  }>;
9
9
  type AugmentedMouseEvent = Merge<MapMouseEvent, {
@@ -11,6 +11,7 @@ type AugmentedMouseEvent = Merge<MapMouseEvent, {
11
11
  }>;
12
12
  type LayeredMapOnClickHandlerProps = {
13
13
  resolveTargetFeature?: (features: AugmentedMouseEvent['features'], event: AugmentedMouseEvent) => ClickableFeature | Promise<ClickableFeature>;
14
+ context?: Record<string, any>;
14
15
  };
15
- export declare function layeredMapOnClickHandler({ resolveTargetFeature, }?: LayeredMapOnClickHandlerProps): (e: AugmentedMouseEvent) => Promise<void>;
16
+ export declare function layeredMapOnClickHandler({ resolveTargetFeature, context, }?: LayeredMapOnClickHandlerProps): (e: AugmentedMouseEvent) => Promise<void>;
16
17
  export {};
@@ -0,0 +1,23 @@
1
+ import type { Map } from 'maplibre-gl';
2
+ export type ParseLayerOrderOpts = {
3
+ map: Map;
4
+ layerIds: string[];
5
+ };
6
+ export declare function parseLayerOrder({ map, layerIds }: ParseLayerOrderOpts): string[];
7
+ export type SyncLayerOrderOpts = {
8
+ expectedLayerOrderId: string[];
9
+ map: Map;
10
+ topAnchorLayerId?: string;
11
+ };
12
+ /**
13
+ * Sync MapLibre layer order to match `expectedLayerOrderId`.
14
+ *
15
+ * - Only layers present in `expectedLayerOrderId` are moved.
16
+ * - Missing layers are ignored (no-op for those entries).
17
+ * - If `topAnchorLayerId` is provided, the last expected layer is positioned
18
+ * just beneath that anchor; otherwise it becomes the topmost layer.
19
+ *
20
+ * This helps counteract dynamic reordering issues when using react-map-gl.
21
+ * See: https://github.com/visgl/react-map-gl/issues/939#issuecomment-1515395161
22
+ */
23
+ export declare function syncLayerOrder({ expectedLayerOrderId, map, topAnchorLayerId, }: SyncLayerOrderOpts): void;
@@ -1 +1,3 @@
1
1
  export * from './SyncedMaps';
2
+ export * from './useTilesLoading';
3
+ export * from './useMapRegistry';
@@ -0,0 +1,7 @@
1
+ import type { Map } from 'maplibre-gl';
2
+ import type { MapEvent } from 'react-map-gl/maplibre';
3
+ export declare function useMapRegistry(): {
4
+ maps: Map[];
5
+ onLoad: (evt: MapEvent) => NodeJS.Timeout;
6
+ onRemove: (evt: MapEvent) => void;
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { Map } from 'maplibre-gl';
2
+ export declare function useTilesLoading(maps: Map | Map[]): boolean;
package/dist/index.mjs CHANGED
@@ -1,24 +1,32 @@
1
1
  import { __assign, __spreadArray, __rest, __awaiter, __generator, __makeTemplateObject } from 'tslib';
2
- import React, { forwardRef, useContext, createContext, useRef, useMemo, useImperativeHandle, useState, useCallback, useLayoutEffect, useEffect, createRef } from 'react';
2
+ import React, { forwardRef, useContext, createContext, useRef, useMemo, useImperativeHandle, useEffect, useState, useCallback, useLayoutEffect, createRef } from 'react';
3
3
  import { Map, Source, Layer, useMap, useControl } from 'react-map-gl/maplibre';
4
- import { isPlainObject, uniq, uniqBy, pick, omit } from 'lodash-es';
4
+ import { isPlainObject, uniq, uniqBy, isEqual, pick, omit } from 'lodash-es';
5
5
  import { Flex, useRefByKey, useLocalState, DropdownMenu } from '@orioro/react-ui-core';
6
6
  import styled from 'styled-components';
7
7
  import { usePrevious } from 'react-use';
8
8
  import { mergeRefs } from 'react-merge-refs';
9
- import { strExpr } from '@orioro/util';
9
+ import { mdiCloseCircleOutline, mdiCheck, mdiTerrain, mdiVideo3d } from '@mdi/js';
10
+ import { interpolate, strExpr } from '@orioro/util';
10
11
  import { ckmeans } from 'simple-statistics';
11
12
  import { schemeYlOrRd } from 'd3-scale-chromatic';
12
13
  import { maxIndex, range, variance, sum } from 'd3';
13
14
  import { bbox } from '@turf/turf';
14
15
  import { createPortal } from 'react-dom';
15
- import { mdiCheck, mdiTerrain, mdiVideo3d } from '@mdi/js';
16
16
  import { Icon } from '@mdi/react';
17
17
  import { Tooltip } from '@radix-ui/themes';
18
18
  import maplibregl from 'maplibre-gl';
19
19
  import mlcontour from 'maplibre-contour';
20
20
  import MaplibreInspect from '@maplibre/maplibre-gl-inspect';
21
21
 
22
+ function _validZIndex(zIndex) {
23
+ return typeof zIndex === 'number' && !Number.isNaN(zIndex);
24
+ }
25
+ // Return 1 makes layerB come first in array order
26
+ // and layerA later, so it will be
27
+ // rendered on top of layerB
28
+ var LAYER_A_ON_TOP_OF_B = 1;
29
+ var LAYER_B_ON_TOP_OF_A = -1;
22
30
  function sortLayers(layers, _a) {
23
31
  var existingLayers = _a.existingLayers;
24
32
  var existingLayersById = existingLayers ? Object.fromEntries(existingLayers.map(function (layer) {
@@ -31,7 +39,27 @@ function sortLayers(layers, _a) {
31
39
  // - Later in the list → Rendered on top (higher z-index).
32
40
  // - Earlier in the list → Rendered below.
33
41
  //
34
- return layers.map(function (layer, index) {
42
+ return layers
43
+ //
44
+ // PREVIOUS SORT:
45
+ // .map((layer, index) => ({
46
+ // ...layer,
47
+ // //
48
+ // // Allow for zIndex overriding
49
+ // // In case no zIndex is set, respect the order in which
50
+ // // layers were provided.
51
+ // //
52
+ // // Layers that come after are rendered on top of previous layers
53
+ // //
54
+ // zIndex: typeof layer.zIndex === 'number' ? layer.zIndex : index,
55
+ // }))
56
+ // //
57
+ // // Order layers in ascending zIndex order, so that
58
+ // // layers with higher zIndex are rendered later and on top of
59
+ // // previous ones
60
+ // //
61
+ // .sort((layerA, layerB) => (layerA.zIndex >= layerB.zIndex ? 1 : -1))
62
+ .map(function (layer, index) {
35
63
  return __assign(__assign({}, layer), {
36
64
  //
37
65
  // Allow for zIndex overriding
@@ -40,7 +68,8 @@ function sortLayers(layers, _a) {
40
68
  //
41
69
  // Layers that come after are rendered on top of previous layers
42
70
  //
43
- zIndex: typeof layer.zIndex === 'number' ? layer.zIndex : index
71
+ _inputOrderIndex: index,
72
+ zIndex: typeof layer.zIndex === 'number' ? layer.zIndex : null
44
73
  });
45
74
  })
46
75
  //
@@ -48,9 +77,38 @@ function sortLayers(layers, _a) {
48
77
  // layers with higher zIndex are rendered later and on top of
49
78
  // previous ones
50
79
  //
80
+ // Layers without zIndex are always rendered beneath
81
+ // those with zIndex
82
+ //
83
+ // Layers with the same zIndex are compared using _inputOrderIndex
84
+ //
51
85
  .sort(function (layerA, layerB) {
52
- return layerA.zIndex >= layerB.zIndex ? 1 : -1;
53
- }).map(function (layer, index, sortedLayers) {
86
+ var aValid = _validZIndex(layerA.zIndex);
87
+ var bValid = _validZIndex(layerB.zIndex);
88
+ var aZIndex = layerA.zIndex;
89
+ var bZIndex = layerB.zIndex;
90
+ if (aValid && !bValid) {
91
+ return LAYER_A_ON_TOP_OF_B;
92
+ } else if (!aValid && bValid) {
93
+ return LAYER_B_ON_TOP_OF_A;
94
+ } else if (!aValid && !bValid || aZIndex === bZIndex) {
95
+ return layerA._inputOrderIndex >= layerB._inputOrderIndex ? LAYER_A_ON_TOP_OF_B : LAYER_B_ON_TOP_OF_A;
96
+ } else {
97
+ return aZIndex > bZIndex ? LAYER_A_ON_TOP_OF_B : LAYER_B_ON_TOP_OF_A;
98
+ }
99
+ })
100
+ //
101
+ // This was a strategy to use beforeId to
102
+ // sync map order. We've faced some pitfalls:
103
+ // - Some layers do not exist when multiple layers
104
+ // are created at once, thus maplibre fails to
105
+ // apply beforeId (and throws errors)
106
+ // - There are issues related to layer dynamic ordering,
107
+ // see: https://github.com/visgl/react-map-gl/issues/939#issuecomment-1515395161
108
+ // - We've opted for manually syncing layer orders when
109
+ // layers update
110
+ //
111
+ .map(function (layer, index, sortedLayers) {
54
112
  if (index === sortedLayers.length - 1) {
55
113
  // is last layer
56
114
  return __assign(__assign({}, layer), {
@@ -164,6 +222,62 @@ function parseMapViews(orderedViews, _a) {
164
222
  };
165
223
  }
166
224
 
225
+ //
226
+ // Retrive a list of current ordered layer ids
227
+ // within a given set of layer ids
228
+ //
229
+ function parseLayerOrder(_a) {
230
+ var map = _a.map,
231
+ layerIds = _a.layerIds;
232
+ return map.getLayersOrder().filter(function (layerId) {
233
+ return layerIds.includes(layerId);
234
+ });
235
+ }
236
+ /**
237
+ * Sync MapLibre layer order to match `expectedLayerOrderId`.
238
+ *
239
+ * - Only layers present in `expectedLayerOrderId` are moved.
240
+ * - Missing layers are ignored (no-op for those entries).
241
+ * - If `topAnchorLayerId` is provided, the last expected layer is positioned
242
+ * just beneath that anchor; otherwise it becomes the topmost layer.
243
+ *
244
+ * This helps counteract dynamic reordering issues when using react-map-gl.
245
+ * See: https://github.com/visgl/react-map-gl/issues/939#issuecomment-1515395161
246
+ */
247
+ function syncLayerOrder(_a) {
248
+ var expectedLayerOrderId = _a.expectedLayerOrderId,
249
+ map = _a.map,
250
+ _b = _a.topAnchorLayerId,
251
+ topAnchorLayerId = _b === void 0 ? undefined : _b;
252
+ var currentLayerOrderId = parseLayerOrder({
253
+ map: map,
254
+ layerIds: expectedLayerOrderId
255
+ });
256
+ if (isEqual(expectedLayerOrderId, currentLayerOrderId)) {
257
+ return;
258
+ }
259
+ expectedLayerOrderId.forEach(function (layerId, index) {
260
+ if (!map.getLayer(layerId)) {
261
+ return;
262
+ }
263
+ if (index === expectedLayerOrderId.length - 1) {
264
+ // Place the last expected layer just beneath the top anchor (if any),
265
+ // otherwise move it to the absolute top.
266
+ map.moveLayer(layerId, topAnchorLayerId);
267
+ } else {
268
+ var beneathLayerId = expectedLayerOrderId[index + 1];
269
+ if (!map.getLayer(beneathLayerId)) {
270
+ return;
271
+ }
272
+ try {
273
+ map.moveLayer(layerId, beneathLayerId);
274
+ } catch (err) {
275
+ console.warn("failed to move ".concat(layerId, " beneath ").concat(beneathLayerId));
276
+ }
277
+ }
278
+ });
279
+ }
280
+
167
281
  // import { mergeRefs } from 'react-merge-refs'
168
282
  //
169
283
  // Augment mouse events with info from original view
@@ -230,6 +344,28 @@ var LayeredMap = /*#__PURE__*/forwardRef(function LayeredMapInner(_a, layeredMap
230
344
  useImperativeHandle(layeredMapRef, function () {
231
345
  return imperativeHandle;
232
346
  }, [imperativeHandle]);
347
+ //
348
+ // Force sync layer order
349
+ // https://github.com/visgl/react-map-gl/issues/939#issuecomment-1515395161
350
+ //
351
+ useEffect(function () {
352
+ if (!(parsed === null || parsed === void 0 ? void 0 : parsed.layers) || !mapRef.current) {
353
+ return;
354
+ }
355
+ // Timeout ensures layers are added to map before moving
356
+ var timeoutId = setTimeout(function () {
357
+ var expectedLayerOrderId = parsed.layers.map(function (layer) {
358
+ return layer.id;
359
+ });
360
+ syncLayerOrder({
361
+ expectedLayerOrderId: expectedLayerOrderId,
362
+ map: mapRef.current
363
+ });
364
+ }, 0);
365
+ return function () {
366
+ clearTimeout(timeoutId);
367
+ };
368
+ }, [parsed === null || parsed === void 0 ? void 0 : parsed.layers]);
233
369
  return /*#__PURE__*/React.createElement(Map, __assign({
234
370
  ref: mapRef,
235
371
  interactiveLayerIds: __spreadArray(__spreadArray([], interactiveLayerIdsInput, true), parsed.interactiveLayerIds, true)
@@ -259,7 +395,9 @@ function selectFirstClickableFeature(features) {
259
395
  function layeredMapOnClickHandler(_a) {
260
396
  var _b = _a === void 0 ? {} : _a,
261
397
  _c = _b.resolveTargetFeature,
262
- resolveTargetFeature = _c === void 0 ? selectFirstClickableFeature : _c;
398
+ resolveTargetFeature = _c === void 0 ? selectFirstClickableFeature : _c,
399
+ _d = _b.context,
400
+ context = _d === void 0 ? {} : _d;
263
401
  return function onClick(e) {
264
402
  return __awaiter(this, void 0, void 0, function () {
265
403
  var features, clickableFeatures, targetFeature, _a;
@@ -282,7 +420,7 @@ function layeredMapOnClickHandler(_a) {
282
420
  _b.label = 3;
283
421
  case 3:
284
422
  targetFeature = _a;
285
- targetFeature.layer.onClick(targetFeature, e);
423
+ targetFeature.layer.onClick(targetFeature, e, context);
286
424
  _b.label = 4;
287
425
  case 4:
288
426
  return [2 /*return*/];
@@ -296,6 +434,9 @@ var Container = styled.div(templateObject_1$2 || (templateObject_1$2 = __makeTem
296
434
  var DataSectionHeading = styled.h3(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n margin: 0;\n line-height: 1.2;\n font-size: 1rem;\n"], ["\n margin: 0;\n line-height: 1.2;\n font-size: 1rem;\n"])));
297
435
  var DataSectionContainer = styled(Flex)(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n padding: 15px 10px;\n"], ["\n padding: 15px 10px;\n"])));
298
436
  var EntriesList = styled.ul(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n padding: 0;\n list-style: none;\n margin-top: 0;\n margin-bottom: 0;\n > li + li {\n margin-top: 4px;\n }\n"], ["\n padding: 0;\n list-style: none;\n margin-top: 0;\n margin-bottom: 0;\n > li + li {\n margin-top: 4px;\n }\n"])));
437
+ function _notEmpty(value) {
438
+ return typeof value !== 'undefined' && value !== null && value !== '';
439
+ }
299
440
  function DataSection(_a) {
300
441
  var title = _a.title,
301
442
  entries = _a.entries,
@@ -303,12 +444,12 @@ function DataSection(_a) {
303
444
  return Array.isArray(entries) && entries.length > 0 && (/*#__PURE__*/React.createElement(DataSectionContainer, __assign({
304
445
  direction: "column",
305
446
  gap: "10px"
306
- }, props), title && /*#__PURE__*/React.createElement(DataSectionHeading, null, title), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(EntriesList, null, entries.map(function (_a, index) {
447
+ }, props), title && /*#__PURE__*/React.createElement(DataSectionHeading, null, title), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(EntriesList, null, entries.filter(Boolean).map(function (_a, index) {
307
448
  var label = _a[0],
308
449
  value = _a[1];
309
450
  return /*#__PURE__*/React.createElement("li", {
310
451
  key: index
311
- }, typeof label === 'string' ? /*#__PURE__*/React.createElement("span", null, label, ": ") : label, typeof value === 'string' ? (/*#__PURE__*/React.createElement("span", {
452
+ }, typeof label === 'string' ? (/*#__PURE__*/React.createElement("span", null, "".concat(label).concat(_notEmpty(value) ? ': ' : ''))) : label, typeof value === 'string' ? (/*#__PURE__*/React.createElement("span", {
312
453
  style: {
313
454
  fontWeight: 'bold'
314
455
  }
@@ -836,6 +977,14 @@ function makeSyncedMaps(_a) {
836
977
  mapInstances: mapInstanceRefs
837
978
  };
838
979
  }, [mapInstanceRefs]);
980
+ // Let's review, this might have performance impact
981
+ var cursor = useMemo(function () {
982
+ var _a;
983
+ return isDragging ? 'grabbing' : (((_a = hoverInfo === null || hoverInfo === void 0 ? void 0 : hoverInfo.features) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0 ? hoverInfo.features.some(function (feat) {
984
+ var _a;
985
+ return typeof ((_a = feat.layer) === null || _a === void 0 ? void 0 : _a.onClick) === 'function';
986
+ }) ? 'pointer' : 'default' : 'default';
987
+ }, [isDragging, hoverInfo]);
839
988
  return /*#__PURE__*/React.createElement(Flex, {
840
989
  ref: containerRef,
841
990
  direction: "row",
@@ -861,7 +1010,9 @@ function makeSyncedMaps(_a) {
861
1010
  }
862
1011
  })) : null, Array.isArray(tooltips) && tooltips[index], /*#__PURE__*/React.createElement(MapComponent, __assign({
863
1012
  ref: setMapInstanceRef(index),
864
- cursor: isDragging ? 'grabbing' : 'default'
1013
+ // cursor={isDragging ? 'grabbing' : 'default'}
1014
+ // Let's review, this might have performance impact
1015
+ cursor: cursor
865
1016
  }, baseMapProps, mapProps, maps.length > 1 ? viewState || {} : {}, {
866
1017
  style: __assign(__assign({}, mapProps.style || {}), {
867
1018
  position: 'absolute',
@@ -897,6 +1048,70 @@ var SyncedMaps = makeSyncedMaps({
897
1048
  });
898
1049
  var templateObject_1;
899
1050
 
1051
+ function useTilesLoading(maps) {
1052
+ maps = Array.isArray(maps) ? maps : [maps];
1053
+ var _a = useState(false),
1054
+ loading = _a[0],
1055
+ setLoading = _a[1];
1056
+ var checkLoading = function checkLoading() {
1057
+ var loading = maps.some(function (map) {
1058
+ return !map.areTilesLoaded();
1059
+ });
1060
+ setLoading(loading);
1061
+ };
1062
+ useEffect(function () {
1063
+ var handlers = maps.map(function (map) {
1064
+ var update = function update() {
1065
+ return checkLoading();
1066
+ };
1067
+ map.on('dataloading', update);
1068
+ // map.on('data', update)
1069
+ map.on('idle', update);
1070
+ return function () {
1071
+ map.off('dataloading', update);
1072
+ // map.off('data', update)
1073
+ map.off('idle', update);
1074
+ };
1075
+ });
1076
+ checkLoading(); // initial
1077
+ return function () {
1078
+ return handlers.forEach(function (off) {
1079
+ return off();
1080
+ });
1081
+ };
1082
+ }, [maps]);
1083
+ return loading;
1084
+ }
1085
+
1086
+ function useMapRegistry() {
1087
+ var _a = useState([]),
1088
+ maps = _a[0],
1089
+ setMaps = _a[1];
1090
+ var onLoad = useCallback(function (evt) {
1091
+ //
1092
+ // Set maps in next tick, so that does not interfere
1093
+ // in map element rendering
1094
+ //
1095
+ return setTimeout(function () {
1096
+ return setMaps(function (currMaps) {
1097
+ return __spreadArray(__spreadArray([], currMaps, true), [evt.target], false);
1098
+ });
1099
+ }, 0);
1100
+ }, []);
1101
+ var onRemove = useCallback(function (evt) {
1102
+ return setMaps(function (currMaps) {
1103
+ return currMaps.filter(function (map) {
1104
+ return map !== evt.target;
1105
+ });
1106
+ });
1107
+ }, []);
1108
+ return {
1109
+ maps: maps,
1110
+ onLoad: onLoad,
1111
+ onRemove: onRemove
1112
+ };
1113
+ }
1114
+
900
1115
  function DynamicImages(_a) {
901
1116
  var _this = this;
902
1117
  var onGenerateImage = _a.onGenerateImage;
@@ -961,6 +1176,7 @@ function iconPathToSvg(path, _a) {
961
1176
  var pathAttrs = ["fill=\"".concat(fill, "\""), style ? "style=\"".concat(style, "\"") : ''].filter(Boolean).join(' ');
962
1177
  return "<svg ".concat(svgAttrs, "><path ").concat(pathAttrs, " d=\"").concat(path, "\" /></svg>");
963
1178
  }
1179
+
964
1180
  /**
965
1181
  * Renders an SVG string onto a canvas and returns an object compatible with maplibre `addImage`.
966
1182
  *
@@ -969,9 +1185,9 @@ function iconPathToSvg(path, _a) {
969
1185
  * @returns Promise resolving to { width, height, data, pixelRatio }
970
1186
  */
971
1187
  function svgToMaplibreImage(svgString_1) {
972
- return __awaiter(this, arguments, void 0, function (svgString, pixelRatio) {
973
- if (pixelRatio === void 0) {
974
- pixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
1188
+ return __awaiter(this, arguments, void 0, function (svgString, inputPixelRatio) {
1189
+ if (inputPixelRatio === void 0) {
1190
+ inputPixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
975
1191
  }
976
1192
  return __generator(this, function (_a) {
977
1193
  return [2 /*return*/, new Promise(function (resolve, reject) {
@@ -981,6 +1197,8 @@ function svgToMaplibreImage(svgString_1) {
981
1197
  var url = URL.createObjectURL(svgBlob);
982
1198
  var img = new Image();
983
1199
  img.onload = function () {
1200
+ // Normalize pixel ratio: integer and at least 1
1201
+ var pixelRatio = Math.max(1, Math.round(inputPixelRatio || 1));
984
1202
  var width = img.width * pixelRatio;
985
1203
  var height = img.height * pixelRatio;
986
1204
  var canvas = document.createElement('canvas');
@@ -1008,21 +1226,64 @@ function svgToMaplibreImage(svgString_1) {
1008
1226
  });
1009
1227
  });
1010
1228
  }
1011
- function svgIconId(iconId, options) {
1012
- return options ? "".concat(iconId, "(").concat(JSON.stringify(options), ")") : iconId;
1229
+
1230
+ function svgImageId(imageId, options) {
1231
+ return options ? "".concat(imageId, "(").concat(JSON.stringify(options), ")") : imageId;
1013
1232
  }
1014
- function svgIconGenerator(iconPathsById) {
1015
- var fns = Object.fromEntries(Object.entries(iconPathsById).map(function (_a) {
1016
- var iconId = _a[0],
1017
- iconPath = _a[1];
1018
- return [iconId, function (options) {
1019
- if (options === void 0) {
1020
- options = {};
1021
- }
1022
- return function () {
1023
- return iconPathToSvg(iconPath, options);
1024
- };
1025
- }];
1233
+ var ERROR_ICON_EXPR = function ERROR_ICON_EXPR(options) {
1234
+ if (options === void 0) {
1235
+ options = {};
1236
+ }
1237
+ return function () {
1238
+ return iconPathToSvg(mdiCloseCircleOutline, __assign({
1239
+ fill: 'red'
1240
+ }, options));
1241
+ };
1242
+ };
1243
+ function svgImageGenerator(svgImageSpecsById) {
1244
+ var fns = Object.fromEntries(Object.entries(svgImageSpecsById).map(function (_a) {
1245
+ var imageId = _a[0],
1246
+ iconSpec = _a[1];
1247
+ if (typeof iconSpec === 'string' && iconSpec.startsWith('<svg')) {
1248
+ //
1249
+ // Full svg
1250
+ //
1251
+ return [imageId, function (options) {
1252
+ if (options === void 0) {
1253
+ options = {};
1254
+ }
1255
+ return function () {
1256
+ return interpolate(iconSpec, options);
1257
+ };
1258
+ }];
1259
+ } else if (typeof iconSpec === 'string') {
1260
+ //
1261
+ // Its a string, assume it is an svg path
1262
+ //
1263
+ return [imageId, function (options) {
1264
+ if (options === void 0) {
1265
+ options = {};
1266
+ }
1267
+ return function () {
1268
+ return iconPathToSvg(iconSpec, options);
1269
+ };
1270
+ }];
1271
+ } else if (typeof iconSpec === 'function') {
1272
+ //
1273
+ // Function that returns custom svg
1274
+ //
1275
+ return [imageId, function (options) {
1276
+ if (options === void 0) {
1277
+ options = {};
1278
+ }
1279
+ return function () {
1280
+ return iconSpec(options);
1281
+ };
1282
+ }];
1283
+ } else {
1284
+ console.warn("Invalid icon spec for ".concat(imageId, ", will ignore"), iconSpec);
1285
+ return [imageId, ERROR_ICON_EXPR];
1286
+ }
1026
1287
  }));
1027
1288
  var expr = strExpr({
1028
1289
  expressions: fns
@@ -1043,13 +1304,89 @@ function svgIconGenerator(iconPathsById) {
1043
1304
  });
1044
1305
  });
1045
1306
  }
1046
- Object.assign(onGenerateSvgImage, Object.fromEntries(Object.keys(iconPathsById).map(function (iconId) {
1047
- return [iconId, function (options) {
1048
- return svgIconId(iconId, options);
1307
+ //
1308
+ // Expose generator fns
1309
+ //
1310
+ Object.assign(onGenerateSvgImage, Object.fromEntries(Object.keys(svgImageSpecsById).map(function (imageId) {
1311
+ return [imageId, function (options) {
1312
+ return svgImageId(imageId, options);
1049
1313
  }];
1050
1314
  })));
1051
1315
  return onGenerateSvgImage;
1052
1316
  }
1317
+ function svgIconGenerator() {
1318
+ var args = [];
1319
+ for (var _i = 0; _i < arguments.length; _i++) {
1320
+ args[_i] = arguments[_i];
1321
+ }
1322
+ console.warn('svgIconGenerator is deprecated, migrate to svgImageGenerator');
1323
+ return svgImageGenerator.apply(void 0, args);
1324
+ }
1325
+
1326
+ function _pattern(strTemplate) {
1327
+ return function (data) {
1328
+ return interpolate(strTemplate, data);
1329
+ };
1330
+ }
1331
+ function _svgViewBox(_a) {
1332
+ var height = _a.height,
1333
+ width = _a.width;
1334
+ return "viewBox=\"0 0 ".concat(width, " ").concat(height, "\" height=\"").concat(height, "\" width=\"").concat(width, "\"");
1335
+ }
1336
+ function _bgRect() {
1337
+ return "<rect\n width=\"100%\"\n height=\"100%\"\n fill=\"${ fill = transparent }\"\n />";
1338
+ }
1339
+ // Source:
1340
+ // https://pattern.monster/cross-section
1341
+ var squares_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1342
+ height: 20,
1343
+ width: 20
1344
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"20\"\n height=\"20\"\n patternUnits=\"userSpaceOnUse\"\n >\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"2\"\n d=\"M10 0v20ZM0 10h20Z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1345
+ // Source:
1346
+ // https://pattern.monster/triangles-4
1347
+ var triangles_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1348
+ height: 40,
1349
+ width: 20
1350
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"20\"\n height=\"40\"\n patternUnits=\"userSpaceOnUse\"\n >\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M0 30h20L10 50zm-10-20h20L0 30zm20 0h20L20 30zM0-10h20L10 10z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1351
+ // Source:
1352
+ // https://pattern.monster/diamonds-1
1353
+ var diamonds_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1354
+ height: 50,
1355
+ width: 50
1356
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"50\"\n height=\"50\"\n patternUnits=\"userSpaceOnUse\"\n >\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M50 25 37.5 50 25 25 37.5 0zm-25 0L12.5 50 0 25 12.5 0z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1357
+ var cross_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1358
+ height: 20,
1359
+ width: 20
1360
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"20\"\n height=\"20\"\n patternUnits=\"userSpaceOnUse\"\n >\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n stroke-linecap=\"square\"\n d=\"M3.25 10h13.5M10 3.25v13.5\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1361
+ // https://pattern.monster/cubes-1
1362
+ var mosaic_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1363
+ width: 60,
1364
+ height: 60
1365
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"60\"\n height=\"60\"\n patternUnits=\"userSpaceOnUse\">\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M15 30v30m-7.5 0V30h15v30m7.5 0H0V30h30M45 0v30m7.5-30v30m-15 0V0M30 0h30v30M30 45h30m-30-7.5h30m0 15H30M30 30h30v30H30zM0 15h30M0 7.5h30m0 15H0M0 0h30v30H0z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1366
+ var waves_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1367
+ width: 120,
1368
+ height: 80
1369
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"120\"\n height=\"80\"\n patternUnits=\"userSpaceOnUse\">\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M-50.129 12.685C-33.346 12.358-16.786 4.918 0 5c16.787.082 43.213 10 60 10s43.213-9.918 60-10 33.346 7.358 50.129 7.685\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M-50.129 32.685C-33.346 32.358-16.786 24.918 0 25c16.787.082 43.213 10 60 10s43.213-9.918 60-10 33.346 7.358 50.129 7.685\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M-50.129 52.685C-33.346 52.358-16.786 44.918 0 45c16.787.082 43.213 10 60 10s43.213-9.918 60-10 33.346 7.358 50.129 7.685\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M-50.129 72.685C-33.346 72.358-16.786 64.918 0 65c16.787.082 43.213 10 60 10s43.213-9.918 60-10 33.346 7.358 50.129 7.685\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1370
+ var circles_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1371
+ width: 20,
1372
+ height: 20
1373
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"20\"\n height=\"20\"\n patternUnits=\"userSpaceOnUse\">\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M10 15a5 5 0 1 1 0-10 5 5 0 0 1 0 10z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1374
+ var lines_1 = _pattern("<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n ".concat(_svgViewBox({
1375
+ width: 20,
1376
+ height: 80
1377
+ }), ">\n <defs>\n <pattern\n patternTransform=\"scale(${ scale = 1 })\"\n id=\"a\"\n width=\"20\"\n height=\"80\"\n patternUnits=\"userSpaceOnUse\">\n ").concat(_bgRect(), "\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M0 10h20z\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M0 30h20z\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M0 50h20z\"\n />\n <path\n fill=\"transparent\"\n stroke=\"${ stroke = #000000 }\"\n stroke-width=\"${ strokeWidth = 2 }\"\n d=\"M0 70h20z\"\n />\n </pattern>\n </defs>\n <rect width=\"800%\" height=\"800%\" fill=\"url(#a)\" />\n</svg>"));
1378
+
1379
+ var index = /*#__PURE__*/Object.freeze({
1380
+ __proto__: null,
1381
+ circles_1: circles_1,
1382
+ cross_1: cross_1,
1383
+ diamonds_1: diamonds_1,
1384
+ lines_1: lines_1,
1385
+ mosaic_1: mosaic_1,
1386
+ squares_1: squares_1,
1387
+ triangles_1: triangles_1,
1388
+ waves_1: waves_1
1389
+ });
1053
1390
 
1054
1391
  var DEFAULT_MIN_K = 3;
1055
1392
  var DEFAULT_MAX_K = 9;
@@ -1571,4 +1908,4 @@ function InspectControl(props) {
1571
1908
  return null;
1572
1909
  }
1573
1910
 
1574
- export { $naturalBreaks, ControlContainer, DynamicImages, HoverTooltip, InspectControl, LayeredMap, MapWindow, SyncedMaps, TerrainControl, applyReactStyle, augmentFeature, ensureAddLayer, ensureAddSource, ensureRemoveLayer, ensureRemoveSource, fitGeometry, fmtLayerAbsoluteId, getSrcLayer, getSrcViewByLayerId, hoverParseEvent, iconPathToSvg, layeredMapOnClickHandler, makeSyncedMaps, mapSetFeaturesState, naturalBreakBounds, parseMapViews, scaleNaturalBreaks, sortLayers, svgIconGenerator, svgIconId, svgToMaplibreImage, useClientRect, useHover, useLayeredMap, withHover };
1911
+ export { $naturalBreaks, ControlContainer, DynamicImages, HoverTooltip, InspectControl, LayeredMap, MapWindow, index as SVG_PATTERNS, SyncedMaps, TerrainControl, applyReactStyle, augmentFeature, circles_1, cross_1, diamonds_1, ensureAddLayer, ensureAddSource, ensureRemoveLayer, ensureRemoveSource, fitGeometry, fmtLayerAbsoluteId, getSrcLayer, getSrcViewByLayerId, hoverParseEvent, layeredMapOnClickHandler, lines_1, makeSyncedMaps, mapSetFeaturesState, mosaic_1, naturalBreakBounds, parseMapViews, scaleNaturalBreaks, sortLayers, squares_1, svgIconGenerator, svgImageGenerator, svgImageId, triangles_1, useClientRect, useHover, useLayeredMap, useMapRegistry, useTilesLoading, waves_1, withHover };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orioro/react-maplibre-util",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "packageManager": "yarn@4.0.2",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -51,12 +51,12 @@
51
51
  "@maplibre/maplibre-gl-inspect": "^1.7.1",
52
52
  "@mdi/js": "^7.4.47",
53
53
  "@mdi/react": "^1.6.1",
54
- "@orioro/react-chart-util": "^0.2.0",
54
+ "@orioro/react-chart-util": "^0.3.0",
55
55
  "@orioro/react-select": "^3.0.2",
56
- "@orioro/react-ui-core": "^0.0.6",
57
- "@orioro/resolve": "^0.1.2",
56
+ "@orioro/react-ui-core": "^0.0.14",
57
+ "@orioro/resolve": "^0.1.9",
58
58
  "@orioro/scale-util": "^0.0.2",
59
- "@orioro/util": "^0.13.0",
59
+ "@orioro/util": "^0.15.1",
60
60
  "@turf/turf": "^7.2.0",
61
61
  "d3": "^7.9.0",
62
62
  "d3-scale-chromatic": "^3.1.0",