@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
|
@@ -6,17 +6,28 @@ import { ADAPTER_EVENTS } from '../../adapterEvents.js'
|
|
|
6
6
|
import { createLiveStroke } from '../../validation/liveStroke.js'
|
|
7
7
|
import { createLiveDrawChecks } from '../../validation/liveDrawChecks.js'
|
|
8
8
|
import { resolvePointSymbol, hasSymbolStyle } from './pointSymbolImages.js'
|
|
9
|
+
import { getCoords, getMidpointCoords } from './modes/editVertexMode/geometryHelpers.js'
|
|
9
10
|
|
|
10
11
|
const polygonFeature = (coordinates) => ({ type: 'Feature', geometry: { type: 'Polygon', coordinates } })
|
|
11
12
|
const lineFeature = (coordinates) => ({ type: 'Feature', geometry: { type: 'LineString', coordinates } })
|
|
12
13
|
const pointFeature = (coordinates) => ({ type: 'Feature', geometry: { type: 'Point', coordinates } })
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// mapbox-gl-draw's public get() returns closed rings (first coord repeated as last), but the
|
|
16
|
+
// running mode's own internal indexing has that duplicate stripped — getVertexItems() below
|
|
17
|
+
// reads via the public API, so it must correct for this or indexes drift across rings/parts.
|
|
18
|
+
const dedupeClosedRings = (geometry) => {
|
|
19
|
+
if (geometry.type === 'Polygon') {
|
|
20
|
+
return { ...geometry, coordinates: geometry.coordinates.map(ring => ring.slice(0, -1)) }
|
|
21
|
+
}
|
|
22
|
+
if (geometry.type === 'MultiPolygon') {
|
|
23
|
+
return { ...geometry, coordinates: geometry.coordinates.map(poly => poly.map(ring => ring.slice(0, -1))) }
|
|
24
|
+
}
|
|
25
|
+
return geometry
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The displayed feature + vertex count for the live stroke check. MapLibre's fire() clobbers
|
|
29
|
+
// the payload's own geometry type, so it's inferred instead from the mode, or (in edit mode,
|
|
30
|
+
// which covers both shapes) from the coordinate nesting.
|
|
20
31
|
export const displayedShape = (mode, coordinates) => {
|
|
21
32
|
if (mode === 'draw_polygon') {
|
|
22
33
|
return { feature: polygonFeature(coordinates), numVertices: (coordinates[0]?.length ?? 1) - 1 }
|
|
@@ -25,10 +36,7 @@ export const displayedShape = (mode, coordinates) => {
|
|
|
25
36
|
return { feature: lineFeature(coordinates), numVertices: (coordinates?.length ?? 1) - 1 }
|
|
26
37
|
}
|
|
27
38
|
if (mode === 'edit_vertex') {
|
|
28
|
-
//
|
|
29
|
-
// non-nullish whenever it's true, so the polygon branch can use it directly with no
|
|
30
|
-
// second optional-chaining/fallback (which `coordinates[0]` re-read a second time,
|
|
31
|
-
// unreachably, used to need).
|
|
39
|
+
// Array.isArray(ring?.[0]) already proves ring is non-nullish, so the polygon branch below reuses it directly.
|
|
32
40
|
const ring = coordinates[0]
|
|
33
41
|
return Array.isArray(ring?.[0])
|
|
34
42
|
? { feature: polygonFeature(coordinates), numVertices: ring.length }
|
|
@@ -53,6 +61,7 @@ export const displayedShape = (mode, coordinates) => {
|
|
|
53
61
|
* setInterfaceType(type)
|
|
54
62
|
* done() / cancel() / undo() / deleteVertex()
|
|
55
63
|
* nudgeSelectedVertex(dx, dy, isLargeStep)
|
|
64
|
+
* getVertexItems() / selectVertex(index) / insertVertexAtMidpoint(index)
|
|
56
65
|
* get(id) / add(feature) / setStyle(id, properties) / delete(id) / deleteAll()
|
|
57
66
|
* setSnapEnabled(bool) / setSnapLayers(layers) / isSnapEnabled()
|
|
58
67
|
* setFeatureProperty(id, property, value) / setDrawingPreviewProperty(property, value)
|
|
@@ -65,10 +74,8 @@ export class MaplibreDrawAdapter {
|
|
|
65
74
|
this._map = mapProvider.map
|
|
66
75
|
this._bus = createEventBus()
|
|
67
76
|
this._editingFeatureId = null
|
|
68
|
-
// Assigned once per feature, on creation only
|
|
69
|
-
//
|
|
70
|
-
// within the same shared layer, instead of the order being incidental to mapbox-gl-draw's
|
|
71
|
-
// internal cold/hot bucket placement.
|
|
77
|
+
// Assigned once per feature, on creation only (see styles.js's SORT_KEY_PROP) — drives
|
|
78
|
+
// paint order so a later-drawn feature reliably layers above an earlier one.
|
|
72
79
|
this._sortKeyCounter = 0
|
|
73
80
|
|
|
74
81
|
const { draw, remove } = createMapboxDraw({
|
|
@@ -83,12 +90,9 @@ export class MaplibreDrawAdapter {
|
|
|
83
90
|
this._draw = draw
|
|
84
91
|
this._cleanupDraw = remove
|
|
85
92
|
|
|
86
|
-
// Single owner of the dashed-stroke state
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// the rendered layers. onChange does the actual layer toggle; in edit mode the
|
|
90
|
-
// displayed shape is exactly what Done finishes, so validity flips also gate
|
|
91
|
-
// the Done button (events.js dispatches them).
|
|
93
|
+
// Single owner of the dashed-stroke state — live moves feed update(), committed verdicts
|
|
94
|
+
// land via setInvalid(). onChange does the actual layer toggle, and in edit mode also
|
|
95
|
+
// gates the Done button (the displayed shape is exactly what Done finishes).
|
|
92
96
|
this._liveStroke = createLiveStroke({
|
|
93
97
|
onChange: (invalid, reason) => {
|
|
94
98
|
this._applyStrokeInvalid(invalid)
|
|
@@ -99,13 +103,10 @@ export class MaplibreDrawAdapter {
|
|
|
99
103
|
}
|
|
100
104
|
})
|
|
101
105
|
|
|
102
|
-
// Draw-mode-only: computes both the stroke/Done verdict and the Add-point
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// also driven independently by edit mode and must stay the single source of
|
|
107
|
-
// truth for the rendered stroke across mode switches. The Add-point verdict
|
|
108
|
-
// has no such cross-mode instance to stay in sync with, so it emits directly.
|
|
106
|
+
// Draw-mode-only: computes both the stroke/Done verdict and the Add-point verdict from one
|
|
107
|
+
// throttled user callback (see liveDrawChecks.js). The stroke verdict still routes through
|
|
108
|
+
// _liveStroke.set() to stay the single source of truth across mode switches; Add-point has
|
|
109
|
+
// no such shared instance, so it emits directly.
|
|
109
110
|
this._liveDrawChecks = createLiveDrawChecks({
|
|
110
111
|
onStrokeChange: (invalid, reason) => this._liveStroke.set(invalid, reason),
|
|
111
112
|
onPlaceChange: (vetoed, reason) => this._bus.emit(ADAPTER_EVENTS.CAN_PLACE_CHANGE, { canPlace: !vetoed, reason })
|
|
@@ -114,42 +115,34 @@ export class MaplibreDrawAdapter {
|
|
|
114
115
|
// Normalise ML map events → the shared adapter event contract (adapterEvents.js).
|
|
115
116
|
// The OL adapter emits the same contract directly from OLDrawManager.
|
|
116
117
|
this._mapHandlers = {
|
|
117
|
-
create: (
|
|
118
|
-
const feature =
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// freshly-drawn feature to its caller-requested id via a delete+re-add (see
|
|
122
|
-
// drawPointMode.js's onCreate / drawMode/clickHandlers.js's reidCreatedFeature) —
|
|
123
|
-
// mutating this SAME `feature` object's `id` in place. Deferring a tick lets that
|
|
124
|
-
// re-id finish first, so `feature.id` is already final by the time this runs, and
|
|
125
|
-
// setFeatureProperty targets the feature that's actually still in the store.
|
|
118
|
+
create: (event) => {
|
|
119
|
+
const feature = event.features[0]
|
|
120
|
+
// Deferred a tick so the mode's own re-id (delete+re-add to the caller-requested id —
|
|
121
|
+
// see drawPointMode.js's onCreate) finishes first, or this targets a stale feature id.
|
|
126
122
|
setTimeout(() => {
|
|
127
123
|
this._draw.setFeatureProperty(feature.id, 'sortKey', this._nextSortKey())
|
|
128
124
|
this._bus.emit(ADAPTER_EVENTS.CREATE, this._draw.get(feature.id))
|
|
129
125
|
}, 0)
|
|
130
126
|
},
|
|
131
|
-
editfinish: (
|
|
127
|
+
editfinish: (event) => this._bus.emit(ADAPTER_EVENTS.EDIT_FINISH, event.features[0]),
|
|
132
128
|
cancel: () => this._bus.emit(ADAPTER_EVENTS.CANCEL),
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!e?.phase) {
|
|
145
|
-
this._updateLiveStroke(e)
|
|
146
|
-
this._currentDrawEvent = e
|
|
129
|
+
vertexselection: (event) => this._bus.emit(ADAPTER_EVENTS.VERTEX_SELECTION, event),
|
|
130
|
+
vertexchange: (event) => this._bus.emit(ADAPTER_EVENTS.VERTEX_CHANGE, event),
|
|
131
|
+
undochange: (event) => this._bus.emit(ADAPTER_EVENTS.UNDO_CHANGE, event.length),
|
|
132
|
+
update: (event) => this._bus.emit(ADAPTER_EVENTS.UPDATE, event.features[0]),
|
|
133
|
+
geometrychange: (event) => {
|
|
134
|
+
// Phase-less events are rubber-band moves carrying the in-progress feature — they
|
|
135
|
+
// drive the live invalid stroke, and are cached for setDrawingPreviewProperty (which
|
|
136
|
+
// has no stable id yet to target via setFeatureProperty).
|
|
137
|
+
if (!event?.phase) {
|
|
138
|
+
this._updateLiveStroke(event)
|
|
139
|
+
this._currentDrawEvent = event
|
|
147
140
|
}
|
|
148
|
-
this._bus.emit(ADAPTER_EVENTS.GEOMETRY_CHANGE,
|
|
141
|
+
this._bus.emit(ADAPTER_EVENTS.GEOMETRY_CHANGE, event)
|
|
149
142
|
},
|
|
150
|
-
placementblocked: (
|
|
151
|
-
interfacetypechange: (
|
|
152
|
-
modechange: (
|
|
143
|
+
placementblocked: (event) => this._bus.emit(ADAPTER_EVENTS.PLACEMENT_BLOCKED, event),
|
|
144
|
+
interfacetypechange: (event) => this._bus.emit(ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE, { interfaceType: event.interfaceType }),
|
|
145
|
+
modechange: (event) => this._handleModeChange(event),
|
|
153
146
|
styledata: () => this._handleStyleData()
|
|
154
147
|
}
|
|
155
148
|
|
|
@@ -177,36 +170,30 @@ export class MaplibreDrawAdapter {
|
|
|
177
170
|
this._liveStroke.set(false)
|
|
178
171
|
this._liveDrawChecks.reset()
|
|
179
172
|
}
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// decoupled from how a symbol image actually gets resolved/registered.
|
|
173
|
+
// The resolver is injected here so draw_point stays decoupled from how a symbol image
|
|
174
|
+
// actually gets resolved/registered.
|
|
183
175
|
if (name === 'draw_point') {
|
|
184
176
|
options = { ...options, resolvePointSymbol: (featureId, properties) => this._resolvePointSymbol(featureId, properties) }
|
|
185
177
|
}
|
|
186
178
|
this._draw.changeMode(name, options)
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// through this adapter must drive the same cleanup manually.
|
|
179
|
+
// mapbox-gl-draw's own changeMode never fires 'draw.modechange', so every mode change
|
|
180
|
+
// requested through this adapter must drive the same cleanup manually.
|
|
190
181
|
this._handleModeChange({ mode: name })
|
|
191
182
|
}
|
|
192
183
|
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// onto the feature so the data-driven point-symbol layer (styles.js) can render it.
|
|
184
|
+
// Resolves and registers the feature's symbol-config icon, then writes the resolved image
|
|
185
|
+
// id/anchor back so styles.js's point-symbol layer can render it.
|
|
196
186
|
_resolvePointSymbol (featureId, properties) {
|
|
197
187
|
return resolvePointSymbol({ draw: this._draw, mapProvider: this._mapProvider, map: this._map, featureId, properties })
|
|
198
188
|
}
|
|
199
189
|
|
|
200
|
-
// Live invalid-stroke driver
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// liveDrawChecks.js).
|
|
206
|
-
_updateLiveStroke (e) {
|
|
207
|
-
if (!e?.coordinates) { return }
|
|
190
|
+
// Live invalid-stroke driver, called on every rubber-band move (draw) and vertex drag/nudge
|
|
191
|
+
// (edit). Draw mode routes through _liveDrawChecks (also computes the Add-point verdict);
|
|
192
|
+
// edit mode goes straight through the live-stroke controller.
|
|
193
|
+
_updateLiveStroke (event) {
|
|
194
|
+
if (!event?.coordinates) { return }
|
|
208
195
|
const mode = this._draw.getMode()
|
|
209
|
-
const shape = displayedShape(mode,
|
|
196
|
+
const shape = displayedShape(mode, event.coordinates)
|
|
210
197
|
if (!shape) { return }
|
|
211
198
|
if (mode === 'draw_polygon' || mode === 'draw_line') {
|
|
212
199
|
this._liveDrawChecks.update({ feature: shape.feature, numVertices: shape.numVertices, context: { mode }, onGeometryChange: this._geometryValidator })
|
|
@@ -240,13 +227,9 @@ export class MaplibreDrawAdapter {
|
|
|
240
227
|
cancel () {
|
|
241
228
|
this._mapProvider.undoStack?.clear()
|
|
242
229
|
const mode = this._draw.getMode()
|
|
243
|
-
// trash() only belongs to an in-progress,
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// here would instead run mapbox-gl-draw's direct_select onTrash on the
|
|
247
|
-
// mode's own live state (removing the selected vertex, or — if the
|
|
248
|
-
// in-progress edit happened to leave the shape invalid — deleting the
|
|
249
|
-
// entire feature), silently discarding the just-restored original.
|
|
230
|
+
// trash() only belongs to an in-progress, uncommitted draw. For edit_vertex, events.js has
|
|
231
|
+
// already restored the original feature — trash() here would instead run direct_select's
|
|
232
|
+
// own onTrash and silently discard that restore.
|
|
250
233
|
if (mode === 'draw_polygon' || mode === 'draw_line') {
|
|
251
234
|
this._draw.trash()
|
|
252
235
|
}
|
|
@@ -258,14 +241,41 @@ export class MaplibreDrawAdapter {
|
|
|
258
241
|
this._map.fire(CUSTOM_DRAW_EVENTS.UNDO)
|
|
259
242
|
}
|
|
260
243
|
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// same way setInterfaceType is, since the adapter has no direct reference to the
|
|
264
|
-
// mode's live state.
|
|
244
|
+
// MapControls' D-pad, routed here via mapProvider.activeMoveTarget once a vertex is
|
|
245
|
+
// selected — bridged into the mode via event, same as setInterfaceType.
|
|
265
246
|
nudgeSelectedVertex (dx, dy, isLargeStep) {
|
|
266
247
|
this._map.fire(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, { dx, dy, isLargeStep })
|
|
267
248
|
}
|
|
268
249
|
|
|
250
|
+
// Read-only vertex/midpoint coordinates for the shared spatial listbox (useSpatialList.js).
|
|
251
|
+
// Reads the committed feature via the public get() — the adapter has no reference into the
|
|
252
|
+
// live mode (see selectVertex below) — flat across all rings/parts, matching the mode's own
|
|
253
|
+
// selectedVertexIndex convention.
|
|
254
|
+
getVertexItems () {
|
|
255
|
+
const empty = { vertices: [], midpoints: [] }
|
|
256
|
+
if (this._draw.getMode() !== 'edit_vertex' || !this._editingFeatureId) {
|
|
257
|
+
return empty
|
|
258
|
+
}
|
|
259
|
+
const feature = this._draw.get(this._editingFeatureId)
|
|
260
|
+
if (!feature) {
|
|
261
|
+
return empty
|
|
262
|
+
}
|
|
263
|
+
const geometry = dedupeClosedRings(feature.geometry)
|
|
264
|
+
return { vertices: getCoords(geometry), midpoints: getMidpointCoords(geometry) }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Moves the live vertex/midpoint cursor to `index` — preview only, no geometry change.
|
|
268
|
+
// Bridged into the mode via a custom event, same pattern as nudgeSelectedVertex.
|
|
269
|
+
selectVertex (index) {
|
|
270
|
+
this._map.fire(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, { index })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Commits a new vertex exactly at midpoint `index` (no directional offset, unlike the
|
|
274
|
+
// keyboard/D-pad path) — the destructive counterpart to selectVertex's preview-only move.
|
|
275
|
+
insertVertexAtMidpoint (index) {
|
|
276
|
+
this._map.fire(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, { index })
|
|
277
|
+
}
|
|
278
|
+
|
|
269
279
|
// Record the current geometry validity so the draw mode can block finish gestures
|
|
270
280
|
// (double-click / click-to-close) while the in-progress shape is invalid.
|
|
271
281
|
setGeometryValid (valid) {
|
|
@@ -283,10 +293,8 @@ export class MaplibreDrawAdapter {
|
|
|
283
293
|
this._liveStroke.set(invalid)
|
|
284
294
|
}
|
|
285
295
|
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
// while invalid so the shape reads as an outline only. Only the live-stroke
|
|
289
|
-
// controller calls this — everything else goes through setInvalid.
|
|
296
|
+
// Swaps which overlaid stroke layer is visible (solid vs dashed); fill is hidden while
|
|
297
|
+
// invalid. Only the live-stroke controller calls this — everything else goes through setInvalid.
|
|
290
298
|
_applyStrokeInvalid (invalid) {
|
|
291
299
|
this._setLayerVisibility('stroke-active', !invalid)
|
|
292
300
|
this._setLayerVisibility('stroke-active-invalid', invalid)
|
|
@@ -303,12 +311,9 @@ export class MaplibreDrawAdapter {
|
|
|
303
311
|
}
|
|
304
312
|
|
|
305
313
|
deleteVertex () {
|
|
306
|
-
// Intentionally a no-op on MapLibre
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// deleteVertexButtonId (see editVertexMode.onButtonClick). Deleting here too would
|
|
310
|
-
// double-delete. The OL adapter implements this for real; here it only satisfies the
|
|
311
|
-
// shared adapter interface.
|
|
314
|
+
// Intentionally a no-op on MapLibre — the ML edit mode already handles vertex deletion
|
|
315
|
+
// itself (keyboard + its own click listener); deleting here too would double-delete.
|
|
316
|
+
// The OL adapter implements this for real; here it only satisfies the shared interface.
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
get (id) { return this._draw.get(id) }
|
|
@@ -318,9 +323,8 @@ export class MaplibreDrawAdapter {
|
|
|
318
323
|
return this._sortKeyCounter
|
|
319
324
|
}
|
|
320
325
|
|
|
321
|
-
// A directly-added Point
|
|
322
|
-
//
|
|
323
|
-
// point-symbol layer would otherwise render nothing at all.
|
|
326
|
+
// A directly-added Point skips draw_point's own icon-resolving handler, so it must be
|
|
327
|
+
// resolved here instead, or styles.js's point-symbol layer renders nothing.
|
|
324
328
|
add (feature) {
|
|
325
329
|
// Only a genuinely new feature gets a fresh sort key — setStyle() re-adds an existing one
|
|
326
330
|
// (via a shallow properties spread) to re-render it, and must not bump it back to the front.
|
|
@@ -335,9 +339,8 @@ export class MaplibreDrawAdapter {
|
|
|
335
339
|
return ids
|
|
336
340
|
}
|
|
337
341
|
|
|
338
|
-
// Patches an existing feature's style properties (
|
|
339
|
-
//
|
|
340
|
-
// same way a directly-added one does, for free.
|
|
342
|
+
// Patches an existing feature's style properties and re-renders via add(), so a Point's
|
|
343
|
+
// icon gets re-resolved the same way a directly-added one does.
|
|
341
344
|
setStyle (id, properties) {
|
|
342
345
|
const feature = this._draw.get(id)
|
|
343
346
|
if (!feature) {
|
|
@@ -380,17 +383,14 @@ export class MaplibreDrawAdapter {
|
|
|
380
383
|
this._draw.setFeatureProperty(id, property, value)
|
|
381
384
|
}
|
|
382
385
|
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
// re-render so the change is visible immediately. Used for live preview styling
|
|
386
|
-
// while drawing, e.g. split's valid/invalid line colour. A no-op once nothing
|
|
387
|
-
// has been drawn yet this session, or outside draw_polygon/draw_line.
|
|
386
|
+
// Tags the in-progress rubber-band feature (no stable id yet) with a property and
|
|
387
|
+
// re-renders — used for live preview styling, e.g. split's valid/invalid line colour.
|
|
388
388
|
setDrawingPreviewProperty (property, value) {
|
|
389
|
-
const
|
|
390
|
-
if (
|
|
391
|
-
|
|
389
|
+
const event = this._currentDrawEvent
|
|
390
|
+
if (event?.properties) {
|
|
391
|
+
event.properties[property] = value
|
|
392
392
|
}
|
|
393
|
-
|
|
393
|
+
event?.ctx?.store?.render()
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
on (type, handler) {
|
|
@@ -401,9 +401,9 @@ export class MaplibreDrawAdapter {
|
|
|
401
401
|
this._bus.off(type, handler)
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
_handleModeChange (
|
|
404
|
+
_handleModeChange (event) {
|
|
405
405
|
const DRAW_MODES = new Set(['draw_polygon', 'draw_line', 'draw_point', 'edit_vertex', 'edit_point'])
|
|
406
|
-
if (!DRAW_MODES.has(
|
|
406
|
+
if (!DRAW_MODES.has(event.mode)) {
|
|
407
407
|
clearSnapIndicator(getSnapInstance(this._map), this._map)
|
|
408
408
|
}
|
|
409
409
|
}
|
|
@@ -414,9 +414,8 @@ export class MaplibreDrawAdapter {
|
|
|
414
414
|
// 'styledata' after the map's been torn down — getStyle() returns undefined then; no-op.
|
|
415
415
|
const style = this._map.getStyle()
|
|
416
416
|
if (!style) { return }
|
|
417
|
-
// A style reload
|
|
418
|
-
//
|
|
419
|
-
// an invalid shape stays dashed across the reload.
|
|
417
|
+
// A style reload resets layers to spec-default visibility — re-assert the cached stroke
|
|
418
|
+
// state so an invalid shape stays dashed.
|
|
420
419
|
this._liveStroke.refresh()
|
|
421
420
|
const layers = style.layers || []
|
|
422
421
|
if (!layers.length || layers[layers.length - 1].source?.startsWith('mapbox-gl-draw')) {
|
|
@@ -179,11 +179,11 @@ describe('map event normalisation', () => {
|
|
|
179
179
|
expect(bus.emit).toHaveBeenCalledWith('cancel')
|
|
180
180
|
})
|
|
181
181
|
|
|
182
|
-
test('vertexselection/vertexchange
|
|
182
|
+
test('vertexselection/vertexchange forward the raw event', () => {
|
|
183
183
|
const { map, bus } = setup()
|
|
184
184
|
|
|
185
|
-
onHandler(map, CUSTOM_DRAW_EVENTS.VERTEX_SELECTION)({
|
|
186
|
-
onHandler(map, CUSTOM_DRAW_EVENTS.VERTEX_CHANGE)({
|
|
185
|
+
onHandler(map, CUSTOM_DRAW_EVENTS.VERTEX_SELECTION)({ numVertices: 3, index: 1 })
|
|
186
|
+
onHandler(map, CUSTOM_DRAW_EVENTS.VERTEX_CHANGE)({ numVertices: 2 })
|
|
187
187
|
|
|
188
188
|
expect(bus.emit).toHaveBeenCalledWith('vertexselection', expect.objectContaining({ numVertices: 3, index: 1 }))
|
|
189
189
|
expect(bus.emit).toHaveBeenCalledWith('vertexchange', expect.objectContaining({ numVertices: 2 }))
|
|
@@ -197,16 +197,16 @@ describe('map event normalisation', () => {
|
|
|
197
197
|
|
|
198
198
|
test('geometrychange forwards the raw event', () => {
|
|
199
199
|
const { map, bus } = setup()
|
|
200
|
-
const
|
|
201
|
-
onHandler(map, CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE)(
|
|
202
|
-
expect(bus.emit).toHaveBeenCalledWith('geometrychange',
|
|
200
|
+
const event = { type: 'Polygon' }
|
|
201
|
+
onHandler(map, CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE)(event)
|
|
202
|
+
expect(bus.emit).toHaveBeenCalledWith('geometrychange', event)
|
|
203
203
|
})
|
|
204
204
|
|
|
205
205
|
test('placementblocked forwards the raw event', () => {
|
|
206
206
|
const { map, bus } = setup()
|
|
207
|
-
const
|
|
208
|
-
onHandler(map, CUSTOM_DRAW_EVENTS.PLACEMENT_BLOCKED)(
|
|
209
|
-
expect(bus.emit).toHaveBeenCalledWith('placementblocked',
|
|
207
|
+
const event = { phase: 'place', reason: 'outside region' }
|
|
208
|
+
onHandler(map, CUSTOM_DRAW_EVENTS.PLACEMENT_BLOCKED)(event)
|
|
209
|
+
expect(bus.emit).toHaveBeenCalledWith('placementblocked', event)
|
|
210
210
|
})
|
|
211
211
|
})
|
|
212
212
|
|
|
@@ -582,6 +582,18 @@ describe('simple delegations', () => {
|
|
|
582
582
|
expect(map.fire).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, { dx: 1, dy: 0, isLargeStep: true })
|
|
583
583
|
})
|
|
584
584
|
|
|
585
|
+
test('selectVertex fires the select-vertex event with the given index', () => {
|
|
586
|
+
const { adapter, map } = setup()
|
|
587
|
+
adapter.selectVertex(3)
|
|
588
|
+
expect(map.fire).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.SELECT_VERTEX, { index: 3 })
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
test('insertVertexAtMidpoint fires the insert-vertex-at-midpoint event with the given index', () => {
|
|
592
|
+
const { adapter, map } = setup()
|
|
593
|
+
adapter.insertVertexAtMidpoint(4)
|
|
594
|
+
expect(map.fire).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.INSERT_VERTEX_AT_MIDPOINT, { index: 4 })
|
|
595
|
+
})
|
|
596
|
+
|
|
585
597
|
test('deleteVertex is a no-op', () => {
|
|
586
598
|
const { adapter, draw, map } = setup()
|
|
587
599
|
expect(() => adapter.deleteVertex()).not.toThrow()
|
|
@@ -761,6 +773,67 @@ describe('simple delegations', () => {
|
|
|
761
773
|
})
|
|
762
774
|
})
|
|
763
775
|
|
|
776
|
+
describe('getVertexItems', () => {
|
|
777
|
+
test('returns empty vertices/midpoints when not in edit_vertex mode', () => {
|
|
778
|
+
const { adapter, draw } = setup()
|
|
779
|
+
draw.getMode.mockReturnValue('draw_polygon')
|
|
780
|
+
expect(adapter.getVertexItems()).toEqual({ vertices: [], midpoints: [] })
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
test('returns empty vertices/midpoints when in edit_vertex but no editing feature id is set', () => {
|
|
784
|
+
const { adapter, draw } = setup()
|
|
785
|
+
draw.getMode.mockReturnValue('edit_vertex')
|
|
786
|
+
expect(adapter.getVertexItems()).toEqual({ vertices: [], midpoints: [] })
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
test('returns empty vertices/midpoints when the editing feature no longer exists', () => {
|
|
790
|
+
const { adapter, draw } = setup()
|
|
791
|
+
adapter.changeMode('edit_vertex', { featureId: 'f1' })
|
|
792
|
+
draw.getMode.mockReturnValue('edit_vertex')
|
|
793
|
+
draw.get.mockReturnValue(undefined)
|
|
794
|
+
expect(adapter.getVertexItems()).toEqual({ vertices: [], midpoints: [] })
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
test('flattens a LineString — no closing duplicate to strip', () => {
|
|
798
|
+
const { adapter, draw } = setup()
|
|
799
|
+
adapter.changeMode('edit_vertex', { featureId: 'f1' })
|
|
800
|
+
draw.getMode.mockReturnValue('edit_vertex')
|
|
801
|
+
draw.get.mockReturnValue({ geometry: { type: 'LineString', coordinates: [[0, 0], [10, 0], [10, 10]] } })
|
|
802
|
+
const result = adapter.getVertexItems()
|
|
803
|
+
expect(result.vertices).toEqual([[0, 0], [10, 0], [10, 10]])
|
|
804
|
+
expect(result.midpoints).toEqual([[5, 0], [10, 5]])
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
test('strips the closing duplicate coordinate for a Polygon ring — index-aligned with the mode\'s own selectedVertexIndex', () => {
|
|
808
|
+
const { adapter, draw } = setup()
|
|
809
|
+
adapter.changeMode('edit_vertex', { featureId: 'f1' })
|
|
810
|
+
draw.getMode.mockReturnValue('edit_vertex')
|
|
811
|
+
draw.get.mockReturnValue({ geometry: { type: 'Polygon', coordinates: [[[0, 0], [10, 0], [10, 10], [0, 0]]] } })
|
|
812
|
+
const result = adapter.getVertexItems()
|
|
813
|
+
expect(result.vertices).toEqual([[0, 0], [10, 0], [10, 10]])
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
test('strips the closing duplicate for every ring/part of a MultiPolygon, keeping index continuity across parts', () => {
|
|
817
|
+
const { adapter, draw } = setup()
|
|
818
|
+
adapter.changeMode('edit_vertex', { featureId: 'f1' })
|
|
819
|
+
draw.getMode.mockReturnValue('edit_vertex')
|
|
820
|
+
draw.get.mockReturnValue({
|
|
821
|
+
geometry: {
|
|
822
|
+
type: 'MultiPolygon',
|
|
823
|
+
coordinates: [
|
|
824
|
+
[[[0, 0], [10, 0], [10, 10], [0, 0]]],
|
|
825
|
+
[[[20, 20], [30, 20], [30, 30], [20, 20]]]
|
|
826
|
+
]
|
|
827
|
+
}
|
|
828
|
+
})
|
|
829
|
+
const result = adapter.getVertexItems()
|
|
830
|
+
expect(result.vertices).toEqual([
|
|
831
|
+
[0, 0], [10, 0], [10, 10],
|
|
832
|
+
[20, 20], [30, 20], [30, 30]
|
|
833
|
+
])
|
|
834
|
+
})
|
|
835
|
+
})
|
|
836
|
+
|
|
764
837
|
describe('done', () => {
|
|
765
838
|
test('clears the undo stack and fires editfinish when editing a vertex', () => {
|
|
766
839
|
const { adapter, map, draw, undoStack } = setup()
|
|
@@ -28,7 +28,9 @@ export const CUSTOM_DRAW_EVENTS = {
|
|
|
28
28
|
GEOMETRY_CHANGE: 'draw.geometrychange',
|
|
29
29
|
INTERFACE_TYPE_CHANGE: 'draw.interfacetypechange',
|
|
30
30
|
PLACEMENT_BLOCKED: 'draw.placementblocked',
|
|
31
|
-
NUDGE_VERTEX: 'draw.nudgevertex'
|
|
31
|
+
NUDGE_VERTEX: 'draw.nudgevertex',
|
|
32
|
+
SELECT_VERTEX: 'draw.selectvertex',
|
|
33
|
+
INSERT_VERTEX_AT_MIDPOINT: 'draw.insertvertexatmidpoint'
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
// Native MapLibre map event (not a draw event) — fires whenever the map style data changes.
|
|
@@ -21,7 +21,7 @@ describe('createDrawMode composition', () => {
|
|
|
21
21
|
const mode = build()
|
|
22
22
|
const contributed = [
|
|
23
23
|
'onSetup', 'onStop', // lifecycle
|
|
24
|
-
'onClick', 'onTap', 'doClick', 'onVertexButtonClick', 'onCreate', // click
|
|
24
|
+
'onClick', 'onTap', 'doClick', '_placeAtCrossHair', 'onVertexButtonClick', 'onCreate', // click
|
|
25
25
|
'pushDrawUndo', 'undoVertex', 'onUndo', // undo
|
|
26
26
|
'onKeydown', 'onKeyup', 'onKeyUp', // keyboard
|
|
27
27
|
'onMove', 'onMouseMove', 'onTouchStart', 'onBlur', // pointer
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DrawPolygonMode } from '../../drawPolygonMode.js'
|
|
2
2
|
import { DrawLineMode } from '../../drawLineMode.js'
|
|
3
3
|
import { createUndoStack } from '../../../../../utils/undoStack.js'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Real mapbox-gl-draw Feature classes, not exported from its package entrypoint — needed here
|
|
5
|
+
// so this harness can construct the actual objects mapbox-gl-draw itself hands to a mode.
|
|
6
|
+
import PolygonFeature from '../../../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/polygon.js' // NOSONAR
|
|
7
|
+
import LineStringFeature from '../../../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/line_string.js' // NOSONAR
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Shared test harness for the createDrawMode handler modules. Exercises the real
|
|
@@ -12,6 +14,10 @@ import LineStringFeature from '../../../../../../../../node_modules/@mapbox/mapb
|
|
|
12
14
|
|
|
13
15
|
export { DrawPolygonMode, DrawLineMode }
|
|
14
16
|
export const CENTER = { lng: 5, lat: 5 }
|
|
17
|
+
// Fixed point a mock-active snap resolves to, distinct from CENTER so tests can tell whether
|
|
18
|
+
// a click landed at the real coordinate or was pulled onto the snap target — the value itself
|
|
19
|
+
// is arbitrary, just needs to differ from CENTER.
|
|
20
|
+
export const SNAP_TARGET = [9, 9] // NOSONAR
|
|
15
21
|
|
|
16
22
|
const createMap = () => {
|
|
17
23
|
const canvas = document.createElement('canvas')
|
|
@@ -25,9 +31,12 @@ const createMap = () => {
|
|
|
25
31
|
getCenter: () => ({ ...CENTER }),
|
|
26
32
|
project: jest.fn(() => ({ x: 50, y: 50 })),
|
|
27
33
|
unproject: jest.fn(() => ({ ...CENTER })),
|
|
28
|
-
fire: jest.fn(function (type,
|
|
29
|
-
on: jest.fn((type,
|
|
30
|
-
|
|
34
|
+
fire: jest.fn(function (type, event) { (listeners[type] ?? []).forEach((handler) => handler(event)) }),
|
|
35
|
+
on: jest.fn((type, handler) => {
|
|
36
|
+
listeners[type] ??= []
|
|
37
|
+
listeners[type].push(handler)
|
|
38
|
+
}),
|
|
39
|
+
off: jest.fn((type, handler) => { listeners[type] = (listeners[type] ?? []).filter((x) => x !== handler) })
|
|
31
40
|
}
|
|
32
41
|
}
|
|
33
42
|
|
|
@@ -64,8 +73,8 @@ const createModeContext = (mode) => {
|
|
|
64
73
|
|
|
65
74
|
// Remove window/container/map listeners registered by onSetup so tests don't leak into each other
|
|
66
75
|
const contexts = []
|
|
67
|
-
const removeListeners = (ctx) => ctx._listeners?.forEach(([
|
|
68
|
-
|
|
76
|
+
const removeListeners = (ctx) => ctx._listeners?.forEach(([target, eventName, handler, opts]) =>
|
|
77
|
+
target.removeEventListener ? target.removeEventListener(eventName, handler, opts) : target.off(eventName, handler))
|
|
69
78
|
|
|
70
79
|
const createContainer = () => {
|
|
71
80
|
const container = document.createElement('div')
|
|
@@ -104,9 +113,9 @@ export const clickEvent = (ctx, lng, lat, overrides = {}) => ({
|
|
|
104
113
|
|
|
105
114
|
export const clickAt = (ctx, state, lng, lat) => ctx.onClick(state, clickEvent(ctx, lng, lat))
|
|
106
115
|
|
|
107
|
-
export const firedWith = (map, type) => map.fire.mock.calls.filter(([
|
|
116
|
+
export const firedWith = (map, type) => map.fire.mock.calls.filter(([eventType]) => eventType === type).map(([, event]) => event)
|
|
108
117
|
|
|
109
|
-
export const activeSnap = () => ({ status: true, snapStatus: true, snapCoords:
|
|
118
|
+
export const activeSnap = () => ({ status: true, snapStatus: true, snapCoords: SNAP_TARGET, snapToClosestPoint: jest.fn() })
|
|
110
119
|
|
|
111
120
|
afterEach(() => {
|
|
112
121
|
contexts.splice(0).forEach(removeListeners)
|
|
@@ -60,7 +60,7 @@ const createClickHelpers = ({ geometryType, getFeature, getCoords }) => ({
|
|
|
60
60
|
dispatchVertexChange (coords) {
|
|
61
61
|
// Both polygon ring and LineString store [v0...vN, rubber_band] during drawing — subtract 1 to get placed vertex count
|
|
62
62
|
this.map.fire('draw.vertexchange', {
|
|
63
|
-
|
|
63
|
+
numVertices: Math.max(0, coords.length - 1)
|
|
64
64
|
})
|
|
65
65
|
},
|
|
66
66
|
|
|
@@ -179,6 +179,14 @@ const createClickActions = ({ ParentMode, getFeature, getCoords, validateClick,
|
|
|
179
179
|
this.pushDrawUndo(state)
|
|
180
180
|
this.dispatchVertexChange(newCoords)
|
|
181
181
|
this.emitDrawValidation(state)
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Alias for lifecycle.js's shared crosshair.activate wiring (createLifecycle calls
|
|
185
|
+
// this._placeAtCrossHair?.(state)) — drawPointMode.js defines its own _placeAtCrossHair
|
|
186
|
+
// directly, but polygon/line's equivalent "commit at crosshair" action is doClick above
|
|
187
|
+
// (also used by the touch add-vertex button), just under a different name.
|
|
188
|
+
_placeAtCrossHair (state) {
|
|
189
|
+
this.doClick(state)
|
|
182
190
|
}
|
|
183
191
|
}
|
|
184
192
|
}
|