@defra/interactive-map 0.0.43-fmp-experimental → 0.0.45-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 +11 -7
- package/assets/templates/add-symbols.njk +5 -1
- package/assets/templates/select-feature.njk +72 -15
- 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/examples/add-marker-with-panel.mdx +4 -0
- package/docs/examples/add-polygons.mdx +16 -12
- package/docs/examples/add-symbols.mdx +4 -0
- package/docs/examples/basic-map.mdx +4 -0
- package/docs/examples/button-map.mdx +4 -0
- package/docs/examples/draw-tools.mdx +4 -0
- package/docs/examples/place-marker.mdx +4 -0
- package/docs/examples/search-control.mdx +4 -0
- package/docs/examples/select-feature.mdx +4 -0
- package/docs/examples/style-switcher.mdx +36 -44
- package/docs/examples/toggle-marker-label.mdx +4 -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/components/Form/Form.jsx +3 -3
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +2 -2
- package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +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/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/utils/spatial.js +15 -13
- 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/beta/openlayers/src/utils/spatial.js +16 -14
- package/providers/beta/openlayers/src/utils/spatial.test.js +10 -6
- 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/maplibreProvider.js +1 -1
- 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 +19 -74
- package/providers/maplibre/src/utils/spatial.test.js +8 -32
- 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 +9 -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 +17 -17
- 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/scss/tools/_govuk-content.scss +29 -0
- package/src/scss/tools/_index.scss +2 -1
- package/src/test-utils.js +8 -0
- package/src/types.js +53 -11
- 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
|
@@ -7,10 +7,8 @@ import { TOLERANCES } from '../defaults.js'
|
|
|
7
7
|
|
|
8
8
|
const MODE = 'draw_point'
|
|
9
9
|
|
|
10
|
-
// A point has no ring/rubber-band to build a candidate from
|
|
11
|
-
//
|
|
12
|
-
// Polygon/LineString via MODE_BY_GEOMETRY, so a Point candidate is built and validated
|
|
13
|
-
// directly (same approach as the MapLibre adapter's drawPointMode.js).
|
|
10
|
+
// A point has no ring/rubber-band to build a candidate from, so it's built and validated
|
|
11
|
+
// directly rather than via validateGeometry.js's Polygon/LineString-only attemptPlacement.
|
|
14
12
|
const canPlaceVertex = (manager) => (coordinate) => {
|
|
15
13
|
const candidate = { type: 'Feature', geometry: { type: 'Point', coordinates: coordinate }, properties: {} }
|
|
16
14
|
const { valid, reason } = validatePlacement(
|
|
@@ -24,14 +22,9 @@ const canPlaceVertex = (manager) => (coordinate) => {
|
|
|
24
22
|
return valid
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
// Commit a finished point to the store under the requested id and emit CREATE
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// Symbol resolution runs after the feature is in the store (not before), matching the
|
|
31
|
-
// MapLibre adapter's own ordering. The resolver itself is injected by OLDrawManager via
|
|
32
|
-
// changeMode's options (mirrors the ML adapter's state.resolvePointSymbol convention — see
|
|
33
|
-
// MaplibreDrawAdapter.js's changeMode) so this mode has no direct dependency on
|
|
34
|
-
// symbolRegistry/mapProvider/pointSymbolImages.js.
|
|
25
|
+
// Commit a finished point to the store under the requested id and emit CREATE. Symbol
|
|
26
|
+
// resolution runs after the feature is in the store, and the resolver itself is injected by
|
|
27
|
+
// OLDrawManager (mirrors the MapLibre adapter's own ordering and resolvePointSymbol convention).
|
|
35
28
|
const finalizeDrawnFeature = (manager, resolvePointSymbol, olFeature, featureId, properties) => {
|
|
36
29
|
olFeature.setId(String(featureId))
|
|
37
30
|
olFeature.setProperties(properties)
|
|
@@ -41,45 +34,41 @@ const finalizeDrawnFeature = (manager, resolvePointSymbol, olFeature, featureId,
|
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
/**
|
|
44
|
-
* Touch/keyboard/"Add point" input for draw_point —
|
|
45
|
-
*
|
|
46
|
-
* directly, but with a much smaller placement step: a point has no rubber band to update
|
|
47
|
-
* while positioning (the crosshair itself already shows where it'll land) and nothing to
|
|
48
|
-
* undo before a single-click commit, so those callbacks are no-ops here.
|
|
37
|
+
* Touch/keyboard/"Add point" input for draw_point — reuses draw/drawInput.js's crosshair and
|
|
38
|
+
* interface-type wiring, but with no rubber band or undo since a point commits on one click.
|
|
49
39
|
*/
|
|
50
40
|
const buildPointInput = ({ drawInteraction, options, canPlace }) => {
|
|
51
41
|
const { container, addVertexButtonId, mapProvider, snap, crossHair } = options
|
|
52
42
|
let interfaceType = options.interfaceType ?? 'mouse'
|
|
53
43
|
const getInterfaceType = () => interfaceType
|
|
54
44
|
|
|
55
|
-
// appendCoordinates() only
|
|
56
|
-
//
|
|
57
|
-
//
|
|
45
|
+
// appendCoordinates() only starts the sketch for a Point-type Draw interaction, so
|
|
46
|
+
// finishDrawing() must be called explicitly to commit. Only ever reached via the crosshair
|
|
47
|
+
// path (Enter, the add-vertex button, or the crosshair's own click), never a real mouse
|
|
48
|
+
// click, so snap always applies here when available.
|
|
58
49
|
const placePoint = () => {
|
|
59
50
|
const raw = mapProvider.getCenter()
|
|
60
|
-
const coord =
|
|
51
|
+
const coord = snap ? snap.apply(raw) : raw
|
|
61
52
|
snap?.hideIndicator()
|
|
62
53
|
if (!canPlace(coord)) { return }
|
|
63
54
|
drawInteraction.appendCoordinates([coord])
|
|
64
55
|
drawInteraction.finishDrawing()
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
// Keeps the snap indicator visible at the crosshair
|
|
68
|
-
//
|
|
69
|
-
//
|
|
58
|
+
// Keeps the snap indicator visible at the crosshair — the point-shaped equivalent of
|
|
59
|
+
// updateRubberbanding. Callers decide when this is worth calling (see applyInterfaceType
|
|
60
|
+
// below and draw/drawInput.js's onCenterChange).
|
|
70
61
|
const updateSnapIndicator = () => {
|
|
71
|
-
if (
|
|
62
|
+
if (snap) { snap.apply(mapProvider.getCenter()) }
|
|
72
63
|
}
|
|
73
64
|
|
|
74
|
-
// Mirrors draw/drawInput.js's createDrawInput:
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// after entering the mode (the same gap the MapLibre adapter's onInterfaceTypeChange already
|
|
78
|
-
// guards against by calling onMove(state) right after _setInterface).
|
|
65
|
+
// Mirrors draw/drawInput.js's createDrawInput: a switch to touch/keyboard refreshes the
|
|
66
|
+
// snap indicator immediately rather than waiting for the next pan. Switching back to mouse
|
|
67
|
+
// has no crosshair target to refresh.
|
|
79
68
|
const applyInterfaceType = (type) => {
|
|
80
69
|
interfaceType = type
|
|
81
70
|
applyCrossHairVisibility(crossHair, type)
|
|
82
|
-
updateSnapIndicator()
|
|
71
|
+
if (type !== 'mouse') { updateSnapIndicator() }
|
|
83
72
|
}
|
|
84
73
|
|
|
85
74
|
const events = wireInputEvents({
|
|
@@ -96,29 +85,29 @@ const buildPointInput = ({ drawInteraction, options, canPlace }) => {
|
|
|
96
85
|
|
|
97
86
|
applyCrossHairVisibility(crossHair, interfaceType)
|
|
98
87
|
|
|
88
|
+
// Shared "commit here" entry point for the crosshair button's own onClick — same action as
|
|
89
|
+
// Enter/the touch add-vertex button.
|
|
90
|
+
if (crossHair) {
|
|
91
|
+
crossHair.activate = placePoint
|
|
92
|
+
}
|
|
93
|
+
|
|
99
94
|
return {
|
|
100
95
|
getInterfaceType,
|
|
101
96
|
setInterfaceType: applyInterfaceType,
|
|
102
|
-
destroy () {
|
|
97
|
+
destroy () {
|
|
98
|
+
if (crossHair?.activate === placePoint) { crossHair.activate = null }
|
|
99
|
+
events.destroy()
|
|
100
|
+
}
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
/**
|
|
107
|
-
* Draw mode for placing a single point: mouse click, or crosshair + Enter/"Add point"
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* immediately (confirmed by reading its handleUpEvent — mode 'Point' always attempts
|
|
114
|
-
* finishDrawing() on pointer-up, no second click needed), so the mouse path needs no
|
|
115
|
-
* extra wiring beyond the placement-veto condition every mode already has. Deliberately
|
|
116
|
-
* NOT built via draw/DrawMode.js's createDrawMode()/createVertexPlacement() — those are
|
|
117
|
-
* parametrised around a ring of placed vertices + a trailing rubber-band coordinate,
|
|
118
|
-
* which a single-click commit doesn't have (mirrors the same decision already made for
|
|
119
|
-
* the MapLibre adapter, and the OL EditMode/DrawMode split noted in
|
|
120
|
-
* feedback_adapter_parity.md: don't force point through machinery shaped for lines/polygons
|
|
121
|
-
* just because they happen to share a file).
|
|
105
|
+
* Draw mode for placing a single point: mouse click, or crosshair + Enter/"Add point" button
|
|
106
|
+
* for touch/keyboard. Commits immediately, with no rubber band, undo, or "Done" step. OL's own
|
|
107
|
+
* Draw({type:'Point'}) interaction already finishes a real mouse click on pointer-up, so the
|
|
108
|
+
* mouse path needs no extra wiring. Deliberately not built via draw/DrawMode.js's
|
|
109
|
+
* createDrawMode()/createVertexPlacement() — those assume a ring of vertices plus a trailing
|
|
110
|
+
* rubber-band coordinate, which a single-click commit doesn't have.
|
|
122
111
|
*/
|
|
123
112
|
export const createDrawPointMode = ({ map, manager, options }) => {
|
|
124
113
|
const { featureId, properties = {} } = options
|
|
@@ -126,18 +115,15 @@ export const createDrawPointMode = ({ map, manager, options }) => {
|
|
|
126
115
|
|
|
127
116
|
const drawInteraction = new Draw({
|
|
128
117
|
type: 'Point',
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
// suppresses this same default for the "ghost" Point sketch OL renders alongside a
|
|
132
|
-
// Polygon/LineString sketch (see its own comment); a real Point sketch needs the same
|
|
133
|
-
// treatment, since it commits on the very first click with nothing meaningful to preview.
|
|
118
|
+
// Suppresses OL's default sketch style (a blue circle following the cursor) — nothing
|
|
119
|
+
// meaningful to preview since this commits on the first click.
|
|
134
120
|
style: () => [],
|
|
135
121
|
snapTolerance: TOLERANCES.snapRadius,
|
|
136
|
-
condition: (
|
|
122
|
+
condition: (event) => noModifierKeys(event) && canPlace(event.coordinate)
|
|
137
123
|
})
|
|
138
124
|
map.addInteraction(drawInteraction)
|
|
139
125
|
|
|
140
|
-
drawInteraction.on('drawend', (
|
|
126
|
+
drawInteraction.on('drawend', (event) => finalizeDrawnFeature(manager, options.resolvePointSymbol, event.feature, featureId, properties))
|
|
141
127
|
drawInteraction.on('drawabort', () => { manager.emit(ADAPTER_EVENTS.CANCEL) })
|
|
142
128
|
|
|
143
129
|
const input = buildPointInput({ drawInteraction, options, canPlace })
|
|
@@ -56,7 +56,7 @@ describe('createDrawPointMode', () => {
|
|
|
56
56
|
const { interaction, manager, emitted } = setup()
|
|
57
57
|
interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })
|
|
58
58
|
|
|
59
|
-
const created = emitted().filter((
|
|
59
|
+
const created = emitted().filter((event) => event.type === ADAPTER_EVENTS.CREATE)
|
|
60
60
|
expect(created).toHaveLength(1)
|
|
61
61
|
expect(created[0].payload.id).toBe('point-1') // re-id'd, not the OL-generated one
|
|
62
62
|
expect(created[0].payload.geometry.coordinates).toEqual([1, 2])
|
|
@@ -82,7 +82,7 @@ describe('createDrawPointMode', () => {
|
|
|
82
82
|
test('defaults properties to {} when none are supplied', () => {
|
|
83
83
|
const { interaction, manager, emitted } = setup({ properties: undefined })
|
|
84
84
|
interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })
|
|
85
|
-
const created = emitted().filter((
|
|
85
|
+
const created = emitted().filter((event) => event.type === ADAPTER_EVENTS.CREATE)
|
|
86
86
|
// OL's GeoJSON writer reports no properties as null (not {}) — the meaningful
|
|
87
87
|
// assertion is what setProperties() was actually called with, on the stored feature.
|
|
88
88
|
expect(created[0].payload.properties).toBeNull()
|
|
@@ -96,7 +96,7 @@ describe('createDrawPointMode', () => {
|
|
|
96
96
|
const condition = interaction.condition_
|
|
97
97
|
const allowed = condition({ coordinate: [1, 2], originalEvent: {} })
|
|
98
98
|
expect(allowed).toBe(false)
|
|
99
|
-
const blocked = emitted().filter((
|
|
99
|
+
const blocked = emitted().filter((event) => event.type === ADAPTER_EVENTS.PLACEMENT_BLOCKED)
|
|
100
100
|
expect(blocked).toHaveLength(1)
|
|
101
101
|
expect(blocked[0].payload).toMatchObject({ reason: 'not allowed here', phase: 'place', mode: 'draw_point' })
|
|
102
102
|
})
|
|
@@ -116,7 +116,7 @@ describe('createDrawPointMode', () => {
|
|
|
116
116
|
test('drawabort emits CANCEL', () => {
|
|
117
117
|
const { interaction, emitted } = setup()
|
|
118
118
|
interaction.dispatchEvent({ type: 'drawabort' })
|
|
119
|
-
expect(emitted().some((
|
|
119
|
+
expect(emitted().some((event) => event.type === ADAPTER_EVENTS.CANCEL)).toBe(true)
|
|
120
120
|
})
|
|
121
121
|
|
|
122
122
|
describe('touch/keyboard placement (via the wired placeVertex callback)', () => {
|
|
@@ -142,13 +142,13 @@ describe('createDrawPointMode', () => {
|
|
|
142
142
|
expect(finish).toHaveBeenCalled()
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
-
test('
|
|
146
|
-
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
145
|
+
test('still snaps with interfaceType mouse — placeVertex is only ever reached via the crosshair path (Enter, the add-vertex button, or the crosshair\'s own click), never a real mouse click', () => {
|
|
146
|
+
const snap = { apply: jest.fn(() => [9, 9]), hideIndicator: jest.fn() }
|
|
147
147
|
const { wireArgs, interaction } = setup({ snap, interfaceType: 'mouse' })
|
|
148
148
|
const { append } = spyOnCommit(interaction)
|
|
149
149
|
wireArgs.placeVertex()
|
|
150
|
-
expect(snap.apply).
|
|
151
|
-
expect(append).toHaveBeenCalledWith([[
|
|
150
|
+
expect(snap.apply).toHaveBeenCalledWith([5, 5])
|
|
151
|
+
expect(append).toHaveBeenCalledWith([[9, 9]])
|
|
152
152
|
})
|
|
153
153
|
|
|
154
154
|
test('a vetoed crosshair placement never commits', () => {
|
|
@@ -158,7 +158,7 @@ describe('createDrawPointMode', () => {
|
|
|
158
158
|
wireArgs.placeVertex()
|
|
159
159
|
expect(append).not.toHaveBeenCalled()
|
|
160
160
|
expect(finish).not.toHaveBeenCalled()
|
|
161
|
-
expect(emitted().filter((
|
|
161
|
+
expect(emitted().filter((event) => event.type === ADAPTER_EVENTS.PLACEMENT_BLOCKED)).toHaveLength(1)
|
|
162
162
|
})
|
|
163
163
|
|
|
164
164
|
test('updateRubberbanding shows the snap indicator at the crosshair for touch/keyboard, not mouse', () => {
|
|
@@ -169,11 +169,12 @@ describe('createDrawPointMode', () => {
|
|
|
169
169
|
expect(snap.apply).toHaveBeenCalledWith([7, 7])
|
|
170
170
|
})
|
|
171
171
|
|
|
172
|
-
test('updateRubberbanding
|
|
172
|
+
test('updateRubberbanding still refreshes the indicator regardless of interfaceType — the caller decides when it\'s worth calling this at all', () => {
|
|
173
173
|
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
174
|
-
const
|
|
174
|
+
const mapProvider = { getCenter: () => [7, 7] }
|
|
175
|
+
const { wireArgs } = setup({ snap, mapProvider, interfaceType: 'mouse' })
|
|
175
176
|
wireArgs.updateRubberbanding()
|
|
176
|
-
expect(snap.apply).
|
|
177
|
+
expect(snap.apply).toHaveBeenCalledWith([7, 7])
|
|
177
178
|
})
|
|
178
179
|
|
|
179
180
|
// Regression: without this, switching to touch/keyboard left the indicator hidden until
|
|
@@ -239,8 +240,26 @@ describe('createDrawPointMode', () => {
|
|
|
239
240
|
const { mode, map, interaction, emitted } = setup()
|
|
240
241
|
const removeSpy = jest.spyOn(map, 'removeInteraction')
|
|
241
242
|
mode.destroy()
|
|
242
|
-
expect(emitted().some((
|
|
243
|
+
expect(emitted().some((event) => event.type === ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE)).toBe(true)
|
|
243
244
|
expect(removeSpy).toHaveBeenCalledWith(interaction)
|
|
244
245
|
})
|
|
246
|
+
|
|
247
|
+
test('wires crossHair.activate to the same placeVertex callback given to wireInputEvents — the crosshair button\'s own onClick (CrossHair.jsx) does exactly what Enter/the touch add-vertex button already do', () => {
|
|
248
|
+
const crossHair = {}
|
|
249
|
+
const { wireArgs } = setup({ crossHair })
|
|
250
|
+
expect(crossHair.activate).toBe(wireArgs.placeVertex)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('does not wire crossHair.activate when no crossHair is provided', () => {
|
|
254
|
+
expect(() => setup({ crossHair: null })).not.toThrow()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('destroy clears crossHair.activate so a stale closure cannot fire after the mode has gone', () => {
|
|
258
|
+
const crossHair = {}
|
|
259
|
+
const { mode } = setup({ crossHair })
|
|
260
|
+
expect(typeof crossHair.activate).toBe('function')
|
|
261
|
+
mode.destroy()
|
|
262
|
+
expect(crossHair.activate).toBeNull()
|
|
263
|
+
})
|
|
245
264
|
})
|
|
246
265
|
})
|
|
@@ -250,7 +250,7 @@ export const createEditPointMode = ({ map, manager, options }) => {
|
|
|
250
250
|
// Matches MaplibreDrawAdapter.deleteVertex's existing no-op precedent.
|
|
251
251
|
deleteVertex () {},
|
|
252
252
|
|
|
253
|
-
//
|
|
253
|
+
// MapControls' D-pad, routed here via mapProvider.activeMoveTarget (events.js) — claimed
|
|
254
254
|
// once at construction (pointSelectionState's VERTEX_SELECTION) and held for the session.
|
|
255
255
|
nudgeSelectedVertex: keyboardHandler.nudgeByDelta,
|
|
256
256
|
|
|
@@ -208,7 +208,7 @@ describe('mouse hit-testing respects the icon\'s own rendered pixels, not a fixe
|
|
|
208
208
|
})
|
|
209
209
|
})
|
|
210
210
|
|
|
211
|
-
test('nudgeSelectedVertex (
|
|
211
|
+
test('nudgeSelectedVertex (MapControls D-pad) moves the point and is undoable', () => {
|
|
212
212
|
const { manager, mode, coord } = setup()
|
|
213
213
|
mode.nudgeSelectedVertex(1, 0, true)
|
|
214
214
|
expect(coord()).not.toEqual([5, 5])
|
|
@@ -249,6 +249,22 @@ test('keyboard: Delete is a no-op — there is no delete-vertex action for a poi
|
|
|
249
249
|
expect(coord()).toEqual([5, 5])
|
|
250
250
|
})
|
|
251
251
|
|
|
252
|
+
// A point is "always selected" (pointSelectionState.js's selectedVertexIndex never leaves 0),
|
|
253
|
+
// so the shared keyboardHandler.js's Alt+<key> shadow guard (gated on selectedVertexIndex >= 0)
|
|
254
|
+
// stays unconditionally true here — unlike edit_vertex, where it only applies once a vertex is
|
|
255
|
+
// actually selected.
|
|
256
|
+
test('keyboard: Alt+Enter/Alt+Arrow keyup are shadowed from the app-wide label shortcuts for the whole session', () => {
|
|
257
|
+
setup()
|
|
258
|
+
const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', altKey: true, cancelable: true, bubbles: true })
|
|
259
|
+
const arrowEvent = new KeyboardEvent('keyup', { key: 'ArrowRight', altKey: true, cancelable: true, bubbles: true })
|
|
260
|
+
const enterSpy = jest.spyOn(enterEvent, 'stopPropagation')
|
|
261
|
+
const arrowSpy = jest.spyOn(arrowEvent, 'stopPropagation')
|
|
262
|
+
window.dispatchEvent(enterEvent)
|
|
263
|
+
window.dispatchEvent(arrowEvent)
|
|
264
|
+
expect(enterSpy).toHaveBeenCalled()
|
|
265
|
+
expect(arrowSpy).toHaveBeenCalled()
|
|
266
|
+
})
|
|
267
|
+
|
|
252
268
|
test('keyboard: Cmd/Ctrl+Z undoes via the same op-popping logic as mode.undo()', () => {
|
|
253
269
|
const { manager, coord } = setup()
|
|
254
270
|
key('keydown', { key: 'ArrowRight' })
|
|
@@ -36,8 +36,8 @@ const createGeometryInvalidEmitter = (eventBus, hints) => (payload) => {
|
|
|
36
36
|
if (payload.reason && !isIncompleteShape(payload.reason)) { hints.show(payload.reason) }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Claims
|
|
40
|
-
// mapProvider.activeMoveTarget contract (
|
|
39
|
+
// Claims MapControls' D-pad for the currently selected vertex — see the generic
|
|
40
|
+
// mapProvider.activeMoveTarget contract (MapControls.jsx). Any plugin could use
|
|
41
41
|
// this slot; the draw plugin is just its first consumer.
|
|
42
42
|
const buildVertexMoveTarget = (draw) => ({
|
|
43
43
|
move: (dx, dy, isLargeStep) => draw.nudgeSelectedVertex(dx, dy, isLargeStep),
|
|
@@ -45,7 +45,7 @@ const buildVertexMoveTarget = (draw) => ({
|
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
// Vertex-selection handlers: sync pluginState.selectedVertexIndex, and claim/release
|
|
48
|
-
//
|
|
48
|
+
// MapControls' D-pad via buildVertexMoveTarget above.
|
|
49
49
|
const createVertexSelectionHandlers = ({ draw, pluginState, mapProvider, eventBus }) => ({
|
|
50
50
|
onVertexSelection: (e) => {
|
|
51
51
|
pluginState.dispatch({ type: 'SET_SELECTED_VERTEX_INDEX', payload: e })
|
|
@@ -233,7 +233,7 @@ export function attachEvents ({ appState, appConfig, mapState, pluginState, mapP
|
|
|
233
233
|
const resetState = () => {
|
|
234
234
|
pluginState.dispatch({ type: 'SET_MODE', payload: null })
|
|
235
235
|
pluginState.dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null } })
|
|
236
|
-
// Release
|
|
236
|
+
// Release MapControls' D-pad back to panning the map whenever a draw/edit
|
|
237
237
|
// session ends — a stale claim here would silently hijack it for good.
|
|
238
238
|
mapProvider.activeMoveTarget = null
|
|
239
239
|
}
|
|
@@ -179,7 +179,7 @@ describe('draw event handlers', () => {
|
|
|
179
179
|
expect(() => drawHandler(draw, 'cancel')()).not.toThrow()
|
|
180
180
|
})
|
|
181
181
|
|
|
182
|
-
test('vertexselection dispatches, emits, and claims mapProvider.activeMoveTarget for
|
|
182
|
+
test('vertexselection dispatches, emits, and claims mapProvider.activeMoveTarget for MapControls', () => {
|
|
183
183
|
const { draw, dispatch, eventBus, mapProvider } = setup()
|
|
184
184
|
drawHandler(draw, 'vertexselection')({ index: 2 })
|
|
185
185
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SELECTED_VERTEX_INDEX', payload: { index: 2 } })
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { EVENTS } from '../../../../src/config/events.js'
|
|
3
|
+
import { scaleFactor } from '../../../../src/config/appConfig.js'
|
|
4
|
+
import { ADAPTER_EVENTS } from '../adapterEvents.js'
|
|
5
|
+
|
|
6
|
+
const EDIT_VERTEX_MODE = 'edit_vertex'
|
|
7
|
+
const DRAW_PROVIDER_ID = 'draw'
|
|
8
|
+
|
|
9
|
+
// Mirrors interact's own projectToScreen (plugins/interact/src/hooks/useSpatialList.js) —
|
|
10
|
+
// mapProvider.mapToScreen is the provider-agnostic projection every mapProvider implements,
|
|
11
|
+
// not any adapter-internal pixel conversion.
|
|
12
|
+
const projectToScreen = (mapProvider, mapSize, coord) => {
|
|
13
|
+
const { x, y } = mapProvider.mapToScreen(coord)
|
|
14
|
+
return { x: x * scaleFactor[mapSize], y: y * scaleFactor[mapSize] }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ids stay flat vertices-then-midpoints indices — matches the mode's own selectedVertexIndex
|
|
18
|
+
// convention on both adapters (see the adapters' getVertexItems() doc comments) and is what
|
|
19
|
+
// selectVertex/insertVertexAtMidpoint expect, so it's unrelated to display order. The array
|
|
20
|
+
// order below is interleaved for display only: Point 1, Insert 1-2, Point 2, ... The "between
|
|
21
|
+
// N and M" label (and this interleave) only reads correctly for a single ring/part (the
|
|
22
|
+
// common case); a shape with holes or multiple parts would need ring-aware numbering to stay
|
|
23
|
+
// accurate, not done here.
|
|
24
|
+
const buildVertexItems = (mapProvider, mapSize) => {
|
|
25
|
+
const { vertices, midpoints } = mapProvider.draw?.getVertexItems?.() ?? { vertices: [], midpoints: [] }
|
|
26
|
+
const toVertexItem = (coord, i) => ({
|
|
27
|
+
id: String(i),
|
|
28
|
+
label: `Point ${i + 1}`,
|
|
29
|
+
isMidpoint: false,
|
|
30
|
+
...projectToScreen(mapProvider, mapSize, coord)
|
|
31
|
+
})
|
|
32
|
+
const toMidpointItem = (coord, i) => ({
|
|
33
|
+
id: String(vertices.length + i),
|
|
34
|
+
// Kept as short as possible — a voice user hears/reads this in full each time, and
|
|
35
|
+
// "Insert point between N and M" was long enough to lose.
|
|
36
|
+
label: `Insert ${i + 1}-${((i + 1) % vertices.length) + 1}`,
|
|
37
|
+
isMidpoint: true,
|
|
38
|
+
...projectToScreen(mapProvider, mapSize, coord)
|
|
39
|
+
})
|
|
40
|
+
const items = []
|
|
41
|
+
vertices.forEach((coord, i) => {
|
|
42
|
+
items.push(toVertexItem(coord, i))
|
|
43
|
+
if (i < midpoints.length) {
|
|
44
|
+
items.push(toMidpointItem(midpoints[i], i))
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
return items
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rebuilds the vertex/midpoint item list whenever edit_vertex is entered/left, on every
|
|
52
|
+
* geometry change, and on every map move (pan/zoom moves each item's screen position even
|
|
53
|
+
* though the underlying geographic coordinate hasn't changed — same reason interact's own
|
|
54
|
+
* hook rebuilds on MAP_MOVE_END). Keeps the result in itemsRef for useConfirmHandler to
|
|
55
|
+
* resolve against later, and registers as an exclusive spatialListRegistry provider —
|
|
56
|
+
* claiming ownership while edit_vertex is active (interact's own items are excluded
|
|
57
|
+
* meanwhile, restored automatically once the claim is released) rather than contributing
|
|
58
|
+
* additively like interact does.
|
|
59
|
+
*/
|
|
60
|
+
function useVertexItemSync ({ pluginState, mapProvider, mapSize, spatialListRegistry, itemsRef, eventBus }) {
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const rebuild = () => {
|
|
63
|
+
itemsRef.current = pluginState.mode === EDIT_VERTEX_MODE ? buildVertexItems(mapProvider, mapSize) : []
|
|
64
|
+
spatialListRegistry.notifyItemsChanged(DRAW_PROVIDER_ID)
|
|
65
|
+
}
|
|
66
|
+
rebuild()
|
|
67
|
+
eventBus.on(EVENTS.MAP_MOVE_END, rebuild)
|
|
68
|
+
const { draw } = mapProvider
|
|
69
|
+
if (!draw) {
|
|
70
|
+
return () => eventBus.off(EVENTS.MAP_MOVE_END, rebuild)
|
|
71
|
+
}
|
|
72
|
+
draw.on(ADAPTER_EVENTS.UPDATE, rebuild)
|
|
73
|
+
return () => {
|
|
74
|
+
eventBus.off(EVENTS.MAP_MOVE_END, rebuild)
|
|
75
|
+
draw.off(ADAPTER_EVENTS.UPDATE, rebuild)
|
|
76
|
+
}
|
|
77
|
+
}, [pluginState.mode, mapProvider, mapSize, spatialListRegistry, itemsRef, eventBus])
|
|
78
|
+
|
|
79
|
+
// Registers once, for the lifetime of the hook — safe outside edit_vertex too, since an
|
|
80
|
+
// exclusive-capable provider only ever contributes while it holds the claim (see
|
|
81
|
+
// spatialListRegistry.js). Declared after the effect above for the same initial-recompute
|
|
82
|
+
// ordering reason interact's own hook documents.
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
spatialListRegistry.registerItemProvider(DRAW_PROVIDER_ID, {
|
|
85
|
+
// focusable: false — Tab-only keyboard users already have a full path via the map's own
|
|
86
|
+
// arrow-key nudge/Alt+Arrow navigate, so this list exists purely for Voice Control and a
|
|
87
|
+
// screen reader's own virtual-cursor discovery + activation. See SpatialList.jsx's own
|
|
88
|
+
// comment on why forcing every item's tabIndex to -1 is deliberate here.
|
|
89
|
+
getItems: () => ({ items: itemsRef.current, multiselectable: false, label: 'Shape points', focusable: false }),
|
|
90
|
+
exclusive: true
|
|
91
|
+
})
|
|
92
|
+
return () => { spatialListRegistry.unregisterItemProvider(DRAW_PROVIDER_ID) }
|
|
93
|
+
}, [spatialListRegistry, itemsRef])
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (pluginState.mode !== EDIT_VERTEX_MODE) {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
spatialListRegistry.claimExclusive(DRAW_PROVIDER_ID)
|
|
100
|
+
return () => spatialListRegistry.releaseExclusive(DRAW_PROVIDER_ID)
|
|
101
|
+
}, [pluginState.mode, spatialListRegistry])
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Listens for MAP_SET_ACTIVE_ITEM and previews (non-destructively) whichever vertex or
|
|
106
|
+
* midpoint the listbox cursor is now on — every arrow-key move through the list, mirroring
|
|
107
|
+
* how mouse/touch/keyboard selection already behaves live elsewhere in edit_vertex. Unlike
|
|
108
|
+
* interact's real map features (which need an explicit Enter/Space confirm before anything
|
|
109
|
+
* happens), there's no separate "preview" step for a real vertex anywhere else in this mode —
|
|
110
|
+
* arrowing onto one already is the live selection. A midpoint is also just previewed here;
|
|
111
|
+
* confirming the actual insert is useConfirmHandler's job below.
|
|
112
|
+
*/
|
|
113
|
+
function useActiveVertexHandler ({ eventBus, mapProvider, pluginState, activeIdRef }) {
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const handle = ({ id }) => {
|
|
116
|
+
activeIdRef.current = id
|
|
117
|
+
if (id === null || pluginState.mode !== EDIT_VERTEX_MODE) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
mapProvider.draw?.selectVertex?.(Number(id))
|
|
121
|
+
}
|
|
122
|
+
eventBus.on(EVENTS.MAP_SET_ACTIVE_ITEM, handle)
|
|
123
|
+
return () => { eventBus.off(EVENTS.MAP_SET_ACTIVE_ITEM, handle) }
|
|
124
|
+
}, [eventBus, mapProvider, pluginState.mode, activeIdRef])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Handles MAP_SELECT_ITEM (Enter/Space, or a click/Voice-Control click — and, since none of
|
|
129
|
+
* these items are ever a real Tab stop, a screen reader's own virtual-cursor activation gesture
|
|
130
|
+
* too, which likewise fires as a click — all three arrive here identically). For a midpoint,
|
|
131
|
+
* this is the destructive step: commits a new vertex there (a real vertex is already the live
|
|
132
|
+
* selection the moment it's previewed, so confirming one doesn't change anything on the map).
|
|
133
|
+
* Either way, confirming announces what happened and hands keyboard focus back to the viewport
|
|
134
|
+
* — landing the user exactly where the pre-existing, untouched arrow-key nudge/Alt+Arrow flow
|
|
135
|
+
* already works, same as if they'd selected that point with a mouse click. This is deliberately
|
|
136
|
+
* unconditional (not just for midpoints) so every input path ends up in the same place.
|
|
137
|
+
*/
|
|
138
|
+
function useConfirmHandler ({ eventBus, mapProvider, pluginState, activeIdRef, itemsRef, viewportRef, announce }) {
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const handleConfirm = () => {
|
|
141
|
+
if (pluginState.mode !== EDIT_VERTEX_MODE || activeIdRef.current === null) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
const item = itemsRef.current.find(i => i.id === activeIdRef.current)
|
|
145
|
+
if (!item) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
if (item.isMidpoint) {
|
|
149
|
+
mapProvider.draw?.insertVertexAtMidpoint?.(Number(activeIdRef.current))
|
|
150
|
+
announce('New point inserted', 'action')
|
|
151
|
+
} else {
|
|
152
|
+
announce(`${item.label} selected`, 'action')
|
|
153
|
+
}
|
|
154
|
+
viewportRef.current?.focus()
|
|
155
|
+
}
|
|
156
|
+
eventBus.on(EVENTS.MAP_SELECT_ITEM, handleConfirm)
|
|
157
|
+
return () => { eventBus.off(EVENTS.MAP_SELECT_ITEM, handleConfirm) }
|
|
158
|
+
}, [eventBus, mapProvider, pluginState.mode, activeIdRef, itemsRef, viewportRef, announce])
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Orchestrates the accessible (Voice Control / screen-reader-discoverable) vertex/midpoint
|
|
163
|
+
* targets for draw's edit_vertex mode. Mirrors plugins/interact/src/hooks/useSpatialList.js's
|
|
164
|
+
* structure (item-sync + active-item concerns), but claims the registry's exclusive slot
|
|
165
|
+
* instead of contributing additively, declares focusable: false (no real Tab stop — see
|
|
166
|
+
* useVertexItemSync's own comment on why), and splits "preview" (any listbox move, non-
|
|
167
|
+
* destructive) from "confirm" (inserts at a midpoint, then always hands focus back to the
|
|
168
|
+
* viewport) rather than interact's own "preview, then explicit confirm for everything" split.
|
|
169
|
+
*
|
|
170
|
+
* @param {{ mapState: object, pluginState: object, services: object, mapProvider: object, spatialListRegistry: object, viewportRef: React.RefObject }} params
|
|
171
|
+
*/
|
|
172
|
+
export function useSpatialList ({ mapState, pluginState, services, mapProvider, spatialListRegistry, viewportRef }) {
|
|
173
|
+
const { mapSize } = mapState
|
|
174
|
+
const { eventBus, announce } = services
|
|
175
|
+
const itemsRef = useRef([])
|
|
176
|
+
const activeIdRef = useRef(null)
|
|
177
|
+
useVertexItemSync({ pluginState, mapProvider, mapSize, spatialListRegistry, itemsRef, eventBus })
|
|
178
|
+
useActiveVertexHandler({ eventBus, mapProvider, pluginState, activeIdRef })
|
|
179
|
+
useConfirmHandler({ eventBus, mapProvider, pluginState, activeIdRef, itemsRef, viewportRef, announce })
|
|
180
|
+
}
|