@longline/aqua-ui 1.0.249 → 1.0.251
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/formatters/HumanFormatter/HumanFormatter.d.ts +1 -1
- package/formatters/HumanFormatter/HumanFormatter.js +1 -1
- package/map/controls/FullscreenButton/FullscreenButton.d.ts +8 -1
- package/map/controls/FullscreenButton/FullscreenButton.js +9 -5
- package/map/layers/ClusterLayer/ClusterLayer.d.ts +6 -1
- package/map/layers/ClusterLayer/ClusterLayer.js +3 -3
- package/map/layers/HtmlMarkerLayer/HtmlMarkerLayer copy.d.ts +35 -0
- package/map/layers/HtmlMarkerLayer/HtmlMarkerLayer copy.js +176 -0
- package/map/layers/HtmlMarkerLayer/HtmlMarkerLayer.js +95 -101
- package/map/markers/ClusterMarker/ClusterMarker.d.ts +6 -1
- package/map/markers/ClusterMarker/ClusterMarker.js +3 -3
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import * as React from 'react';
|
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
11
|
var HumanFormatter = function (_a) {
|
|
12
|
-
var value = _a.value, _b = _a.locale, locale = _b === void 0 ? 'en' : _b, _c = _a.decimals, decimals = _c === void 0 ?
|
|
12
|
+
var value = _a.value, _b = _a.locale, locale = _b === void 0 ? 'en' : _b, _c = _a.decimals, decimals = _c === void 0 ? 0 : _c;
|
|
13
13
|
if (value == null)
|
|
14
14
|
return null;
|
|
15
15
|
var num = typeof value === 'string' ? parseFloat(value) : value;
|
|
@@ -6,6 +6,13 @@ interface IFullscreenButtonProps {
|
|
|
6
6
|
* will be the map's direct container.
|
|
7
7
|
*/
|
|
8
8
|
container?: HTMLDivElement;
|
|
9
|
+
/**
|
|
10
|
+
* For `map` mode, fullscreen mode affect map control only, but this will
|
|
11
|
+
* not work if the map has children that have expanding elements using
|
|
12
|
+
* portals. In `document` mode, the entire application is made fullscreen.
|
|
13
|
+
* @default document
|
|
14
|
+
*/
|
|
15
|
+
mode?: 'map' | 'document';
|
|
9
16
|
}
|
|
10
17
|
/**
|
|
11
18
|
* The `FullscreenButton` toggles the map full-screen when clicked.
|
|
@@ -18,7 +25,7 @@ interface IFullscreenButtonProps {
|
|
|
18
25
|
* ```
|
|
19
26
|
*/
|
|
20
27
|
declare const FullscreenButton: {
|
|
21
|
-
({ hint, ...props }: IMapButtonProps & IFullscreenButtonProps): React.JSX.Element;
|
|
28
|
+
({ hint, mode, ...props }: IMapButtonProps & IFullscreenButtonProps): React.JSX.Element;
|
|
22
29
|
displayName: string;
|
|
23
30
|
};
|
|
24
31
|
export { FullscreenButton, IFullscreenButtonProps };
|
|
@@ -34,9 +34,8 @@ import { MapButton } from '../base/MapButton';
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
var FullscreenButton = function (_a) {
|
|
37
|
-
var _b = _a.hint, hint = _b === void 0 ? React.createElement(React.Fragment, null, "Toggle fullscreen map") : _b, props = __rest(_a, ["hint"]);
|
|
37
|
+
var _b = _a.hint, hint = _b === void 0 ? React.createElement(React.Fragment, null, "Toggle fullscreen map") : _b, _c = _a.mode, mode = _c === void 0 ? 'document' : _c, props = __rest(_a, ["hint", "mode"]);
|
|
38
38
|
var map = useMap().current;
|
|
39
|
-
//
|
|
40
39
|
// Does the browser offer full screen support?
|
|
41
40
|
var checkFullscreenSupport = function () {
|
|
42
41
|
return !!(window.document.fullscreenEnabled ||
|
|
@@ -44,11 +43,16 @@ var FullscreenButton = function (_a) {
|
|
|
44
43
|
};
|
|
45
44
|
var toggle = function () {
|
|
46
45
|
if (document.fullscreenElement === null) {
|
|
47
|
-
if (
|
|
48
|
-
props.container
|
|
46
|
+
if (mode == 'map') {
|
|
47
|
+
if (props.container) {
|
|
48
|
+
props.container.requestFullscreen();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
map.getContainer().requestFullscreen();
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
else {
|
|
51
|
-
|
|
55
|
+
window.document.documentElement.requestFullscreen();
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
else {
|
|
@@ -61,6 +61,11 @@ interface IClusterLayerProps {
|
|
|
61
61
|
* @default 99
|
|
62
62
|
*/
|
|
63
63
|
maxZoom?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Number of decimals to use in number formatting.
|
|
66
|
+
* @default 0
|
|
67
|
+
*/
|
|
68
|
+
decimals?: number;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
66
71
|
* A `ClusterLayer` manages its own `Source`. It does not take GeoJSON, but a list of objects which must at least have
|
|
@@ -86,7 +91,7 @@ interface IClusterLayerProps {
|
|
|
86
91
|
* ```
|
|
87
92
|
*/
|
|
88
93
|
declare const ClusterLayer: {
|
|
89
|
-
({ relativeSize, radius, clusterRadius, clusterMaxZoom, minZoom, maxZoom, ringFields, clickZoom, ...props }: IClusterLayerProps): React.JSX.Element;
|
|
94
|
+
({ relativeSize, radius, clusterRadius, clusterMaxZoom, minZoom, maxZoom, decimals, ringFields, clickZoom, ...props }: IClusterLayerProps): React.JSX.Element;
|
|
90
95
|
displayName: string;
|
|
91
96
|
};
|
|
92
97
|
export { ClusterLayer, IClusterLayerProps };
|
|
@@ -106,14 +106,14 @@ var ClusterLayerBase = function (_a) {
|
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
108
|
var ClusterLayer = function (_a) {
|
|
109
|
-
var _b = _a.relativeSize, relativeSize = _b === void 0 ? 0 : _b, _c = _a.radius, radius = _c === void 0 ? 24 : _c, _d = _a.clusterRadius, clusterRadius = _d === void 0 ? 80 : _d, _e = _a.clusterMaxZoom, clusterMaxZoom = _e === void 0 ? 18 : _e, _f = _a.minZoom, minZoom = _f === void 0 ? 0 : _f, _g = _a.maxZoom, maxZoom = _g === void 0 ? 99 : _g, ringFields = _a.ringFields,
|
|
110
|
-
var
|
|
109
|
+
var _b = _a.relativeSize, relativeSize = _b === void 0 ? 0 : _b, _c = _a.radius, radius = _c === void 0 ? 24 : _c, _d = _a.clusterRadius, clusterRadius = _d === void 0 ? 80 : _d, _e = _a.clusterMaxZoom, clusterMaxZoom = _e === void 0 ? 18 : _e, _f = _a.minZoom, minZoom = _f === void 0 ? 0 : _f, _g = _a.maxZoom, maxZoom = _g === void 0 ? 99 : _g, _h = _a.decimals, decimals = _h === void 0 ? 0 : _h, ringFields = _a.ringFields, _j = _a.clickZoom, clickZoom = _j === void 0 ? false : _j, props = __rest(_a, ["relativeSize", "radius", "clusterRadius", "clusterMaxZoom", "minZoom", "maxZoom", "decimals", "ringFields", "clickZoom"]);
|
|
110
|
+
var _k = React.useState(0), key = _k[0], setKey = _k[1];
|
|
111
111
|
// When aggregation changes, we mount a whole new component. Mapbox does not
|
|
112
112
|
// appear to deal with changes to clusterProperties well.
|
|
113
113
|
React.useEffect(function () {
|
|
114
114
|
setKey(Date.now());
|
|
115
115
|
}, [props.aggregation, ringFields]);
|
|
116
|
-
return React.createElement(ClusterLayerBase, __assign({ key: key, relativeSize: relativeSize, radius: radius, clusterRadius: clusterRadius, clusterMaxZoom: clusterMaxZoom, minZoom: minZoom, maxZoom: maxZoom, ringFields: ringFields, clickZoom: clickZoom }, props));
|
|
116
|
+
return React.createElement(ClusterLayerBase, __assign({ key: key, relativeSize: relativeSize, radius: radius, clusterRadius: clusterRadius, clusterMaxZoom: clusterMaxZoom, minZoom: minZoom, maxZoom: maxZoom, decimals: decimals, ringFields: ringFields, clickZoom: clickZoom }, props));
|
|
117
117
|
};
|
|
118
118
|
ClusterLayer.displayName = "ClusterLayer";
|
|
119
119
|
export { ClusterLayer };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface IHtmlMarkerLayerProps {
|
|
3
|
+
/**
|
|
4
|
+
* Function that returns a marker component. Properties that `HtmlMarkerLayer`
|
|
5
|
+
* makes available to the component are passed in as `props`.
|
|
6
|
+
* e.g. `{(p) => <MyMarker {...p} bordered flashing/>}`
|
|
7
|
+
*/
|
|
8
|
+
children?: (props: any) => React.ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* ID of the source that's queried.
|
|
11
|
+
*/
|
|
12
|
+
sourceId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Every feature must have a unique ID in its properties. This is used to
|
|
15
|
+
* tell features apart. If the `source` should ever change, then it is
|
|
16
|
+
* important that new items have new IDs, so that old markers get removed
|
|
17
|
+
* correctly. Features that do not have an ID field are not displayed.
|
|
18
|
+
*/
|
|
19
|
+
idField: string;
|
|
20
|
+
/**
|
|
21
|
+
* Minimum zoom level at which to show markers.
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
minZoom?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum zoom level at which to show markers.
|
|
27
|
+
* @default 99
|
|
28
|
+
*/
|
|
29
|
+
maxZoom?: number;
|
|
30
|
+
}
|
|
31
|
+
declare const HtmlMarkerLayer: {
|
|
32
|
+
({ minZoom, maxZoom, ...props }: IHtmlMarkerLayerProps): React.JSX.Element;
|
|
33
|
+
displayName: string;
|
|
34
|
+
};
|
|
35
|
+
export { HtmlMarkerLayer, IHtmlMarkerLayerProps };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
13
|
+
var t = {};
|
|
14
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
15
|
+
t[p] = s[p];
|
|
16
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
17
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
18
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
19
|
+
t[p[i]] = s[p[i]];
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
};
|
|
23
|
+
import * as React from 'react';
|
|
24
|
+
import * as ReactDOM from 'react-dom/client';
|
|
25
|
+
import { Layer, useMap } from 'react-map-gl/mapbox';
|
|
26
|
+
import { Marker } from 'mapbox-gl';
|
|
27
|
+
/**
|
|
28
|
+
* The `HtmlMarkerLayer` manages HTML markers on a map. HTML markers are far slower
|
|
29
|
+
* than OpenGL layers, but much more flexible: with SVG, any shape of marker can
|
|
30
|
+
* be designed, and the markers can easily be made responsive to hover.
|
|
31
|
+
*
|
|
32
|
+
* The `HtmlMarkerLayer` keeps an internal cache, creating and rendering markers only
|
|
33
|
+
* when they are visible in the map viewport. This is a suitable solution for clusters
|
|
34
|
+
* (where by definition there are never very many icons in the viewport), or when
|
|
35
|
+
* there are simply not _that_ many markers: up to a few hundred in the viewport is the
|
|
36
|
+
* upper limit.
|
|
37
|
+
*
|
|
38
|
+
* The `HtmlMarkerLayer` must be placed inside a GeoJSON `Source`. In that source,
|
|
39
|
+
* each feature must have a unique ID (passed to this layer as the `idField`), which
|
|
40
|
+
* is used to tell features apart. When features are refreshed, it is imported that
|
|
41
|
+
* they all get new IDs so that old markers get removed correctly.
|
|
42
|
+
*
|
|
43
|
+
* The `HtmlMarkerLayer` component takes a _function child_. This function accepts
|
|
44
|
+
* a `props` object, which will be the properties from each GeoJSON feature for which
|
|
45
|
+
* a marker is rendered.
|
|
46
|
+
*
|
|
47
|
+
* The `minZoom` and `maxZoom` properties can be used to make markers appear only
|
|
48
|
+
* within a range of zoom levels.
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* // featureCollection has a property "id" with unique values
|
|
52
|
+
* <Source id="mysource" type="geojson" data={featureCollection}>
|
|
53
|
+
* <HtmlMarkerLayer sourceId="mysource" idField="id">
|
|
54
|
+
* {(markerProps) => <MyMarkerComponent color="gold" radius={45} {...markerProps}/>
|
|
55
|
+
* </HtmlMarkerLayer>
|
|
56
|
+
* </Source>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
var HtmlMarkerLayerBase = function (props) {
|
|
60
|
+
var map = useMap().current;
|
|
61
|
+
// Can't call props.children directly from map render event handler.
|
|
62
|
+
// Make a ref copy:
|
|
63
|
+
var children = React.useRef(props.children);
|
|
64
|
+
var markers = React.useRef({});
|
|
65
|
+
var markersOnScreen = React.useRef({});
|
|
66
|
+
var minZoom = React.useRef(props.minZoom);
|
|
67
|
+
var maxZoom = React.useRef(props.maxZoom);
|
|
68
|
+
// Add render event to map, and remove it when this component
|
|
69
|
+
// is unmounted. Whenever the map renders, we must refresh
|
|
70
|
+
// all markers.
|
|
71
|
+
React.useEffect(function () {
|
|
72
|
+
map.on('render', updateMarkers);
|
|
73
|
+
return function () { map.off('render', updateMarkers); };
|
|
74
|
+
}, []);
|
|
75
|
+
// If the child function changes, then update the children ref copy.
|
|
76
|
+
// Also clear map and repaint it, because child component props may
|
|
77
|
+
// have changed.
|
|
78
|
+
React.useEffect(function () {
|
|
79
|
+
minZoom.current = props.minZoom;
|
|
80
|
+
maxZoom.current = props.maxZoom;
|
|
81
|
+
children.current = props.children;
|
|
82
|
+
for (var id in markersOnScreen.current) {
|
|
83
|
+
markersOnScreen.current[id].remove();
|
|
84
|
+
}
|
|
85
|
+
markers.current = {};
|
|
86
|
+
markersOnScreen.current = {};
|
|
87
|
+
map.triggerRepaint();
|
|
88
|
+
}, [props.children, props.minZoom, props.maxZoom]);
|
|
89
|
+
// When component is unmounted, remove all its markers from the map.
|
|
90
|
+
React.useEffect(function () {
|
|
91
|
+
return function () {
|
|
92
|
+
for (var id in markersOnScreen.current) {
|
|
93
|
+
markersOnScreen.current[id].remove();
|
|
94
|
+
}
|
|
95
|
+
map.triggerRepaint();
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
//
|
|
99
|
+
// Given a native or React component and props, turn it into an
|
|
100
|
+
// HTML element. Return this element.
|
|
101
|
+
//
|
|
102
|
+
var createHtmlElementFromReactNode = function (latlngfeatureProps) {
|
|
103
|
+
// Don't render anything if there is no child.
|
|
104
|
+
if (!children.current)
|
|
105
|
+
return null;
|
|
106
|
+
// Get the first child.
|
|
107
|
+
var elem = children.current(latlngfeatureProps);
|
|
108
|
+
// Render child.
|
|
109
|
+
var el = document.createElement('div');
|
|
110
|
+
var root = ReactDOM.createRoot(el);
|
|
111
|
+
root.render(elem);
|
|
112
|
+
return el;
|
|
113
|
+
};
|
|
114
|
+
//
|
|
115
|
+
// Calculate which markers are offscreen, and remove them.
|
|
116
|
+
// Add new markers for markers which are now onscreen.
|
|
117
|
+
var updateMarkers = function () {
|
|
118
|
+
// Only run after GeoJSON is loaded:
|
|
119
|
+
if (!map.getSource(props.sourceId))
|
|
120
|
+
return;
|
|
121
|
+
if (!map.isSourceLoaded(props.sourceId))
|
|
122
|
+
return;
|
|
123
|
+
// Retrieve all features that are currently rendered on a visible map tile,
|
|
124
|
+
// thus excluding everything that's off-screen, for speed. The use of
|
|
125
|
+
// querySourceFeatures requires that at least one Layer exists, which is
|
|
126
|
+
// why we have a layer that renders only invisible circles.
|
|
127
|
+
// querySourceFeatures often results duplicates due to buffering.
|
|
128
|
+
// Remove these duplicates by looking at each feature's unique ID.
|
|
129
|
+
var seen = {};
|
|
130
|
+
var features = (map.getZoom() < minZoom.current || map.getZoom() > maxZoom.current) ? [] : map.querySourceFeatures(props.sourceId).filter(function (f) {
|
|
131
|
+
var id = f.properties[props.idField];
|
|
132
|
+
return seen.hasOwnProperty(id) ? false : (seen[id] = true);
|
|
133
|
+
});
|
|
134
|
+
// For every marker on the screen, create an HTML marker (if we didn't yet),
|
|
135
|
+
// and add it to the map if it's not there already
|
|
136
|
+
var newMarkers = {};
|
|
137
|
+
for (var _i = 0, features_1 = features; _i < features_1.length; _i++) {
|
|
138
|
+
var feature = features_1[_i];
|
|
139
|
+
var coords = feature.geometry.coordinates;
|
|
140
|
+
var fprops = feature.properties;
|
|
141
|
+
var id = fprops[props.idField];
|
|
142
|
+
// Do not render makers without an ID field:
|
|
143
|
+
if (!id)
|
|
144
|
+
continue;
|
|
145
|
+
// Is there a marker for this feature?
|
|
146
|
+
var marker = markers.current[id];
|
|
147
|
+
// If not, create one:
|
|
148
|
+
if (!marker) {
|
|
149
|
+
var el = createHtmlElementFromReactNode(__assign({ longitude: coords[0], latitude: coords[1] }, fprops));
|
|
150
|
+
marker = markers.current[id] = new Marker({
|
|
151
|
+
element: el
|
|
152
|
+
});
|
|
153
|
+
marker.setLngLat(coords);
|
|
154
|
+
}
|
|
155
|
+
newMarkers[id] = marker;
|
|
156
|
+
if (!markersOnScreen.current[id])
|
|
157
|
+
marker.addTo(map.getMap());
|
|
158
|
+
}
|
|
159
|
+
// for every marker we've added previously, remove those
|
|
160
|
+
// that are no longer visible:
|
|
161
|
+
for (var id in markersOnScreen.current) {
|
|
162
|
+
if (!newMarkers[id])
|
|
163
|
+
markersOnScreen.current[id].remove();
|
|
164
|
+
}
|
|
165
|
+
markersOnScreen.current = newMarkers;
|
|
166
|
+
};
|
|
167
|
+
// querySourceFeatures will only work if we have a layer, even
|
|
168
|
+
// though it's empty.
|
|
169
|
+
return (React.createElement(Layer, { source: props.sourceId, type: "circle", paint: { "circle-radius": 0 } }));
|
|
170
|
+
};
|
|
171
|
+
var HtmlMarkerLayer = function (_a) {
|
|
172
|
+
var _b = _a.minZoom, minZoom = _b === void 0 ? 0 : _b, _c = _a.maxZoom, maxZoom = _c === void 0 ? 99 : _c, props = __rest(_a, ["minZoom", "maxZoom"]);
|
|
173
|
+
return React.createElement(HtmlMarkerLayerBase, __assign({ minZoom: minZoom, maxZoom: maxZoom }, props));
|
|
174
|
+
};
|
|
175
|
+
HtmlMarkerLayer.displayName = 'HtmlMarkerLayer';
|
|
176
|
+
export { HtmlMarkerLayer };
|
|
@@ -56,117 +56,111 @@ import { Marker } from 'mapbox-gl';
|
|
|
56
56
|
* </Source>
|
|
57
57
|
* ```
|
|
58
58
|
*/
|
|
59
|
-
var HtmlMarkerLayerBase = function (
|
|
59
|
+
var HtmlMarkerLayerBase = function (_a) {
|
|
60
|
+
var children = _a.children, sourceId = _a.sourceId, idField = _a.idField, _b = _a.minZoom, minZoom = _b === void 0 ? 0 : _b, _c = _a.maxZoom, maxZoom = _c === void 0 ? 99 : _c;
|
|
60
61
|
var map = useMap().current;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
var
|
|
64
|
-
var
|
|
65
|
-
var
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
children.current = props.children;
|
|
82
|
-
for (var id in markersOnScreen.current) {
|
|
83
|
-
markersOnScreen.current[id].remove();
|
|
62
|
+
var markersRef = React.useRef({});
|
|
63
|
+
var rootsRef = React.useRef({});
|
|
64
|
+
var visibleRef = React.useRef({});
|
|
65
|
+
var childrenFn = React.useRef(children);
|
|
66
|
+
var ensureMarker = React.useCallback(function (feature) {
|
|
67
|
+
var _a, _b, _c, _d, _e;
|
|
68
|
+
var id = feature.properties[idField];
|
|
69
|
+
if (!id)
|
|
70
|
+
return undefined;
|
|
71
|
+
var coords = feature.geometry.coordinates;
|
|
72
|
+
var props = __assign({ longitude: coords[0], latitude: coords[1] }, feature.properties);
|
|
73
|
+
var marker = markersRef.current[id];
|
|
74
|
+
if (!marker) {
|
|
75
|
+
var el = document.createElement('div');
|
|
76
|
+
var root = ReactDOM.createRoot(el);
|
|
77
|
+
root.render((_b = (_a = childrenFn.current) === null || _a === void 0 ? void 0 : _a.call(childrenFn, props)) !== null && _b !== void 0 ? _b : null);
|
|
78
|
+
marker = new Marker({ element: el }).setLngLat(coords);
|
|
79
|
+
marker.addTo(map.getMap());
|
|
80
|
+
markersRef.current[id] = marker;
|
|
81
|
+
rootsRef.current[id] = root;
|
|
84
82
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
map.triggerRepaint();
|
|
96
|
-
};
|
|
97
|
-
}, []);
|
|
98
|
-
//
|
|
99
|
-
// Given a native or React component and props, turn it into an
|
|
100
|
-
// HTML element. Return this element.
|
|
101
|
-
//
|
|
102
|
-
var createHtmlElementFromReactNode = function (latlngfeatureProps) {
|
|
103
|
-
// Don't render anything if there is no child.
|
|
104
|
-
if (!children.current)
|
|
105
|
-
return null;
|
|
106
|
-
// Get the first child.
|
|
107
|
-
var elem = children.current(latlngfeatureProps);
|
|
108
|
-
// Render child.
|
|
109
|
-
var el = document.createElement('div');
|
|
110
|
-
var root = ReactDOM.createRoot(el);
|
|
111
|
-
root.render(elem);
|
|
112
|
-
return el;
|
|
113
|
-
};
|
|
114
|
-
//
|
|
115
|
-
// Calculate which markers are offscreen, and remove them.
|
|
116
|
-
// Add new markers for markers which are now onscreen.
|
|
117
|
-
var updateMarkers = function () {
|
|
118
|
-
// Only run after GeoJSON is loaded:
|
|
119
|
-
if (!map.getSource(props.sourceId))
|
|
83
|
+
else {
|
|
84
|
+
(_c = rootsRef.current[id]) === null || _c === void 0 ? void 0 : _c.render((_e = (_d = childrenFn.current) === null || _d === void 0 ? void 0 : _d.call(childrenFn, props)) !== null && _e !== void 0 ? _e : null);
|
|
85
|
+
marker.setLngLat(coords);
|
|
86
|
+
}
|
|
87
|
+
return marker;
|
|
88
|
+
}, [idField, map]);
|
|
89
|
+
var updateMarkers = React.useCallback(function () {
|
|
90
|
+
var _a;
|
|
91
|
+
if (!map.getSource(sourceId))
|
|
120
92
|
return;
|
|
121
|
-
if (!map.isSourceLoaded(
|
|
93
|
+
if (!map.isSourceLoaded(sourceId))
|
|
94
|
+
return;
|
|
95
|
+
if (map.getZoom() < minZoom || map.getZoom() > maxZoom)
|
|
122
96
|
return;
|
|
123
|
-
// Retrieve all features that are currently rendered on a visible map tile,
|
|
124
|
-
// thus excluding everything that's off-screen, for speed. The use of
|
|
125
|
-
// querySourceFeatures requires that at least one Layer exists, which is
|
|
126
|
-
// why we have a layer that renders only invisible circles.
|
|
127
|
-
// querySourceFeatures often results duplicates due to buffering.
|
|
128
|
-
// Remove these duplicates by looking at each feature's unique ID.
|
|
129
97
|
var seen = {};
|
|
130
|
-
var features =
|
|
131
|
-
var
|
|
132
|
-
|
|
98
|
+
var features = map.querySourceFeatures(sourceId).filter(function (f) {
|
|
99
|
+
var _a;
|
|
100
|
+
var id = (_a = f.properties) === null || _a === void 0 ? void 0 : _a[idField];
|
|
101
|
+
if (!id || seen[id])
|
|
102
|
+
return false;
|
|
103
|
+
seen[id] = true;
|
|
104
|
+
return true;
|
|
133
105
|
});
|
|
134
|
-
|
|
135
|
-
// and add it to the map if it's not there already
|
|
136
|
-
var newMarkers = {};
|
|
106
|
+
var newVisible = {};
|
|
137
107
|
for (var _i = 0, features_1 = features; _i < features_1.length; _i++) {
|
|
138
108
|
var feature = features_1[_i];
|
|
139
|
-
var
|
|
140
|
-
var
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!id)
|
|
144
|
-
continue;
|
|
145
|
-
// Is there a marker for this feature?
|
|
146
|
-
var marker = markers.current[id];
|
|
147
|
-
// If not, create one:
|
|
148
|
-
if (!marker) {
|
|
149
|
-
var el = createHtmlElementFromReactNode(__assign({ longitude: coords[0], latitude: coords[1] }, fprops));
|
|
150
|
-
marker = markers.current[id] = new Marker({
|
|
151
|
-
element: el
|
|
152
|
-
});
|
|
153
|
-
marker.setLngLat(coords);
|
|
154
|
-
}
|
|
155
|
-
newMarkers[id] = marker;
|
|
156
|
-
if (!markersOnScreen.current[id])
|
|
157
|
-
marker.addTo(map.getMap());
|
|
109
|
+
var id = feature.properties[idField];
|
|
110
|
+
var marker = ensureMarker(feature);
|
|
111
|
+
if (marker)
|
|
112
|
+
newVisible[id] = marker;
|
|
158
113
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
114
|
+
for (var id in visibleRef.current) {
|
|
115
|
+
if (!newVisible[id]) {
|
|
116
|
+
visibleRef.current[id].remove();
|
|
117
|
+
(_a = rootsRef.current[id]) === null || _a === void 0 ? void 0 : _a.unmount();
|
|
118
|
+
delete markersRef.current[id];
|
|
119
|
+
delete rootsRef.current[id];
|
|
120
|
+
}
|
|
164
121
|
}
|
|
165
|
-
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
122
|
+
visibleRef.current = newVisible;
|
|
123
|
+
}, [map, sourceId, idField, minZoom, maxZoom, ensureMarker]);
|
|
124
|
+
React.useEffect(function () {
|
|
125
|
+
if (!map)
|
|
126
|
+
return undefined;
|
|
127
|
+
var onSourceData = function (e) {
|
|
128
|
+
if (e.sourceId === sourceId && e.isSourceLoaded) {
|
|
129
|
+
updateMarkers();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var onRender = function () {
|
|
133
|
+
updateMarkers();
|
|
134
|
+
};
|
|
135
|
+
map.on('sourcedata', onSourceData);
|
|
136
|
+
map.on('render', onRender);
|
|
137
|
+
return function () {
|
|
138
|
+
map.off('sourcedata', onSourceData);
|
|
139
|
+
map.off('render', onRender);
|
|
140
|
+
// ✅ Defer cleanup until after React has finished unmounting
|
|
141
|
+
requestAnimationFrame(function () {
|
|
142
|
+
var _a;
|
|
143
|
+
for (var id in markersRef.current) {
|
|
144
|
+
try {
|
|
145
|
+
markersRef.current[id].remove();
|
|
146
|
+
}
|
|
147
|
+
catch (_b) { }
|
|
148
|
+
try {
|
|
149
|
+
(_a = rootsRef.current[id]) === null || _a === void 0 ? void 0 : _a.unmount();
|
|
150
|
+
}
|
|
151
|
+
catch (_c) { }
|
|
152
|
+
}
|
|
153
|
+
markersRef.current = {};
|
|
154
|
+
rootsRef.current = {};
|
|
155
|
+
visibleRef.current = {};
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
}, [map, sourceId, updateMarkers]);
|
|
159
|
+
React.useEffect(function () {
|
|
160
|
+
childrenFn.current = children;
|
|
161
|
+
updateMarkers();
|
|
162
|
+
}, [children, updateMarkers]);
|
|
163
|
+
return React.createElement(Layer, { source: sourceId, type: "circle", paint: { 'circle-radius': 0 } });
|
|
170
164
|
};
|
|
171
165
|
var HtmlMarkerLayer = function (_a) {
|
|
172
166
|
var _b = _a.minZoom, minZoom = _b === void 0 ? 0 : _b, _c = _a.maxZoom, maxZoom = _c === void 0 ? 99 : _c, props = __rest(_a, ["minZoom", "maxZoom"]);
|
|
@@ -23,6 +23,11 @@ interface IClusterMarkerProps {
|
|
|
23
23
|
* ranging from green to red. There should be exactly 5 values.
|
|
24
24
|
*/
|
|
25
25
|
ringValues?: number[];
|
|
26
|
+
/**
|
|
27
|
+
* Number of decimals to use in number formatting.
|
|
28
|
+
* @default 0
|
|
29
|
+
*/
|
|
30
|
+
decimals?: number;
|
|
26
31
|
/**
|
|
27
32
|
* Fired when the marker is clicked.
|
|
28
33
|
*/
|
|
@@ -33,7 +38,7 @@ interface IClusterMarkerProps {
|
|
|
33
38
|
*
|
|
34
39
|
*/
|
|
35
40
|
declare const ClusterMarker: {
|
|
36
|
-
({ radius, relativeSize, value, ...props }: IClusterMarkerProps): React.JSX.Element;
|
|
41
|
+
({ radius, relativeSize, value, decimals, ...props }: IClusterMarkerProps): React.JSX.Element;
|
|
37
42
|
displayName: string;
|
|
38
43
|
};
|
|
39
44
|
export { ClusterMarker, IClusterMarkerProps };
|
|
@@ -85,7 +85,7 @@ var ClusterMarkerBase = function (props) {
|
|
|
85
85
|
props.ringValues &&
|
|
86
86
|
React.createElement("svg", null, renderValueRings()),
|
|
87
87
|
React.createElement("span", null,
|
|
88
|
-
React.createElement(HumanFormatter, { value: props.value }))));
|
|
88
|
+
React.createElement(HumanFormatter, { value: props.value, decimals: props.decimals }))));
|
|
89
89
|
};
|
|
90
90
|
var ClusterMarkerStyled = styled(ClusterMarkerBase)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n // Position and size:\n position: relative;\n box-sizing: border-box;\n\n // Appearance:\n border-radius: 50%;\n background-color: rgb(255 255 255 / 100%);\n box-shadow: \n 0 0 0 6px rgb(255 255 255 / 44%), // outer ring small\n 0 0 0 12px rgb(255 255 255 / 20%), // outer ring big\n inset 0px 0 0px 4px #fff, // inner white ring\n inset 0px 7px 3px 0px rgba(0, 0, 0, 0.25); // inner dropshadow\n cursor: ", ";\n outline: none;\n user-select: none;\n\n & > svg {\n position: absolute;\n left: 50%;\n top: 50%;\n width: 100%;\n height: 100%;\n margin-left: -50%;\n margin-top: -50%;\n }\n\n & > span {\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n // Content:\n display: flex;\n justify-content: center;\n align-items: center;\n color: #3C698A;\n font: normal 700 12px/12px \"Roboto Condensed\";\n }\n"], ["\n // Position and size:\n position: relative;\n box-sizing: border-box;\n\n // Appearance:\n border-radius: 50%;\n background-color: rgb(255 255 255 / 100%);\n box-shadow: \n 0 0 0 6px rgb(255 255 255 / 44%), // outer ring small\n 0 0 0 12px rgb(255 255 255 / 20%), // outer ring big\n inset 0px 0 0px 4px #fff, // inner white ring\n inset 0px 7px 3px 0px rgba(0, 0, 0, 0.25); // inner dropshadow\n cursor: ", ";\n outline: none;\n user-select: none;\n\n & > svg {\n position: absolute;\n left: 50%;\n top: 50%;\n width: 100%;\n height: 100%;\n margin-left: -50%;\n margin-top: -50%;\n }\n\n & > span {\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n // Content:\n display: flex;\n justify-content: center;\n align-items: center;\n color: #3C698A;\n font: normal 700 12px/12px \"Roboto Condensed\";\n }\n"
|
|
91
91
|
/**
|
|
@@ -98,10 +98,10 @@ var ClusterMarkerStyled = styled(ClusterMarkerBase)(templateObject_1 || (templat
|
|
|
98
98
|
*
|
|
99
99
|
*/
|
|
100
100
|
var ClusterMarker = function (_a) {
|
|
101
|
-
var _b = _a.radius, radius = _b === void 0 ? 24 : _b, _c = _a.relativeSize, relativeSize = _c === void 0 ? 0 : _c, _d = _a.value, value = _d === void 0 ? 0 : _d, props = __rest(_a, ["radius", "relativeSize", "value"]);
|
|
101
|
+
var _b = _a.radius, radius = _b === void 0 ? 24 : _b, _c = _a.relativeSize, relativeSize = _c === void 0 ? 0 : _c, _d = _a.value, value = _d === void 0 ? 0 : _d, _e = _a.decimals, decimals = _e === void 0 ? 0 : _e, props = __rest(_a, ["radius", "relativeSize", "value", "decimals"]);
|
|
102
102
|
var step = Math.log10(Math.max(1, value));
|
|
103
103
|
var r = radius + step * relativeSize;
|
|
104
|
-
return React.createElement(ClusterMarkerStyled, __assign({ radius: r, value: value }, props));
|
|
104
|
+
return React.createElement(ClusterMarkerStyled, __assign({ radius: r, value: value, decimals: decimals }, props));
|
|
105
105
|
};
|
|
106
106
|
ClusterMarker.displayName = 'ClusterMarker';
|
|
107
107
|
export { ClusterMarker };
|