@defra/interactive-map 0.0.43-alpha → 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/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.md +16 -8
- package/docs/plugins/draw.md +1 -1
- package/package.json +1 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/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/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 +31 -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 +8 -2
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +35 -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 +31 -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 +32 -57
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +14 -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/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 +5 -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 +4 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +7 -7
- package/plugins/interact/src/hooks/{useMapItemList.js → useSpatialList.js} +87 -52
- package/plugins/interact/src/hooks/{useMapItemList.test.js → useSpatialList.test.js} +152 -71
- package/plugins/interact/src/manifest.js +12 -1
- package/plugins/interact/src/manifest.test.js +20 -0
- package/plugins/interact/src/utils/spatial.js +1 -1
- package/providers/beta/openlayers/src/utils/queryFeatures.js +1 -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/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/src/App/components/CrossHair/CrossHair.jsx +10 -4
- package/src/App/components/CrossHair/CrossHair.module.scss +16 -12
- package/src/App/components/CrossHair/CrossHair.test.jsx +9 -4
- package/src/App/components/Hints/Hints.jsx +3 -3
- package/src/App/components/Hints/Hints.test.jsx +3 -3
- package/src/App/components/MapButton/MapButton.module.scss +1 -2
- 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.test.jsx +2 -2
- 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} +3 -3
- package/src/App/components/Viewport/{Features.test.jsx → SpatialList.test.jsx} +60 -43
- package/src/App/components/Viewport/Viewport.jsx +20 -10
- package/src/App/components/Viewport/Viewport.test.jsx +18 -2
- package/src/App/hooks/useCrossHairAPI.js +5 -5
- package/src/App/hooks/useCrossHairAPI.test.js +5 -5
- package/src/App/hooks/useHintsAPI.js +5 -5
- package/src/App/hooks/useSpatialListFocus.js +296 -0
- package/src/App/hooks/{useFeatureFocus.test.js → useSpatialListFocus.test.js} +318 -163
- 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/registry/spatialListRegistry.js +104 -0
- package/src/App/registry/spatialListRegistry.test.js +218 -0
- package/src/App/store/AppProvider.jsx +3 -2
- package/src/config/appConfig.js +14 -14
- 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 +11 -9
- package/src/utils/findNearestItemInDirection.js +29 -0
- package/src/utils/findNearestItemInDirection.test.js +67 -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/src/App/components/Viewport/Features.jsx +0 -49
- package/src/App/hooks/useFeatureFocus.js +0 -257
- package/src/App/hooks/useFeatureItems.js +0 -39
- package/src/App/hooks/useFeatureItems.test.js +0 -155
|
@@ -35,8 +35,7 @@ export const EditVertexMode = {
|
|
|
35
35
|
scale: options.scale ?? 1
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
// Clear undo stack only
|
|
39
|
-
// Only clear if we're starting a new editing session (not already editing)
|
|
38
|
+
// Clear the undo stack only when starting a new editing session for this feature.
|
|
40
39
|
if (this.map._lastEditFeatureId !== state.featureId) {
|
|
41
40
|
this.map._undoStack?.clear()
|
|
42
41
|
this.map._lastEditFeatureId = state.featureId
|
|
@@ -46,7 +45,7 @@ export const EditVertexMode = {
|
|
|
46
45
|
const feature = this.getFeature(state.featureId)
|
|
47
46
|
state.featureType = feature?.type
|
|
48
47
|
|
|
49
|
-
state.
|
|
48
|
+
state.vertices = this.getVertices(state.featureId)
|
|
50
49
|
state.midpoints = this.getMidpoints(state.featureId)
|
|
51
50
|
this.setupEventListeners(state)
|
|
52
51
|
|
|
@@ -59,7 +58,7 @@ export const EditVertexMode = {
|
|
|
59
58
|
|
|
60
59
|
// Show touch target if entering with a selected vertex on touch interface
|
|
61
60
|
if (state.interfaceType === 'touch' && state.selectedVertexIndex >= 0 && state.selectedVertexType === 'vertex') {
|
|
62
|
-
const vertex = state.
|
|
61
|
+
const vertex = state.vertices[state.selectedVertexIndex]
|
|
63
62
|
if (vertex) {
|
|
64
63
|
setTimeout(() => {
|
|
65
64
|
this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
|
|
@@ -78,12 +77,16 @@ export const EditVertexMode = {
|
|
|
78
77
|
const handlers = this.handlers = buildEditModeHandlers(this, state, {
|
|
79
78
|
nudge: this.onNudgeVertex,
|
|
80
79
|
selectionchange: this.onSelectionChange,
|
|
81
|
-
update: this.onUpdate
|
|
80
|
+
update: this.onUpdate,
|
|
81
|
+
selectvertex: this.onSelectVertex,
|
|
82
|
+
insertvertexatmidpoint: this.onInsertVertexAtMidpoint
|
|
82
83
|
})
|
|
83
84
|
|
|
84
85
|
bindEditModeListeners(state, this.map, handlers)
|
|
85
86
|
this.map.on('draw.selectionchange', handlers.selectionchange)
|
|
86
87
|
this.map.on('draw.update', handlers.update)
|
|
88
|
+
this.map.on(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, handlers.selectvertex)
|
|
89
|
+
this.map.on(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, handlers.insertvertexatmidpoint)
|
|
87
90
|
},
|
|
88
91
|
|
|
89
92
|
applyVertexSelection (state, options) {
|
|
@@ -92,7 +95,7 @@ export const EditVertexMode = {
|
|
|
92
95
|
this.clearSelectedCoordinates()
|
|
93
96
|
state.feature.changed()
|
|
94
97
|
this._ctx.store.render()
|
|
95
|
-
this.updateMidpoint(state.midpoints[options.selectedVertexIndex - state.
|
|
98
|
+
this.updateMidpoint(state.midpoints[options.selectedVertexIndex - state.vertices.length])
|
|
96
99
|
return
|
|
97
100
|
}
|
|
98
101
|
if (options.selectedVertexIndex === -1) {
|
|
@@ -104,13 +107,12 @@ export const EditVertexMode = {
|
|
|
104
107
|
},
|
|
105
108
|
|
|
106
109
|
onSelectionChange (state, event) {
|
|
107
|
-
// Refresh vertex list so
|
|
110
|
+
// Refresh vertex list so numVertices reflects the latest geometry (e.g. after midpoint insertion)
|
|
108
111
|
this.syncVertices(state)
|
|
109
112
|
|
|
110
113
|
const vertexCoord = event.points[event.points.length - 1]?.geometry.coordinates
|
|
111
114
|
|
|
112
|
-
//
|
|
113
|
-
// For keyboard mode or when we have coordPath, trust the existing selectedVertexIndex
|
|
115
|
+
// For keyboard mode or when coordPath is set, trust the existing selectedVertexIndex.
|
|
114
116
|
if (state.interfaceType !== 'keyboard' && vertexCoord && !state.coordPath) {
|
|
115
117
|
// No coordPath available - need to search for vertex by coordinates
|
|
116
118
|
const geom = event.features[0]?.geometry
|
|
@@ -123,11 +125,11 @@ export const EditVertexMode = {
|
|
|
123
125
|
|
|
124
126
|
this.map.fire(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, {
|
|
125
127
|
index: state.selectedVertexType === 'vertex' ? state.selectedVertexIndex : -1,
|
|
126
|
-
|
|
128
|
+
numVertices: state.vertices.length
|
|
127
129
|
})
|
|
128
130
|
|
|
129
131
|
// Use vertex from event if available, otherwise fall back to state
|
|
130
|
-
const vertex = vertexCoord || (state.selectedVertexIndex >= 0 ? state.
|
|
132
|
+
const vertex = vertexCoord || (state.selectedVertexIndex >= 0 ? state.vertices[state.selectedVertexIndex] : null)
|
|
131
133
|
this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
|
|
132
134
|
},
|
|
133
135
|
|
|
@@ -137,13 +139,13 @@ export const EditVertexMode = {
|
|
|
137
139
|
|
|
138
140
|
onInterfaceTypeChange (state, event) {
|
|
139
141
|
state.interfaceType = event.interfaceType
|
|
140
|
-
const vertex = state.selectedVertexIndex >= 0 ? state.
|
|
142
|
+
const vertex = state.selectedVertexIndex >= 0 ? state.vertices[state.selectedVertexIndex] : null
|
|
141
143
|
this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
|
|
142
144
|
},
|
|
143
145
|
|
|
144
146
|
onUpdate (state) {
|
|
145
|
-
const prev = new Set(state.
|
|
146
|
-
if (prev.size === state.
|
|
147
|
+
const prev = new Set(state.vertices.map(coordinate => JSON.stringify(coordinate)))
|
|
148
|
+
if (prev.size === state.vertices.length) {
|
|
147
149
|
return
|
|
148
150
|
}
|
|
149
151
|
// Duplicate coordinates exist (e.g. a self-touching ring). Comparing the list
|
|
@@ -153,26 +155,48 @@ export const EditVertexMode = {
|
|
|
153
155
|
},
|
|
154
156
|
|
|
155
157
|
onMove (state) {
|
|
156
|
-
const vertex = state.
|
|
158
|
+
const vertex = state.vertices[state.selectedVertexIndex]
|
|
157
159
|
if (vertex) {
|
|
158
160
|
this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
|
|
159
161
|
}
|
|
160
162
|
},
|
|
161
163
|
|
|
162
|
-
// Inbound signal from MaplibreDrawAdapter.nudgeSelectedVertex — bridges into the
|
|
163
|
-
//
|
|
164
|
-
// direct reference to this mode's live state. Unlike OL (where setState's shared
|
|
165
|
-
// onUpdate hook repositions the touch target for any vertex move), moveVertex
|
|
166
|
-
// here has no equivalent hook, so the reposition has to happen explicitly —
|
|
167
|
-
// same as onMove/onSelectionChange/onInterfaceTypeChange already do.
|
|
164
|
+
// Inbound signal from MaplibreDrawAdapter.nudgeSelectedVertex — bridges into the running
|
|
165
|
+
// mode since the adapter has no direct reference to its live state.
|
|
168
166
|
onNudgeVertex (state, event) {
|
|
169
167
|
this.nudgeVertexByDelta(state, event.dx, event.dy, event.isLargeStep)
|
|
170
|
-
const vertex = state.
|
|
168
|
+
const vertex = state.vertices[state.selectedVertexIndex]
|
|
171
169
|
if (vertex) {
|
|
172
170
|
this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
|
|
173
171
|
}
|
|
174
172
|
},
|
|
175
173
|
|
|
174
|
+
// Inbound signal from MaplibreDrawAdapter.selectVertex — previews a vertex/midpoint by flat
|
|
175
|
+
// index (no geometry change) for the spatial listbox's roving move.
|
|
176
|
+
onSelectVertex (state, event) {
|
|
177
|
+
if (!state.vertices?.length) { // lazy-populate guard, same as startKeyboardSelection
|
|
178
|
+
state.vertices = this.getVertices(state.featureId)
|
|
179
|
+
state.midpoints = this.getMidpoints(state.featureId)
|
|
180
|
+
}
|
|
181
|
+
const type = event.index < state.vertices.length ? 'vertex' : 'midpoint'
|
|
182
|
+
this.changeMode(state, {
|
|
183
|
+
selectedVertexIndex: event.index,
|
|
184
|
+
selectedVertexType: type,
|
|
185
|
+
...(type === 'vertex' && { coordPath: this.getCoordPath(state, event.index) })
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Inbound signal from MaplibreDrawAdapter.insertVertexAtMidpoint — commits a new vertex at
|
|
190
|
+
// midpoint `event.index` for the spatial listbox's confirm, landing exactly on the midpoint
|
|
191
|
+
// (no offset), same as touchHandlers.js's onTap does for a midpoint tap.
|
|
192
|
+
onInsertVertexAtMidpoint (state, event) {
|
|
193
|
+
if (!state.vertices?.length) {
|
|
194
|
+
state.vertices = this.getVertices(state.featureId)
|
|
195
|
+
state.midpoints = this.getMidpoints(state.featureId)
|
|
196
|
+
}
|
|
197
|
+
this.insertVertex({ ...state, selectedVertexIndex: event.index, selectedVertexType: 'midpoint' })
|
|
198
|
+
},
|
|
199
|
+
|
|
176
200
|
onButtonClick (state, event) {
|
|
177
201
|
if (event.target.closest(`#${state.deleteVertexButtonId}`) && state.selectedVertexType === 'vertex') {
|
|
178
202
|
this.deleteVertex(state)
|
|
@@ -200,6 +224,8 @@ export const EditVertexMode = {
|
|
|
200
224
|
const handlers = this.handlers
|
|
201
225
|
this.map.off('draw.selectionchange', handlers.selectionchange)
|
|
202
226
|
this.map.off('draw.update', handlers.update)
|
|
227
|
+
this.map.off(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, handlers.selectvertex)
|
|
228
|
+
this.map.off(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, handlers.insertvertexatmidpoint)
|
|
203
229
|
unbindEditModeListeners(state, this.map, handlers)
|
|
204
230
|
this.hideTouchVertexIndicator(state)
|
|
205
231
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHarness, POLYGON } from './editVertexMode/__helpers__/harness.js'
|
|
2
|
+
import { CUSTOM_DRAW_EVENTS } from '../drawEvents.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Tests for EditVertexMode's own methods: setup/teardown lifecycle, selection/scale/update
|
|
@@ -12,7 +13,7 @@ describe('onSetup / onStop lifecycle', () => {
|
|
|
12
13
|
const { ctx, state, map } = createHarness()
|
|
13
14
|
expect(state.featureId).toBe('feat-1')
|
|
14
15
|
// Real PolygonFeature stores rings without the duplicate closing coordinate
|
|
15
|
-
expect(state.
|
|
16
|
+
expect(state.vertices).toHaveLength(4)
|
|
16
17
|
expect(state.midpoints).toHaveLength(4)
|
|
17
18
|
expect(map._lastEditFeatureId).toBe('feat-1')
|
|
18
19
|
expect(map._drawEditContainer).toBe(state.container)
|
|
@@ -73,7 +74,7 @@ describe('selection, scale and update events', () => {
|
|
|
73
74
|
const geom = ctx.getFeature('feat-1').toGeoJSON().geometry
|
|
74
75
|
ctx.onSelectionChange(state, { points: [{ geometry: { coordinates: [10, 0] } }], features: [{ geometry: geom }] })
|
|
75
76
|
expect(state.selectedVertexIndex).toBe(1)
|
|
76
|
-
expect(map.fire).toHaveBeenCalledWith('draw.vertexselection', expect.objectContaining({
|
|
77
|
+
expect(map.fire).toHaveBeenCalledWith('draw.vertexselection', expect.objectContaining({ numVertices: 4 }))
|
|
77
78
|
|
|
78
79
|
state.coordPath = '0.2'
|
|
79
80
|
state.selectedVertexIndex = 2
|
|
@@ -101,14 +102,14 @@ describe('selection, scale and update events', () => {
|
|
|
101
102
|
ctx.onInterfaceTypeChange({ ...state, selectedVertexIndex: -1 }, { interfaceType: 'mouse' })
|
|
102
103
|
})
|
|
103
104
|
|
|
104
|
-
test('draw.nudgevertex moves the selected vertex and repositions the touch target — the inbound bridge for
|
|
105
|
+
test('draw.nudgevertex moves the selected vertex and repositions the touch target — the inbound bridge for MapControls.mapProvider.activeMoveTarget', () => {
|
|
105
106
|
jest.useFakeTimers()
|
|
106
107
|
const { state, map } = createHarness(POLYGON(), { interfaceType: 'touch', selectedVertexIndex: 0, selectedVertexType: 'vertex' })
|
|
107
108
|
jest.runAllTimers() // flush onSetup's deferred initial touch-target positioning
|
|
108
|
-
const before = [...state.
|
|
109
|
+
const before = [...state.vertices[0]]
|
|
109
110
|
const targetBefore = { top: state.touchVertexTarget.style.top, left: state.touchVertexTarget.style.left }
|
|
110
111
|
map.fire('draw.nudgevertex', { dx: 1, dy: 0, isLargeStep: true })
|
|
111
|
-
expect(state.
|
|
112
|
+
expect(state.vertices[0]).not.toEqual(before)
|
|
112
113
|
expect({ top: state.touchVertexTarget.style.top, left: state.touchVertexTarget.style.left }).not.toEqual(targetBefore)
|
|
113
114
|
})
|
|
114
115
|
|
|
@@ -120,7 +121,7 @@ describe('selection, scale and update events', () => {
|
|
|
120
121
|
test('onUpdate re-selects a changed vertex only when the vertex count is ambiguous', () => {
|
|
121
122
|
const { ctx, state } = createHarness()
|
|
122
123
|
ctx.onUpdate(state) // unique coords → no change
|
|
123
|
-
state.
|
|
124
|
+
state.vertices = [[0, 0], [0, 0], [1, 1]]
|
|
124
125
|
ctx.onUpdate(state)
|
|
125
126
|
expect(state.selectedVertexIndex).toBe(-1)
|
|
126
127
|
})
|
|
@@ -158,3 +159,70 @@ describe('move, button and changeMode routing', () => {
|
|
|
158
159
|
expect(api.changeMode).not.toHaveBeenCalled()
|
|
159
160
|
})
|
|
160
161
|
})
|
|
162
|
+
|
|
163
|
+
// onSelectVertex/onInsertVertexAtMidpoint bridge MaplibreDrawAdapter.selectVertex/
|
|
164
|
+
// insertVertexAtMidpoint into the running mode (see drawEvents.js's SELECT_VERTEX/
|
|
165
|
+
// INSERT_VERTEX_AT_MIDPOINT) for the shared spatial listbox.
|
|
166
|
+
describe('onSelectVertex / onInsertVertexAtMidpoint (spatial listbox bridge)', () => {
|
|
167
|
+
test('registers the select-vertex/insert-vertex-at-midpoint listeners on setup', () => {
|
|
168
|
+
const { map } = createHarness()
|
|
169
|
+
expect(map.on).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, expect.any(Function))
|
|
170
|
+
expect(map.on).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, expect.any(Function))
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('onSelectVertex moves the cursor to a real vertex, including its coordPath', () => {
|
|
174
|
+
const { ctx, state } = createHarness()
|
|
175
|
+
const changeModeSpy = jest.spyOn(ctx, 'changeMode')
|
|
176
|
+
ctx.onSelectVertex(state, { index: 2 })
|
|
177
|
+
expect(changeModeSpy).toHaveBeenCalledWith(state, {
|
|
178
|
+
selectedVertexIndex: 2,
|
|
179
|
+
selectedVertexType: 'vertex',
|
|
180
|
+
coordPath: expect.any(String)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('onSelectVertex moves the cursor to a midpoint, without a coordPath', () => {
|
|
185
|
+
const { ctx, state } = createHarness()
|
|
186
|
+
const changeModeSpy = jest.spyOn(ctx, 'changeMode')
|
|
187
|
+
const midpointIndex = state.vertices.length
|
|
188
|
+
ctx.onSelectVertex(state, { index: midpointIndex })
|
|
189
|
+
expect(changeModeSpy).toHaveBeenCalledWith(state, {
|
|
190
|
+
selectedVertexIndex: midpointIndex,
|
|
191
|
+
selectedVertexType: 'midpoint'
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('onSelectVertex lazily (re)populates vertices/midpoints when not already cached', () => {
|
|
196
|
+
const { ctx, state } = createHarness()
|
|
197
|
+
state.vertices = []
|
|
198
|
+
state.midpoints = []
|
|
199
|
+
const changeModeSpy = jest.spyOn(ctx, 'changeMode')
|
|
200
|
+
ctx.onSelectVertex(state, { index: 0 })
|
|
201
|
+
expect(state.vertices.length).toBeGreaterThan(0)
|
|
202
|
+
expect(changeModeSpy).toHaveBeenCalled()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('onInsertVertexAtMidpoint commits a new vertex exactly at the midpoint — no directional offset', () => {
|
|
206
|
+
const { ctx, state } = createHarness()
|
|
207
|
+
const midpointFlatIndex = state.vertices.length // first midpoint, between vertex 0 and 1
|
|
208
|
+
const expectedCoord = state.midpoints[0]
|
|
209
|
+
ctx.onInsertVertexAtMidpoint(state, { index: midpointFlatIndex })
|
|
210
|
+
const updated = ctx.getVertices(state.featureId)
|
|
211
|
+
expect(updated).toHaveLength(state.vertices.length + 1)
|
|
212
|
+
expect(updated[1]).toEqual(expectedCoord)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('onInsertVertexAtMidpoint lazily (re)populates vertices/midpoints when not already cached', () => {
|
|
216
|
+
const { ctx, state } = createHarness()
|
|
217
|
+
state.vertices = []
|
|
218
|
+
state.midpoints = []
|
|
219
|
+
expect(() => ctx.onInsertVertexAtMidpoint(state, { index: 4 })).not.toThrow()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('unregisters the select-vertex/insert-vertex-at-midpoint listeners on stop', () => {
|
|
223
|
+
const { ctx, state, map } = createHarness()
|
|
224
|
+
ctx.onStop(state)
|
|
225
|
+
expect(map.off).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, expect.any(Function))
|
|
226
|
+
expect(map.off).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, expect.any(Function))
|
|
227
|
+
})
|
|
228
|
+
})
|
|
@@ -20,7 +20,7 @@ export const sharedSnapMovement = {
|
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
// Explicit-delta counterpart to getOffset, driven by a unit direction vector and
|
|
23
|
-
//
|
|
23
|
+
// MapControls' own Precision toggle rather than a KeyboardEvent — used by
|
|
24
24
|
// nudgeVertexByDelta/nudgePointByDelta.
|
|
25
25
|
getOffsetByDelta (coord, dx, dy, isLargeStep) {
|
|
26
26
|
const pt = this.map.project(coord)
|
|
@@ -30,7 +30,7 @@ export const sharedSnapMovement = {
|
|
|
30
30
|
|
|
31
31
|
// Resolves the destination coordinate for a nudge by (dx, dy) unit direction, applying
|
|
32
32
|
// snap or breaking out of an already-active one — shared by the keyboard arrow-key path
|
|
33
|
-
// and
|
|
33
|
+
// and MapControls' explicit-delta path so both snap identically. dx/dy are only needed
|
|
34
34
|
// for the snap-escape offset; getCandidate computes the raw, un-snapped destination the
|
|
35
35
|
// caller would otherwise have used.
|
|
36
36
|
resolveSnapTarget (state, dx, dy, currentCoord, getCandidate) {
|
|
@@ -26,6 +26,7 @@ const DRAW_OUTLINE_STYLE_LAYER = 'stroke-inactive.cold'
|
|
|
26
26
|
* setInterfaceType(type)
|
|
27
27
|
* done() / cancel() / undo() / deleteVertex()
|
|
28
28
|
* nudgeSelectedVertex(dx, dy, isLargeStep)
|
|
29
|
+
* getVertexItems() / selectVertex(index) / insertVertexAtMidpoint(index)
|
|
29
30
|
* get(id) / add(feature) / setStyle(id, properties) / delete(id) / deleteAll()
|
|
30
31
|
* setSnapEnabled(bool) / setSnapLayers(layers) / isSnapEnabled()
|
|
31
32
|
* setFeatureProperty(id, property, value) / setDrawingPreviewProperty(property, value)
|
|
@@ -77,6 +78,11 @@ export class OLDrawAdapter {
|
|
|
77
78
|
deleteVertex () { this._manager.deleteVertex() }
|
|
78
79
|
nudgeSelectedVertex (dx, dy, isLargeStep) { this._manager.nudgeSelectedVertex(dx, dy, isLargeStep) }
|
|
79
80
|
|
|
81
|
+
// Read-only vertex/midpoint coordinates for the shared spatial listbox.
|
|
82
|
+
getVertexItems () { return this._manager.getVertexItems() }
|
|
83
|
+
selectVertex (index) { this._manager.selectVertex(index) }
|
|
84
|
+
insertVertexAtMidpoint (index) { this._manager.insertVertexAtMidpoint(index) }
|
|
85
|
+
|
|
80
86
|
// Record the current geometry validity so the draw mode can block finish gestures
|
|
81
87
|
// (double-click / click-to-close) while the in-progress shape is invalid.
|
|
82
88
|
setGeometryValid (valid) { this._manager._geometryValid = valid }
|
|
@@ -17,6 +17,9 @@ const fakeManager = () => ({
|
|
|
17
17
|
undo: jest.fn(),
|
|
18
18
|
deleteVertex: jest.fn(),
|
|
19
19
|
nudgeSelectedVertex: jest.fn(),
|
|
20
|
+
getVertexItems: jest.fn(() => ({ vertices: [[0, 0]], midpoints: [] })),
|
|
21
|
+
selectVertex: jest.fn(),
|
|
22
|
+
insertVertexAtMidpoint: jest.fn(),
|
|
20
23
|
setInvalid: jest.fn(),
|
|
21
24
|
setDrawingPreviewProperty: jest.fn(),
|
|
22
25
|
get: jest.fn(() => 'feature'),
|
|
@@ -136,6 +139,12 @@ test('remaining calls delegate straight through; setFeatureProperty is a deliber
|
|
|
136
139
|
expect(manager.undo).toHaveBeenCalled()
|
|
137
140
|
expect(manager.deleteVertex).toHaveBeenCalled()
|
|
138
141
|
expect(manager.nudgeSelectedVertex).toHaveBeenCalledWith(1, 0, true)
|
|
142
|
+
|
|
143
|
+
expect(adapter.getVertexItems()).toEqual({ vertices: [[0, 0]], midpoints: [] })
|
|
144
|
+
adapter.selectVertex(2)
|
|
145
|
+
adapter.insertVertexAtMidpoint(3)
|
|
146
|
+
expect(manager.selectVertex).toHaveBeenCalledWith(2)
|
|
147
|
+
expect(manager.insertVertexAtMidpoint).toHaveBeenCalledWith(3)
|
|
139
148
|
})
|
|
140
149
|
|
|
141
150
|
// A directly-added Point skips draw_point's own icon-resolving drawend handler.
|
|
@@ -82,8 +82,8 @@ export class OLDrawManager {
|
|
|
82
82
|
// Array.from, not [...handlers] — see the comment in utils/eventBus.js:
|
|
83
83
|
// under a loose-mode Babel build (Docusaurus's docs site), spreading a Set
|
|
84
84
|
// compiles to [].concat(handlers), which doesn't flatten it — it appends
|
|
85
|
-
// the whole Set as one non-function element, and
|
|
86
|
-
if (handlers) { Array.from(handlers).forEach(
|
|
85
|
+
// the whole Set as one non-function element, and handler(...) throws.
|
|
86
|
+
if (handlers) { Array.from(handlers).forEach(handler => handler(detail)) }
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// --- Mode machine ---
|
|
@@ -146,6 +146,19 @@ export class OLDrawManager {
|
|
|
146
146
|
this._modeInstance?.nudgeSelectedVertex?.(dx, dy, isLargeStep)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// Read-only vertex/midpoint coordinates for the shared spatial listbox.
|
|
150
|
+
getVertexItems () {
|
|
151
|
+
return this._modeInstance?.getVertexItems?.() ?? { vertices: [], midpoints: [] }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
selectVertex (index) {
|
|
155
|
+
this._modeInstance?.selectVertex?.(index)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
insertVertexAtMidpoint (index) {
|
|
159
|
+
this._modeInstance?.insertVertexAtMidpoint?.(index)
|
|
160
|
+
}
|
|
161
|
+
|
|
149
162
|
// Show/hide the dashed invalid stroke on the active draw sketch or edit feature.
|
|
150
163
|
setInvalid (invalid) {
|
|
151
164
|
this._modeInstance?.setInvalid?.(invalid)
|
|
@@ -14,7 +14,19 @@ jest.mock('../draw/DrawMode.js', () => ({
|
|
|
14
14
|
createDrawMode: jest.fn(() => ({ destroy: jest.fn(), done: jest.fn(), cancel: jest.fn(), undo: jest.fn(), deleteVertex: jest.fn(), nudgeSelectedVertex: jest.fn(), setInterfaceType: jest.fn(), setInvalid: jest.fn(), setDrawingPreviewProperty: jest.fn() }))
|
|
15
15
|
}))
|
|
16
16
|
jest.mock('../edit/EditMode.js', () => ({
|
|
17
|
-
createEditMode: jest.fn(() => ({
|
|
17
|
+
createEditMode: jest.fn(() => ({
|
|
18
|
+
destroy: jest.fn(),
|
|
19
|
+
done: jest.fn(),
|
|
20
|
+
cancel: jest.fn(),
|
|
21
|
+
undo: jest.fn(),
|
|
22
|
+
deleteVertex: jest.fn(),
|
|
23
|
+
nudgeSelectedVertex: jest.fn(),
|
|
24
|
+
setInterfaceType: jest.fn(),
|
|
25
|
+
setInvalid: jest.fn(),
|
|
26
|
+
getVertexItems: jest.fn(() => ({ vertices: [[0, 0]], midpoints: [] })),
|
|
27
|
+
selectVertex: jest.fn(),
|
|
28
|
+
insertVertexAtMidpoint: jest.fn()
|
|
29
|
+
}))
|
|
18
30
|
}))
|
|
19
31
|
jest.mock('../point/editPointMode.js', () => ({
|
|
20
32
|
createEditPointMode: jest.fn(() => ({ destroy: jest.fn(), done: jest.fn(), cancel: jest.fn(), undo: jest.fn(), deleteVertex: jest.fn(), nudgeSelectedVertex: jest.fn(), setInterfaceType: jest.fn(), setInvalid: jest.fn() }))
|
|
@@ -109,6 +121,8 @@ describe('mode machine', () => {
|
|
|
109
121
|
const { manager } = setup()
|
|
110
122
|
manager.done(); manager.undo(); manager.deleteVertex(); manager.nudgeSelectedVertex(1, 0, true); manager.setInterfaceType('touch'); manager.setInvalid(true) // no mode — no throw
|
|
111
123
|
manager.setDrawingPreviewProperty('splitter', 'valid') // no mode — no throw
|
|
124
|
+
expect(manager.getVertexItems()).toEqual({ vertices: [], midpoints: [] }) // no mode — safe default
|
|
125
|
+
manager.selectVertex(1); manager.insertVertexAtMidpoint(1) // no mode — no throw
|
|
112
126
|
|
|
113
127
|
await manager.changeMode('draw_polygon')
|
|
114
128
|
const instance = createDrawMode.mock.results[0].value
|
|
@@ -135,6 +149,18 @@ describe('mode machine', () => {
|
|
|
135
149
|
expect(instance.cancel).toHaveBeenCalled()
|
|
136
150
|
expect(manager.getMode()).toBe('disabled')
|
|
137
151
|
})
|
|
152
|
+
|
|
153
|
+
test('getVertexItems/selectVertex/insertVertexAtMidpoint delegate to the edit_vertex mode instance', async () => {
|
|
154
|
+
const { manager } = setup()
|
|
155
|
+
await manager.changeMode('edit_vertex', { featureId: 'f1' })
|
|
156
|
+
const instance = createEditMode.mock.results[0].value
|
|
157
|
+
|
|
158
|
+
expect(manager.getVertexItems()).toEqual({ vertices: [[0, 0]], midpoints: [] })
|
|
159
|
+
manager.selectVertex(2)
|
|
160
|
+
manager.insertVertexAtMidpoint(3)
|
|
161
|
+
expect(instance.selectVertex).toHaveBeenCalledWith(2)
|
|
162
|
+
expect(instance.insertVertexAtMidpoint).toHaveBeenCalledWith(3)
|
|
163
|
+
})
|
|
138
164
|
})
|
|
139
165
|
|
|
140
166
|
test('undo stack changes are published on the adapter bus; off unsubscribes', () => {
|
|
@@ -275,7 +275,7 @@ describe('drawing lifecycle', () => {
|
|
|
275
275
|
expect(changed).toHaveBeenCalled()
|
|
276
276
|
})
|
|
277
277
|
|
|
278
|
-
test('setInterfaceType forwards to the draw input (e.g. DrawInit syncing
|
|
278
|
+
test('setInterfaceType forwards to the draw input (e.g. DrawInit syncing MapControls-driven interface changes mid-session)', () => {
|
|
279
279
|
const { mode, input } = setup('Polygon')
|
|
280
280
|
mode.setInterfaceType('touch')
|
|
281
281
|
expect(input.setInterfaceType).toHaveBeenCalledWith('touch')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createVertexPlacement } from './vertexPlacement.js'
|
|
2
|
+
import { stopIfGlobalAltKey } from '../../../../../../src/utils/globalAltShortcuts.js'
|
|
2
3
|
|
|
3
4
|
const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'])
|
|
4
5
|
|
|
@@ -23,40 +24,47 @@ export const wireInputEvents = ({
|
|
|
23
24
|
getInterfaceType, setInterfaceType, clearLastCoord,
|
|
24
25
|
updateRubberbanding, placeVertex
|
|
25
26
|
}) => {
|
|
27
|
+
// Normally gated to touch/keyboard — a real mouse cursor drives the candidate via its own
|
|
28
|
+
// pointermove instead. But Voice Control's simulated clicks report pointerType 'mouse' (so
|
|
29
|
+
// interfaceType never leaves 'mouse') and produce no pointermove at all, so panning via
|
|
30
|
+
// MapControls while interfaceType is 'mouse' would otherwise leave the candidate stale.
|
|
31
|
+
// olView.getAnimating() is also true for MapControls' own panBy/zoomIn/zoomOut (both use
|
|
32
|
+
// view.animate) but not for a live mouse drag (which sets the center directly, no
|
|
33
|
+
// animation) — the same fallback signal used below in onMapRender.
|
|
26
34
|
const onCenterChange = () => {
|
|
27
|
-
if (getInterfaceType() !== 'mouse') {
|
|
35
|
+
if (getInterfaceType() !== 'mouse' || olView?.getAnimating()) {
|
|
28
36
|
updateRubberbanding()
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
olView?.on('change:center', onCenterChange)
|
|
32
40
|
|
|
33
|
-
const onKeydown = (
|
|
41
|
+
const onKeydown = (event) => {
|
|
34
42
|
if (!container.contains(document.activeElement)) {
|
|
35
43
|
return
|
|
36
44
|
}
|
|
37
|
-
if (ARROW_KEYS.has(
|
|
45
|
+
if (ARROW_KEYS.has(event.key)) {
|
|
38
46
|
setInterfaceType('keyboard')
|
|
39
47
|
return
|
|
40
48
|
}
|
|
41
|
-
if (
|
|
42
|
-
|
|
49
|
+
if (event.key === 'Enter') {
|
|
50
|
+
event.preventDefault()
|
|
43
51
|
setInterfaceType('keyboard')
|
|
44
52
|
placeVertex()
|
|
45
53
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
54
|
+
if (event.key === 'z' && (event.metaKey || event.ctrlKey)) {
|
|
55
|
+
event.preventDefault()
|
|
48
56
|
onUndo?.()
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
const onButtonClick = (
|
|
53
|
-
if (addVertexButtonId &&
|
|
60
|
+
const onButtonClick = (event) => {
|
|
61
|
+
if (addVertexButtonId && event.target.closest(`#${addVertexButtonId}`)) {
|
|
54
62
|
placeVertex()
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
const onPointerdown = (
|
|
59
|
-
if (
|
|
66
|
+
const onPointerdown = (event) => {
|
|
67
|
+
if (event.pointerType !== 'touch') {
|
|
60
68
|
setInterfaceType('mouse')
|
|
61
69
|
clearLastCoord()
|
|
62
70
|
}
|
|
@@ -78,6 +86,9 @@ export const wireInputEvents = ({
|
|
|
78
86
|
container.addEventListener('pointerdown', onPointerdown)
|
|
79
87
|
container.addEventListener('touchstart', onTouchstart, { passive: true })
|
|
80
88
|
container.addEventListener('pointermove', onPointerMove)
|
|
89
|
+
// Capture phase, separate from onKeydown above — shadows global Alt+<key> shortcuts
|
|
90
|
+
// (src/utils/globalAltShortcuts.js) unconditionally while drawing.
|
|
91
|
+
globalThis.addEventListener('keyup', stopIfGlobalAltKey, { capture: true })
|
|
81
92
|
|
|
82
93
|
return {
|
|
83
94
|
destroy () {
|
|
@@ -87,6 +98,7 @@ export const wireInputEvents = ({
|
|
|
87
98
|
container.removeEventListener('pointerdown', onPointerdown)
|
|
88
99
|
container.removeEventListener('touchstart', onTouchstart)
|
|
89
100
|
container.removeEventListener('pointermove', onPointerMove)
|
|
101
|
+
globalThis.removeEventListener('keyup', stopIfGlobalAltKey, { capture: true })
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
104
|
}
|
|
@@ -108,8 +120,7 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
108
120
|
mapProvider,
|
|
109
121
|
snap,
|
|
110
122
|
canFinish,
|
|
111
|
-
canPlace
|
|
112
|
-
getInterfaceType
|
|
123
|
+
canPlace
|
|
113
124
|
})
|
|
114
125
|
|
|
115
126
|
const map = drawInteraction.getMap()
|
|
@@ -121,7 +132,7 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
121
132
|
olView,
|
|
122
133
|
onUndo,
|
|
123
134
|
getInterfaceType,
|
|
124
|
-
setInterfaceType: (
|
|
135
|
+
setInterfaceType: (type) => { interfaceType = type; applyCrossHairVisibility(crossHair, type) },
|
|
125
136
|
clearLastCoord: placement.clearLastCoord,
|
|
126
137
|
updateRubberbanding: placement.updateRubberbanding,
|
|
127
138
|
placeVertex: placement.placeVertex
|
|
@@ -138,9 +149,12 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
138
149
|
crossHair.activate = placement.placeVertex
|
|
139
150
|
}
|
|
140
151
|
|
|
141
|
-
// change:center fires once when
|
|
152
|
+
// change:center fires once when an animated pan starts; postrender tracks each frame.
|
|
153
|
+
// getAnimating() alone (not also gated on interfaceType, unlike onCenterChange above) is
|
|
154
|
+
// enough here: it's already only true for an animated move, which is what needs a
|
|
155
|
+
// per-frame follow-up regardless of what interfaceType happens to be.
|
|
142
156
|
const onMapRender = () => {
|
|
143
|
-
if (
|
|
157
|
+
if (olView?.getAnimating()) {
|
|
144
158
|
placement.updateRubberbanding()
|
|
145
159
|
}
|
|
146
160
|
}
|
|
@@ -149,7 +163,7 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
149
163
|
return {
|
|
150
164
|
getInterfaceType,
|
|
151
165
|
// Called when the global interface type changes without any pointer/touch/key
|
|
152
|
-
// event landing on the map container (e.g. panning via
|
|
166
|
+
// event landing on the map container (e.g. panning via MapControls after
|
|
153
167
|
// switching to touch) — refresh the rubber band immediately rather than
|
|
154
168
|
// waiting for the next incidental change:center/postrender event.
|
|
155
169
|
setInterfaceType (type) {
|
|
@@ -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')
|