@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
|
@@ -56,6 +56,41 @@ test('arrow keys switch to the keyboard interface without placing', () => {
|
|
|
56
56
|
expect(placement.placeVertex).not.toHaveBeenCalled()
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
describe('global Alt+<key> shortcut shadowing', () => {
|
|
60
|
+
test('Alt+Enter keyup stops propagation while drawing — never reaches the app-wide "highlight label at center" shortcut', () => {
|
|
61
|
+
setup()
|
|
62
|
+
const event = new KeyboardEvent('keyup', { key: 'Enter', altKey: true, cancelable: true, bubbles: true })
|
|
63
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
64
|
+
window.dispatchEvent(event)
|
|
65
|
+
expect(stopSpy).toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('Alt+Arrow keyup stops propagation too — never reaches the app-wide "highlight next label" shortcut', () => {
|
|
69
|
+
setup()
|
|
70
|
+
const event = new KeyboardEvent('keyup', { key: 'ArrowRight', altKey: true, cancelable: true, bubbles: true })
|
|
71
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
72
|
+
window.dispatchEvent(event)
|
|
73
|
+
expect(stopSpy).toHaveBeenCalled()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('a plain Enter keyup (no Alt) is left alone', () => {
|
|
77
|
+
setup()
|
|
78
|
+
const event = new KeyboardEvent('keyup', { key: 'Enter', altKey: false, cancelable: true, bubbles: true })
|
|
79
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
80
|
+
window.dispatchEvent(event)
|
|
81
|
+
expect(stopSpy).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('the shadow listener is removed on destroy — Alt+Enter bubbles normally again', () => {
|
|
85
|
+
const { input } = setup()
|
|
86
|
+
input.destroy()
|
|
87
|
+
const event = new KeyboardEvent('keyup', { key: 'Enter', altKey: true, cancelable: true, bubbles: true })
|
|
88
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
89
|
+
window.dispatchEvent(event)
|
|
90
|
+
expect(stopSpy).not.toHaveBeenCalled()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
59
94
|
test('ctrl/cmd+z triggers undo', () => {
|
|
60
95
|
const { container, onUndo } = setup()
|
|
61
96
|
container.focus()
|
|
@@ -111,7 +146,26 @@ test('pointer moves and map pans update the rubber band except for the mouse int
|
|
|
111
146
|
expect(mouse.placement.updateRubberbanding).not.toHaveBeenCalled()
|
|
112
147
|
})
|
|
113
148
|
|
|
114
|
-
|
|
149
|
+
// Voice Control's simulated clicks report pointerType 'mouse' (interfaceType never leaves
|
|
150
|
+
// 'mouse') and produce no real pointermove, so panning via MapControls needs its own
|
|
151
|
+
// fallback: getAnimating() is true for MapControls' own panBy/zoomIn/zoomOut (both animate
|
|
152
|
+
// the view) but not for a live mouse drag (which sets the center directly), so it's a safe
|
|
153
|
+
// signal to also track even while interfaceType is 'mouse'.
|
|
154
|
+
test('a programmatic move (e.g. MapControls panBy) still updates the rubber band while interfaceType is mouse', () => {
|
|
155
|
+
const { view, placement } = setup('mouse')
|
|
156
|
+
view.getAnimating.mockReturnValue(true)
|
|
157
|
+
view.emit('change:center')
|
|
158
|
+
expect(placement.updateRubberbanding).toHaveBeenCalledTimes(1)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('postrender tracks an animated move for the mouse interface too, same as keyboard', () => {
|
|
162
|
+
const { map, view, placement } = setup('mouse')
|
|
163
|
+
view.getAnimating.mockReturnValue(true)
|
|
164
|
+
map.emit('postrender')
|
|
165
|
+
expect(placement.updateRubberbanding).toHaveBeenCalledTimes(1)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('setInterfaceType updates the interface and refreshes the rubber band immediately for non-mouse types, e.g. switching to touch and panning via MapControls mid-session', () => {
|
|
115
169
|
const { input, placement } = setup('mouse')
|
|
116
170
|
input.setInterfaceType('touch')
|
|
117
171
|
expect(input.getInterfaceType()).toBe('touch')
|
|
@@ -84,7 +84,14 @@ const tryClose = ({ drawInteraction, canFinish, geom, sketchCoords, coord, lastP
|
|
|
84
84
|
*
|
|
85
85
|
* @returns {{ placeVertex, updateRubberbanding, clearLastCoord }}
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
// Both snappedCenter and updateRubberbanding are only ever reached via the crosshair path
|
|
88
|
+
// (Enter, the add-vertex button, or the crosshair's own click) — never a real mouse click,
|
|
89
|
+
// which goes through OL's own Draw interaction and its separate snap interaction instead.
|
|
90
|
+
// So there's no interfaceType to gate on here: whenever these run, the target is by
|
|
91
|
+
// definition the crosshair, and snap (when available) should always apply. Callers decide
|
|
92
|
+
// *when* to call updateRubberbanding at all (see draw/drawInput.js's onCenterChange etc.),
|
|
93
|
+
// not this file.
|
|
94
|
+
export const createVertexPlacement = ({ drawInteraction, mapProvider, snap, canFinish, canPlace }) => {
|
|
88
95
|
let sketchFeature = null
|
|
89
96
|
let lastPlacedCoord = null
|
|
90
97
|
|
|
@@ -92,21 +99,20 @@ export const createVertexPlacement = ({ drawInteraction, mapProvider, snap, canF
|
|
|
92
99
|
sketchFeature = feature
|
|
93
100
|
lastPlacedCoord = null
|
|
94
101
|
}
|
|
95
|
-
drawInteraction.on('drawstart', (
|
|
102
|
+
drawInteraction.on('drawstart', (event) => resetSketch(event.feature))
|
|
96
103
|
drawInteraction.on('drawend', () => resetSketch())
|
|
97
104
|
drawInteraction.on('drawabort', () => resetSketch())
|
|
98
105
|
|
|
99
106
|
const snappedCenter = () => {
|
|
100
107
|
const raw = mapProvider.getCenter()
|
|
101
|
-
return
|
|
108
|
+
return snap ? snap.apply(raw) : raw
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
const updateRubberbanding = () => {
|
|
105
112
|
if (!sketchFeature) {
|
|
106
|
-
// No sketch yet — update snap indicator at crosshair
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
if (getInterfaceType() !== 'mouse' && snap) {
|
|
113
|
+
// No sketch yet — update the snap indicator at the crosshair so targets are visible
|
|
114
|
+
// before the first vertex is placed.
|
|
115
|
+
if (snap) {
|
|
110
116
|
snap.apply(mapProvider.getCenter())
|
|
111
117
|
}
|
|
112
118
|
return
|
|
@@ -6,7 +6,7 @@ import { createFakeMap, createEmitter, polygonFeature, lineFeature } from '../__
|
|
|
6
6
|
|
|
7
7
|
const CENTER = [5, 5]
|
|
8
8
|
|
|
9
|
-
const setup = ({
|
|
9
|
+
const setup = ({ snap = null, canFinish = () => true, canPlace } = {}) => {
|
|
10
10
|
const map = createFakeMap()
|
|
11
11
|
const bus = createEmitter()
|
|
12
12
|
const drawInteraction = {
|
|
@@ -21,8 +21,7 @@ const setup = ({ interfaceType = 'touch', snap = null, canFinish = () => true, c
|
|
|
21
21
|
mapProvider: { getCenter: () => CENTER },
|
|
22
22
|
snap,
|
|
23
23
|
canFinish,
|
|
24
|
-
canPlace
|
|
25
|
-
getInterfaceType: () => interfaceType
|
|
24
|
+
canPlace
|
|
26
25
|
})
|
|
27
26
|
const startSketch = (feature) => { bus.emit('drawstart', { feature }); return feature }
|
|
28
27
|
return { drawInteraction, placement, startSketch, bus }
|
|
@@ -59,13 +58,6 @@ describe('placing vertices', () => {
|
|
|
59
58
|
expect(drawInteraction.finishDrawing).toHaveBeenCalledTimes(1)
|
|
60
59
|
})
|
|
61
60
|
|
|
62
|
-
test('the mouse interface ignores snapping (the OL snap interaction covers it)', () => {
|
|
63
|
-
const snap = { apply: jest.fn(() => [9, 9]), hideIndicator: jest.fn() }
|
|
64
|
-
const { placement, drawInteraction } = setup({ interfaceType: 'mouse', snap })
|
|
65
|
-
placement.placeVertex()
|
|
66
|
-
expect(drawInteraction.appendCoordinates).toHaveBeenCalledWith([CENTER])
|
|
67
|
-
})
|
|
68
|
-
|
|
69
61
|
test('a second tap at the same spot finishes the shape instead of duplicating — when finishable', () => {
|
|
70
62
|
const { placement, drawInteraction, startSketch } = setup()
|
|
71
63
|
startSketch(lineFeature([[0, 0], [50, 50]]))
|
|
@@ -152,16 +144,16 @@ describe('rubber-banding', () => {
|
|
|
152
144
|
expect(feature.getGeometry().getCoordinates()[1]).toEqual(hole) // inner rings untouched
|
|
153
145
|
})
|
|
154
146
|
|
|
155
|
-
test('without a sketch it
|
|
147
|
+
test('without a sketch it refreshes the snap indicator at the crosshair — the caller decides when it\'s worth calling this at all', () => {
|
|
156
148
|
const snap = { apply: jest.fn((c) => c), hideIndicator: jest.fn() }
|
|
157
149
|
const { placement } = setup({ snap })
|
|
158
150
|
placement.updateRubberbanding()
|
|
159
151
|
expect(snap.apply).toHaveBeenCalledWith(CENTER)
|
|
152
|
+
})
|
|
160
153
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expect(snap.apply).not.toHaveBeenCalled()
|
|
154
|
+
test('without a sketch and no snap manager, updateRubberbanding is a harmless no-op', () => {
|
|
155
|
+
const { placement } = setup()
|
|
156
|
+
expect(() => placement.updateRubberbanding()).not.toThrow()
|
|
165
157
|
})
|
|
166
158
|
|
|
167
159
|
test('an empty sketch geometry is left alone', () => {
|
|
@@ -258,8 +258,8 @@ const wireMapSync = ({ map, manager, layers, selection, touchHandler, live }) =>
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// The mode interface consumed by OLDrawManager
|
|
261
|
-
const buildModeApi = ({ manager, store, olFeature, originalFeatureStyle, selection, actions, parts }) => {
|
|
262
|
-
const { state } = selection
|
|
261
|
+
const buildModeApi = ({ manager, store, olFeature, originalFeatureStyle, selection, actions, parts, undoStack }) => {
|
|
262
|
+
const { state, setState, syncGeom, emitGeometryValidation } = selection
|
|
263
263
|
const { touchHandler } = parts
|
|
264
264
|
|
|
265
265
|
return {
|
|
@@ -291,10 +291,41 @@ const buildModeApi = ({ manager, store, olFeature, originalFeatureStyle, selecti
|
|
|
291
291
|
|
|
292
292
|
undo: actions.doUndo,
|
|
293
293
|
deleteVertex: actions.doDeleteVertex,
|
|
294
|
-
//
|
|
294
|
+
// MapControls' D-pad, routed here via mapProvider.activeMoveTarget (see
|
|
295
295
|
// events.js) once a vertex is selected.
|
|
296
296
|
nudgeSelectedVertex: parts.keyboardHandler.nudgeByDelta,
|
|
297
297
|
|
|
298
|
+
// Read-only vertex/midpoint coordinates for the shared spatial listbox
|
|
299
|
+
// (plugins/draw/src/hooks/useSpatialList.js) — already kept live and correctly indexed
|
|
300
|
+
// by syncGeom()/updateLayersFromGeom() (selectionState.js), so nothing to recompute here.
|
|
301
|
+
getVertexItems: () => ({ vertices: state.vertices, midpoints: state.midpoints }),
|
|
302
|
+
|
|
303
|
+
// Moves the live cursor to a vertex or midpoint by flat index — preview only, no geometry
|
|
304
|
+
// change — for the spatial listbox's roving-tabindex move. Same shape as the local
|
|
305
|
+
// selectVertex closure in wireTouchHandler above, promoted here since buildModeApi needs
|
|
306
|
+
// its own entry point too.
|
|
307
|
+
selectVertex (index) {
|
|
308
|
+
setState({ selectedVertexIndex: index, selectedVertexType: index < state.vertices.length ? VERTEX_TYPE : 'midpoint' })
|
|
309
|
+
touchHandler.updateTargetPosition()
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
// Commits a new vertex exactly at midpoint `index` (no directional offset) — the
|
|
313
|
+
// destructive counterpart to selectVertex's preview-only move, for the spatial listbox's
|
|
314
|
+
// Enter/Space confirm on a midpoint item. Mirrors onTap's own midpoint-insert handling
|
|
315
|
+
// above exactly (insertAtMidpoint already inserts with no offset — that's how tap-to-insert
|
|
316
|
+
// already works, no keyboard-style directional nudge to strip).
|
|
317
|
+
insertVertexAtMidpoint (index) {
|
|
318
|
+
const result = insertAtMidpoint(olFeature, state.midpoints, index, state.vertices.length)
|
|
319
|
+
if (!result) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
undoStack.push({ type: 'insert_vertex', vertexIndex: result.insertedIndex })
|
|
323
|
+
syncGeom()
|
|
324
|
+
emitGeometryValidation(INSERT_VERTEX, result.insertedIndex)
|
|
325
|
+
setState({ selectedVertexIndex: result.insertedIndex, selectedVertexType: VERTEX_TYPE })
|
|
326
|
+
touchHandler.updateTargetPosition()
|
|
327
|
+
},
|
|
328
|
+
|
|
298
329
|
destroy () {
|
|
299
330
|
parts.live.destroy()
|
|
300
331
|
olFeature.setStyle(originalFeatureStyle)
|
|
@@ -312,7 +343,7 @@ const buildModeApi = ({ manager, store, olFeature, originalFeatureStyle, selecti
|
|
|
312
343
|
}
|
|
313
344
|
|
|
314
345
|
/**
|
|
315
|
-
* @returns {{ setInterfaceType, done, cancel, undo, deleteVertex, nudgeSelectedVertex, destroy } | null}
|
|
346
|
+
* @returns {{ setInterfaceType, done, cancel, undo, deleteVertex, nudgeSelectedVertex, getVertexItems, selectVertex, insertVertexAtMidpoint, destroy } | null}
|
|
316
347
|
*/
|
|
317
348
|
export const createEditMode = ({ map, manager, options }) => {
|
|
318
349
|
const { featureId, container, interfaceType, deleteVertexButtonId, snap } = options
|
|
@@ -380,6 +411,7 @@ export const createEditMode = ({ map, manager, options }) => {
|
|
|
380
411
|
originalFeatureStyle,
|
|
381
412
|
selection,
|
|
382
413
|
actions,
|
|
414
|
+
undoStack,
|
|
383
415
|
parts: { touchHandler, keyboardHandler, pointerHandlers, modify, mapSync, layers, live }
|
|
384
416
|
})
|
|
385
417
|
}
|
|
@@ -154,7 +154,7 @@ test('select via pointer, delete the vertex, then undo restores it', () => {
|
|
|
154
154
|
mode.undo() // empty stack — no-op
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
test('nudgeSelectedVertex (
|
|
157
|
+
test('nudgeSelectedVertex (MapControls D-pad) moves the selected vertex and is undoable', () => {
|
|
158
158
|
const { container, manager, mode, ring } = setup()
|
|
159
159
|
container.dispatchEvent(domEvent('pointerdown', { pointerType: 'mouse', clientX: 100, clientY: 0 }))
|
|
160
160
|
mode.nudgeSelectedVertex(1, 0, true)
|
|
@@ -165,6 +165,46 @@ test('nudgeSelectedVertex (MoveControls D-pad) moves the selected vertex and is
|
|
|
165
165
|
expect(ring()[1]).toEqual([100, 0])
|
|
166
166
|
})
|
|
167
167
|
|
|
168
|
+
// getVertexItems/selectVertex/insertVertexAtMidpoint feed the shared spatial listbox
|
|
169
|
+
// (plugins/draw/src/hooks/useSpatialList.js).
|
|
170
|
+
describe('getVertexItems / selectVertex / insertVertexAtMidpoint (spatial listbox bridge)', () => {
|
|
171
|
+
test('getVertexItems reads the live vertices/midpoints kept in sync by selectionState', () => {
|
|
172
|
+
const { mode } = setup()
|
|
173
|
+
const { vertices, midpoints } = mode.getVertexItems()
|
|
174
|
+
expect(vertices).toEqual([[0, 0], [100, 0], [100, 100], [0, 100]])
|
|
175
|
+
expect(midpoints).toHaveLength(4)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('selectVertex moves the cursor to a real vertex, without changing the geometry', () => {
|
|
179
|
+
const { manager, mode, ring } = setup()
|
|
180
|
+
mode.selectVertex(1)
|
|
181
|
+
expect(manager.emit).toHaveBeenCalledWith(ADAPTER_EVENTS.VERTEX_SELECTION, expect.objectContaining({ index: 1 }))
|
|
182
|
+
expect(ring()).toHaveLength(5) // unchanged
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('selectVertex moves the cursor to a midpoint (flat index past the real vertices) — reports index: -1', () => {
|
|
186
|
+
const { manager, mode } = setup()
|
|
187
|
+
mode.selectVertex(4) // first midpoint, between vertex 0 and 1
|
|
188
|
+
expect(manager.emit).toHaveBeenCalledWith(ADAPTER_EVENTS.VERTEX_SELECTION, expect.objectContaining({ index: -1 }))
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('insertVertexAtMidpoint commits a new vertex exactly at the midpoint, selects it, and is undoable', () => {
|
|
192
|
+
const { manager, mode, ring } = setup()
|
|
193
|
+
mode.insertVertexAtMidpoint(4) // first midpoint, between vertex 0 and 1
|
|
194
|
+
expect(ring()).toHaveLength(6)
|
|
195
|
+
expect(ring()[1]).toEqual([50, 0]) // exactly the midpoint — no directional offset
|
|
196
|
+
expect(manager.undoStack.pop()).toEqual({ type: 'insert_vertex', vertexIndex: 1 })
|
|
197
|
+
expect(manager.emit).toHaveBeenCalledWith(ADAPTER_EVENTS.VERTEX_SELECTION, expect.objectContaining({ index: 1 }))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('insertVertexAtMidpoint is a no-op for an out-of-range index', () => {
|
|
201
|
+
const { manager, mode, ring } = setup()
|
|
202
|
+
mode.insertVertexAtMidpoint(999)
|
|
203
|
+
expect(ring()).toHaveLength(5)
|
|
204
|
+
expect(manager.undoStack.length).toBe(0)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
168
208
|
test('undo re-validates with the inverse change phase (undo of a delete re-inserts)', () => {
|
|
169
209
|
jest.useFakeTimers()
|
|
170
210
|
const { container, manager, mode } = setup()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { coordToPixel } from '../utils/olCoords.js'
|
|
2
|
-
import { spatialNavigate } from '
|
|
2
|
+
import { spatialNavigate } from '../../../../../../src/utils/spatialNavigate.js'
|
|
3
|
+
import { stopIfGlobalAltKey } from '../../../../../../src/utils/globalAltShortcuts.js'
|
|
3
4
|
import { wireNudge } from './nudge.js'
|
|
4
5
|
|
|
5
6
|
const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'])
|
|
@@ -54,34 +55,34 @@ const isInteractiveElementFocused = (appViewport) => {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
const buildKeydownHandler = ({ map, getState, setState, nudge, keyMove, onUndo, onKeyboardActive, isFocused }) => {
|
|
57
|
-
const handleArrowKey = (
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
navigateTo(
|
|
58
|
+
const handleArrowKey = (event) => {
|
|
59
|
+
if (event.altKey) {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
event.stopPropagation()
|
|
62
|
+
navigateTo(event.key, map, getState, setState)
|
|
62
63
|
} else if (getState().selectedVertexIndex >= 0) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
nudge(
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
event.stopPropagation()
|
|
66
|
+
nudge(event)
|
|
66
67
|
} else {
|
|
67
68
|
// No action: arrow with no selection and no alt modifier
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const handleKey = (
|
|
72
|
+
const handleKey = (event) => {
|
|
72
73
|
onKeyboardActive?.()
|
|
73
|
-
if (
|
|
74
|
-
|
|
74
|
+
if (event.key === ' ') {
|
|
75
|
+
event.preventDefault()
|
|
75
76
|
if (getState().selectedVertexIndex < 0) {
|
|
76
77
|
selectNearest(map, getState, setState)
|
|
77
78
|
}
|
|
78
|
-
} else if (ARROW_KEYS.has(
|
|
79
|
-
handleArrowKey(
|
|
80
|
-
} else if (
|
|
79
|
+
} else if (ARROW_KEYS.has(event.key)) {
|
|
80
|
+
handleArrowKey(event)
|
|
81
|
+
} else if (event.key === 'z' && (event.metaKey || event.ctrlKey)) {
|
|
81
82
|
const tag = document.activeElement?.tagName
|
|
82
83
|
if (!INTERACTIVE_TAGS.has(tag)) {
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
event.preventDefault()
|
|
85
|
+
event.stopPropagation()
|
|
85
86
|
onUndo()
|
|
86
87
|
}
|
|
87
88
|
} else {
|
|
@@ -89,33 +90,42 @@ const buildKeydownHandler = ({ map, getState, setState, nudge, keyMove, onUndo,
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
return (
|
|
93
|
+
return (event) => {
|
|
93
94
|
if (isFocused()) {
|
|
94
95
|
return
|
|
95
96
|
}
|
|
96
|
-
if (
|
|
97
|
-
|
|
97
|
+
if (event.key === 'Escape' && getState().selectedVertexIndex >= 0) {
|
|
98
|
+
event.preventDefault()
|
|
98
99
|
keyMove.start = null
|
|
99
100
|
keyMove.index = null
|
|
100
101
|
setState({ selectedVertexIndex: -1, selectedVertexType: null })
|
|
101
102
|
} else {
|
|
102
|
-
handleKey(
|
|
103
|
+
handleKey(event)
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
const buildKeyupHandler = ({ keyMove, onVertexMoved, onDeleted, isFocused }) => (
|
|
108
|
+
const buildKeyupHandler = ({ getState, keyMove, onVertexMoved, onDeleted, isFocused }) => (event) => {
|
|
108
109
|
if (isFocused()) {
|
|
109
110
|
return
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
+
// Registered with capture:true below, so this runs first — shadows global Alt+<key>
|
|
113
|
+
// shortcuts (src/utils/globalAltShortcuts.js) only while something is actually selected —
|
|
114
|
+
// the same condition navigateTo/nudge already require locally. edit_point's state always
|
|
115
|
+
// reports selectedVertexIndex: 0 (a point is "always selected" — see pointSelectionState.js),
|
|
116
|
+
// so this naturally stays unconditional for that mode while genuinely gating on selection
|
|
117
|
+
// for edit_vertex, without needing to special-case either.
|
|
118
|
+
if (getState().selectedVertexIndex >= 0) {
|
|
119
|
+
stopIfGlobalAltKey(event)
|
|
120
|
+
}
|
|
121
|
+
if (ARROW_KEYS.has(event.key) && keyMove.start && keyMove.index != null) {
|
|
112
122
|
// Not hiding the snap indicator here — nudge.js's own snap.apply() already left it showing
|
|
113
123
|
// correctly, and it should stay that way after the key is released.
|
|
114
124
|
onVertexMoved({ vertexIndex: keyMove.index, previousCoord: keyMove.start })
|
|
115
125
|
keyMove.start = null
|
|
116
126
|
keyMove.index = null
|
|
117
127
|
}
|
|
118
|
-
if (
|
|
128
|
+
if (event.key === 'Delete') {
|
|
119
129
|
onDeleted()
|
|
120
130
|
}
|
|
121
131
|
}
|
|
@@ -146,7 +156,7 @@ export const createKeyboardHandler = (options) => {
|
|
|
146
156
|
const isFocused = () => isInteractiveElementFocused(appViewport)
|
|
147
157
|
|
|
148
158
|
const onKeydown = buildKeydownHandler({ map, getState, setState, nudge, keyMove, onUndo, onKeyboardActive, isFocused })
|
|
149
|
-
const onKeyup = buildKeyupHandler({ keyMove, onVertexMoved, onDeleted, isFocused })
|
|
159
|
+
const onKeyup = buildKeyupHandler({ getState, keyMove, onVertexMoved, onDeleted, isFocused })
|
|
150
160
|
|
|
151
161
|
globalThis.addEventListener('keydown', onKeydown, { capture: true })
|
|
152
162
|
globalThis.addEventListener('keyup', onKeyup, { capture: true })
|
|
@@ -27,13 +27,13 @@ const setup = () => {
|
|
|
27
27
|
|
|
28
28
|
const liveHandlers = []
|
|
29
29
|
afterEach(() => {
|
|
30
|
-
liveHandlers.splice(0).forEach((
|
|
30
|
+
liveHandlers.splice(0).forEach((handler) => handler.destroy())
|
|
31
31
|
document.body.innerHTML = ''
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
const key = (type, props) => window.dispatchEvent(new KeyboardEvent(type, { cancelable: true, ...props }))
|
|
35
35
|
|
|
36
|
-
test('nudgeByDelta (exposed for
|
|
36
|
+
test('nudgeByDelta (exposed for MapControls) moves the selected vertex and reports it via onVertexMoved', () => {
|
|
37
37
|
const { state, handler, onVertexMoved } = setup()
|
|
38
38
|
Object.assign(state, { selectedVertexIndex: 1, selectedVertexType: 'vertex' })
|
|
39
39
|
handler.nudgeByDelta(1, 0, true)
|
|
@@ -178,6 +178,44 @@ test('Delete deletes, ctrl/cmd+z undoes — but not while typing in an input ins
|
|
|
178
178
|
expect(onUndo).toHaveBeenCalledTimes(1)
|
|
179
179
|
})
|
|
180
180
|
|
|
181
|
+
test('Alt+Enter keyup stops propagation while a vertex is selected — never reaches the app-wide "highlight label at center" shortcut', () => {
|
|
182
|
+
const { state } = setup()
|
|
183
|
+
state.selectedVertexIndex = 1
|
|
184
|
+
const event = new KeyboardEvent('keyup', { key: 'Enter', altKey: true, cancelable: true, bubbles: true })
|
|
185
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
186
|
+
window.dispatchEvent(event)
|
|
187
|
+
expect(stopSpy).toHaveBeenCalled()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('Alt+Arrow keyup stops propagation too, while a vertex is selected — never reaches the app-wide "highlight next label" shortcut', () => {
|
|
191
|
+
const { state } = setup()
|
|
192
|
+
state.selectedVertexIndex = 1
|
|
193
|
+
const event = new KeyboardEvent('keyup', { key: 'ArrowRight', altKey: true, cancelable: true, bubbles: true })
|
|
194
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
195
|
+
window.dispatchEvent(event)
|
|
196
|
+
expect(stopSpy).toHaveBeenCalled()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('leaves Alt+Enter/Alt+Arrow alone with nothing selected — no local meaning to protect, so map-label selection still works', () => {
|
|
200
|
+
setup() // default state.selectedVertexIndex is -1 — nothing selected
|
|
201
|
+
const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', altKey: true, cancelable: true, bubbles: true })
|
|
202
|
+
const arrowEvent = new KeyboardEvent('keyup', { key: 'ArrowRight', altKey: true, cancelable: true, bubbles: true })
|
|
203
|
+
const enterSpy = jest.spyOn(enterEvent, 'stopPropagation')
|
|
204
|
+
const arrowSpy = jest.spyOn(arrowEvent, 'stopPropagation')
|
|
205
|
+
window.dispatchEvent(enterEvent)
|
|
206
|
+
window.dispatchEvent(arrowEvent)
|
|
207
|
+
expect(enterSpy).not.toHaveBeenCalled()
|
|
208
|
+
expect(arrowSpy).not.toHaveBeenCalled()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('a plain Enter keyup (no Alt) is left alone', () => {
|
|
212
|
+
setup()
|
|
213
|
+
const event = new KeyboardEvent('keyup', { key: 'Enter', altKey: false, cancelable: true, bubbles: true })
|
|
214
|
+
const stopSpy = jest.spyOn(event, 'stopPropagation')
|
|
215
|
+
window.dispatchEvent(event)
|
|
216
|
+
expect(stopSpy).not.toHaveBeenCalled()
|
|
217
|
+
})
|
|
218
|
+
|
|
181
219
|
test('keys are ignored while an interactive element outside the viewport has focus', () => {
|
|
182
220
|
const { onDeleted, onKeyboardActive } = setup()
|
|
183
221
|
const button = document.createElement('button')
|
|
@@ -106,12 +106,12 @@ export const wireNudge = ({ map, snap, getState, setState, onInserted, onVertexM
|
|
|
106
106
|
moveSelectedVertexBy(dx, dy)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// Explicit-delta counterpart to nudge(e) — the entry point for
|
|
109
|
+
// Explicit-delta counterpart to nudge(e) — the entry point for MapControls'
|
|
110
110
|
// D-pad (see mapProvider.activeMoveTarget in events.js). Unlike nudge(e)'s
|
|
111
111
|
// held-key sequencing (undo pushed on keyup via keyMove, so repeated keydowns
|
|
112
112
|
// while held batch into one undo step), each call here is one complete,
|
|
113
113
|
// undoable action — a button click has no "held" state to batch. Midpoints
|
|
114
|
-
// aren't handled here:
|
|
114
|
+
// aren't handled here: MapControls only claims the D-pad once a real vertex is
|
|
115
115
|
// selected (see buildVertexMoveTarget in events.js).
|
|
116
116
|
const nudgeByDelta = (dx, dy, isLargeStep) => {
|
|
117
117
|
const step = isLargeStep ? KEYBOARD.stepAmount : KEYBOARD.nudgeAmount
|
|
@@ -53,7 +53,7 @@ describe('nudging a vertex', () => {
|
|
|
53
53
|
})
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
describe('nudgeByDelta (
|
|
56
|
+
describe('nudgeByDelta (MapControls D-pad)', () => {
|
|
57
57
|
test('moves the selected vertex by an explicit direction, scaled by isLargeStep, and reports the move for undo', () => {
|
|
58
58
|
const { state, nudgeByDelta, olFeature, onVertexMoved } = setup()
|
|
59
59
|
Object.assign(state, { selectedVertexIndex: 1, selectedVertexType: 'vertex' })
|
|
@@ -79,7 +79,7 @@ describe('nudgeByDelta (MoveControls D-pad)', () => {
|
|
|
79
79
|
const { state, nudgeByDelta, onVertexMoved } = setup()
|
|
80
80
|
nudgeByDelta(1, 0, true) // nothing selected
|
|
81
81
|
Object.assign(state, { selectedVertexIndex: 4, selectedVertexType: 'midpoint' })
|
|
82
|
-
nudgeByDelta(1, 0, true) // midpoint selected —
|
|
82
|
+
nudgeByDelta(1, 0, true) // midpoint selected — MapControls only claims a real vertex
|
|
83
83
|
state.olFeature = null
|
|
84
84
|
Object.assign(state, { selectedVertexIndex: 1, selectedVertexType: 'vertex' })
|
|
85
85
|
nudgeByDelta(1, 0, true)
|