@defra/interactive-map 0.0.37-alpha → 0.0.39-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/templates/draw-tools.njk +4 -3
- 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 +32 -0
- package/docs/examples/draw-tools.mdx +0 -4
- package/docs/plugins/draw.md +482 -0
- package/docs/plugins.md +2 -6
- package/govuk-prototype-kit.config.json +3 -2
- package/package.json +2 -4
- 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/css/index.css +1 -97
- 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/registry/dataset.js +3 -1
- package/plugins/datasets/src/registry/datasetRegistry.js +30 -24
- package/plugins/datasets/src/registry/datasetRegistry.test.js +25 -18
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
- package/plugins/draw/dist/esm/index.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/index.js +2 -0
- package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/draw/src/DrawInit.jsx +89 -0
- package/plugins/draw/src/DrawInit.test.jsx +184 -0
- package/plugins/draw/src/adapterEvents.js +127 -0
- package/plugins/draw/src/adapterEvents.test.js +20 -0
- package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
- package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
- package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
- package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
- package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
- package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
- package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
- package/plugins/draw/src/api/addFeature.js +22 -0
- package/plugins/draw/src/api/addFeature.test.js +41 -0
- package/plugins/draw/src/api/deleteFeature.js +11 -0
- package/plugins/draw/src/api/deleteFeature.test.js +24 -0
- package/plugins/draw/src/api/editFeature.js +54 -0
- package/plugins/draw/src/api/editFeature.test.js +134 -0
- package/plugins/draw/src/api/merge.js +30 -0
- package/plugins/draw/src/api/merge.test.js +57 -0
- package/plugins/draw/src/api/newLine.js +41 -0
- package/plugins/draw/src/api/newLine.test.js +93 -0
- package/plugins/draw/src/api/newPolygon.js +41 -0
- package/plugins/draw/src/api/newPolygon.test.js +93 -0
- package/plugins/draw/src/api/split.js +117 -0
- package/plugins/draw/src/api/split.test.js +204 -0
- package/plugins/draw/src/defaults.js +52 -0
- package/plugins/draw/src/defaults.test.js +18 -0
- package/plugins/draw/src/draw.scss +43 -0
- package/plugins/draw/src/events.js +237 -0
- package/plugins/draw/src/events.test.js +369 -0
- package/plugins/draw/src/index.js +10 -0
- package/plugins/draw/src/index.test.js +24 -0
- package/plugins/draw/src/manifest.js +170 -0
- package/plugins/draw/src/manifest.test.js +147 -0
- package/plugins/draw/src/reducer.js +69 -0
- package/plugins/draw/src/reducer.test.js +85 -0
- package/plugins/draw/src/utils/debounce.js +16 -0
- package/plugins/draw/src/utils/debounce.test.js +40 -0
- package/plugins/draw/src/utils/eventBus.js +36 -0
- package/plugins/draw/src/utils/eventBus.test.js +47 -0
- package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
- package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
- package/plugins/draw/src/utils/resolveColors.js +39 -0
- package/plugins/draw/src/utils/resolveColors.test.js +34 -0
- package/plugins/draw/src/utils/spatial.js +243 -0
- package/plugins/draw/src/utils/spatial.test.js +243 -0
- package/plugins/draw/src/utils/touchTarget.js +95 -0
- package/plugins/draw/src/utils/touchTarget.test.js +107 -0
- package/plugins/draw/src/utils/undoStack.js +31 -0
- package/plugins/draw/src/utils/undoStack.test.js +49 -0
- package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
- package/plugins/draw/src/validation/liveStroke.js +85 -0
- package/plugins/draw/src/validation/liveStroke.test.js +150 -0
- package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
- package/plugins/draw/src/validation/rules.js +186 -0
- package/plugins/draw/src/validation/rules.test.js +120 -0
- package/plugins/draw/src/validation/validateGeometry.js +119 -0
- package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
- 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/src/hooks/useInteractionHandlers.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
- package/plugins/interact/src/utils/buildStylesMap.js +1 -0
- package/plugins/search/dist/css/index.css +1 -317
- package/rollup.esm.mjs +35 -28
- package/sonar-project.properties +2 -2
- package/src/App/components/Attributions/Attributions.module.scss +1 -0
- package/src/App/components/Hints/Hints.module.scss +5 -4
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
- package/src/App/components/MoveControl/MoveControl.jsx +73 -9
- package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
- package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
- package/src/App/hooks/useHintsAPI.js +67 -0
- package/src/App/hooks/useHintsAPI.test.js +145 -0
- package/src/App/renderer/PluginInits.jsx +4 -0
- package/src/App/renderer/PluginInits.test.jsx +7 -1
- package/src/InteractiveMap/InteractiveMap.js +20 -0
- package/src/InteractiveMap/InteractiveMap.test.js +7 -0
- package/src/config/events.js +4 -0
- package/src/scss/settings/_dimensions.scss +3 -0
- package/src/services/announcer.js +38 -3
- package/src/services/announcer.test.js +133 -1
- package/src/services/hints.js +3 -0
- package/src/services/hints.test.js +19 -0
- package/src/types.js +12 -0
- package/src/utils/detectInterfaceType.js +5 -1
- package/src/utils/isMac.js +15 -0
- package/src/utils/isMac.test.js +37 -0
- package/webpack.umd.mjs +1 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSnapInstance,
|
|
3
|
+
isSnapActive,
|
|
4
|
+
getSnapLngLat,
|
|
5
|
+
getSnapCoords,
|
|
6
|
+
triggerSnapAtPoint,
|
|
7
|
+
triggerSnapAtCenter,
|
|
8
|
+
clearSnapIndicator,
|
|
9
|
+
clearSnapState,
|
|
10
|
+
getSnapRadius,
|
|
11
|
+
isSnapEnabled,
|
|
12
|
+
createSnappedEvent,
|
|
13
|
+
createSnappedClickEvent
|
|
14
|
+
} from './snapHelpers.js'
|
|
15
|
+
import { TOLERANCES } from '../../../defaults.js'
|
|
16
|
+
|
|
17
|
+
const activeSnap = (overrides = {}) => ({
|
|
18
|
+
status: true,
|
|
19
|
+
snapStatus: true,
|
|
20
|
+
snapCoords: [1, 2],
|
|
21
|
+
...overrides
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('getSnapInstance', () => {
|
|
25
|
+
test('returns the snap instance when present', () => {
|
|
26
|
+
const snap = {}
|
|
27
|
+
expect(getSnapInstance({ _snapInstance: snap })).toBe(snap)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('returns null when the map has no instance', () => {
|
|
31
|
+
expect(getSnapInstance({})).toBeNull()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('returns null when the map is nullish', () => {
|
|
35
|
+
expect(getSnapInstance(null)).toBeNull()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('isSnapActive', () => {
|
|
40
|
+
test('returns true when status, snapStatus and >=2 coords are present', () => {
|
|
41
|
+
expect(isSnapActive(activeSnap())).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('returns false when snap is nullish', () => {
|
|
45
|
+
expect(isSnapActive(null)).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('returns false when status is falsy', () => {
|
|
49
|
+
expect(isSnapActive(activeSnap({ status: false }))).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('returns false when snapStatus is falsy', () => {
|
|
53
|
+
expect(isSnapActive(activeSnap({ snapStatus: false }))).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('returns false when coords have fewer than 2 entries', () => {
|
|
57
|
+
expect(isSnapActive(activeSnap({ snapCoords: [1] }))).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('getSnapLngLat', () => {
|
|
62
|
+
test('returns lng/lat object when snap is active', () => {
|
|
63
|
+
expect(getSnapLngLat(activeSnap())).toEqual({ lng: 1, lat: 2 })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('returns null when snap is inactive', () => {
|
|
67
|
+
expect(getSnapLngLat(null)).toBeNull()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('getSnapCoords', () => {
|
|
72
|
+
test('returns [lng, lat] array when snap is active', () => {
|
|
73
|
+
expect(getSnapCoords(activeSnap())).toEqual([1, 2])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('returns null when snap is inactive', () => {
|
|
77
|
+
expect(getSnapCoords(null)).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('triggerSnapAtPoint', () => {
|
|
82
|
+
test('unprojects the point and triggers snap detection', () => {
|
|
83
|
+
const snap = { status: true, snapToClosestPoint: jest.fn() }
|
|
84
|
+
const lngLat = { lng: 5, lat: 6 }
|
|
85
|
+
const map = { unproject: jest.fn(() => lngLat) }
|
|
86
|
+
const point = { x: 10, y: 20 }
|
|
87
|
+
|
|
88
|
+
expect(triggerSnapAtPoint(snap, map, point)).toBe(true)
|
|
89
|
+
expect(map.unproject).toHaveBeenCalledWith(point)
|
|
90
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('returns false when snap is missing', () => {
|
|
94
|
+
expect(triggerSnapAtPoint(null, {}, {})).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('returns false when map is missing', () => {
|
|
98
|
+
expect(triggerSnapAtPoint({ status: true }, null, {})).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('returns false when snap is disabled', () => {
|
|
102
|
+
expect(triggerSnapAtPoint({ status: false }, {}, {})).toBe(false)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('triggerSnapAtCenter', () => {
|
|
107
|
+
test('projects the map centre and triggers snap detection', () => {
|
|
108
|
+
const snap = { status: true, snapToClosestPoint: jest.fn() }
|
|
109
|
+
const center = { lng: 1, lat: 2 }
|
|
110
|
+
const point = { x: 3, y: 4 }
|
|
111
|
+
const map = { getCenter: jest.fn(() => center), project: jest.fn(() => point) }
|
|
112
|
+
|
|
113
|
+
expect(triggerSnapAtCenter(snap, map)).toBe(true)
|
|
114
|
+
expect(map.project).toHaveBeenCalledWith(center)
|
|
115
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat: center })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('returns false when snap is missing', () => {
|
|
119
|
+
expect(triggerSnapAtCenter(null, {})).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('returns false when map is missing', () => {
|
|
123
|
+
expect(triggerSnapAtCenter({ status: true }, null)).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('returns false when snap is disabled', () => {
|
|
127
|
+
expect(triggerSnapAtCenter({ status: false }, {})).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('clearSnapIndicator', () => {
|
|
132
|
+
test('resets snap state, empties the feature arrays and hides the layer', () => {
|
|
133
|
+
const snap = {
|
|
134
|
+
snapStatus: true,
|
|
135
|
+
snapCoords: [1, 2],
|
|
136
|
+
snappedFeatures: [{}, {}],
|
|
137
|
+
closeFeatures: [{}],
|
|
138
|
+
lines: [{}]
|
|
139
|
+
}
|
|
140
|
+
const map = {
|
|
141
|
+
getLayer: jest.fn(() => ({})),
|
|
142
|
+
setLayoutProperty: jest.fn()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
clearSnapIndicator(snap, map)
|
|
146
|
+
|
|
147
|
+
expect(snap.snapStatus).toBe(false)
|
|
148
|
+
expect(snap.snapCoords).toBeNull()
|
|
149
|
+
expect(snap.snappedFeatures).toHaveLength(0)
|
|
150
|
+
expect(snap.closeFeatures).toHaveLength(0)
|
|
151
|
+
expect(snap.lines).toHaveLength(0)
|
|
152
|
+
expect(map.setLayoutProperty).toHaveBeenCalledWith('snap-helper-circle', 'visibility', 'none')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('handles a snap with empty/absent arrays and no map', () => {
|
|
156
|
+
const snap = { snapStatus: true, snapCoords: [1, 2] }
|
|
157
|
+
expect(() => clearSnapIndicator(snap)).not.toThrow()
|
|
158
|
+
expect(snap.snapStatus).toBe(false)
|
|
159
|
+
expect(snap.snapCoords).toBeNull()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('does not touch the layer when it is absent', () => {
|
|
163
|
+
const map = { getLayer: jest.fn(() => null), setLayoutProperty: jest.fn() }
|
|
164
|
+
clearSnapIndicator(null, map)
|
|
165
|
+
expect(map.setLayoutProperty).not.toHaveBeenCalled()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('clearSnapState', () => {
|
|
170
|
+
test('returns early when snap is nullish', () => {
|
|
171
|
+
expect(() => clearSnapState(null)).not.toThrow()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('resets snap state and empties the feature arrays', () => {
|
|
175
|
+
const snap = {
|
|
176
|
+
snapStatus: true,
|
|
177
|
+
snapCoords: [1, 2],
|
|
178
|
+
snappedFeatures: [{}, {}],
|
|
179
|
+
closeFeatures: [{}],
|
|
180
|
+
lines: [{}]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
clearSnapState(snap)
|
|
184
|
+
|
|
185
|
+
expect(snap.snapStatus).toBe(false)
|
|
186
|
+
expect(snap.snapCoords).toBeNull()
|
|
187
|
+
expect(snap.snappedFeatures).toHaveLength(0)
|
|
188
|
+
expect(snap.closeFeatures).toHaveLength(0)
|
|
189
|
+
expect(snap.lines).toHaveLength(0)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('handles a snap with empty/absent arrays', () => {
|
|
193
|
+
const snap = { snapStatus: true, snapCoords: [1, 2] }
|
|
194
|
+
expect(() => clearSnapState(snap)).not.toThrow()
|
|
195
|
+
expect(snap.snapCoords).toBeNull()
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('getSnapRadius', () => {
|
|
200
|
+
test('returns the configured radius', () => {
|
|
201
|
+
expect(getSnapRadius({ options: { radius: 25 } })).toBe(25)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('falls back to the configured snap radius when unset', () => {
|
|
205
|
+
expect(getSnapRadius(null)).toBe(TOLERANCES.snapRadius)
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('isSnapEnabled', () => {
|
|
210
|
+
test('returns true when getSnapEnabled returns true', () => {
|
|
211
|
+
expect(isSnapEnabled({ getSnapEnabled: () => true })).toBe(true)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('returns false when getSnapEnabled returns a non-true value', () => {
|
|
215
|
+
expect(isSnapEnabled({ getSnapEnabled: () => false })).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('returns false when getSnapEnabled is not a function', () => {
|
|
219
|
+
expect(isSnapEnabled({})).toBe(false)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('createSnappedEvent', () => {
|
|
224
|
+
test('merges the snapped lngLat into the event when snapping', () => {
|
|
225
|
+
const e = { type: 'click', lngLat: { lng: 0, lat: 0 } }
|
|
226
|
+
const result = createSnappedEvent(e, activeSnap())
|
|
227
|
+
expect(result).toEqual({ type: 'click', lngLat: { lng: 1, lat: 2 } })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('returns the original event when there is no snap', () => {
|
|
231
|
+
const e = { type: 'click' }
|
|
232
|
+
expect(createSnappedEvent(e, null)).toBe(e)
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('createSnappedClickEvent', () => {
|
|
237
|
+
test('builds a synthetic click event at the snapped point', () => {
|
|
238
|
+
const point = { x: 12, y: 34 }
|
|
239
|
+
const map = { project: jest.fn(() => point) }
|
|
240
|
+
|
|
241
|
+
const result = createSnappedClickEvent(map, activeSnap())
|
|
242
|
+
|
|
243
|
+
expect(map.project).toHaveBeenCalledWith([1, 2])
|
|
244
|
+
expect(result.lngLat).toEqual({ lng: 1, lat: 2 })
|
|
245
|
+
expect(result.point).toBe(point)
|
|
246
|
+
expect(result.originalEvent).toBeInstanceOf(MouseEvent)
|
|
247
|
+
expect(result.originalEvent.type).toBe('click')
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('returns null when there is no snap', () => {
|
|
251
|
+
expect(createSnappedClickEvent({}, null)).toBeNull()
|
|
252
|
+
})
|
|
253
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const MAX_TAP_DURATION_MS = 300
|
|
2
|
+
const MAX_TAP_MOVEMENT_PX = 10
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Workaround: mapbox-gl-draw calls preventDefault() on touchend even in disabled
|
|
6
|
+
* mode, which stops the browser synthesizing a click event. Detect quick taps
|
|
7
|
+
* while in disabled mode and manually dispatch a click on the canvas so app-level
|
|
8
|
+
* click handlers (e.g. feature selection) still fire.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} map - MapLibre map instance
|
|
11
|
+
* @param {Object} draw - MapboxDraw instance (used to check the current mode)
|
|
12
|
+
* @returns {{ remove: Function }}
|
|
13
|
+
*/
|
|
14
|
+
export const setupTouchClickWorkaround = (map, draw) => {
|
|
15
|
+
const canvas = map.getCanvas()
|
|
16
|
+
let touchStart = null
|
|
17
|
+
|
|
18
|
+
const handleTouchStart = (e) => {
|
|
19
|
+
if (e.touches.length === 1) {
|
|
20
|
+
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY, time: Date.now() }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handleTouchEnd = (e) => {
|
|
25
|
+
if (draw.getMode() !== 'disabled' || !touchStart) {
|
|
26
|
+
touchStart = null
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const touch = e.changedTouches[0]
|
|
31
|
+
const dx = touch.clientX - touchStart.x
|
|
32
|
+
const dy = touch.clientY - touchStart.y
|
|
33
|
+
const duration = Date.now() - touchStart.time
|
|
34
|
+
|
|
35
|
+
// Only synthesize click for quick taps with minimal movement
|
|
36
|
+
if (duration < MAX_TAP_DURATION_MS && Math.abs(dx) < MAX_TAP_MOVEMENT_PX && Math.abs(dy) < MAX_TAP_MOVEMENT_PX) {
|
|
37
|
+
canvas.dispatchEvent(new MouseEvent('click', {
|
|
38
|
+
bubbles: true,
|
|
39
|
+
cancelable: true,
|
|
40
|
+
clientX: touch.clientX,
|
|
41
|
+
clientY: touch.clientY
|
|
42
|
+
}))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
touchStart = null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
canvas.addEventListener('touchstart', handleTouchStart, { passive: true })
|
|
49
|
+
canvas.addEventListener('touchend', handleTouchEnd, { passive: true })
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
remove () {
|
|
53
|
+
canvas.removeEventListener('touchstart', handleTouchStart)
|
|
54
|
+
canvas.removeEventListener('touchend', handleTouchEnd)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { setupTouchClickWorkaround } from './touchClickWorkaround.js'
|
|
2
|
+
|
|
3
|
+
const createCanvas = () => {
|
|
4
|
+
const listeners = {}
|
|
5
|
+
return {
|
|
6
|
+
addEventListener: jest.fn((type, handler) => { listeners[type] = handler }),
|
|
7
|
+
removeEventListener: jest.fn(),
|
|
8
|
+
dispatchEvent: jest.fn(),
|
|
9
|
+
fire (type, event) { listeners[type]?.(event) }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const touchStartEvent = (touches) => ({ touches })
|
|
14
|
+
const touchEndEvent = (changedTouches) => ({ changedTouches })
|
|
15
|
+
|
|
16
|
+
describe('setupTouchClickWorkaround', () => {
|
|
17
|
+
let canvas
|
|
18
|
+
let map
|
|
19
|
+
let draw
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
canvas = createCanvas()
|
|
23
|
+
map = { getCanvas: () => canvas }
|
|
24
|
+
draw = { getMode: jest.fn(() => 'disabled') }
|
|
25
|
+
jest.spyOn(Date, 'now')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
Date.now.mockRestore()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('registers passive touchstart/touchend listeners on the canvas', () => {
|
|
33
|
+
setupTouchClickWorkaround(map, draw)
|
|
34
|
+
expect(canvas.addEventListener).toHaveBeenCalledWith('touchstart', expect.any(Function), { passive: true })
|
|
35
|
+
expect(canvas.addEventListener).toHaveBeenCalledWith('touchend', expect.any(Function), { passive: true })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('synthesizes a click for a quick, stationary tap in disabled mode', () => {
|
|
39
|
+
Date.now.mockReturnValueOnce(1000).mockReturnValueOnce(1100)
|
|
40
|
+
setupTouchClickWorkaround(map, draw)
|
|
41
|
+
|
|
42
|
+
canvas.fire('touchstart', touchStartEvent([{ clientX: 50, clientY: 60 }]))
|
|
43
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 52, clientY: 61 }]))
|
|
44
|
+
|
|
45
|
+
expect(canvas.dispatchEvent).toHaveBeenCalledTimes(1)
|
|
46
|
+
const dispatched = canvas.dispatchEvent.mock.calls[0][0]
|
|
47
|
+
expect(dispatched).toBeInstanceOf(MouseEvent)
|
|
48
|
+
expect(dispatched.type).toBe('click')
|
|
49
|
+
expect(dispatched.clientX).toBe(52)
|
|
50
|
+
expect(dispatched.clientY).toBe(61)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('does not synthesize a click when the tap is too slow', () => {
|
|
54
|
+
Date.now.mockReturnValueOnce(1000).mockReturnValueOnce(1400)
|
|
55
|
+
setupTouchClickWorkaround(map, draw)
|
|
56
|
+
|
|
57
|
+
canvas.fire('touchstart', touchStartEvent([{ clientX: 50, clientY: 60 }]))
|
|
58
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 50, clientY: 60 }]))
|
|
59
|
+
|
|
60
|
+
expect(canvas.dispatchEvent).not.toHaveBeenCalled()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('does not synthesize a click when the finger moves too far', () => {
|
|
64
|
+
Date.now.mockReturnValueOnce(1000).mockReturnValueOnce(1100)
|
|
65
|
+
setupTouchClickWorkaround(map, draw)
|
|
66
|
+
|
|
67
|
+
canvas.fire('touchstart', touchStartEvent([{ clientX: 50, clientY: 60 }]))
|
|
68
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 100, clientY: 60 }]))
|
|
69
|
+
|
|
70
|
+
expect(canvas.dispatchEvent).not.toHaveBeenCalled()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('ignores multi-touch gestures on touchstart', () => {
|
|
74
|
+
setupTouchClickWorkaround(map, draw)
|
|
75
|
+
|
|
76
|
+
canvas.fire('touchstart', touchStartEvent([{ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 2 }]))
|
|
77
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 1, clientY: 1 }]))
|
|
78
|
+
|
|
79
|
+
expect(canvas.dispatchEvent).not.toHaveBeenCalled()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('does nothing when the current mode is not disabled', () => {
|
|
83
|
+
draw.getMode.mockReturnValue('draw_polygon')
|
|
84
|
+
setupTouchClickWorkaround(map, draw)
|
|
85
|
+
|
|
86
|
+
canvas.fire('touchstart', touchStartEvent([{ clientX: 50, clientY: 60 }]))
|
|
87
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 50, clientY: 60 }]))
|
|
88
|
+
|
|
89
|
+
expect(canvas.dispatchEvent).not.toHaveBeenCalled()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('does nothing on touchend without a preceding single-finger touchstart', () => {
|
|
93
|
+
setupTouchClickWorkaround(map, draw)
|
|
94
|
+
|
|
95
|
+
canvas.fire('touchend', touchEndEvent([{ clientX: 50, clientY: 60 }]))
|
|
96
|
+
|
|
97
|
+
expect(canvas.dispatchEvent).not.toHaveBeenCalled()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('remove() detaches both listeners', () => {
|
|
101
|
+
const { remove } = setupTouchClickWorkaround(map, draw)
|
|
102
|
+
remove()
|
|
103
|
+
expect(canvas.removeEventListener).toHaveBeenCalledWith('touchstart', expect.any(Function))
|
|
104
|
+
expect(canvas.removeEventListener).toHaveBeenCalledWith('touchend', expect.any(Function))
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createOLDraw } from './olDraw.js'
|
|
2
|
+
|
|
3
|
+
// split.js passes this literal — MapLibre's own always-present "already-drawn
|
|
4
|
+
// shapes" style layer id — as a snapLayers entry so the split line snaps to the
|
|
5
|
+
// polygon it's splitting. On MapLibre it needs no help: mapbox-gl-snap resolves
|
|
6
|
+
// it by querying the map's rendered style layers directly. OL's snap engine has
|
|
7
|
+
// no such string-based lookup for plain vector layers (only direct instances or
|
|
8
|
+
// vector-tile layer names), so this adapter recognises the same literal here and
|
|
9
|
+
// resolves it to the draw plugin's own VectorLayer instance instead.
|
|
10
|
+
const DRAW_OUTLINE_STYLE_LAYER = 'stroke-inactive.cold'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Draw adapter for OpenLayers.
|
|
14
|
+
*
|
|
15
|
+
* Wraps OLDrawManager (via createOLDraw) and exposes the shared adapter interface
|
|
16
|
+
* consumed by events.js, DrawInit, and the api entry points.
|
|
17
|
+
*
|
|
18
|
+
* OLDrawManager already exposes on/off/emit, changeMode, done/cancel/undo/deleteVertex,
|
|
19
|
+
* get/add/delete/deleteAll, and setInterfaceType. This adapter adds the snap methods
|
|
20
|
+
* and undo-stack clearing (OL manages its own undo stack on the manager).
|
|
21
|
+
*
|
|
22
|
+
* Adapter interface (also implemented by MaplibreDrawAdapter):
|
|
23
|
+
* changeMode(name, options)
|
|
24
|
+
* getMode()
|
|
25
|
+
* setInterfaceType(type)
|
|
26
|
+
* done() / cancel() / undo() / deleteVertex()
|
|
27
|
+
* nudgeSelectedVertex(dx, dy, isLargeStep)
|
|
28
|
+
* get(id) / add(feature) / delete(id) / deleteAll()
|
|
29
|
+
* setSnapEnabled(bool) / setSnapLayers(layers) / isSnapEnabled()
|
|
30
|
+
* setFeatureProperty(id, property, value) / setDrawingPreviewProperty(property, value)
|
|
31
|
+
* on(event, handler) / off(event, handler)
|
|
32
|
+
* remove()
|
|
33
|
+
*/
|
|
34
|
+
export class OLDrawAdapter {
|
|
35
|
+
_snapEnabled = false
|
|
36
|
+
|
|
37
|
+
constructor (mapProvider, options) {
|
|
38
|
+
const { manager, remove } = createOLDraw({
|
|
39
|
+
mapProvider,
|
|
40
|
+
events: options.events,
|
|
41
|
+
eventBus: options.eventBus,
|
|
42
|
+
// The full pluginConfig, not just snapLayers — OLDrawManager also reads
|
|
43
|
+
// colour/size overrides (shapeStroke, editStroke, strokeWidth, snapRadius,
|
|
44
|
+
// etc.) off this object via resolveColors().
|
|
45
|
+
pluginConfig: options.pluginConfig ?? { snapLayers: options.snapLayers },
|
|
46
|
+
mapStyle: options.mapStyle
|
|
47
|
+
})
|
|
48
|
+
this._cleanupOLDraw = remove
|
|
49
|
+
this._manager = manager
|
|
50
|
+
this._mapProvider = mapProvider
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
changeMode (name, options = {}) {
|
|
54
|
+
// Inject OL-specific options that the unified api doesn't supply
|
|
55
|
+
const opts = { ...options, mapProvider: this._mapProvider }
|
|
56
|
+
if (name === 'draw_polygon') { opts.geometryType = 'Polygon' }
|
|
57
|
+
if (name === 'draw_line') { opts.geometryType = 'LineString' }
|
|
58
|
+
return this._manager.changeMode(name, opts)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getMode () { return this._manager.getMode() }
|
|
62
|
+
|
|
63
|
+
setInterfaceType (type) { this._manager.setInterfaceType(type) }
|
|
64
|
+
|
|
65
|
+
done () {
|
|
66
|
+
this._manager.undoStack.clear()
|
|
67
|
+
this._manager.done()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cancel () {
|
|
71
|
+
this._manager.undoStack.clear()
|
|
72
|
+
this._manager.cancel()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
undo () { this._manager.undo() }
|
|
76
|
+
deleteVertex () { this._manager.deleteVertex() }
|
|
77
|
+
nudgeSelectedVertex (dx, dy, isLargeStep) { this._manager.nudgeSelectedVertex(dx, dy, isLargeStep) }
|
|
78
|
+
|
|
79
|
+
// Record the current geometry validity so the draw mode can block finish gestures
|
|
80
|
+
// (double-click / click-to-close) while the in-progress shape is invalid.
|
|
81
|
+
setGeometryValid (valid) { this._manager._geometryValid = valid }
|
|
82
|
+
|
|
83
|
+
// The api entry points assign the active user validator to the adapter; store it
|
|
84
|
+
// on the manager so the draw mode can veto placements synchronously.
|
|
85
|
+
set _geometryValidator (fn) { this._manager._geometryValidator = fn }
|
|
86
|
+
get _geometryValidator () { return this._manager._geometryValidator }
|
|
87
|
+
|
|
88
|
+
// Show/hide the dashed invalid stroke on the active sketch or edit feature.
|
|
89
|
+
setInvalid (invalid) { this._manager.setInvalid(invalid) }
|
|
90
|
+
|
|
91
|
+
get (id) { return this._manager.get(id) }
|
|
92
|
+
add (feature) { return this._manager.add(feature) }
|
|
93
|
+
delete (id) { return this._manager.delete(id) }
|
|
94
|
+
deleteAll () { return this._manager.deleteAll() }
|
|
95
|
+
|
|
96
|
+
setSnapEnabled (bool) {
|
|
97
|
+
this._snapEnabled = bool
|
|
98
|
+
this._manager.snap?.setActive(bool)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setSnapLayers (layers) {
|
|
102
|
+
const translated = layers?.map((l) => (l === DRAW_OUTLINE_STYLE_LAYER ? this._manager._layer : l))
|
|
103
|
+
this._manager.snap?.setSnapLayers(translated)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
isSnapEnabled () { return this._snapEnabled }
|
|
107
|
+
|
|
108
|
+
setFeatureProperty () { /* not implemented for OL */ }
|
|
109
|
+
setDrawingPreviewProperty (property, value) { this._manager.setDrawingPreviewProperty(property, value) }
|
|
110
|
+
|
|
111
|
+
on (type, handler) { this._manager.on(type, handler) }
|
|
112
|
+
off (type, handler) { this._manager.off(type, handler) }
|
|
113
|
+
|
|
114
|
+
remove () {
|
|
115
|
+
this._cleanupOLDraw()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { OLDrawAdapter } from './OLDrawAdapter.js'
|
|
2
|
+
import { createOLDraw } from './olDraw.js'
|
|
3
|
+
|
|
4
|
+
// The literal split.js passes — see OLDrawAdapter.js's DRAW_OUTLINE_STYLE_LAYER comment.
|
|
5
|
+
const DRAW_OUTLINE_STYLE_LAYER = 'stroke-inactive.cold'
|
|
6
|
+
|
|
7
|
+
const fakeVectorLayer = { id: 'draw-layer' }
|
|
8
|
+
|
|
9
|
+
const fakeManager = () => ({
|
|
10
|
+
_layer: fakeVectorLayer,
|
|
11
|
+
changeMode: jest.fn(),
|
|
12
|
+
getMode: jest.fn(() => 'disabled'),
|
|
13
|
+
setInterfaceType: jest.fn(),
|
|
14
|
+
done: jest.fn(),
|
|
15
|
+
cancel: jest.fn(),
|
|
16
|
+
undo: jest.fn(),
|
|
17
|
+
deleteVertex: jest.fn(),
|
|
18
|
+
nudgeSelectedVertex: jest.fn(),
|
|
19
|
+
setInvalid: jest.fn(),
|
|
20
|
+
setDrawingPreviewProperty: jest.fn(),
|
|
21
|
+
get: jest.fn(() => 'feature'),
|
|
22
|
+
add: jest.fn(),
|
|
23
|
+
delete: jest.fn(),
|
|
24
|
+
deleteAll: jest.fn(),
|
|
25
|
+
on: jest.fn(),
|
|
26
|
+
off: jest.fn(),
|
|
27
|
+
undoStack: { clear: jest.fn() },
|
|
28
|
+
snap: { setActive: jest.fn(), setSnapLayers: jest.fn() }
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
jest.mock('./olDraw.js', () => ({
|
|
32
|
+
createOLDraw: jest.fn(({ mapProvider }) => ({ manager: mapProvider._testManager, remove: jest.fn() }))
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const setup = () => {
|
|
36
|
+
const manager = fakeManager()
|
|
37
|
+
const mapProvider = { _testManager: manager }
|
|
38
|
+
const adapter = new OLDrawAdapter(mapProvider, {
|
|
39
|
+
events: { MAP_SET_STYLE: 's' },
|
|
40
|
+
eventBus: {},
|
|
41
|
+
snapLayers: ['boundaries'],
|
|
42
|
+
mapStyle: { id: 'default' }
|
|
43
|
+
})
|
|
44
|
+
return { manager, mapProvider, adapter }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
afterEach(() => jest.clearAllMocks())
|
|
48
|
+
|
|
49
|
+
test('wires olDraw with the plugin options and uses the returned manager', () => {
|
|
50
|
+
const { manager, adapter } = setup()
|
|
51
|
+
expect(createOLDraw).toHaveBeenCalledWith(expect.objectContaining({
|
|
52
|
+
pluginConfig: { snapLayers: ['boundaries'] },
|
|
53
|
+
mapStyle: { id: 'default' }
|
|
54
|
+
}))
|
|
55
|
+
expect(adapter.getMode()).toBe('disabled')
|
|
56
|
+
expect(manager.getMode).toHaveBeenCalled()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('forwards the full pluginConfig (colour/size overrides too), not just snapLayers', () => {
|
|
60
|
+
const manager = fakeManager()
|
|
61
|
+
const mapProvider = { _testManager: manager }
|
|
62
|
+
const pluginConfig = { snapLayers: ['boundaries'], shapeStroke: '#custom', strokeWidth: 5 }
|
|
63
|
+
// eslint-disable-next-line no-new
|
|
64
|
+
new OLDrawAdapter(mapProvider, {
|
|
65
|
+
events: { MAP_SET_STYLE: 's' },
|
|
66
|
+
eventBus: {},
|
|
67
|
+
pluginConfig,
|
|
68
|
+
mapStyle: { id: 'default' }
|
|
69
|
+
})
|
|
70
|
+
expect(createOLDraw).toHaveBeenCalledWith(expect.objectContaining({ pluginConfig }))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('changeMode injects the mapProvider and the OL geometry type per draw mode', () => {
|
|
74
|
+
const { manager, mapProvider, adapter } = setup()
|
|
75
|
+
adapter.changeMode('draw_polygon', { featureId: 'f1' })
|
|
76
|
+
expect(manager.changeMode).toHaveBeenCalledWith('draw_polygon', { featureId: 'f1', mapProvider, geometryType: 'Polygon' })
|
|
77
|
+
adapter.changeMode('draw_line')
|
|
78
|
+
expect(manager.changeMode).toHaveBeenCalledWith('draw_line', { mapProvider, geometryType: 'LineString' })
|
|
79
|
+
adapter.changeMode('edit_vertex', { featureId: 'f1' })
|
|
80
|
+
expect(manager.changeMode).toHaveBeenCalledWith('edit_vertex', { featureId: 'f1', mapProvider })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('done and cancel clear the undo stack before delegating', () => {
|
|
84
|
+
const { manager, adapter } = setup()
|
|
85
|
+
adapter.done()
|
|
86
|
+
adapter.cancel()
|
|
87
|
+
expect(manager.undoStack.clear).toHaveBeenCalledTimes(2)
|
|
88
|
+
expect(manager.done).toHaveBeenCalled()
|
|
89
|
+
expect(manager.cancel).toHaveBeenCalled()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('snap state is tracked locally and forwarded, tolerating a missing snap manager', () => {
|
|
93
|
+
const { manager, adapter } = setup()
|
|
94
|
+
expect(adapter.isSnapEnabled()).toBe(false)
|
|
95
|
+
adapter.setSnapEnabled(true)
|
|
96
|
+
expect(adapter.isSnapEnabled()).toBe(true)
|
|
97
|
+
expect(manager.snap.setActive).toHaveBeenCalledWith(true)
|
|
98
|
+
adapter.setSnapLayers(['x'])
|
|
99
|
+
expect(manager.snap.setSnapLayers).toHaveBeenCalledWith(['x'])
|
|
100
|
+
|
|
101
|
+
// The shared sentinel translates onto the draw plugin's own VectorLayer instance.
|
|
102
|
+
adapter.setSnapLayers([DRAW_OUTLINE_STYLE_LAYER, 'x'])
|
|
103
|
+
expect(manager.snap.setSnapLayers).toHaveBeenCalledWith([fakeVectorLayer, 'x'])
|
|
104
|
+
|
|
105
|
+
manager.snap = null
|
|
106
|
+
adapter.setSnapEnabled(false)
|
|
107
|
+
adapter.setSnapLayers(['y']) // no throw
|
|
108
|
+
expect(adapter.isSnapEnabled()).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('remaining calls delegate straight through; setFeatureProperty is a deliberate no-op', () => {
|
|
112
|
+
const { manager, adapter } = setup()
|
|
113
|
+
adapter.setInterfaceType('touch')
|
|
114
|
+
expect(adapter.get('f1')).toBe('feature')
|
|
115
|
+
adapter.add({ id: 'f2' })
|
|
116
|
+
adapter.delete('f1')
|
|
117
|
+
adapter.deleteAll()
|
|
118
|
+
adapter.undo()
|
|
119
|
+
adapter.deleteVertex()
|
|
120
|
+
adapter.nudgeSelectedVertex(1, 0, true)
|
|
121
|
+
const handler = () => {}
|
|
122
|
+
adapter.on('create', handler)
|
|
123
|
+
adapter.off('create', handler)
|
|
124
|
+
expect(adapter.setFeatureProperty('f1', 'stroke', '#000')).toBeUndefined()
|
|
125
|
+
adapter.setDrawingPreviewProperty('splitter', 'valid')
|
|
126
|
+
expect(manager.setDrawingPreviewProperty).toHaveBeenCalledWith('splitter', 'valid')
|
|
127
|
+
expect(manager.setInterfaceType).toHaveBeenCalledWith('touch')
|
|
128
|
+
expect(manager.on).toHaveBeenCalledWith('create', handler)
|
|
129
|
+
expect(manager.off).toHaveBeenCalledWith('create', handler)
|
|
130
|
+
expect(manager.undo).toHaveBeenCalled()
|
|
131
|
+
expect(manager.deleteVertex).toHaveBeenCalled()
|
|
132
|
+
expect(manager.nudgeSelectedVertex).toHaveBeenCalledWith(1, 0, true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('setGeometryValid records validity on the manager for finish gating', () => {
|
|
136
|
+
const { adapter, manager } = setup()
|
|
137
|
+
adapter.setGeometryValid(false)
|
|
138
|
+
expect(manager._geometryValid).toBe(false)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('_geometryValidator accessor stores and retrieves the user callback on the manager', () => {
|
|
142
|
+
const { adapter, manager } = setup()
|
|
143
|
+
const validator = jest.fn(() => ({ valid: true }))
|
|
144
|
+
adapter._geometryValidator = validator
|
|
145
|
+
expect(manager._geometryValidator).toBe(validator)
|
|
146
|
+
expect(adapter._geometryValidator).toBe(validator)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('setInvalid delegates to the manager to toggle the dashed stroke', () => {
|
|
150
|
+
const { adapter, manager } = setup()
|
|
151
|
+
adapter.setInvalid(true)
|
|
152
|
+
expect(manager.setInvalid).toHaveBeenCalledWith(true)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('remove runs the olDraw cleanup', () => {
|
|
156
|
+
const { adapter } = setup()
|
|
157
|
+
adapter.remove()
|
|
158
|
+
expect(createOLDraw.mock.results.at(-1).value.remove).toHaveBeenCalled()
|
|
159
|
+
})
|