@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
|
@@ -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)
|
|
@@ -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,10 +85,8 @@ const buildPointInput = ({ drawInteraction, options, canPlace }) => {
|
|
|
96
85
|
|
|
97
86
|
applyCrossHairVisibility(crossHair, interfaceType)
|
|
98
87
|
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// placeVertex, so a click (real, touch, or Voice Control's "Click Target") does exactly
|
|
102
|
-
// what those already do.
|
|
88
|
+
// Shared "commit here" entry point for the crosshair button's own onClick — same action as
|
|
89
|
+
// Enter/the touch add-vertex button.
|
|
103
90
|
if (crossHair) {
|
|
104
91
|
crossHair.activate = placePoint
|
|
105
92
|
}
|
|
@@ -115,21 +102,12 @@ const buildPointInput = ({ drawInteraction, options, canPlace }) => {
|
|
|
115
102
|
}
|
|
116
103
|
|
|
117
104
|
/**
|
|
118
|
-
* Draw mode for placing a single point: mouse click, or crosshair + Enter/"Add point"
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* immediately (confirmed by reading its handleUpEvent — mode 'Point' always attempts
|
|
125
|
-
* finishDrawing() on pointer-up, no second click needed), so the mouse path needs no
|
|
126
|
-
* extra wiring beyond the placement-veto condition every mode already has. Deliberately
|
|
127
|
-
* NOT built via draw/DrawMode.js's createDrawMode()/createVertexPlacement() — those are
|
|
128
|
-
* parametrised around a ring of placed vertices + a trailing rubber-band coordinate,
|
|
129
|
-
* which a single-click commit doesn't have (mirrors the same decision already made for
|
|
130
|
-
* the MapLibre adapter, and the OL EditMode/DrawMode split noted in
|
|
131
|
-
* feedback_adapter_parity.md: don't force point through machinery shaped for lines/polygons
|
|
132
|
-
* 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.
|
|
133
111
|
*/
|
|
134
112
|
export const createDrawPointMode = ({ map, manager, options }) => {
|
|
135
113
|
const { featureId, properties = {} } = options
|
|
@@ -137,18 +115,15 @@ export const createDrawPointMode = ({ map, manager, options }) => {
|
|
|
137
115
|
|
|
138
116
|
const drawInteraction = new Draw({
|
|
139
117
|
type: 'Point',
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// suppresses this same default for the "ghost" Point sketch OL renders alongside a
|
|
143
|
-
// Polygon/LineString sketch (see its own comment); a real Point sketch needs the same
|
|
144
|
-
// 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.
|
|
145
120
|
style: () => [],
|
|
146
121
|
snapTolerance: TOLERANCES.snapRadius,
|
|
147
|
-
condition: (
|
|
122
|
+
condition: (event) => noModifierKeys(event) && canPlace(event.coordinate)
|
|
148
123
|
})
|
|
149
124
|
map.addInteraction(drawInteraction)
|
|
150
125
|
|
|
151
|
-
drawInteraction.on('drawend', (
|
|
126
|
+
drawInteraction.on('drawend', (event) => finalizeDrawnFeature(manager, options.resolvePointSymbol, event.feature, featureId, properties))
|
|
152
127
|
drawInteraction.on('drawabort', () => { manager.emit(ADAPTER_EVENTS.CANCEL) })
|
|
153
128
|
|
|
154
129
|
const input = buildPointInput({ drawInteraction, options, canPlace })
|