@dynatrace/strato-geo 3.7.0 → 3.8.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 +2057 -0
- package/esm/map/components/toolbar/MapToolbar.js +1 -1
- package/esm/map/components/toolbar/MapToolbar.js.map +1 -1
- package/esm/map/hooks/use-active-interaction.js +35 -20
- package/esm/map/hooks/use-active-interaction.js.map +2 -2
- package/esm/map/hooks/use-attach-symbol-to-map.js +7 -7
- package/esm/map/hooks/use-attach-symbol-to-map.js.map +2 -2
- package/esm/map/hooks/use-hover-interaction.js +12 -0
- package/esm/map/hooks/use-hover-interaction.js.map +2 -2
- package/esm/map/hooks/use-overlay-events.js +27 -15
- package/esm/map/hooks/use-overlay-events.js.map +2 -2
- package/esm/map/utils/get-scaled-symbol-size.js +1 -1
- package/esm/map/utils/get-scaled-symbol-size.js.map +2 -2
- package/lang/uncompiled/en.json +16 -16
- package/map/components/toolbar/MapToolbar.js +1 -1
- package/map/hooks/use-active-interaction.js +35 -20
- package/map/hooks/use-attach-symbol-to-map.d.ts +1 -1
- package/map/hooks/use-attach-symbol-to-map.js +6 -6
- package/map/hooks/use-hover-interaction.js +12 -0
- package/map/hooks/use-overlay-events.js +26 -14
- package/map/utils/get-scaled-symbol-size.js +1 -1
- package/package.json +4 -4
|
@@ -66,7 +66,7 @@ const MapToolbar = () => {
|
|
|
66
66
|
initialA11yState: initialA11yToolbarTabIndexContext,
|
|
67
67
|
onMouseEnter: handleMouseEnter,
|
|
68
68
|
onMouseLeave: handleMouseLeave,
|
|
69
|
-
menuDisplayMode: "
|
|
69
|
+
menuDisplayMode: "collapsed",
|
|
70
70
|
children: [
|
|
71
71
|
isZoomEnabled && /* @__PURE__ */ jsx(MapZoomInOutButtons, {}),
|
|
72
72
|
isZoomEnabled && /* @__PURE__ */ jsx(MapResetButton, {}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/map/components/toolbar/MapToolbar.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\n\nimport {\n _ChartToolbarRenderer as ChartToolbarRenderer,\n _useOverlayTooltipReducer as useTooltipReducer,\n} from '@dynatrace/strato-components/charts';\n\nimport { MapDownloadDataButton } from './buttons/MapDownloadDataButton.js';\nimport { MapResetButton } from './buttons/MapResetButton.js';\nimport { MapZoomInOutButtons } from './buttons/MapZoomInOutButtons.js';\nimport { MapZoomToFitButton } from './buttons/MapZoomToFitButton.js';\nimport { initialA11yToolbarTabIndexContext } from './constants.js';\nimport { useMapConfig } from '../../hooks/use-map-config.js';\nimport { useMapRawData } from '../../hooks/use-map-raw-data.js';\nimport { MapInitialViewProvider } from '../../providers/map-initial-view.provider.js';\nimport { useMapOverlayState } from '../../store/selectors.js';\nimport { useSetState } from '../../store/store.js';\nimport { buildAndDownloadCsv } from '../../utils/build-and-download-csv.js';\n\nconst defaultToolbarPlacement = 'top-right';\n\nexport const MapToolbar = () => {\n const { toolbar: toolbarConfig, interactions: interactionsConfig } =\n useMapConfig();\n const data = useMapRawData();\n\n const { current: map } = useMap();\n const { mouseInBounds } = useMapOverlayState();\n const setState = useSetState();\n const dispatch = useTooltipReducer();\n const isZoomEnabled =\n toolbarConfig?.zoom?.enabled ?? interactionsConfig?.zoom?.enabled ?? false;\n const isZoomToFitEnabled =\n toolbarConfig?.zoom?.enabled ??\n interactionsConfig?.zoomToFit?.enabled ??\n false;\n const isDownloadDataEnabled = toolbarConfig?.downloadCSV?.enabled ?? false;\n\n const hasZoomControls = isZoomEnabled || isZoomToFitEnabled;\n const hasControls = hasZoomControls || isDownloadDataEnabled;\n const hasOnlyDownloadCSV = isDownloadDataEnabled && !hasZoomControls;\n\n const handleMouseEnter = () => {\n dispatch({ type: 'RESET_TOOLTIP' })();\n if (map) {\n map.queryRenderedFeatures().forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { active: false, isAnyActive: false },\n );\n }\n });\n }\n setState((prev) => ({\n ...prev,\n }));\n };\n\n const handleMouseLeave = () => {\n setState((prev) => ({\n ...prev,\n tooltip: { ...prev.tooltip, enabled: true },\n }));\n };\n\n const handleDownloadDataToolbar = () => {\n buildAndDownloadCsv(data);\n };\n\n return hasControls ? (\n <MapInitialViewProvider>\n <ChartToolbarRenderer\n visible={mouseInBounds}\n collapsed={toolbarConfig?.collapsed}\n draggable={toolbarConfig?.draggable}\n placement={toolbarConfig?.placement ?? defaultToolbarPlacement}\n handleDownloadDataToolbar={handleDownloadDataToolbar}\n isDownloadEnabled={!hasOnlyDownloadCSV && isDownloadDataEnabled}\n initialA11yState={initialA11yToolbarTabIndexContext}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n menuDisplayMode=\"
|
|
4
|
+
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\n\nimport {\n _ChartToolbarRenderer as ChartToolbarRenderer,\n _useOverlayTooltipReducer as useTooltipReducer,\n} from '@dynatrace/strato-components/charts';\n\nimport { MapDownloadDataButton } from './buttons/MapDownloadDataButton.js';\nimport { MapResetButton } from './buttons/MapResetButton.js';\nimport { MapZoomInOutButtons } from './buttons/MapZoomInOutButtons.js';\nimport { MapZoomToFitButton } from './buttons/MapZoomToFitButton.js';\nimport { initialA11yToolbarTabIndexContext } from './constants.js';\nimport { useMapConfig } from '../../hooks/use-map-config.js';\nimport { useMapRawData } from '../../hooks/use-map-raw-data.js';\nimport { MapInitialViewProvider } from '../../providers/map-initial-view.provider.js';\nimport { useMapOverlayState } from '../../store/selectors.js';\nimport { useSetState } from '../../store/store.js';\nimport { buildAndDownloadCsv } from '../../utils/build-and-download-csv.js';\n\nconst defaultToolbarPlacement = 'top-right';\n\nexport const MapToolbar = () => {\n const { toolbar: toolbarConfig, interactions: interactionsConfig } =\n useMapConfig();\n const data = useMapRawData();\n\n const { current: map } = useMap();\n const { mouseInBounds } = useMapOverlayState();\n const setState = useSetState();\n const dispatch = useTooltipReducer();\n const isZoomEnabled =\n toolbarConfig?.zoom?.enabled ?? interactionsConfig?.zoom?.enabled ?? false;\n const isZoomToFitEnabled =\n toolbarConfig?.zoom?.enabled ??\n interactionsConfig?.zoomToFit?.enabled ??\n false;\n const isDownloadDataEnabled = toolbarConfig?.downloadCSV?.enabled ?? false;\n\n const hasZoomControls = isZoomEnabled || isZoomToFitEnabled;\n const hasControls = hasZoomControls || isDownloadDataEnabled;\n const hasOnlyDownloadCSV = isDownloadDataEnabled && !hasZoomControls;\n\n const handleMouseEnter = () => {\n dispatch({ type: 'RESET_TOOLTIP' })();\n if (map) {\n map.queryRenderedFeatures().forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { active: false, isAnyActive: false },\n );\n }\n });\n }\n setState((prev) => ({\n ...prev,\n }));\n };\n\n const handleMouseLeave = () => {\n setState((prev) => ({\n ...prev,\n tooltip: { ...prev.tooltip, enabled: true },\n }));\n };\n\n const handleDownloadDataToolbar = () => {\n buildAndDownloadCsv(data);\n };\n\n return hasControls ? (\n <MapInitialViewProvider>\n <ChartToolbarRenderer\n visible={mouseInBounds}\n collapsed={toolbarConfig?.collapsed}\n draggable={toolbarConfig?.draggable}\n placement={toolbarConfig?.placement ?? defaultToolbarPlacement}\n handleDownloadDataToolbar={handleDownloadDataToolbar}\n isDownloadEnabled={!hasOnlyDownloadCSV && isDownloadDataEnabled}\n initialA11yState={initialA11yToolbarTabIndexContext}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n menuDisplayMode=\"collapsed\"\n >\n {isZoomEnabled && <MapZoomInOutButtons />}\n {isZoomEnabled && <MapResetButton />}\n {isZoomToFitEnabled && <MapZoomToFitButton />}\n {isDownloadDataEnabled && <MapDownloadDataButton />}\n </ChartToolbarRenderer>\n </MapInitialViewProvider>\n ) : null;\n};\n\nMapToolbar['displayName'] = 'MapToolbar';\n"],
|
|
5
5
|
"mappings": "AAwEM,SAYoB,KAZpB;AAxEN,SAAS,cAAc;AAEvB;AAAA,EACE,yBAAyB;AAAA,EACzB,6BAA6B;AAAA,OACxB;AAEP,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,yCAAyC;AAClD,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AAEpC,MAAM,0BAA0B;AAEzB,MAAM,aAAa,MAAM;AAC9B,QAAM,EAAE,SAAS,eAAe,cAAc,mBAAmB,IAC/D,aAAa;AACf,QAAM,OAAO,cAAc;AAE3B,QAAM,EAAE,SAAS,IAAI,IAAI,OAAO;AAChC,QAAM,EAAE,cAAc,IAAI,mBAAmB;AAC7C,QAAM,WAAW,YAAY;AAC7B,QAAM,WAAW,kBAAkB;AACnC,QAAM,gBACJ,eAAe,MAAM,WAAW,oBAAoB,MAAM,WAAW;AACvE,QAAM,qBACJ,eAAe,MAAM,WACrB,oBAAoB,WAAW,WAC/B;AACF,QAAM,wBAAwB,eAAe,aAAa,WAAW;AAErE,QAAM,kBAAkB,iBAAiB;AACzC,QAAM,cAAc,mBAAmB;AACvC,QAAM,qBAAqB,yBAAyB,CAAC;AAErD,QAAM,mBAAmB,MAAM;AAC7B,aAAS,EAAE,MAAM,gBAAgB,CAAC,EAAE;AACpC,QAAI,KAAK;AACP,UAAI,sBAAsB,EAAE,QAAQ,CAAC,YAAY;AAC/C,YAAI,QAAQ,OAAO,UAAa,QAAQ,MAAM,QAAQ;AACpD,cAAI;AAAA,YACF,EAAE,QAAQ,QAAQ,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAAA,YAC/C,EAAE,QAAQ,OAAO,aAAa,MAAM;AAAA,UACtC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,aAAS,CAAC,UAAU;AAAA,MAClB,GAAG;AAAA,IACL,EAAE;AAAA,EACJ;AAEA,QAAM,mBAAmB,MAAM;AAC7B,aAAS,CAAC,UAAU;AAAA,MAClB,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,KAAK,SAAS,SAAS,KAAK;AAAA,IAC5C,EAAE;AAAA,EACJ;AAEA,QAAM,4BAA4B,MAAM;AACtC,wBAAoB,IAAI;AAAA,EAC1B;AAEA,SAAO,cACL,oBAAC,0BACC;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,WAAW,eAAe;AAAA,MAC1B,WAAW,eAAe;AAAA,MAC1B,WAAW,eAAe,aAAa;AAAA,MACvC;AAAA,MACA,mBAAmB,CAAC,sBAAsB;AAAA,MAC1C,kBAAkB;AAAA,MAClB,cAAc;AAAA,MACd,cAAc;AAAA,MACd,iBAAgB;AAAA,MAEf;AAAA,yBAAiB,oBAAC,uBAAoB;AAAA,QACtC,iBAAiB,oBAAC,kBAAe;AAAA,QACjC,sBAAsB,oBAAC,sBAAmB;AAAA,QAC1C,yBAAyB,oBAAC,yBAAsB;AAAA;AAAA;AAAA,EACnD,GACF,IACE;AACN;AAEA,WAAW,aAAa,IAAI;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import { useMap } from "@vis.gl/react-maplibre";
|
|
2
2
|
import { isNil, isUndefined } from "lodash-es";
|
|
3
3
|
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { useOverlayTooltipSelector } from "./use-geo-tooltip-sync.js";
|
|
4
5
|
import { getMinValueFeature } from "../utils/get-min-value-feature.js";
|
|
5
6
|
const useActiveInteraction = (interactiveLayerIds) => {
|
|
6
7
|
const map = useMap().current;
|
|
7
8
|
const featureIdRef = useRef(void 0);
|
|
8
9
|
const sourceIdRef = useRef(void 0);
|
|
10
|
+
const pinnedFromStore = useOverlayTooltipSelector((state) => state.pinned);
|
|
11
|
+
const prevPinnedRef = useRef(false);
|
|
9
12
|
const interactiveLayerIdsRef = useRef(interactiveLayerIds);
|
|
10
13
|
interactiveLayerIdsRef.current = interactiveLayerIds;
|
|
14
|
+
const clearActiveFeatureStates = () => {
|
|
15
|
+
if (!isUndefined(featureIdRef.current) && !isUndefined(sourceIdRef.current)) {
|
|
16
|
+
map.setFeatureState(
|
|
17
|
+
{ source: sourceIdRef.current, id: featureIdRef.current },
|
|
18
|
+
{ active: false }
|
|
19
|
+
);
|
|
20
|
+
try {
|
|
21
|
+
const allFeatures = map.queryRenderedFeatures(void 0, {
|
|
22
|
+
layers: interactiveLayerIdsRef.current
|
|
23
|
+
});
|
|
24
|
+
allFeatures.forEach((feature) => {
|
|
25
|
+
if (feature.id !== void 0 && feature.layer.source !== void 0) {
|
|
26
|
+
map.setFeatureState(
|
|
27
|
+
{ source: feature.layer.source, id: feature.id },
|
|
28
|
+
{ isAnyActive: false }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
11
36
|
const handleClick = useCallback(
|
|
12
37
|
({ point }) => {
|
|
13
38
|
let features = [];
|
|
@@ -70,30 +95,20 @@ const useActiveInteraction = (interactiveLayerIds) => {
|
|
|
70
95
|
const onEscapeKeyPressedHandler = useCallback(
|
|
71
96
|
(event) => {
|
|
72
97
|
if (event.key === "Escape") {
|
|
73
|
-
|
|
74
|
-
map.setFeatureState(
|
|
75
|
-
{ source: sourceIdRef.current, id: featureIdRef.current },
|
|
76
|
-
{ active: false }
|
|
77
|
-
);
|
|
78
|
-
try {
|
|
79
|
-
const allFeatures = map.queryRenderedFeatures(void 0, {
|
|
80
|
-
layers: interactiveLayerIdsRef.current
|
|
81
|
-
});
|
|
82
|
-
allFeatures.forEach((feature) => {
|
|
83
|
-
if (feature.id !== void 0 && feature.layer.source !== void 0) {
|
|
84
|
-
map.setFeatureState(
|
|
85
|
-
{ source: feature.layer.source, id: feature.id },
|
|
86
|
-
{ isAnyActive: false }
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
}
|
|
98
|
+
clearActiveFeatureStates();
|
|
93
99
|
}
|
|
94
100
|
},
|
|
95
101
|
[map]
|
|
96
102
|
);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const wasJustUnpinned = prevPinnedRef.current && !pinnedFromStore;
|
|
105
|
+
prevPinnedRef.current = pinnedFromStore;
|
|
106
|
+
if (wasJustUnpinned) {
|
|
107
|
+
clearActiveFeatureStates();
|
|
108
|
+
featureIdRef.current = void 0;
|
|
109
|
+
sourceIdRef.current = void 0;
|
|
110
|
+
}
|
|
111
|
+
}, [pinnedFromStore, map]);
|
|
97
112
|
useEffect(() => {
|
|
98
113
|
map.on("click", handleClick);
|
|
99
114
|
window.addEventListener("keyup", onEscapeKeyPressedHandler);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/map/hooks/use-active-interaction.ts"],
|
|
4
|
-
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\nimport { isNil, isUndefined } from 'lodash-es';\nimport type { MapLayerMouseEvent } from 'maplibre-gl';\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport { getMinValueFeature } from '../utils/get-min-value-feature.js';\n\n/**\n * Handles click-to-activate interactions on map features.\n *\n * Scopes `queryRenderedFeatures` to `interactiveLayerIds` to prevent accidental\n * activation of base/background layers. Both query calls are try-caught to\n * contain maplibre-gl runtime errors without crashing the component tree.\n */\nexport const useActiveInteraction = (interactiveLayerIds: string[]) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const map = useMap().current!;\n\n /** Tracks the ID of the currently active feature across renders. */\n const featureIdRef = useRef<string | number | undefined>(undefined);\n /** Tracks the source of the currently active feature across renders. */\n const sourceIdRef = useRef<string | undefined>(undefined);\n /**\n * Kept in a ref so that stable callbacks always read the latest layer list\n * without needing to be recreated when the array reference changes.\n */\n const interactiveLayerIdsRef = useRef(interactiveLayerIds);\n interactiveLayerIdsRef.current = interactiveLayerIds;\n\n const handleClick = useCallback(\n ({ point }: MapLayerMouseEvent) => {\n let features: ReturnType<typeof map.queryRenderedFeatures> = [];\n\n try {\n features = map.queryRenderedFeatures(point, {\n layers: interactiveLayerIdsRef.current,\n });\n } catch {\n return;\n }\n\n // Keep the broad query for isAnyActive so all interactive features receive the flag.\n let allFeatures: ReturnType<typeof map.queryRenderedFeatures> = [];\n try {\n allFeatures = map.queryRenderedFeatures(undefined, {\n layers: interactiveLayerIdsRef.current,\n });\n } catch {\n // falls through with empty allFeatures\n }\n\n const layerId = features?.[0]?.layer?.id;\n\n const hasHoveredFeatures =\n !isNil(features) && features.length > 0 && !isUndefined(layerId);\n\n if (hasHoveredFeatures) {\n if (\n !isUndefined(featureIdRef.current) &&\n !isUndefined(sourceIdRef.current)\n ) {\n // If there's already an active feature, remove the active state.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: false },\n );\n }\n\n const minFeature = getMinValueFeature(features);\n\n featureIdRef.current = minFeature.id;\n sourceIdRef.current = minFeature.layer.source;\n const activeState = features[0].state.active;\n\n // Add the active state to the closest feature.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: !activeState },\n );\n allFeatures.forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source !== undefined) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { isAnyActive: !activeState },\n );\n }\n }); //TODO: change to inactive\n } else if (\n !isUndefined(featureIdRef.current) &&\n !isUndefined(sourceIdRef.current)\n ) {\n // Remove the active state from the last active feature.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: false },\n );\n allFeatures.forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source !== undefined) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { isAnyActive: false },\n );\n }\n });\n }\n },\n [map],\n );\n\n const onEscapeKeyPressedHandler = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAc;AACvB,SAAS,OAAO,mBAAmB;AAEnC,SAAS,aAAa,WAAW,cAAc;AAE/C,SAAS,0BAA0B;AAS5B,MAAM,uBAAuB,CAAC,wBAAkC;AAErE,QAAM,MAAM,OAAO,EAAE;AAGrB,QAAM,eAAe,OAAoC,MAAS;AAElE,QAAM,cAAc,OAA2B,MAAS;
|
|
4
|
+
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\nimport { isNil, isUndefined } from 'lodash-es';\nimport type { MapLayerMouseEvent } from 'maplibre-gl';\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport { useOverlayTooltipSelector } from './use-geo-tooltip-sync.js';\nimport { getMinValueFeature } from '../utils/get-min-value-feature.js';\n\n/**\n * Handles click-to-activate interactions on map features.\n *\n * Scopes `queryRenderedFeatures` to `interactiveLayerIds` to prevent accidental\n * activation of base/background layers. Both query calls are try-caught to\n * contain maplibre-gl runtime errors without crashing the component tree.\n */\nexport const useActiveInteraction = (interactiveLayerIds: string[]) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const map = useMap().current!;\n\n /** Tracks the ID of the currently active feature across renders. */\n const featureIdRef = useRef<string | number | undefined>(undefined);\n /** Tracks the source of the currently active feature across renders. */\n const sourceIdRef = useRef<string | undefined>(undefined);\n\n const pinnedFromStore = useOverlayTooltipSelector((state) => state.pinned);\n const prevPinnedRef = useRef(false);\n /**\n * Kept in a ref so that stable callbacks always read the latest layer list\n * without needing to be recreated when the array reference changes.\n */\n const interactiveLayerIdsRef = useRef(interactiveLayerIds);\n interactiveLayerIdsRef.current = interactiveLayerIds;\n\n const clearActiveFeatureStates = () => {\n if (\n !isUndefined(featureIdRef.current) &&\n !isUndefined(sourceIdRef.current)\n ) {\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: false },\n );\n\n try {\n const allFeatures = map.queryRenderedFeatures(undefined, {\n layers: interactiveLayerIdsRef.current,\n });\n allFeatures.forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source !== undefined) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { isAnyActive: false },\n );\n }\n });\n } catch {\n // falls through \u2014 active state is already cleared above\n }\n }\n };\n\n const handleClick = useCallback(\n ({ point }: MapLayerMouseEvent) => {\n let features: ReturnType<typeof map.queryRenderedFeatures> = [];\n\n try {\n features = map.queryRenderedFeatures(point, {\n layers: interactiveLayerIdsRef.current,\n });\n } catch {\n return;\n }\n\n // Keep the broad query for isAnyActive so all interactive features receive the flag.\n let allFeatures: ReturnType<typeof map.queryRenderedFeatures> = [];\n try {\n allFeatures = map.queryRenderedFeatures(undefined, {\n layers: interactiveLayerIdsRef.current,\n });\n } catch {\n // falls through with empty allFeatures\n }\n\n const layerId = features?.[0]?.layer?.id;\n\n const hasHoveredFeatures =\n !isNil(features) && features.length > 0 && !isUndefined(layerId);\n\n if (hasHoveredFeatures) {\n if (\n !isUndefined(featureIdRef.current) &&\n !isUndefined(sourceIdRef.current)\n ) {\n // If there's already an active feature, remove the active state.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: false },\n );\n }\n\n const minFeature = getMinValueFeature(features);\n\n featureIdRef.current = minFeature.id;\n sourceIdRef.current = minFeature.layer.source;\n const activeState = features[0].state.active;\n\n // Add the active state to the closest feature.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: !activeState },\n );\n allFeatures.forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source !== undefined) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { isAnyActive: !activeState },\n );\n }\n }); //TODO: change to inactive\n } else if (\n !isUndefined(featureIdRef.current) &&\n !isUndefined(sourceIdRef.current)\n ) {\n // Remove the active state from the last active feature.\n map.setFeatureState(\n { source: sourceIdRef.current, id: featureIdRef.current },\n { active: false },\n );\n allFeatures.forEach((feature) => {\n if (feature.id !== undefined && feature.layer.source !== undefined) {\n map.setFeatureState(\n { source: feature.layer.source, id: feature.id },\n { isAnyActive: false },\n );\n }\n });\n }\n },\n [map],\n );\n\n const onEscapeKeyPressedHandler = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n clearActiveFeatureStates();\n }\n },\n [map],\n );\n\n useEffect(() => {\n const wasJustUnpinned = prevPinnedRef.current && !pinnedFromStore;\n prevPinnedRef.current = pinnedFromStore;\n\n if (wasJustUnpinned) {\n clearActiveFeatureStates();\n featureIdRef.current = undefined;\n sourceIdRef.current = undefined;\n }\n }, [pinnedFromStore, map]);\n\n useEffect(() => {\n map.on('click', handleClick);\n window.addEventListener('keyup', onEscapeKeyPressedHandler);\n return () => {\n map.off('click', handleClick);\n window.removeEventListener('keyup', onEscapeKeyPressedHandler);\n };\n }, [map, handleClick, onEscapeKeyPressedHandler]);\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useMap } from "@vis.gl/react-maplibre";
|
|
2
|
-
import { isString
|
|
2
|
+
import { isString } from "lodash-es";
|
|
3
|
+
import { useEffect } from "react";
|
|
3
4
|
import { useAttachImageFromIcon } from "./use-attach-image-from-icon.js";
|
|
4
5
|
import { attachImageFromString } from "../utils/attach-image-from-string.js";
|
|
5
6
|
const useAttachSymbolToMap = (icon, suffix = "", outputSize) => {
|
|
6
7
|
const { current: map } = useMap();
|
|
7
8
|
const customIconAttached = useAttachImageFromIcon(icon, suffix, outputSize);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!customIconAttached && isString(icon) && map) {
|
|
11
|
+
attachImageFromString(map, icon, suffix, outputSize);
|
|
12
|
+
}
|
|
13
|
+
}, [customIconAttached, icon, map, outputSize, suffix]);
|
|
14
14
|
};
|
|
15
15
|
export {
|
|
16
16
|
useAttachSymbolToMap
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/map/hooks/use-attach-symbol-to-map.ts"],
|
|
4
|
-
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\nimport { isString
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAc;AACvB,SAAS,
|
|
4
|
+
"sourcesContent": ["import { useMap } from '@vis.gl/react-maplibre';\nimport { isString } from 'lodash-es';\nimport { useEffect, type ReactNode } from 'react';\n\nimport { useAttachImageFromIcon } from './use-attach-image-from-icon.js';\nimport type { MapShape } from '../types/shapes.js';\nimport { attachImageFromString } from '../utils/attach-image-from-string.js';\n\nexport const useAttachSymbolToMap = (\n icon: string | MapShape | ReactNode,\n suffix: string = '',\n outputSize?: number,\n) => {\n const { current: map } = useMap();\n const customIconAttached = useAttachImageFromIcon(icon, suffix, outputSize);\n\n useEffect(() => {\n if (!customIconAttached && isString(icon) && map) {\n attachImageFromString(map, icon, suffix, outputSize);\n }\n }, [customIconAttached, icon, map, outputSize, suffix]);\n};\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,iBAAiC;AAE1C,SAAS,8BAA8B;AAEvC,SAAS,6BAA6B;AAE/B,MAAM,uBAAuB,CAClC,MACA,SAAiB,IACjB,eACG;AACH,QAAM,EAAE,SAAS,IAAI,IAAI,OAAO;AAChC,QAAM,qBAAqB,uBAAuB,MAAM,QAAQ,UAAU;AAE1E,YAAU,MAAM;AACd,QAAI,CAAC,sBAAsB,SAAS,IAAI,KAAK,KAAK;AAChD,4BAAsB,KAAK,MAAM,QAAQ,UAAU;AAAA,IACrD;AAAA,EACF,GAAG,CAAC,oBAAoB,MAAM,KAAK,YAAY,MAAM,CAAC;AACxD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useRef
|
|
8
8
|
} from "react";
|
|
9
|
+
import { useOverlayTooltipSelector } from "./use-geo-tooltip-sync.js";
|
|
9
10
|
import {
|
|
10
11
|
getAssociatedFeatures,
|
|
11
12
|
hasAssociatedFeatures,
|
|
@@ -67,6 +68,10 @@ const useHoverInteraction = (interactiveLayerIds) => {
|
|
|
67
68
|
const sourceIdRef = useRef(void 0);
|
|
68
69
|
const interactiveLayerIdsRef = useRef(interactiveLayerIds);
|
|
69
70
|
interactiveLayerIdsRef.current = interactiveLayerIds;
|
|
71
|
+
const inBoundsFromStore = useOverlayTooltipSelector(
|
|
72
|
+
(state) => state.inBounds
|
|
73
|
+
);
|
|
74
|
+
const prevInBoundsRef = useRef(false);
|
|
70
75
|
const handleMouseOut = useCallback(() => {
|
|
71
76
|
if (!isNil(map)) {
|
|
72
77
|
blurTrackedFeature(map, featureIdRef, sourceIdRef);
|
|
@@ -107,6 +112,13 @@ const useHoverInteraction = (interactiveLayerIds) => {
|
|
|
107
112
|
),
|
|
108
113
|
[map]
|
|
109
114
|
);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const wasJustHidden = prevInBoundsRef.current && !inBoundsFromStore;
|
|
117
|
+
prevInBoundsRef.current = inBoundsFromStore;
|
|
118
|
+
if (wasJustHidden && !isNil(map)) {
|
|
119
|
+
blurTrackedFeature(map, featureIdRef, sourceIdRef);
|
|
120
|
+
}
|
|
121
|
+
}, [inBoundsFromStore, map]);
|
|
110
122
|
useEffect(() => {
|
|
111
123
|
map?.on("mousemove", throttledMouseMove);
|
|
112
124
|
map?.on("mouseout", handleMouseOut);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/map/hooks/use-hover-interaction.ts"],
|
|
4
|
-
"sourcesContent": ["import type { MapRef } from '@vis.gl/react-maplibre';\nimport { useMap } from '@vis.gl/react-maplibre';\nimport { isNil, isString, isUndefined, throttle } from 'lodash-es';\nimport type { MapLayerMouseEvent } from 'maplibre-gl';\nimport {\n type MutableRefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport {\n getAssociatedFeatures,\n hasAssociatedFeatures,\n isAssociatedFeature,\n} from '../utils/associated-features.js';\nimport { getMinValueFeature } from '../utils/get-min-value-feature.js';\n\n/** Minimum milliseconds between processed mousemove events (~1 frame at 60 fps). */\nconst MOUSEMOVE_THROTTLE_MS = 16;\n\nconst blurTrackedFeature = (\n map: MapRef,\n featureIdRef: MutableRefObject<string | number | undefined>,\n sourceIdRef: MutableRefObject<string | undefined>,\n) => {\n if (!isNil(featureIdRef.current) && !isNil(sourceIdRef.current)) {\n blurFeature(map, sourceIdRef.current, featureIdRef.current);\n featureIdRef.current = undefined;\n sourceIdRef.current = undefined;\n }\n};\n\n/**\n * Checks whether a feature exists from a given source\n *\n * @param map -\n * @param source -\n * @param id -\n */\nconst featureExists = (\n map: MapRef,\n source: string,\n id: string | number,\n): boolean => {\n const isSourcePresent = map.getSource(source) !== undefined;\n\n return (\n isSourcePresent &&\n map.getFeatureState({\n source,\n id,\n }) !== undefined\n );\n};\n\n/**\n * Removes hovered state from a feature and its associated features from a given source\n * @param map -\n * @param source -\n * @param id -\n */\nconst blurFeature = (map: MapRef, source: string, id: string | number) => {\n map.setFeatureState({ source, id }, { hover: false });\n\n if (isString(id) && hasAssociatedFeatures(id)) {\n for (const associatedFeature of getAssociatedFeatures('connection')) {\n const associatedSource = `${source}-direction`;\n const associatedId = `${id}-${associatedFeature}`;\n\n if (featureExists(map, associatedSource, associatedId)) {\n map.setFeatureState(\n { source: associatedSource, id: associatedId },\n {\n hover: false,\n },\n );\n }\n }\n }\n};\n\n/**\n * Sets hovered state to a feature and its associated features from a given source\n * @param map -\n * @param source -\n * @param id -\n */\nconst hoverFeature = (map: MapRef, source: string, id: string | number) => {\n map.setFeatureState({ source, id }, { hover: true });\n\n if (isString(id) && hasAssociatedFeatures(id)) {\n for (const associatedFeature of getAssociatedFeatures('connection')) {\n const associatedSource = `${source}-direction`;\n const associatedId = `${id}-${associatedFeature}`;\n\n if (featureExists(map, associatedSource, associatedId)) {\n map.setFeatureState(\n { source: `${source}-direction`, id: `${id}-${associatedFeature}` },\n {\n hover: true,\n },\n );\n }\n }\n }\n};\n\n/**\n * Sets and removes hovered state to the features depending on mouse position.\n *\n * @param interactiveLayerIds - maplibre-gl layer IDs to scope the feature query.\n * Scoping to data layers prevents touching base-map vector tiles whose internal\n * feature index may be in a transitional state (root cause of APPDEV-17854).\n */\nexport const useHoverInteraction = (interactiveLayerIds: string[]) => {\n const map = useMap().current;\n\n const featureIdRef = useRef<string | number | undefined>(undefined);\n const sourceIdRef = useRef<string | undefined>(undefined);\n\n // Keep a ref so the stable callback always sees the latest layer IDs even\n // if the consumer updates them after the first render.\n const interactiveLayerIdsRef = useRef(interactiveLayerIds);\n interactiveLayerIdsRef.current = interactiveLayerIds;\n\n const handleMouseOut = useCallback(() => {\n if (!isNil(map)) {\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n }\n }, [map]);\n\n const throttledMouseMove = useMemo(\n () =>\n throttle(\n ({ point }: MapLayerMouseEvent) => {\n if (isNil(map)) {\n return;\n }\n\n let features;\n try {\n features = map\n .queryRenderedFeatures(point, {\n layers: interactiveLayerIdsRef.current,\n })\n .filter((feature) => !isAssociatedFeature(feature.properties.id));\n } catch {\n return;\n }\n\n const layerId = features?.[0]?.layer?.id;\n const hasHoveredFeatures =\n !isNil(features) && features.length > 0 && !isUndefined(layerId);\n\n // No BASE_LAYER_IDS check needed: querying only interactiveLayerIds\n // already excludes base-map layers entirely.\n if (!hasHoveredFeatures) {\n map.getCanvas().style.cursor = 'grab';\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n return;\n }\n\n map.getCanvas().style.cursor = 'pointer';\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n\n const minFeature = getMinValueFeature(features);\n featureIdRef.current = minFeature.id;\n sourceIdRef.current = minFeature.layer.source;\n\n if (!isUndefined(featureIdRef.current)) {\n hoverFeature(map, sourceIdRef.current, featureIdRef.current);\n }\n },\n MOUSEMOVE_THROTTLE_MS,\n { trailing: true },\n ),\n [map],\n );\n\n useEffect(() => {\n map?.on('mousemove', throttledMouseMove);\n map?.on('mouseout', handleMouseOut);\n return () => {\n throttledMouseMove.cancel();\n map?.off('mousemove', throttledMouseMove);\n map?.off('mouseout', handleMouseOut);\n };\n }, [map, throttledMouseMove, handleMouseOut]);\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { MapRef } from '@vis.gl/react-maplibre';\nimport { useMap } from '@vis.gl/react-maplibre';\nimport { isNil, isString, isUndefined, throttle } from 'lodash-es';\nimport type { MapLayerMouseEvent } from 'maplibre-gl';\nimport {\n type MutableRefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport { useOverlayTooltipSelector } from './use-geo-tooltip-sync.js';\nimport {\n getAssociatedFeatures,\n hasAssociatedFeatures,\n isAssociatedFeature,\n} from '../utils/associated-features.js';\nimport { getMinValueFeature } from '../utils/get-min-value-feature.js';\n\n/** Minimum milliseconds between processed mousemove events (~1 frame at 60 fps). */\nconst MOUSEMOVE_THROTTLE_MS = 16;\n\nconst blurTrackedFeature = (\n map: MapRef,\n featureIdRef: MutableRefObject<string | number | undefined>,\n sourceIdRef: MutableRefObject<string | undefined>,\n) => {\n if (!isNil(featureIdRef.current) && !isNil(sourceIdRef.current)) {\n blurFeature(map, sourceIdRef.current, featureIdRef.current);\n featureIdRef.current = undefined;\n sourceIdRef.current = undefined;\n }\n};\n\n/**\n * Checks whether a feature exists from a given source\n *\n * @param map -\n * @param source -\n * @param id -\n */\nconst featureExists = (\n map: MapRef,\n source: string,\n id: string | number,\n): boolean => {\n const isSourcePresent = map.getSource(source) !== undefined;\n\n return (\n isSourcePresent &&\n map.getFeatureState({\n source,\n id,\n }) !== undefined\n );\n};\n\n/**\n * Removes hovered state from a feature and its associated features from a given source\n * @param map -\n * @param source -\n * @param id -\n */\nconst blurFeature = (map: MapRef, source: string, id: string | number) => {\n map.setFeatureState({ source, id }, { hover: false });\n\n if (isString(id) && hasAssociatedFeatures(id)) {\n for (const associatedFeature of getAssociatedFeatures('connection')) {\n const associatedSource = `${source}-direction`;\n const associatedId = `${id}-${associatedFeature}`;\n\n if (featureExists(map, associatedSource, associatedId)) {\n map.setFeatureState(\n { source: associatedSource, id: associatedId },\n {\n hover: false,\n },\n );\n }\n }\n }\n};\n\n/**\n * Sets hovered state to a feature and its associated features from a given source\n * @param map -\n * @param source -\n * @param id -\n */\nconst hoverFeature = (map: MapRef, source: string, id: string | number) => {\n map.setFeatureState({ source, id }, { hover: true });\n\n if (isString(id) && hasAssociatedFeatures(id)) {\n for (const associatedFeature of getAssociatedFeatures('connection')) {\n const associatedSource = `${source}-direction`;\n const associatedId = `${id}-${associatedFeature}`;\n\n if (featureExists(map, associatedSource, associatedId)) {\n map.setFeatureState(\n { source: `${source}-direction`, id: `${id}-${associatedFeature}` },\n {\n hover: true,\n },\n );\n }\n }\n }\n};\n\n/**\n * Sets and removes hovered state to the features depending on mouse position.\n *\n * @param interactiveLayerIds - maplibre-gl layer IDs to scope the feature query.\n * Scoping to data layers prevents touching base-map vector tiles whose internal\n * feature index may be in a transitional state (root cause of APPDEV-17854).\n */\nexport const useHoverInteraction = (interactiveLayerIds: string[]) => {\n const map = useMap().current;\n\n const featureIdRef = useRef<string | number | undefined>(undefined);\n const sourceIdRef = useRef<string | undefined>(undefined);\n\n // Keep a ref so the stable callback always sees the latest layer IDs even\n // if the consumer updates them after the first render.\n const interactiveLayerIdsRef = useRef(interactiveLayerIds);\n interactiveLayerIdsRef.current = interactiveLayerIds;\n\n const inBoundsFromStore = useOverlayTooltipSelector(\n (state) => state.inBounds,\n );\n const prevInBoundsRef = useRef(false);\n\n const handleMouseOut = useCallback(() => {\n if (!isNil(map)) {\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n }\n }, [map]);\n\n const throttledMouseMove = useMemo(\n () =>\n throttle(\n ({ point }: MapLayerMouseEvent) => {\n if (isNil(map)) {\n return;\n }\n\n let features;\n try {\n features = map\n .queryRenderedFeatures(point, {\n layers: interactiveLayerIdsRef.current,\n })\n .filter((feature) => !isAssociatedFeature(feature.properties.id));\n } catch {\n return;\n }\n\n const layerId = features?.[0]?.layer?.id;\n const hasHoveredFeatures =\n !isNil(features) && features.length > 0 && !isUndefined(layerId);\n\n // No BASE_LAYER_IDS check needed: querying only interactiveLayerIds\n // already excludes base-map layers entirely.\n if (!hasHoveredFeatures) {\n map.getCanvas().style.cursor = 'grab';\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n return;\n }\n\n map.getCanvas().style.cursor = 'pointer';\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n\n const minFeature = getMinValueFeature(features);\n featureIdRef.current = minFeature.id;\n sourceIdRef.current = minFeature.layer.source;\n\n if (!isUndefined(featureIdRef.current)) {\n hoverFeature(map, sourceIdRef.current, featureIdRef.current);\n }\n },\n MOUSEMOVE_THROTTLE_MS,\n { trailing: true },\n ),\n [map],\n );\n\n useEffect(() => {\n const wasJustHidden = prevInBoundsRef.current && !inBoundsFromStore;\n prevInBoundsRef.current = inBoundsFromStore;\n\n if (wasJustHidden && !isNil(map)) {\n blurTrackedFeature(map, featureIdRef, sourceIdRef);\n }\n }, [inBoundsFromStore, map]);\n\n useEffect(() => {\n map?.on('mousemove', throttledMouseMove);\n map?.on('mouseout', handleMouseOut);\n return () => {\n throttledMouseMove.cancel();\n map?.off('mousemove', throttledMouseMove);\n map?.off('mouseout', handleMouseOut);\n };\n }, [map, throttledMouseMove, handleMouseOut]);\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
1
|
+
import { useContext, useRef } from "react";
|
|
2
2
|
import {
|
|
3
3
|
_useOverlayTooltipReducer as useTooltipReducer,
|
|
4
4
|
_useOverlayTooltipStore as useOverlayTooltipStore,
|
|
@@ -27,6 +27,7 @@ const useOverlayEvents = () => {
|
|
|
27
27
|
const dispatch = useTooltipReducer();
|
|
28
28
|
const store = useOverlayTooltipStore();
|
|
29
29
|
const overlay = useOverlayChart();
|
|
30
|
+
const pinnedFeatureIdRef = useRef(void 0);
|
|
30
31
|
const getAbsolutePosition = (event) => {
|
|
31
32
|
return {
|
|
32
33
|
x: Math.round(event.originalEvent.clientX),
|
|
@@ -46,6 +47,23 @@ const useOverlayEvents = () => {
|
|
|
46
47
|
const hideTooltip = () => {
|
|
47
48
|
dispatch({ type: "RESET_TOOLTIP" })();
|
|
48
49
|
setTooltipMarker(void 0, void 0);
|
|
50
|
+
pinnedFeatureIdRef.current = void 0;
|
|
51
|
+
};
|
|
52
|
+
const showHoverTooltip = (event, data, hoveredLayerId) => {
|
|
53
|
+
overlay.clear();
|
|
54
|
+
setTooltipMarker(hoveredLayerId, event.lngLat);
|
|
55
|
+
const pos = getAbsolutePosition(event);
|
|
56
|
+
store.setState(
|
|
57
|
+
buildGeoTooltipState({
|
|
58
|
+
absoluteX: pos.x,
|
|
59
|
+
absoluteY: pos.y,
|
|
60
|
+
relativeX: Math.round(event.point.x),
|
|
61
|
+
relativeY: Math.round(event.point.y),
|
|
62
|
+
pinned: false,
|
|
63
|
+
geometry: layerIdToGeometry(hoveredLayerId),
|
|
64
|
+
metadata: data
|
|
65
|
+
})
|
|
66
|
+
);
|
|
49
67
|
};
|
|
50
68
|
const handleMouseEnter = () => {
|
|
51
69
|
setOverlayState({ mouseInBounds: true });
|
|
@@ -60,20 +78,7 @@ const useOverlayEvents = () => {
|
|
|
60
78
|
event,
|
|
61
79
|
dataLookupRegistry
|
|
62
80
|
);
|
|
63
|
-
|
|
64
|
-
setTooltipMarker(hoveredLayerId, event.lngLat);
|
|
65
|
-
const pos = getAbsolutePosition(event);
|
|
66
|
-
store.setState(
|
|
67
|
-
buildGeoTooltipState({
|
|
68
|
-
absoluteX: pos.x,
|
|
69
|
-
absoluteY: pos.y,
|
|
70
|
-
relativeX: Math.round(event.point.x),
|
|
71
|
-
relativeY: Math.round(event.point.y),
|
|
72
|
-
pinned: false,
|
|
73
|
-
geometry: layerIdToGeometry(hoveredLayerId),
|
|
74
|
-
metadata: data
|
|
75
|
-
})
|
|
76
|
-
);
|
|
81
|
+
showHoverTooltip(event, data ?? [], hoveredLayerId);
|
|
77
82
|
} else {
|
|
78
83
|
const currentState = store.getState();
|
|
79
84
|
if (!currentState.pinned) {
|
|
@@ -101,8 +106,15 @@ const useOverlayEvents = () => {
|
|
|
101
106
|
hideTooltip();
|
|
102
107
|
return;
|
|
103
108
|
}
|
|
109
|
+
const currentState = store.getState();
|
|
110
|
+
if (currentState.pinned && pinnedFeatureIdRef.current === featureId) {
|
|
111
|
+
pinnedFeatureIdRef.current = void 0;
|
|
112
|
+
showHoverTooltip(event, data ?? [], hoveredLayerId);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
104
115
|
overlay.clear();
|
|
105
116
|
setTooltipMarker(hoveredLayerId, event.lngLat);
|
|
117
|
+
pinnedFeatureIdRef.current = featureId;
|
|
106
118
|
const pos = getAbsolutePosition(event);
|
|
107
119
|
store.setState(
|
|
108
120
|
buildGeoTooltipState({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/map/hooks/use-overlay-events.ts"],
|
|
4
|
-
"sourcesContent": ["import type { MapLayerMouseEvent } from 'maplibre-gl';\nimport { useContext } from 'react';\n\nimport {\n _useOverlayTooltipReducer as useTooltipReducer,\n _useOverlayTooltipStore as useOverlayTooltipStore,\n _useOverlayChart as useOverlayChart,\n type _OverlayTooltipGeometryTypes as GeometryTypes,\n} from '@dynatrace/strato-components/charts';\n\nimport { GeoDataLookupContext } from '../contexts/geo-data-lookup.context.js';\nimport { useSetStateOverlay, useSetState } from '../store/store.js';\nimport { buildGeoTooltipState } from '../utils/build-geo-tooltip-state.js';\nimport { extractDataFromEvent } from '../utils/parse-tooltip-data.js';\n\nconst layerIdToGeometry = (layerId: string | undefined): GeometryTypes => {\n if (layerId?.includes('bubble')) {\n return 'geoBubble';\n }\n if (layerId?.includes('choropleth')) {\n return 'geoChoropleth';\n }\n if (layerId?.includes('connection')) {\n return 'geoConnection';\n }\n return 'geoDot';\n};\n\nexport const useOverlayEvents = () => {\n const dataLookupRegistry = useContext(GeoDataLookupContext);\n const setOverlayState = useSetStateOverlay();\n const setState = useSetState();\n const dispatch = useTooltipReducer();\n const store = useOverlayTooltipStore();\n const overlay = useOverlayChart();\n\n const getAbsolutePosition = (event: MapLayerMouseEvent) => {\n return {\n x: Math.round(event.originalEvent.clientX),\n y: Math.round(event.originalEvent.clientY),\n };\n };\n\n const setTooltipMarker = (\n hoveredLayerId: string | undefined,\n location: { lng: number; lat: number } | undefined,\n ) => {\n setState((prev) => ({\n ...prev,\n tooltip: {\n ...prev.tooltip,\n hoveredLayerId,\n location: location as typeof prev.tooltip.location,\n },\n }));\n };\n\n const hideTooltip = () => {\n dispatch({ type: 'RESET_TOOLTIP' })();\n setTooltipMarker(undefined, undefined);\n
|
|
5
|
-
"mappings": "AACA,SAAS,
|
|
4
|
+
"sourcesContent": ["import type { MapLayerMouseEvent } from 'maplibre-gl';\nimport { useContext, useRef } from 'react';\n\nimport {\n _useOverlayTooltipReducer as useTooltipReducer,\n _useOverlayTooltipStore as useOverlayTooltipStore,\n _useOverlayChart as useOverlayChart,\n type _OverlayTooltipGeometryTypes as GeometryTypes,\n} from '@dynatrace/strato-components/charts';\n\nimport { GeoDataLookupContext } from '../contexts/geo-data-lookup.context.js';\nimport { useSetStateOverlay, useSetState } from '../store/store.js';\nimport { buildGeoTooltipState } from '../utils/build-geo-tooltip-state.js';\nimport { extractDataFromEvent } from '../utils/parse-tooltip-data.js';\n\nconst layerIdToGeometry = (layerId: string | undefined): GeometryTypes => {\n if (layerId?.includes('bubble')) {\n return 'geoBubble';\n }\n if (layerId?.includes('choropleth')) {\n return 'geoChoropleth';\n }\n if (layerId?.includes('connection')) {\n return 'geoConnection';\n }\n return 'geoDot';\n};\n\nexport const useOverlayEvents = () => {\n const dataLookupRegistry = useContext(GeoDataLookupContext);\n const setOverlayState = useSetStateOverlay();\n const setState = useSetState();\n const dispatch = useTooltipReducer();\n const store = useOverlayTooltipStore();\n const overlay = useOverlayChart();\n const pinnedFeatureIdRef = useRef<string | undefined>(undefined);\n\n const getAbsolutePosition = (event: MapLayerMouseEvent) => {\n return {\n x: Math.round(event.originalEvent.clientX),\n y: Math.round(event.originalEvent.clientY),\n };\n };\n\n const setTooltipMarker = (\n hoveredLayerId: string | undefined,\n location: { lng: number; lat: number } | undefined,\n ) => {\n setState((prev) => ({\n ...prev,\n tooltip: {\n ...prev.tooltip,\n hoveredLayerId,\n location: location as typeof prev.tooltip.location,\n },\n }));\n };\n\n const hideTooltip = () => {\n dispatch({ type: 'RESET_TOOLTIP' })();\n setTooltipMarker(undefined, undefined);\n pinnedFeatureIdRef.current = undefined;\n };\n\n const showHoverTooltip = (\n event: MapLayerMouseEvent,\n data: unknown[],\n hoveredLayerId: string | undefined,\n ) => {\n overlay.clear();\n\n setTooltipMarker(hoveredLayerId, event.lngLat);\n\n const pos = getAbsolutePosition(event);\n store.setState(\n buildGeoTooltipState({\n absoluteX: pos.x,\n absoluteY: pos.y,\n relativeX: Math.round(event.point.x),\n relativeY: Math.round(event.point.y),\n pinned: false,\n geometry: layerIdToGeometry(hoveredLayerId),\n metadata: data,\n }),\n );\n };\n\n const handleMouseEnter = () => {\n setOverlayState({ mouseInBounds: true });\n };\n\n const handleMouseMove = (event: MapLayerMouseEvent) => {\n if (event.features?.length) {\n const currentState = store.getState();\n\n if (currentState.pinned) {\n return;\n }\n\n const { data, hoveredLayerId } = extractDataFromEvent(\n event,\n dataLookupRegistry,\n );\n\n showHoverTooltip(event, data ?? [], hoveredLayerId);\n } else {\n const currentState = store.getState();\n if (!currentState.pinned) {\n hideTooltip();\n }\n }\n };\n\n const handleMouseLeave = () => {\n setOverlayState({ mouseInBounds: false });\n const currentState = store.getState();\n if (!currentState.pinned) {\n hideTooltip();\n }\n };\n\n const handleMouseClick = (event: MapLayerMouseEvent) => {\n const { data, featureId, hoveredLayerId } = extractDataFromEvent(\n event,\n dataLookupRegistry,\n );\n\n if (!featureId) {\n hideTooltip();\n return;\n }\n\n if (!hoveredLayerId) {\n hideTooltip();\n return;\n }\n\n const currentState = store.getState();\n if (currentState.pinned && pinnedFeatureIdRef.current === featureId) {\n // Unpin and immediately fall back to the hover tooltip so the feature\n // stays described while the pointer is still over it (mirrors the chart\n // OverlayTooltip behaviour on non-touch devices).\n pinnedFeatureIdRef.current = undefined;\n showHoverTooltip(event, data ?? [], hoveredLayerId);\n return;\n }\n\n overlay.clear();\n\n setTooltipMarker(hoveredLayerId, event.lngLat);\n pinnedFeatureIdRef.current = featureId;\n\n const pos = getAbsolutePosition(event);\n store.setState(\n buildGeoTooltipState({\n absoluteX: pos.x,\n absoluteY: pos.y,\n relativeX: Math.round(event.point.x),\n relativeY: Math.round(event.point.y),\n pinned: true,\n geometry: layerIdToGeometry(hoveredLayerId),\n metadata: data,\n }),\n );\n };\n\n const hideTooltipIfNotPinned = () => {\n const currentState = store.getState();\n if (!currentState.pinned) {\n hideTooltip();\n }\n };\n\n return {\n handleMouseEnter,\n handleMouseMove,\n handleMouseLeave,\n handleMouseClick,\n handleZoom: hideTooltipIfNotPinned,\n handleDrag: hideTooltipIfNotPinned,\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_SYMBOL_SIZE } from "../constants.js";
|
|
2
|
-
const getScaledSymbolSize = (symbolSize = DEFAULT_SYMBOL_SIZE) => symbolSize * devicePixelRatio;
|
|
2
|
+
const getScaledSymbolSize = (symbolSize = DEFAULT_SYMBOL_SIZE) => Math.max(1, symbolSize * devicePixelRatio);
|
|
3
3
|
export {
|
|
4
4
|
getScaledSymbolSize
|
|
5
5
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/map/utils/get-scaled-symbol-size.ts"],
|
|
4
|
-
"sourcesContent": ["import { DEFAULT_SYMBOL_SIZE } from '../constants.js';\n\n/** Scales the icon size by dpr to preserve the size on HiDPI screens */\nexport const getScaledSymbolSize = (symbolSize = DEFAULT_SYMBOL_SIZE) =>\n symbolSize * devicePixelRatio;\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,2BAA2B;AAG7B,MAAM,sBAAsB,CAAC,aAAa,wBAC/C,aAAa;",
|
|
4
|
+
"sourcesContent": ["import { DEFAULT_SYMBOL_SIZE } from '../constants.js';\n\n/** Scales the icon size by dpr to preserve the size on HiDPI screens */\nexport const getScaledSymbolSize = (symbolSize = DEFAULT_SYMBOL_SIZE) =>\n Math.max(1, symbolSize * devicePixelRatio);\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAA2B;AAG7B,MAAM,sBAAsB,CAAC,aAAa,wBAC/C,KAAK,IAAI,GAAG,aAAa,gBAAgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/lang/uncompiled/en.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"11eDVd4MkXWk8Bok": {
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"translation": "Zoom in",
|
|
4
|
+
"notes": "Text in a tooltip shown when hovering the \"Zoom in\" action in the geo map chart toolbar"
|
|
5
5
|
},
|
|
6
6
|
"5DTYqeFoXLMaXUHR": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"translation": "Zoom out",
|
|
8
|
+
"notes": "Text in a tooltip shown when hovering the \"Zoom out\" action in the geo map chart toolbar"
|
|
9
9
|
},
|
|
10
10
|
"EhcEZc1+Xc9wYsXg": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"translation": "Zoom to fit",
|
|
12
|
+
"notes": "Text in a tooltip shown when hovering the \"Zoom to fit\" action in the geo map chart toolbar"
|
|
13
13
|
},
|
|
14
14
|
"TXZNTtB5qReH8XE3": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"translation": "Maps have been disabled in your environment - to enable these, please reach out to your environment administrator.",
|
|
16
|
+
"notes": "Message to display when all maps are hidden."
|
|
17
17
|
},
|
|
18
18
|
"g2qESM+ibB5sUyjf": {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"translation": "Copy coordinates",
|
|
20
|
+
"notes": "Text in a tooltip shown when hovering a tooltip item and hovering the copy button next to its coordinates"
|
|
21
21
|
},
|
|
22
22
|
"hVE7x0G0Q7/ih93A": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"translation": "Reset",
|
|
24
|
+
"notes": "Text in a tooltip shown when hovering the \"Reset\" action in the geo map chart toolbar"
|
|
25
25
|
},
|
|
26
26
|
"hlwWx47trlDMOy+V": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"translation": "Download Data as CSV",
|
|
28
|
+
"notes": "Text in a tooltip shown when hovering the \"Download Data\" action in the geo map chart toolbar"
|
|
29
29
|
},
|
|
30
30
|
"tS7epcIvtrbIhuBy": {
|
|
31
|
-
"
|
|
32
|
-
"
|
|
31
|
+
"translation": "An error occurred: Failed to load map data.",
|
|
32
|
+
"notes": "Message to display when an error occurred while getting map data."
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -86,7 +86,7 @@ const MapToolbar = () => {
|
|
|
86
86
|
initialA11yState: import_constants.initialA11yToolbarTabIndexContext,
|
|
87
87
|
onMouseEnter: handleMouseEnter,
|
|
88
88
|
onMouseLeave: handleMouseLeave,
|
|
89
|
-
menuDisplayMode: "
|
|
89
|
+
menuDisplayMode: "collapsed",
|
|
90
90
|
children: [
|
|
91
91
|
isZoomEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_MapZoomInOutButtons.MapZoomInOutButtons, {}),
|
|
92
92
|
isZoomEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_MapResetButton.MapResetButton, {}),
|
|
@@ -24,13 +24,38 @@ module.exports = __toCommonJS(use_active_interaction_exports);
|
|
|
24
24
|
var import_react_maplibre = require("@vis.gl/react-maplibre");
|
|
25
25
|
var import_lodash_es = require("lodash");
|
|
26
26
|
var import_react = require("react");
|
|
27
|
+
var import_use_geo_tooltip_sync = require("./use-geo-tooltip-sync.js");
|
|
27
28
|
var import_get_min_value_feature = require("../utils/get-min-value-feature.js");
|
|
28
29
|
const useActiveInteraction = (interactiveLayerIds) => {
|
|
29
30
|
const map = (0, import_react_maplibre.useMap)().current;
|
|
30
31
|
const featureIdRef = (0, import_react.useRef)(void 0);
|
|
31
32
|
const sourceIdRef = (0, import_react.useRef)(void 0);
|
|
33
|
+
const pinnedFromStore = (0, import_use_geo_tooltip_sync.useOverlayTooltipSelector)((state) => state.pinned);
|
|
34
|
+
const prevPinnedRef = (0, import_react.useRef)(false);
|
|
32
35
|
const interactiveLayerIdsRef = (0, import_react.useRef)(interactiveLayerIds);
|
|
33
36
|
interactiveLayerIdsRef.current = interactiveLayerIds;
|
|
37
|
+
const clearActiveFeatureStates = () => {
|
|
38
|
+
if (!(0, import_lodash_es.isUndefined)(featureIdRef.current) && !(0, import_lodash_es.isUndefined)(sourceIdRef.current)) {
|
|
39
|
+
map.setFeatureState(
|
|
40
|
+
{ source: sourceIdRef.current, id: featureIdRef.current },
|
|
41
|
+
{ active: false }
|
|
42
|
+
);
|
|
43
|
+
try {
|
|
44
|
+
const allFeatures = map.queryRenderedFeatures(void 0, {
|
|
45
|
+
layers: interactiveLayerIdsRef.current
|
|
46
|
+
});
|
|
47
|
+
allFeatures.forEach((feature) => {
|
|
48
|
+
if (feature.id !== void 0 && feature.layer.source !== void 0) {
|
|
49
|
+
map.setFeatureState(
|
|
50
|
+
{ source: feature.layer.source, id: feature.id },
|
|
51
|
+
{ isAnyActive: false }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
34
59
|
const handleClick = (0, import_react.useCallback)(
|
|
35
60
|
({ point }) => {
|
|
36
61
|
let features = [];
|
|
@@ -93,30 +118,20 @@ const useActiveInteraction = (interactiveLayerIds) => {
|
|
|
93
118
|
const onEscapeKeyPressedHandler = (0, import_react.useCallback)(
|
|
94
119
|
(event) => {
|
|
95
120
|
if (event.key === "Escape") {
|
|
96
|
-
|
|
97
|
-
map.setFeatureState(
|
|
98
|
-
{ source: sourceIdRef.current, id: featureIdRef.current },
|
|
99
|
-
{ active: false }
|
|
100
|
-
);
|
|
101
|
-
try {
|
|
102
|
-
const allFeatures = map.queryRenderedFeatures(void 0, {
|
|
103
|
-
layers: interactiveLayerIdsRef.current
|
|
104
|
-
});
|
|
105
|
-
allFeatures.forEach((feature) => {
|
|
106
|
-
if (feature.id !== void 0 && feature.layer.source !== void 0) {
|
|
107
|
-
map.setFeatureState(
|
|
108
|
-
{ source: feature.layer.source, id: feature.id },
|
|
109
|
-
{ isAnyActive: false }
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
} catch {
|
|
114
|
-
}
|
|
115
|
-
}
|
|
121
|
+
clearActiveFeatureStates();
|
|
116
122
|
}
|
|
117
123
|
},
|
|
118
124
|
[map]
|
|
119
125
|
);
|
|
126
|
+
(0, import_react.useEffect)(() => {
|
|
127
|
+
const wasJustUnpinned = prevPinnedRef.current && !pinnedFromStore;
|
|
128
|
+
prevPinnedRef.current = pinnedFromStore;
|
|
129
|
+
if (wasJustUnpinned) {
|
|
130
|
+
clearActiveFeatureStates();
|
|
131
|
+
featureIdRef.current = void 0;
|
|
132
|
+
sourceIdRef.current = void 0;
|
|
133
|
+
}
|
|
134
|
+
}, [pinnedFromStore, map]);
|
|
120
135
|
(0, import_react.useEffect)(() => {
|
|
121
136
|
map.on("click", handleClick);
|
|
122
137
|
window.addEventListener("keyup", onEscapeKeyPressedHandler);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { MapShape } from '../types/shapes.js';
|
|
3
|
-
export declare const useAttachSymbolToMap: (icon: string | MapShape | ReactNode, suffix?: string, outputSize?: number) =>
|
|
3
|
+
export declare const useAttachSymbolToMap: (icon: string | MapShape | ReactNode, suffix?: string, outputSize?: number) => void;
|
|
@@ -23,15 +23,15 @@ __export(use_attach_symbol_to_map_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(use_attach_symbol_to_map_exports);
|
|
24
24
|
var import_react_maplibre = require("@vis.gl/react-maplibre");
|
|
25
25
|
var import_lodash_es = require("lodash");
|
|
26
|
+
var import_react = require("react");
|
|
26
27
|
var import_use_attach_image_from_icon = require("./use-attach-image-from-icon.js");
|
|
27
28
|
var import_attach_image_from_string = require("../utils/attach-image-from-string.js");
|
|
28
29
|
const useAttachSymbolToMap = (icon, suffix = "", outputSize) => {
|
|
29
30
|
const { current: map } = (0, import_react_maplibre.useMap)();
|
|
30
31
|
const customIconAttached = (0, import_use_attach_image_from_icon.useAttachImageFromIcon)(icon, suffix, outputSize);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
32
|
+
(0, import_react.useEffect)(() => {
|
|
33
|
+
if (!customIconAttached && (0, import_lodash_es.isString)(icon) && map) {
|
|
34
|
+
(0, import_attach_image_from_string.attachImageFromString)(map, icon, suffix, outputSize);
|
|
35
|
+
}
|
|
36
|
+
}, [customIconAttached, icon, map, outputSize, suffix]);
|
|
37
37
|
};
|