@defra/interactive-map 0.0.21-alpha → 0.0.23-alpha
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/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +39 -0
- package/docs/api/context.md +4 -4
- package/docs/api/map-style-config.md +11 -2
- package/docs/api/marker-config.md +38 -7
- package/docs/api/panel-definition.md +7 -3
- package/docs/api/symbol-config.md +10 -26
- package/docs/api/symbol-registry.md +28 -13
- package/docs/api.md +65 -4
- package/docs/assets/css/{docusaurus.css → docusaurus.scss} +18 -12
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/examples/index.mdx +12 -0
- package/docs/examples/toggle-marker-label.mdx +89 -0
- package/docs/plugins/datasets.md +39 -1
- package/docs/plugins/interact.md +4 -11
- package/docusaurus.config.cjs +2 -1
- package/package.json +10 -12
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +1 -0
- package/plugins/beta/datasets/dist/css/index.css +2 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +2 -0
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +2 -0
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +2 -0
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +5 -5
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +24 -29
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +2 -2
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +3 -3
- package/plugins/beta/datasets/src/components/KeySvg.jsx +2 -2
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +1 -2
- package/plugins/beta/datasets/src/components/svgProperties.js +2 -2
- package/plugins/beta/datasets/src/index.maplibre.umd.js +23 -0
- package/plugins/beta/datasets/src/panels/Key.module.scss +2 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +1 -0
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +23 -55
- package/plugins/interact/src/InteractInit.test.js +58 -80
- package/plugins/interact/src/defaults.js +1 -3
- package/plugins/interact/src/hooks/useAttachEvents.js +46 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +65 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +47 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +54 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +30 -8
- package/plugins/interact/src/hooks/useHighlightSync.test.js +127 -80
- package/plugins/interact/src/hooks/useInteractionHandlers.js +18 -17
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +22 -61
- package/plugins/interact/src/hooks/useMapItemList.js +187 -0
- package/plugins/interact/src/hooks/useMapItemList.test.js +584 -0
- package/plugins/interact/src/index.js +0 -2
- package/plugins/interact/src/index.test.js +0 -2
- package/plugins/interact/src/manifest.js +37 -6
- package/plugins/interact/src/reducer.js +8 -4
- package/plugins/interact/src/reducer.test.js +18 -3
- package/plugins/interact/src/utils/buildStylesMap.js +21 -10
- package/plugins/interact/src/utils/buildStylesMap.test.js +29 -29
- package/plugins/interact/src/utils/spatial.js +0 -15
- package/plugins/interact/src/utils/spatial.test.js +1 -13
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/src/components/CloseButton/CloseButton.module.scss +2 -0
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +1 -1
- package/plugins/search/src/components/OpenButton/OpenButton.module.scss +3 -0
- package/plugins/search/src/search.scss +1 -0
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/index.js +7 -1
- package/providers/maplibre/src/index.test.js +15 -2
- package/providers/maplibre/src/mapEvents.js +11 -5
- package/providers/maplibre/src/mapEvents.test.js +14 -3
- package/providers/maplibre/src/maplibreProvider.js +18 -11
- package/providers/maplibre/src/maplibreProvider.test.js +43 -36
- package/providers/maplibre/src/utils/highlightFeatures.js +102 -57
- package/providers/maplibre/src/utils/highlightFeatures.test.js +162 -51
- package/providers/maplibre/src/utils/maplibreFixes.js +10 -0
- package/providers/maplibre/src/utils/maplibreFixes.test.js +31 -0
- package/providers/maplibre/src/utils/patternImages.js +10 -12
- package/providers/maplibre/src/utils/patternImages.test.js +145 -167
- package/providers/maplibre/src/utils/rasteriseToImageData.js +1 -4
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +23 -24
- package/providers/maplibre/src/utils/symbolImages.js +23 -86
- package/providers/maplibre/src/utils/symbolImages.test.js +60 -105
- package/rollup.esm.mjs +1 -1
- package/src/App/components/Actions/Actions.module.scss +1 -0
- package/src/App/components/Actions/Actions.test.jsx +17 -4
- package/src/App/components/Hints/Hints.jsx +52 -0
- package/src/App/components/Hints/Hints.module.scss +37 -0
- package/src/App/components/Hints/Hints.test.jsx +104 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +57 -19
- package/src/App/components/KeyboardHelp/KeyboardHelp.module.scss +10 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +140 -31
- package/src/App/components/Markers/LabelMarker.jsx +14 -0
- package/src/App/components/Markers/LabelMarker.test.jsx +36 -0
- package/src/App/components/Markers/Markers.jsx +44 -75
- package/src/App/components/Markers/Markers.module.scss +58 -0
- package/src/App/components/Markers/Markers.test.jsx +90 -113
- package/src/App/components/Markers/SymbolLabelMarker.jsx +43 -0
- package/src/App/components/Markers/SymbolLabelMarker.test.jsx +56 -0
- package/src/App/components/Markers/SymbolMarker.jsx +31 -0
- package/src/App/components/Markers/SymbolMarker.test.jsx +72 -0
- package/src/App/components/Panel/Panel.jsx +1 -1
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +117 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +132 -85
- package/src/App/components/Tabs/Tabs.jsx +68 -0
- package/src/App/components/Tabs/Tabs.module.scss +57 -0
- package/src/App/components/Tabs/Tabs.test.jsx +121 -0
- package/src/App/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/App/components/Viewport/Features.jsx +35 -0
- package/src/App/components/Viewport/Features.module.scss +31 -0
- package/src/App/components/Viewport/Features.test.jsx +124 -0
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +0 -4
- package/src/App/components/Viewport/Viewport.jsx +35 -29
- package/src/App/components/Viewport/Viewport.module.scss +7 -21
- package/src/App/components/Viewport/Viewport.test.jsx +108 -65
- package/src/App/controls/keyboardActions.js +9 -3
- package/src/App/controls/keyboardActions.test.js +122 -87
- package/src/App/controls/keyboardShortcuts.js +1 -14
- package/src/App/hooks/useFeatureFocus.js +199 -0
- package/src/App/hooks/useFeatureFocus.test.js +635 -0
- package/src/App/hooks/useFeatureItems.js +30 -0
- package/src/App/hooks/useFeatureItems.test.js +97 -0
- package/src/App/hooks/useFocusVisible.js +4 -7
- package/src/App/hooks/useFocusVisible.test.js +6 -6
- package/src/App/hooks/useKeyboardHint.js +9 -34
- package/src/App/hooks/useKeyboardHint.test.js +34 -84
- package/src/App/hooks/useKeyboardShortcuts.js +8 -5
- package/src/App/hooks/useKeyboardShortcuts.test.js +16 -12
- package/src/App/hooks/useLayoutMeasurements.js +10 -4
- package/src/App/hooks/useLayoutMeasurements.test.js +30 -0
- package/src/App/hooks/useMapURLSync.js +3 -3
- package/src/App/hooks/useMapURLSync.test.js +20 -3
- package/src/App/hooks/useMarkerCursor.js +33 -0
- package/src/App/hooks/useMarkerCursor.test.js +87 -0
- package/src/App/hooks/useMarkersAPI.js +32 -9
- package/src/App/hooks/useMarkersAPI.test.js +162 -94
- package/src/App/layout/Layout.jsx +2 -0
- package/src/App/layout/Layout.test.jsx +3 -0
- package/src/App/registry/keyboardShortcutRegistry.js +3 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +6 -8
- package/src/App/renderer/mapButtons.js +5 -1
- package/src/App/renderer/mapButtons.test.js +37 -1
- package/src/App/store/ServiceProvider.jsx +4 -1
- package/src/App/store/ServiceProvider.test.jsx +11 -0
- package/src/App/store/appActionsMap.js +5 -1
- package/src/App/store/appActionsMap.test.js +13 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +13 -1
- package/src/InteractiveMap/InteractiveMap.test.js +6 -4
- package/src/InteractiveMap/behaviourController.js +1 -2
- package/src/InteractiveMap/buttonManager.js +1 -3
- package/src/InteractiveMap/buttonManager.test.js +16 -8
- package/src/InteractiveMap/domStateManager.js +2 -3
- package/src/InteractiveMap/historyManager.js +4 -3
- package/src/config/appConfig.js +3 -1
- package/src/config/appConfig.test.js +4 -0
- package/src/config/defaults.js +3 -2
- package/src/config/events.js +8 -0
- package/src/config/mapTheme.js +8 -4
- package/src/config/mergeConfig.js +8 -2
- package/src/config/mergeConfig.test.js +19 -0
- package/src/config/symbolConfig.js +23 -16
- package/src/scss/main.scss +3 -0
- package/src/scss/tools/_border-focus.scss +0 -1
- package/src/services/eventBus.js +15 -0
- package/src/services/eventBus.test.js +11 -0
- package/src/services/hints.js +47 -0
- package/src/services/hints.test.js +110 -0
- package/src/services/patternRegistry.js +69 -1
- package/src/services/patternRegistry.test.js +112 -1
- package/src/services/reverseGeocode.js +4 -0
- package/src/services/reverseGeocode.test.js +11 -1
- package/src/services/symbolRegistry.js +152 -17
- package/src/services/symbolRegistry.test.js +141 -31
- package/src/types.js +93 -16
- package/src/utils/constrainKeyboardFocus.js +5 -5
- package/src/utils/detectInterfaceType.js +27 -5
- package/src/utils/detectInterfaceType.test.js +29 -3
- package/src/utils/mapStateSync.js +2 -2
- package/src/utils/mapStateSync.test.js +18 -0
- package/src/utils/patternUtils.js +4 -67
- package/src/utils/patternUtils.test.js +1 -123
- package/src/utils/symbolUtils.js +11 -13
- package/src/utils/symbolUtils.test.js +18 -26
- package/webpack.dev.mjs +4 -0
- package/webpack.umd.mjs +9 -6
- package/plugins/beta/datasets/adapters/maplibre/dist/esm/im-datasets-ml-adapter.js +0 -1
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/im-datasets-ml-adapter.js +0 -2
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/index.js +0 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +0 -2
- package/plugins/beta/datasets/dist/umd/index.js +0 -2
- package/plugins/interact/dist/css/index.css +0 -1
- package/plugins/interact/src/interact.scss +0 -24
- /package/plugins/beta/datasets/{adapters/maplibre/dist → dist/adapters/maplibre}/esm/index.js +0 -0
- /package/plugins/beta/datasets/{adapters/maplibre/dist/umd/im-datasets-ml-adapter.js.LICENSE.txt → dist/umd/maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{im-datasets-plugin.js.LICENSE.txt → maplibre/im-datasets-plugin.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{index.js.LICENSE.txt → maplibre/index.js.LICENSE.txt} +0 -0
|
@@ -4,9 +4,10 @@ import { attachAppEvents } from './appEvents.js'
|
|
|
4
4
|
import { createMapLabelNavigator } from './utils/labels.js'
|
|
5
5
|
import { updateHighlightedFeatures } from './utils/highlightFeatures.js'
|
|
6
6
|
import { queryFeatures } from './utils/queryFeatures.js'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { addSymbolsToMap } from './utils/symbolImages.js'
|
|
8
|
+
import { addPatternsToMap } from './utils/patternImages.js'
|
|
9
9
|
import { getAreaDimensions, getCardinalMove, getResolution, getPaddedBounds, isGeometryObscured } from './utils/spatial.js'
|
|
10
|
+
import { symbolRegistry } from '../../../src/services/symbolRegistry.js'
|
|
10
11
|
|
|
11
12
|
jest.mock('./defaults.js', () => ({
|
|
12
13
|
DEFAULTS: { animationDuration: 400, coordinatePrecision: 7 },
|
|
@@ -35,8 +36,8 @@ jest.mock('./utils/labels.js', () => ({
|
|
|
35
36
|
}))
|
|
36
37
|
jest.mock('./utils/highlightFeatures.js', () => ({ updateHighlightedFeatures: jest.fn(() => []) }))
|
|
37
38
|
jest.mock('./utils/queryFeatures.js', () => ({ queryFeatures: jest.fn(() => []) }))
|
|
38
|
-
jest.mock('./utils/symbolImages.js', () => ({
|
|
39
|
-
jest.mock('./utils/patternImages.js', () => ({
|
|
39
|
+
jest.mock('./utils/symbolImages.js', () => ({ addSymbolsToMap: jest.fn(() => Promise.resolve()) }))
|
|
40
|
+
jest.mock('./utils/patternImages.js', () => ({ addPatternsToMap: jest.fn(() => Promise.resolve()) }))
|
|
40
41
|
|
|
41
42
|
describe('MapLibreProvider', () => {
|
|
42
43
|
let map, eventBus, maplibreModule, loadCallback
|
|
@@ -244,69 +245,75 @@ describe('MapLibreProvider', () => {
|
|
|
244
245
|
await doInitMap(p)
|
|
245
246
|
p.getFeaturesAtPoint({ x: 10, y: 20 }, { radius: 5 })
|
|
246
247
|
expect(queryFeatures).toHaveBeenCalledWith(map, { x: 10, y: 20 }, { radius: 5 })
|
|
247
|
-
p.updateHighlightedFeatures(['feat'], { style: 1 })
|
|
248
|
+
p.updateHighlightedFeatures(['feat'], null, { style: 1 })
|
|
248
249
|
expect(updateHighlightedFeatures).toHaveBeenCalledWith({
|
|
249
|
-
LngLatBounds: maplibreModule.LngLatBounds, map, selectedFeatures: ['feat'], stylesMap: { style: 1 }
|
|
250
|
+
LngLatBounds: maplibreModule.LngLatBounds, map, selectedFeatures: ['feat'], activeFeatures: null, stylesMap: { style: 1 }
|
|
250
251
|
})
|
|
251
252
|
})
|
|
252
253
|
|
|
253
|
-
test('
|
|
254
|
+
test('getVisibleFeatures returns empty array when no layers are present on the map', async () => {
|
|
255
|
+
const p = makeProvider()
|
|
256
|
+
await doInitMap(p)
|
|
257
|
+
map.getLayer.mockReturnValue(null)
|
|
258
|
+
expect(p.getVisibleFeatures(['l1', 'l2'])).toEqual([])
|
|
259
|
+
expect(map.queryRenderedFeatures).not.toHaveBeenCalled()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('getVisibleFeatures queries only layers present on the map', async () => {
|
|
263
|
+
const p = makeProvider()
|
|
264
|
+
await doInitMap(p)
|
|
265
|
+
map.getLayer.mockImplementation(id => id === 'l1' ? {} : null) // NOSONAR
|
|
266
|
+
const features = [{ id: 'f1' }]
|
|
267
|
+
map.queryRenderedFeatures.mockReturnValue(features)
|
|
268
|
+
expect(p.getVisibleFeatures(['l1', 'l2'])).toEqual(features)
|
|
269
|
+
expect(map.queryRenderedFeatures).toHaveBeenCalledWith(undefined, { layers: ['l1'] })
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('addSymbolsToMap delegates to utility with map instance', async () => {
|
|
254
273
|
const p = makeProvider()
|
|
255
274
|
await doInitMap(p)
|
|
256
275
|
const configs = [{ symbol: 'pin' }]
|
|
257
276
|
const mapStyle = { id: 'test', selectedColor: '#0b0c0c' }
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
expect(registerSymbols).toHaveBeenCalledWith(map, configs, mapStyle, registry, expect.any(Number))
|
|
277
|
+
await p.addSymbolsToMap(configs, mapStyle, symbolRegistry)
|
|
278
|
+
expect(addSymbolsToMap).toHaveBeenCalledWith(map, configs, mapStyle, symbolRegistry, expect.any(Number))
|
|
261
279
|
})
|
|
262
280
|
|
|
263
|
-
test('
|
|
281
|
+
test('addSymbolsToMap computes pixelRatio from getPixelRatio and mapSize scale factor', async () => {
|
|
264
282
|
const p = makeProvider()
|
|
265
283
|
await doInitMap(p)
|
|
266
284
|
map.getPixelRatio.mockReturnValue(2)
|
|
267
|
-
p.mapSize = 'medium'
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
expect(registerSymbols).toHaveBeenCalledWith(map, [], { id: 'test' }, registry, 3) // 2 * 1.5
|
|
285
|
+
p.mapSize = 'medium'
|
|
286
|
+
await p.addSymbolsToMap([], { id: 'test' }, symbolRegistry)
|
|
287
|
+
expect(addSymbolsToMap).toHaveBeenCalledWith(map, [], { id: 'test' }, symbolRegistry, 2)
|
|
271
288
|
})
|
|
272
289
|
|
|
273
|
-
test('
|
|
290
|
+
test('addSymbolsToMap falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
|
|
274
291
|
const p = makeProvider()
|
|
275
292
|
await doInitMap(p)
|
|
276
293
|
map.getPixelRatio.mockReturnValue(0)
|
|
277
294
|
p.mapSize = 'small' // scaleFactor['small'] = 1
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
expect(registerSymbols).toHaveBeenCalledWith(map, [], { id: 'test' }, registry, 1) // (0 || 1) * 1
|
|
295
|
+
await p.addSymbolsToMap([], { id: 'test' }, symbolRegistry)
|
|
296
|
+
expect(addSymbolsToMap).toHaveBeenCalledWith(map, [], { id: 'test' }, symbolRegistry, 1)
|
|
281
297
|
})
|
|
282
298
|
|
|
283
|
-
test('
|
|
299
|
+
test('addPatternsToMap falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
|
|
284
300
|
const p = makeProvider()
|
|
285
301
|
await doInitMap(p)
|
|
302
|
+
map.getPixelRatio.mockReturnValue(0)
|
|
286
303
|
const configs = [{ fillPattern: 'dot' }]
|
|
287
304
|
const registry = {}
|
|
288
|
-
await p.
|
|
289
|
-
expect(
|
|
305
|
+
await p.addPatternsToMap(configs, 'test', registry)
|
|
306
|
+
expect(addPatternsToMap).toHaveBeenCalledWith(map, configs, 'test', registry, 1)
|
|
290
307
|
})
|
|
291
308
|
|
|
292
|
-
test('
|
|
309
|
+
test('addPatternsToMap is called with pixelRatio from getPixelRatio', async () => {
|
|
293
310
|
const p = makeProvider()
|
|
294
311
|
await doInitMap(p)
|
|
295
312
|
map.getPixelRatio.mockReturnValue(2)
|
|
296
|
-
p.mapSize = 'medium'
|
|
297
|
-
const registry = {}
|
|
298
|
-
await p.registerPatterns([], 'test', registry)
|
|
299
|
-
expect(registerPatterns).toHaveBeenCalledWith(map, [], 'test', registry, 3) // 2 * 1.5
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
test('registerPatterns falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
|
|
303
|
-
const p = makeProvider()
|
|
304
|
-
await doInitMap(p)
|
|
305
|
-
map.getPixelRatio.mockReturnValue(0)
|
|
306
|
-
p.mapSize = 'small' // scaleFactor['small'] = 1
|
|
313
|
+
p.mapSize = 'medium'
|
|
307
314
|
const registry = {}
|
|
308
|
-
await p.
|
|
309
|
-
expect(
|
|
315
|
+
await p.addPatternsToMap([], 'test', registry)
|
|
316
|
+
expect(addPatternsToMap).toHaveBeenCalledWith(map, [], 'test', registry, 2)
|
|
310
317
|
})
|
|
311
318
|
|
|
312
319
|
describe('setHoverCursor', () => {
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
const ICON_IMAGE = 'icon-image'
|
|
2
|
+
|
|
3
|
+
const ACTIVE_PREFIX = 'active-highlight'
|
|
4
|
+
const ACTIVE_INNER_PREFIX = 'active-highlight-inner'
|
|
5
|
+
const SELECTED_PREFIX = 'selected-highlight'
|
|
6
|
+
|
|
7
|
+
// Inner and selected both use the selected colour/width (black, thin)
|
|
8
|
+
const usesSelectedStyle = (prefix) => prefix === SELECTED_PREFIX || prefix === ACTIVE_INNER_PREFIX
|
|
9
|
+
|
|
10
|
+
const getActiveImageId = (map, imageId) => map._activeSymbolImageMap?.[imageId] ?? null
|
|
11
|
+
const getSelectedImageId = (map, imageId) => map._selectedSymbolImageMap?.[imageId] ?? null
|
|
12
|
+
|
|
1
13
|
const groupFeaturesBySource = (map, selectedFeatures) => {
|
|
2
14
|
const featuresBySource = {}
|
|
3
15
|
|
|
@@ -20,7 +32,7 @@ const groupFeaturesBySource = (map, selectedFeatures) => {
|
|
|
20
32
|
}
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
// Track whether any
|
|
35
|
+
// Track whether any feature on this source is a polygon
|
|
24
36
|
if (geometry && (geometry.type === 'Polygon' || geometry.type === 'MultiPolygon')) {
|
|
25
37
|
featuresBySource[sourceId].hasFillGeometry = true
|
|
26
38
|
featuresBySource[sourceId].fillIds.add(featureId)
|
|
@@ -32,12 +44,11 @@ const groupFeaturesBySource = (map, selectedFeatures) => {
|
|
|
32
44
|
return featuresBySource
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
const cleanupStaleSources = (map, previousSources, currentSources) => {
|
|
47
|
+
const cleanupStaleSources = (map, previousSources, currentSources, prefix) => {
|
|
36
48
|
previousSources.forEach(src => {
|
|
37
49
|
if (!currentSources.has(src)) {
|
|
38
|
-
const base =
|
|
39
|
-
|
|
40
|
-
layers.forEach(id => {
|
|
50
|
+
const base = `${prefix}-${src}`
|
|
51
|
+
;[`${base}-fill`, `${base}-line`, `${base}-symbol`].forEach(id => {
|
|
41
52
|
if (map.getLayer(id)) {
|
|
42
53
|
map.setFilter(id, ['==', 'id', ''])
|
|
43
54
|
}
|
|
@@ -46,6 +57,12 @@ const cleanupStaleSources = (map, previousSources, currentSources) => {
|
|
|
46
57
|
})
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
const clearPrefixSources = (map, prefix) => {
|
|
61
|
+
const key = `_${prefix.replaceAll('-', '')}Sources`
|
|
62
|
+
cleanupStaleSources(map, map[key] || new Set(), new Set(), prefix)
|
|
63
|
+
map[key] = new Set()
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
const applyHighlightLayer = (map, id, type, sourceId, srcLayer, paint, filter) => {
|
|
50
67
|
if (!map.getLayer(id)) {
|
|
51
68
|
map.addLayer({
|
|
@@ -71,13 +88,13 @@ const applySymbolHighlightLayer = (map, id, sourceId, srcLayer, originalLayerId,
|
|
|
71
88
|
source: sourceId,
|
|
72
89
|
...(srcLayer && { 'source-layer': srcLayer }),
|
|
73
90
|
layout: {
|
|
74
|
-
|
|
91
|
+
[ICON_IMAGE]: imageId,
|
|
75
92
|
'icon-anchor': map.getLayoutProperty(originalLayerId, 'icon-anchor') ?? 'center',
|
|
76
93
|
'icon-allow-overlap': true
|
|
77
94
|
}
|
|
78
95
|
})
|
|
79
96
|
}
|
|
80
|
-
map.setLayoutProperty(id,
|
|
97
|
+
map.setLayoutProperty(id, ICON_IMAGE, imageId)
|
|
81
98
|
map.setFilter(id, filter)
|
|
82
99
|
map.moveLayer(id)
|
|
83
100
|
}
|
|
@@ -97,67 +114,95 @@ const calculateBounds = (LngLatBounds, renderedFeatures) => {
|
|
|
97
114
|
return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*/
|
|
106
|
-
export function updateHighlightedFeatures ({ LngLatBounds, map, selectedFeatures, stylesMap }) {
|
|
107
|
-
if (!map) {
|
|
108
|
-
return null
|
|
117
|
+
const applySymbolGeomHighlight = (map, base, sourceId, srcLayer, layerId, filter, getSymbolImageId) => {
|
|
118
|
+
const imageId = map.getLayoutProperty(layerId, ICON_IMAGE)
|
|
119
|
+
const symbolImageId = getSymbolImageId(map, imageId)
|
|
120
|
+
if (symbolImageId) {
|
|
121
|
+
applySymbolHighlightLayer(map, `${base}-symbol`, sourceId, srcLayer, layerId, symbolImageId, filter)
|
|
109
122
|
}
|
|
123
|
+
}
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
const
|
|
125
|
+
const applySourceHighlight = (map, sourceId, featuresBySource, stylesMap, prefix, getSymbolImageId) => {
|
|
126
|
+
const { ids, fillIds, idProperty, layerId, hasFillGeometry } = featuresBySource[sourceId]
|
|
127
|
+
const baseLayer = map.getLayer(layerId)
|
|
128
|
+
const srcLayer = baseLayer.sourceLayer
|
|
129
|
+
const geom = hasFillGeometry ? 'fill' : baseLayer.type
|
|
130
|
+
const base = `${prefix}-${sourceId}`
|
|
131
|
+
const { stroke, selectionStroke, strokeWidth, activeStrokeWidth, fill } = stylesMap[layerId]
|
|
132
|
+
const isSelected = prefix === SELECTED_PREFIX
|
|
133
|
+
const selectedStyle = usesSelectedStyle(prefix)
|
|
134
|
+
const lineColor = selectedStyle ? selectionStroke : stroke
|
|
135
|
+
const lineWidth = selectedStyle ? strokeWidth : activeStrokeWidth
|
|
136
|
+
const idExpression = idProperty ? ['get', idProperty] : ['id']
|
|
137
|
+
const filter = ['in', idExpression, ['literal', [...ids]]]
|
|
138
|
+
|
|
139
|
+
if (geom === 'fill') {
|
|
140
|
+
if (isSelected) {
|
|
141
|
+
const fillFilter = ['in', idExpression, ['literal', [...fillIds]]]
|
|
142
|
+
// Only apply fill highlight to polygon features, not to any co-selected line features
|
|
143
|
+
applyHighlightLayer(map, `${base}-fill`, 'fill', sourceId, srcLayer, { 'fill-color': fill }, fillFilter)
|
|
144
|
+
}
|
|
145
|
+
applyHighlightLayer(map, `${base}-line`, 'line', sourceId, srcLayer, { 'line-color': lineColor, 'line-width': lineWidth }, filter)
|
|
146
|
+
}
|
|
113
147
|
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
if (geom === 'line') {
|
|
149
|
+
if (map.getLayer(`${base}-fill`)) {
|
|
150
|
+
// Clear any fill highlight from a previous polygon on the same source
|
|
151
|
+
map.setFilter(`${base}-fill`, ['==', 'id', ''])
|
|
152
|
+
}
|
|
153
|
+
applyHighlightLayer(map, `${base}-line`, 'line', sourceId, srcLayer, { 'line-color': lineColor, 'line-width': lineWidth }, filter)
|
|
154
|
+
}
|
|
116
155
|
|
|
117
|
-
|
|
118
|
-
|
|
156
|
+
if (geom === 'symbol') {
|
|
157
|
+
applySymbolGeomHighlight(map, base, sourceId, srcLayer, layerId, filter, getSymbolImageId)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
119
160
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
161
|
+
const applyFeatureHighlights = (map, features, stylesMap, prefix, getSymbolImageId) => {
|
|
162
|
+
const featuresBySource = groupFeaturesBySource(map, features)
|
|
163
|
+
const currentSources = new Set(Object.keys(featuresBySource))
|
|
164
|
+
const storageKey = `_${prefix.replaceAll('-', '')}Sources`
|
|
165
|
+
const previousSources = map[storageKey] || new Set()
|
|
125
166
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
167
|
+
cleanupStaleSources(map, previousSources, currentSources, prefix)
|
|
168
|
+
map[storageKey] = currentSources
|
|
169
|
+
currentSources.forEach(sourceId => applySourceHighlight(map, sourceId, featuresBySource, stylesMap, prefix, getSymbolImageId))
|
|
129
170
|
|
|
130
|
-
|
|
131
|
-
|
|
171
|
+
return featuresBySource
|
|
172
|
+
}
|
|
132
173
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Update highlighted features using pure filters.
|
|
176
|
+
* activeFeatures (keyboard cursor) render with the active ring (yellow) plus a selected ring inner (black).
|
|
177
|
+
* selectedFeatures render with the selected ring (black) only.
|
|
178
|
+
* Supports fill, line and symbol geometry, multi-source, cleanup, and bounds.
|
|
179
|
+
*/
|
|
180
|
+
export function updateHighlightedFeatures ({ LngLatBounds, map, selectedFeatures, activeFeatures, stylesMap }) {
|
|
181
|
+
if (!map) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
141
184
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
185
|
+
// Active cursor features — rendered first so selected layers appear on top
|
|
186
|
+
if (activeFeatures?.length) {
|
|
187
|
+
applyFeatureHighlights(map, activeFeatures, stylesMap, ACTIVE_PREFIX, getActiveImageId)
|
|
188
|
+
// Black selected stroke on top of yellow active (mirrors resolveActive for symbols)
|
|
189
|
+
applyFeatureHighlights(map, activeFeatures, stylesMap, ACTIVE_INNER_PREFIX, getSelectedImageId)
|
|
190
|
+
} else {
|
|
191
|
+
clearPrefixSources(map, ACTIVE_PREFIX)
|
|
192
|
+
clearPrefixSources(map, ACTIVE_INNER_PREFIX)
|
|
193
|
+
}
|
|
151
194
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
195
|
+
// Selection features
|
|
196
|
+
let featuresBySource = {}
|
|
197
|
+
if (selectedFeatures?.length) {
|
|
198
|
+
featuresBySource = applyFeatureHighlights(map, selectedFeatures, stylesMap, SELECTED_PREFIX, getSelectedImageId)
|
|
199
|
+
} else {
|
|
200
|
+
clearPrefixSources(map, SELECTED_PREFIX)
|
|
201
|
+
}
|
|
159
202
|
|
|
160
|
-
|
|
203
|
+
// Bounds only from selected features
|
|
204
|
+
const renderedFeatures = []
|
|
205
|
+
Object.entries(featuresBySource).forEach(([, { ids, idProperty, layerId }]) => {
|
|
161
206
|
renderedFeatures.push(
|
|
162
207
|
...map
|
|
163
208
|
.queryRenderedFeatures({ layers: [layerId] })
|