@defra/interactive-map 0.0.43-fmp-experimental → 0.0.44-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/assets/templates/add-polygons.njk +5 -1
- package/assets/templates/add-symbols.njk +5 -1
- 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/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/context.md +24 -0
- package/docs/api/keyboard-shortcut-definition.md +102 -0
- package/docs/api.md +18 -8
- package/docs/building-a-plugin.md +1 -0
- package/docs/plugins/draw.md +2 -2
- package/docs/plugins/plugin-manifest.md +9 -0
- package/package.json +4 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/api/newPolygon.js +1 -0
- package/plugins/beta/draw-es/src/events.js +17 -8
- package/plugins/beta/draw-es/src/reducer.js +8 -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/draw-ol/dist/css/index.css +1 -13
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/frame/dist/css/index.css +1 -11
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +28 -2
- package/plugins/beta/map-styles/dist/css/index.css +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/scale-bar/dist/css/index.css +1 -8
- package/plugins/datasets/dist/css/5648.index.css +1 -5
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/src/components/LayersMenu/Layers.module.scss +1 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +2 -2
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +6 -5
- package/plugins/datasets/src/registry/dataset.js +8 -12
- package/plugins/datasets/src/registry/dataset.test.js +17 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +4 -5
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +41 -26
- package/plugins/draw/src/DrawInit.test.jsx +55 -12
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +113 -114
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +82 -9
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +3 -1
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +18 -9
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +11 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +23 -2
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +29 -19
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +17 -2
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +35 -29
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +24 -6
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +22 -20
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +3 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +31 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +8 -1
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +38 -30
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +20 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +5 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +3 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -7
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -4
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +14 -14
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +12 -32
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +10 -10
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +48 -22
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +74 -6
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +2 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +6 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +15 -2
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +1 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +40 -17
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +55 -1
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +13 -7
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +7 -15
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +36 -4
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +41 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +35 -25
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +40 -2
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +2 -2
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +2 -2
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +40 -54
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +32 -13
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +17 -1
- package/plugins/draw/src/events.js +4 -4
- package/plugins/draw/src/events.test.js +1 -1
- package/plugins/draw/src/hooks/useSpatialList.js +180 -0
- package/plugins/draw/src/hooks/useSpatialList.test.js +339 -0
- package/plugins/draw/src/manifest.js +4 -1
- package/plugins/draw/src/manifest.test.js +19 -7
- package/plugins/draw/src/utils/spatial.js +1 -31
- package/plugins/draw/src/utils/spatial.test.js +1 -24
- 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 +2 -2
- package/plugins/interact/src/InteractInit.test.js +3 -3
- package/plugins/interact/src/events.js +11 -2
- package/plugins/interact/src/events.test.js +32 -0
- package/plugins/interact/src/hooks/useAttachEvents.js +20 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +44 -1
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +9 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +7 -7
- package/plugins/interact/src/hooks/useSpatialList.js +251 -0
- package/plugins/interact/src/hooks/{useMapItemList.test.js → useSpatialList.test.js} +216 -70
- package/plugins/interact/src/manifest.js +12 -1
- package/plugins/interact/src/manifest.test.js +20 -0
- package/plugins/interact/src/utils/spatial.js +24 -1
- package/plugins/interact/src/utils/spatial.test.js +26 -1
- package/plugins/map-key/dist/css/index.css +1 -106
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -1
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +1 -1
- package/plugins/map-key/dist/umd/index.js +1 -1
- package/plugins/map-key/src/components/Key/Key.jsx +3 -4
- package/plugins/map-key/src/components/Key/Key.module.scss +10 -8
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeyItem.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvg.jsx +9 -7
- package/plugins/map-key/src/components/Key/KeySvg.test.jsx +4 -4
- package/plugins/map-key/src/components/Key/MapKey.jsx +5 -3
- package/plugins/map-key/src/manifest.js +1 -1
- package/plugins/map-key/src/utils/groupStyles.js +7 -0
- package/plugins/map-key/src/utils/mergeKeyGroupItems.js +12 -0
- package/plugins/map-key/src/utils/mergeKeyGroupItems.test.js +53 -0
- package/plugins/menu/dist/css/index.css +1 -80
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -1
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -1
- package/plugins/menu/dist/umd/index.js +1 -1
- package/plugins/menu/src/initialise/MenuInit.jsx +15 -3
- package/plugins/menu/src/initialise/MenuInit.test.jsx +25 -0
- package/plugins/menu/src/initialise/createMenu.js +3 -1
- package/plugins/menu/src/initialise/createMenu.test.js +20 -5
- package/plugins/menu/src/initialise/updateUrl.js +44 -0
- package/plugins/menu/src/initialise/updateUrl.test.js +91 -0
- package/plugins/menu/src/reducers/menuStateReducer.js +19 -1
- package/plugins/menu/src/reducers/menuStateReducer.test.js +53 -20
- package/plugins/menu/src/reducers/pluginState.js +1 -6
- package/plugins/menu/src/reducers/pluginState.test.js +0 -25
- 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/manifest.js +5 -9
- package/plugins/search/src/manifest.test.js +5 -0
- package/providers/beta/esri/dist/css/index.css +1 -34
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +3 -3
- package/providers/beta/openlayers/src/openlayersProvider.test.js +6 -3
- package/providers/beta/openlayers/src/utils/queryFeatures.js +72 -0
- package/providers/beta/openlayers/src/utils/queryFeatures.test.js +122 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.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/defaults.test.js +26 -0
- package/providers/maplibre/src/utils/labels.js +2 -2
- package/providers/maplibre/src/utils/labels.test.js +5 -5
- package/providers/maplibre/src/utils/spatial.js +0 -57
- package/providers/maplibre/src/utils/spatial.test.js +0 -27
- package/rollup.esm.mjs +2 -1
- package/scripts/publish-package.sh +36 -0
- package/src/App/App.jsx +1 -1
- package/src/App/App.test.jsx +6 -1
- package/src/App/components/CrossHair/CrossHair.jsx +25 -11
- package/src/App/components/CrossHair/CrossHair.module.scss +37 -12
- package/src/App/components/CrossHair/CrossHair.test.jsx +40 -11
- package/src/App/components/Hints/Hints.jsx +3 -3
- package/src/App/components/Hints/Hints.test.jsx +3 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +4 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +17 -0
- package/src/App/components/MapButton/MapButton.jsx +19 -7
- package/src/App/components/MapButton/MapButton.module.scss +1 -2
- package/src/App/components/MapButton/MapButton.test.jsx +42 -4
- package/src/App/components/{MoveControls/MoveControls.jsx → MapControls/MapControls.jsx} +10 -10
- package/src/App/components/{MoveControls/MoveControls.module.scss → MapControls/MapControls.module.scss} +25 -25
- package/src/App/components/{MoveControls/MoveControls.test.jsx → MapControls/MapControls.test.jsx} +48 -48
- package/src/App/components/Markers/Markers.jsx +2 -2
- package/src/App/components/Markers/Markers.test.jsx +3 -3
- package/src/App/components/Markers/SymbolMarker.jsx +2 -3
- package/src/App/components/Markers/SymbolMarker.test.jsx +8 -9
- package/src/App/components/Panel/Panel.jsx +19 -20
- package/src/App/components/Panel/Panel.module.scss +5 -0
- package/src/App/components/Panel/Panel.test.jsx +69 -4
- package/src/App/components/PopupMenu/PopupMenu.jsx +7 -3
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +71 -0
- package/src/App/components/PopupMenu/usePopupMenu.js +10 -9
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/SpatialList.jsx +47 -0
- package/src/App/components/Viewport/{Features.module.scss → SpatialList.module.scss} +6 -6
- package/src/App/components/Viewport/SpatialList.test.jsx +285 -0
- package/src/App/components/Viewport/Viewport.jsx +21 -11
- package/src/App/components/Viewport/Viewport.test.jsx +18 -2
- package/src/App/controls/keyboardMappings.js +7 -2
- package/src/App/controls/keyboardMappings.test.js +21 -0
- package/src/App/controls/keyboardShortcuts.js +16 -2
- package/src/App/controls/keyboardShortcuts.test.js +21 -0
- package/src/App/hooks/useCrossHairAPI.js +29 -1
- package/src/App/hooks/useCrossHairAPI.test.js +17 -1
- package/src/App/hooks/useHintsAPI.js +5 -5
- package/src/App/hooks/useInterfaceAPI.js +2 -1
- package/src/App/hooks/useInterfaceAPI.test.js +9 -0
- package/src/App/hooks/useKeyboardShortcuts.js +66 -35
- package/src/App/hooks/useKeyboardShortcuts.test.js +52 -0
- package/src/App/hooks/useModalPanelBehaviour.js +48 -34
- package/src/App/hooks/useModalPanelBehaviour.test.js +59 -0
- package/src/App/hooks/useSpatialListFocus.js +296 -0
- package/src/App/hooks/useSpatialListFocus.test.js +957 -0
- package/src/App/hooks/useSpatialListItems.js +51 -0
- package/src/App/hooks/useSpatialListItems.test.js +217 -0
- package/src/App/initialiseApp.js +6 -3
- package/src/App/initialiseApp.test.js +11 -0
- package/src/App/layout/Layout.jsx +4 -2
- package/src/App/layout/Layout.test.jsx +21 -0
- package/src/App/layout/layout.module.scss +7 -1
- package/src/App/registry/keyboardShortcutRegistry.js +2 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +16 -0
- package/src/App/registry/pluginRegistry.js +13 -0
- package/src/App/registry/pluginRegistry.test.js +15 -0
- package/src/App/registry/spatialListRegistry.js +104 -0
- package/src/App/registry/spatialListRegistry.test.js +218 -0
- package/src/App/renderer/HtmlElementHost.jsx +12 -32
- package/src/App/renderer/mapButtons.js +4 -0
- package/src/App/renderer/mapButtons.test.js +20 -0
- package/src/App/renderer/mapPanels.js +33 -53
- package/src/App/renderer/mapPanels.test.js +29 -4
- package/src/App/renderer/slotHelpers.js +33 -0
- package/src/App/renderer/slotHelpers.test.js +79 -2
- package/src/App/store/AppProvider.jsx +3 -2
- package/src/App/store/ServiceProvider.jsx +5 -3
- package/src/App/store/ServiceProvider.test.jsx +20 -1
- package/src/InteractiveMap/InteractiveMap.js +13 -2
- package/src/InteractiveMap/InteractiveMap.test.js +33 -0
- package/src/config/appConfig.js +16 -16
- package/src/config/appConfig.test.js +19 -19
- package/src/config/defaults.js +2 -2
- package/src/config/events.js +8 -8
- package/src/scss/main.scss +2 -2
- package/src/test-utils.js +8 -0
- package/src/types.js +51 -9
- package/src/utils/findNearestItemInDirection.js +29 -0
- package/src/utils/findNearestItemInDirection.test.js +67 -0
- package/src/utils/getPanelElementId.js +4 -0
- package/src/utils/getPanelElementId.test.js +11 -0
- package/src/utils/getPanelRole.js +22 -0
- package/src/utils/getPanelRole.test.js +56 -0
- package/src/utils/globalAltShortcuts.js +12 -0
- package/src/utils/globalAltShortcuts.test.js +27 -0
- package/src/utils/spatialNavigate.js +49 -0
- package/src/utils/spatialNavigate.test.js +42 -0
- package/webpack.umd.mjs +6 -1
- package/plugins/interact/src/hooks/useMapItemList.js +0 -187
- package/src/App/components/Viewport/Features.jsx +0 -35
- package/src/App/components/Viewport/Features.test.jsx +0 -124
- package/src/App/hooks/useFeatureFocus.js +0 -199
- package/src/App/hooks/useFeatureFocus.test.js +0 -635
- package/src/App/hooks/useFeatureItems.js +0 -39
- package/src/App/hooks/useFeatureItems.test.js +0 -155
|
@@ -45,6 +45,38 @@ describe('attachEvents — keyboard', () => {
|
|
|
45
45
|
expect(params.handleInteraction).toHaveBeenCalled()
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
+
it('ignores Alt+Enter (a different shortcut, e.g. highlightLabelAtCenter) on viewport', () => {
|
|
49
|
+
const params = createParams()
|
|
50
|
+
cleanup = attachEvents(params)
|
|
51
|
+
|
|
52
|
+
const keydown = new KeyboardEvent('keydown', { key: 'Enter', altKey: true })
|
|
53
|
+
Object.defineProperty(keydown, 'target', { value: document.body })
|
|
54
|
+
document.dispatchEvent(keydown)
|
|
55
|
+
|
|
56
|
+
const keyup = new KeyboardEvent('keyup', { key: 'Enter', altKey: true })
|
|
57
|
+
Object.defineProperty(keyup, 'target', { value: document.body })
|
|
58
|
+
document.dispatchEvent(keyup)
|
|
59
|
+
|
|
60
|
+
expect(params.handleInteraction).not.toHaveBeenCalled()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('ignores other modified Enter combinations (Ctrl/Meta/Shift+Enter) on viewport', () => {
|
|
64
|
+
const params = createParams()
|
|
65
|
+
cleanup = attachEvents(params)
|
|
66
|
+
|
|
67
|
+
;['ctrlKey', 'metaKey', 'shiftKey'].forEach((modifier) => {
|
|
68
|
+
const keydown = new KeyboardEvent('keydown', { key: 'Enter', [modifier]: true })
|
|
69
|
+
Object.defineProperty(keydown, 'target', { value: document.body })
|
|
70
|
+
document.dispatchEvent(keydown)
|
|
71
|
+
|
|
72
|
+
const keyup = new KeyboardEvent('keyup', { key: 'Enter', [modifier]: true })
|
|
73
|
+
Object.defineProperty(keyup, 'target', { value: document.body })
|
|
74
|
+
document.dispatchEvent(keyup)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
expect(params.handleInteraction).not.toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
48
80
|
it('ignores Enter outside viewport or other keys', () => {
|
|
49
81
|
const params = createParams()
|
|
50
82
|
cleanup = attachEvents(params)
|
|
@@ -42,4 +42,24 @@ export function useAttachEvents ({ pluginState, appState, mapState, buttonConfig
|
|
|
42
42
|
|
|
43
43
|
return cleanupEvents
|
|
44
44
|
}, [pluginState.enabled, buttonConfig, eventBus])
|
|
45
|
+
|
|
46
|
+
// crossHair is dispatch-managed state (mapReducer.js's UPDATE_CROSS_HAIR spreads into a NEW
|
|
47
|
+
// object every time — see updateCrossHair in mapActionsMap.js, which fires constantly, e.g.
|
|
48
|
+
// on every fixAtCenter()/hide()), so a `.activate` assignment made only once (inside the
|
|
49
|
+
// effect above, whose deps don't include it) would silently end up on a stale, discarded
|
|
50
|
+
// object the moment the next dispatch replaces it — exactly what CrossHair.jsx's onClick
|
|
51
|
+
// would then find nothing on. Keyed on mapState.crossHair itself, separate from the effect
|
|
52
|
+
// above, so this re-wires onto the current object every time it's replaced, not just once.
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!pluginState.enabled) {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
const activate = () => handleInteractionRef.current(mapState.crossHair.getDetail())
|
|
58
|
+
mapState.crossHair.activate = activate
|
|
59
|
+
return () => {
|
|
60
|
+
if (mapState.crossHair.activate === activate) {
|
|
61
|
+
mapState.crossHair.activate = null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, [pluginState.enabled, mapState.crossHair])
|
|
45
65
|
}
|
|
@@ -18,7 +18,7 @@ beforeEach(() => {
|
|
|
18
18
|
props = {
|
|
19
19
|
pluginState: { enabled: true },
|
|
20
20
|
appState: { interfaceType: 'mouse' },
|
|
21
|
-
mapState: { mapStyle: {} },
|
|
21
|
+
mapState: { mapStyle: {}, crossHair: { getDetail: jest.fn(() => ({ point: {}, coords: [] })) } },
|
|
22
22
|
buttonConfig: {},
|
|
23
23
|
eventBus: { emit: jest.fn() },
|
|
24
24
|
handleInteraction: handleInteractionMock
|
|
@@ -63,4 +63,47 @@ describe('useAttachEvents', () => {
|
|
|
63
63
|
renderHook(() => useAttachEvents({ ...props, pluginState: { enabled: false } }))
|
|
64
64
|
expect(attachEvents).not.toHaveBeenCalled()
|
|
65
65
|
})
|
|
66
|
+
|
|
67
|
+
describe('crossHair.activate — re-wired whenever mapState.crossHair is replaced', () => {
|
|
68
|
+
it('wires crossHair.activate on mount, calling handleInteraction with the live getDetail() result', () => {
|
|
69
|
+
renderHook(() => useAttachEvents(props))
|
|
70
|
+
expect(typeof props.mapState.crossHair.activate).toBe('function')
|
|
71
|
+
|
|
72
|
+
props.mapState.crossHair.activate()
|
|
73
|
+
expect(handleInteractionMock).toHaveBeenCalledWith({ point: {}, coords: [] })
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('re-wires onto a new crossHair object — mapReducer.js replaces it wholesale on every UPDATE_CROSS_HAIR dispatch', () => {
|
|
77
|
+
const { rerender } = renderHook((p) => useAttachEvents(p), { initialProps: props })
|
|
78
|
+
const staleCrossHair = props.mapState.crossHair
|
|
79
|
+
|
|
80
|
+
const nextProps = { ...props, mapState: { ...props.mapState, crossHair: { getDetail: jest.fn(() => ({ point: {}, coords: [] })) } } }
|
|
81
|
+
rerender(nextProps)
|
|
82
|
+
|
|
83
|
+
expect(staleCrossHair.activate).toBeNull() // guarded cleanup nulled the old one
|
|
84
|
+
expect(typeof nextProps.mapState.crossHair.activate).toBe('function')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('nulls activate on unmount, guarded — leaves a newer owner alone', () => {
|
|
88
|
+
const { unmount } = renderHook(() => useAttachEvents(props))
|
|
89
|
+
const ownActivate = props.mapState.crossHair.activate
|
|
90
|
+
expect(typeof ownActivate).toBe('function')
|
|
91
|
+
|
|
92
|
+
unmount()
|
|
93
|
+
expect(props.mapState.crossHair.activate).toBeNull()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('does not wire crossHair.activate when the plugin is not enabled', () => {
|
|
97
|
+
renderHook(() => useAttachEvents({ ...props, pluginState: { enabled: false } }))
|
|
98
|
+
expect(props.mapState.crossHair.activate).toBeUndefined()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('leaves a newer owner\'s crossHair.activate alone on unmount — guarded, not unconditional', () => {
|
|
102
|
+
const { unmount } = renderHook(() => useAttachEvents(props))
|
|
103
|
+
const laterOwnersActivate = () => {}
|
|
104
|
+
props.mapState.crossHair.activate = laterOwnersActivate
|
|
105
|
+
unmount()
|
|
106
|
+
expect(props.mapState.crossHair.activate).toBe(laterOwnersActivate)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
66
109
|
})
|
|
@@ -8,7 +8,7 @@ export function useCrossHairVisibility ({ crossHair, enabled, selectMarkerOnly,
|
|
|
8
8
|
const crossHairRef = useRef(crossHair)
|
|
9
9
|
crossHairRef.current = crossHair
|
|
10
10
|
const listboxFocusRef = useRef(false)
|
|
11
|
-
// True while focus is inside an element (marked `data-map-keyboard-scope`) that actually routes cursor keys to the map, e.g. the viewport or
|
|
11
|
+
// True while focus is inside an element (marked `data-map-keyboard-scope`) that actually routes cursor keys to the map, e.g. the viewport or MapControl's D-pad.
|
|
12
12
|
const mapKeyboardScopedRef = useRef(false)
|
|
13
13
|
const interfaceTypeRef = useRef(appState.interfaceType)
|
|
14
14
|
interfaceTypeRef.current = appState.interfaceType
|
|
@@ -17,16 +17,21 @@ export function useCrossHairVisibility ({ crossHair, enabled, selectMarkerOnly,
|
|
|
17
17
|
const type = interfaceTypeRef.current
|
|
18
18
|
const keyboardCanOperateMap = type === 'keyboard' && mapKeyboardScopedRef.current
|
|
19
19
|
const touchCanOperateMap = type === 'touch' && !selectMarkerOnlyRef.current && !listboxFocusRef.current
|
|
20
|
-
|
|
20
|
+
// MapControls being open is an explicit "I can't drag/pinch this map" signal from the
|
|
21
|
+
// user — same audience this crosshair already exists for — so it counts as its own reason
|
|
22
|
+
// to show it, independent of the currently-inferred interface type (which, for a Voice
|
|
23
|
+
// Control user, will never register as anything but the default 'mouse').
|
|
24
|
+
const mapControlsOpen = appState.expandedButtons?.has('mapControls')
|
|
25
|
+
if (enabledRef.current && (keyboardCanOperateMap || touchCanOperateMap || mapControlsOpen)) {
|
|
21
26
|
crossHairRef.current.fixAtCenter()
|
|
22
27
|
} else {
|
|
23
28
|
crossHairRef.current.hide()
|
|
24
29
|
}
|
|
25
|
-
}, [])
|
|
30
|
+
}, [appState.expandedButtons])
|
|
26
31
|
|
|
27
32
|
useEffect(() => {
|
|
28
33
|
updateCrossHair()
|
|
29
|
-
}, [enabled, selectMarkerOnly, appState.interfaceType, updateCrossHair])
|
|
34
|
+
}, [enabled, selectMarkerOnly, appState.interfaceType, appState.expandedButtons, updateCrossHair])
|
|
30
35
|
|
|
31
36
|
useEffect(() => {
|
|
32
37
|
const container = appState.layoutRefs?.appContainerRef?.current
|
|
@@ -73,13 +73,13 @@ describe('useCrossHairVisibility', () => {
|
|
|
73
73
|
expect(crossHair.hide).toHaveBeenCalledTimes(2)
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
it('shows the crosshair for a control (e.g.
|
|
76
|
+
it('shows the crosshair for a control (e.g. MapControl) that forwards arrow keys to the map without moving DOM focus onto the viewport', () => {
|
|
77
77
|
const container = appState.layoutRefs.appContainerRef.current
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
container.appendChild(
|
|
78
|
+
const mapControlButton = document.createElement('button')
|
|
79
|
+
const mapControlScope = document.createElement('div')
|
|
80
|
+
mapControlScope.setAttribute('data-map-keyboard-scope', '')
|
|
81
|
+
mapControlScope.appendChild(mapControlButton)
|
|
82
|
+
container.appendChild(mapControlScope)
|
|
83
83
|
appState.interfaceType = 'keyboard'
|
|
84
84
|
|
|
85
85
|
renderHook(() => useCrossHairVisibility({ crossHair, enabled: true, selectMarkerOnly: false, appState }))
|
|
@@ -87,7 +87,7 @@ describe('useCrossHairVisibility', () => {
|
|
|
87
87
|
|
|
88
88
|
// Focus lands on a direction button nested inside the scoped control, not
|
|
89
89
|
// on the scope element itself — closest() must still pick it up.
|
|
90
|
-
act(() =>
|
|
90
|
+
act(() => mapControlButton.dispatchEvent(new FocusEvent('focusin', { bubbles: true })))
|
|
91
91
|
expect(crossHair.fixAtCenter).toHaveBeenCalled()
|
|
92
92
|
})
|
|
93
93
|
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { EVENTS } from '../../../../src/config/events.js'
|
|
3
|
+
import { isStandaloneLabel } from '../../../../src/utils/symbolUtils.js'
|
|
4
|
+
import { scaleFactor } from '../../../../src/config/appConfig.js'
|
|
5
|
+
import { buildLayerConfigMap } from '../utils/featureQueries.js'
|
|
6
|
+
import { getGeometryCenter } from '../utils/spatial.js'
|
|
7
|
+
|
|
8
|
+
// Only called after toFeatureItem's own `config?.labelProperty` guard, so config is always
|
|
9
|
+
// truthy here — no defensive null-config branch needed (that was only for findFeatureById's own
|
|
10
|
+
// direct-lookup path, since removed along with the mapProvider.getVisibleFeatures re-query it did).
|
|
11
|
+
const getFeatureId = (feature, config) => feature.properties?.[config.idProperty] ?? feature.id
|
|
12
|
+
|
|
13
|
+
const isInViewport = (el) => {
|
|
14
|
+
const container = el.closest('.im-c-viewport__markers')
|
|
15
|
+
if (!container) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
const containerRect = container.getBoundingClientRect()
|
|
19
|
+
const { left, right, top, bottom } = el.getBoundingClientRect()
|
|
20
|
+
return right > containerRect.left && left < containerRect.right &&
|
|
21
|
+
bottom > containerRect.top && top < containerRect.bottom
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Screen position for a [lng, lat] point, scaled for the current mapSize — same correction
|
|
25
|
+
// useMarkersAPI.js's projectCoords applies (the map canvas itself is rendered at a different
|
|
26
|
+
// resolution per mapSize; nothing to do with CSS transforms). mapProvider.mapToScreen is a
|
|
27
|
+
// provider-agnostic method (implemented by every provider, including OpenLayers/Esri), so this
|
|
28
|
+
// helper works unchanged once OpenLayers' getVisibleFeatures stops being a stub.
|
|
29
|
+
const projectToScreen = (mapProvider, mapSize, coords) => {
|
|
30
|
+
const { x, y } = mapProvider.mapToScreen(coords)
|
|
31
|
+
return { x: x * scaleFactor[mapSize], y: y * scaleFactor[mapSize] }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// x/y are the marker's own screen position — computed fresh here rather than read off
|
|
35
|
+
// marker.x/marker.y, which only reflect where the marker was when added/last updated, not the
|
|
36
|
+
// current pan position. Used to position the corresponding accessible list item so touch/voice
|
|
37
|
+
// AT overlays (e.g. macOS Voice Control's "Show Numbers") land on the actual feature instead of
|
|
38
|
+
// stacking at the top-left of the viewport.
|
|
39
|
+
const collectVisibleMarkers = (markers, mapProvider, mapSize) => {
|
|
40
|
+
const items = []
|
|
41
|
+
for (const marker of markers.items) {
|
|
42
|
+
if (!marker.label) { continue }
|
|
43
|
+
const el = markers.markerRefs?.get(marker.id)
|
|
44
|
+
if (!isStandaloneLabel(marker) && el && isInViewport(el)) {
|
|
45
|
+
const item = { id: marker.id, label: marker.label, isMarker: true }
|
|
46
|
+
if (marker.coords) {
|
|
47
|
+
Object.assign(item, projectToScreen(mapProvider, mapSize, marker.coords))
|
|
48
|
+
}
|
|
49
|
+
items.push(item)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return items
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const toFeatureItem = (feature, layerConfigMap, seenIds, mapProvider, mapSize) => {
|
|
56
|
+
const config = layerConfigMap[feature.layer?.id]
|
|
57
|
+
if (!config?.labelProperty) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
const rawId = getFeatureId(feature, config)
|
|
61
|
+
const stringId = rawId == null ? null : String(rawId)
|
|
62
|
+
if (stringId == null || seenIds.has(stringId)) {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
seenIds.add(stringId)
|
|
66
|
+
const label = feature.properties?.[config.labelProperty] ?? stringId
|
|
67
|
+
// Keeps the full resolved shape (raw id, layer config, geometry, properties) alongside the
|
|
68
|
+
// listbox-facing id/label — not just for building the item, but so useActiveItemHandler below
|
|
69
|
+
// can resolve "what is this" from this same object later, without re-querying the map engine.
|
|
70
|
+
// Only id/label/x/y ever reach MAP_SET_SPATIAL_LIST itself (see toPublicItem) — the rest stays
|
|
71
|
+
// internal, in itemsRef.
|
|
72
|
+
const item = {
|
|
73
|
+
id: stringId,
|
|
74
|
+
label,
|
|
75
|
+
isMarker: false,
|
|
76
|
+
featureId: rawId,
|
|
77
|
+
layerId: config.layerId,
|
|
78
|
+
idProperty: config.idProperty,
|
|
79
|
+
geometry: feature.geometry,
|
|
80
|
+
properties: feature.properties
|
|
81
|
+
}
|
|
82
|
+
// Bbox-centre of the feature's geometry (polygon or line) — see getGeometryCenter's own doc
|
|
83
|
+
// for why a bbox centre rather than a true centroid. null when the feature carries no usable
|
|
84
|
+
// geometry (e.g. OpenLayers' getVisibleFeatures, currently a stub returning [] — see PR notes).
|
|
85
|
+
const center = getGeometryCenter(feature)
|
|
86
|
+
if (center) {
|
|
87
|
+
Object.assign(item, projectToScreen(mapProvider, mapSize, center))
|
|
88
|
+
}
|
|
89
|
+
return item
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const collectVisibleFeatures = (mapProvider, layers, mapSize) => {
|
|
93
|
+
const items = []
|
|
94
|
+
const seenIds = new Set()
|
|
95
|
+
const layerIds = layers.map(layer => layer.layerId)
|
|
96
|
+
const layerConfigMap = buildLayerConfigMap(layers)
|
|
97
|
+
const features = mapProvider.getVisibleFeatures(layerIds)
|
|
98
|
+
for (const feature of features) {
|
|
99
|
+
const item = toFeatureItem(feature, layerConfigMap, seenIds, mapProvider, mapSize)
|
|
100
|
+
if (item) {
|
|
101
|
+
items.push(item)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return items
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Rebuilds the keyboard-navigable item list whenever the map moves or data changes. Collects
|
|
109
|
+
* visible markers (by DOM visibility) and visible features (by viewport query), keeps the
|
|
110
|
+
* result in itemsRef for useActiveItemHandler to resolve against later, and tells
|
|
111
|
+
* spatialListRegistry its items have changed so the shared listbox stays in sync with what's
|
|
112
|
+
* visible. SpatialList.jsx/useSpatialListItems.js only ever render id/label/x/y from each item — the
|
|
113
|
+
* rest (isMarker, geometry, properties, layer config) rides along unused there, but is what
|
|
114
|
+
* lets useActiveItemHandler resolve "what is this" from the same object later, with nothing to
|
|
115
|
+
* re-derive and no separate lean/rich shape to keep in sync.
|
|
116
|
+
*
|
|
117
|
+
* interact no longer emits MAP_SET_SPATIAL_LIST directly — spatialListRegistry owns that (see its
|
|
118
|
+
* own doc comment for why: a single "whoever emits last wins" event doesn't scale once more
|
|
119
|
+
* than one plugin, e.g. draw mid-edit, needs to contribute to or take over the same list).
|
|
120
|
+
*/
|
|
121
|
+
function useItemListSync ({ markers, mapSize, interactionModes, layers, mapProvider, multiSelect, spatialListRegistry, eventBus, itemsRef }) {
|
|
122
|
+
const multiSelectRef = useRef(multiSelect)
|
|
123
|
+
multiSelectRef.current = multiSelect // always-current for the provider's getItems below, same convention as itemsRef
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const handleMoveEnd = () => {
|
|
127
|
+
const items = []
|
|
128
|
+
if (interactionModes?.includes('selectMarker')) {
|
|
129
|
+
items.push(...collectVisibleMarkers(markers, mapProvider, mapSize))
|
|
130
|
+
}
|
|
131
|
+
if (interactionModes?.includes('selectFeature') && layers.length > 0) {
|
|
132
|
+
items.push(...collectVisibleFeatures(mapProvider, layers, mapSize))
|
|
133
|
+
}
|
|
134
|
+
itemsRef.current = items
|
|
135
|
+
spatialListRegistry.notifyItemsChanged('interact')
|
|
136
|
+
}
|
|
137
|
+
handleMoveEnd()
|
|
138
|
+
eventBus.on(EVENTS.MAP_MOVE_END, handleMoveEnd)
|
|
139
|
+
eventBus.on(EVENTS.MAP_DATA_CHANGE, handleMoveEnd)
|
|
140
|
+
return () => {
|
|
141
|
+
eventBus.off(EVENTS.MAP_MOVE_END, handleMoveEnd)
|
|
142
|
+
eventBus.off(EVENTS.MAP_DATA_CHANGE, handleMoveEnd)
|
|
143
|
+
}
|
|
144
|
+
}, [markers, mapSize, interactionModes, layers, mapProvider, multiSelect, spatialListRegistry, eventBus, itemsRef])
|
|
145
|
+
|
|
146
|
+
// Registers once, for the lifetime of the hook — getItems reads itemsRef/multiSelectRef
|
|
147
|
+
// fresh on every call, so there's never a need to re-register just because a prop changed;
|
|
148
|
+
// the effect above already pushes fresh data out via notifyItemsChanged. Declared after
|
|
149
|
+
// that effect so this registration's own immediate recompute (inside the registry) already
|
|
150
|
+
// sees whatever the effect above just computed on this same mount, rather than momentarily
|
|
151
|
+
// registering with empty items and correcting a moment later.
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
spatialListRegistry.registerItemProvider('interact', {
|
|
154
|
+
getItems: () => ({ items: itemsRef.current, multiselectable: multiSelectRef.current, label: 'Map features' })
|
|
155
|
+
})
|
|
156
|
+
return () => { spatialListRegistry.unregisterItemProvider('interact') }
|
|
157
|
+
}, [spatialListRegistry, itemsRef])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Listens for MAP_SET_ACTIVE_ITEM and resolves the active item to its full feature/marker data,
|
|
162
|
+
* storing it in both a ref (for synchronous access) and plugin state (for highlight rendering).
|
|
163
|
+
* Shows the keyboard cursor ring without firing interact:selectionchange — committing the item
|
|
164
|
+
* to the real selection only happens when the user presses Enter/Space.
|
|
165
|
+
*
|
|
166
|
+
* Resolves purely by looking the id up in itemsRef (the same list useItemListSync just built) —
|
|
167
|
+
* deliberately not a fresh mapProvider.getVisibleFeatures() query. This fires on every roving-
|
|
168
|
+
* tabindex move (every arrow-key press while browsing the list), so re-querying the map engine
|
|
169
|
+
* here — as an earlier version of this hook did — meant every keystroke re-ran the same query
|
|
170
|
+
* useItemListSync had just run moments before, plus a second linear scan through the result, to
|
|
171
|
+
* re-derive data already computed once and discarded. A stale/out-of-range id (e.g. the map
|
|
172
|
+
* panned since the list was built) simply resolves to nothing, same as before.
|
|
173
|
+
*/
|
|
174
|
+
function useActiveItemHandler ({ itemsRef, eventBus, dispatch, listboxActiveItemRef }) {
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const handle = ({ id }) => {
|
|
177
|
+
if (id === null) {
|
|
178
|
+
listboxActiveItemRef.current = null
|
|
179
|
+
dispatch({ type: 'SET_LISTBOX_ACTIVE', payload: null })
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const item = itemsRef.current.find(i => i.id === id)
|
|
183
|
+
if (!item) {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
if (item.isMarker) {
|
|
187
|
+
listboxActiveItemRef.current = { id, isMarker: true }
|
|
188
|
+
dispatch({ type: 'SET_LISTBOX_ACTIVE', payload: null })
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
const payload = {
|
|
192
|
+
featureId: item.featureId,
|
|
193
|
+
layerId: item.layerId,
|
|
194
|
+
idProperty: item.idProperty,
|
|
195
|
+
geometry: item.geometry
|
|
196
|
+
}
|
|
197
|
+
listboxActiveItemRef.current = { id, isMarker: false, ...payload, properties: item.properties }
|
|
198
|
+
dispatch({ type: 'SET_LISTBOX_ACTIVE', payload })
|
|
199
|
+
}
|
|
200
|
+
eventBus.on(EVENTS.MAP_SET_ACTIVE_ITEM, handle)
|
|
201
|
+
return () => { eventBus.off(EVENTS.MAP_SET_ACTIVE_ITEM, handle) }
|
|
202
|
+
}, [itemsRef, eventBus, dispatch, listboxActiveItemRef])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Handles MAP_SELECT_ITEM (Enter/Space keypress) by promoting the currently active
|
|
207
|
+
* listbox item to a confirmed selection, dispatching TOGGLE_SELECTED_FEATURES or
|
|
208
|
+
* TOGGLE_SELECTED_MARKERS and triggering interact:selectionchange downstream.
|
|
209
|
+
*/
|
|
210
|
+
function useSelectItemHandler ({ eventBus, dispatch, listboxActiveItemRef, multiSelect }) {
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const handleConfirm = () => {
|
|
213
|
+
const item = listboxActiveItemRef.current
|
|
214
|
+
if (!item) {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
if (item.isMarker) {
|
|
218
|
+
dispatch({ type: 'TOGGLE_SELECTED_MARKERS', payload: { markerId: item.id, multiSelect } })
|
|
219
|
+
} else {
|
|
220
|
+
const { featureId, layerId, idProperty, geometry, properties } = item
|
|
221
|
+
dispatch({
|
|
222
|
+
type: 'TOGGLE_SELECTED_FEATURES',
|
|
223
|
+
payload: { featureId, layerId, idProperty, geometry, properties, multiSelect, replaceAll: !multiSelect }
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
eventBus.on(EVENTS.MAP_SELECT_ITEM, handleConfirm)
|
|
228
|
+
return () => { eventBus.off(EVENTS.MAP_SELECT_ITEM, handleConfirm) }
|
|
229
|
+
}, [eventBus, dispatch, listboxActiveItemRef, multiSelect])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Orchestrates the keyboard-accessible listbox for the interact plugin.
|
|
234
|
+
*
|
|
235
|
+
* Composes three concerns:
|
|
236
|
+
* - Item list sync — keeps the listbox populated with currently visible markers and features
|
|
237
|
+
* - Active item resolution — translates a listbox cursor position into full feature/marker data
|
|
238
|
+
* - Selection confirmation — commits the active item to the selection on Enter/Space
|
|
239
|
+
*
|
|
240
|
+
* @param {{ mapState: object, pluginState: object, services: object, mapProvider: object, spatialListRegistry: object }} params
|
|
241
|
+
*/
|
|
242
|
+
export function useSpatialList ({ mapState, pluginState, services, mapProvider, spatialListRegistry }) {
|
|
243
|
+
const { markers, mapSize } = mapState
|
|
244
|
+
const { dispatch, interactionModes, layers, multiSelect } = pluginState
|
|
245
|
+
const { eventBus } = services
|
|
246
|
+
const listboxActiveItemRef = useRef(null)
|
|
247
|
+
const itemsRef = useRef([])
|
|
248
|
+
useItemListSync({ markers, mapSize, interactionModes, layers, mapProvider, multiSelect, spatialListRegistry, eventBus, itemsRef })
|
|
249
|
+
useActiveItemHandler({ itemsRef, eventBus, dispatch, listboxActiveItemRef })
|
|
250
|
+
useSelectItemHandler({ eventBus, dispatch, listboxActiveItemRef, multiSelect })
|
|
251
|
+
}
|