@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 @@
|
|
|
1
|
+
export { COLORS, SIZES, TOLERANCES, KEYBOARD, MAP_SIZE_SCALES } from '../../defaults.js'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event names used by the MapLibre draw adapter, grouped by origin.
|
|
3
|
+
*
|
|
4
|
+
* - MAPBOX_DRAW_EVENTS: stock events fired by @mapbox/mapbox-gl-draw itself.
|
|
5
|
+
* - CUSTOM_DRAW_EVENTS: events this codebase's custom modes / adapter dispatch on the map
|
|
6
|
+
* (also under the `draw.*` namespace).
|
|
7
|
+
* - STYLE_DATA_EVENT: the one native MapLibre map event the adapter listens to (to keep draw
|
|
8
|
+
* layers on top after a style reload).
|
|
9
|
+
*
|
|
10
|
+
* The adapter normalises all of these onto the shared, framework-agnostic event bus.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Stock @mapbox/mapbox-gl-draw events, fired by the library itself.
|
|
14
|
+
export const MAPBOX_DRAW_EVENTS = {
|
|
15
|
+
CREATE: 'draw.create',
|
|
16
|
+
UPDATE: 'draw.update',
|
|
17
|
+
MODE_CHANGE: 'draw.modechange'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Custom draw events dispatched by this codebase's modes / adapter.
|
|
21
|
+
export const CUSTOM_DRAW_EVENTS = {
|
|
22
|
+
EDIT_FINISH: 'draw.editfinish',
|
|
23
|
+
CANCEL: 'draw.cancel',
|
|
24
|
+
VERTEX_SELECTION: 'draw.vertexselection',
|
|
25
|
+
VERTEX_CHANGE: 'draw.vertexchange',
|
|
26
|
+
UNDO_CHANGE: 'draw.undochange',
|
|
27
|
+
UNDO: 'draw.undo',
|
|
28
|
+
GEOMETRY_CHANGE: 'draw.geometrychange',
|
|
29
|
+
INTERFACE_TYPE_CHANGE: 'draw.interfacetypechange',
|
|
30
|
+
PLACEMENT_BLOCKED: 'draw.placementblocked',
|
|
31
|
+
NUDGE_VERTEX: 'draw.nudgevertex'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Native MapLibre map event (not a draw event) — fires whenever the map style data changes.
|
|
35
|
+
export const STYLE_DATA_EVENT = 'styledata'
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import MapboxDraw from '@mapbox/mapbox-gl-draw'
|
|
2
|
+
import { DisabledMode } from './modes/disabledMode.js'
|
|
3
|
+
import { EditVertexMode } from './modes/editVertexMode.js'
|
|
4
|
+
import { DrawPolygonMode } from './modes/drawPolygonMode.js'
|
|
5
|
+
import { DrawLineMode } from './modes/drawLineMode.js'
|
|
6
|
+
import { createDrawStyles, updateDrawStyles } from './styles.js'
|
|
7
|
+
import { initMapLibreSnap } from './mapboxSnap.js'
|
|
8
|
+
import { createUndoStack } from '../../utils/undoStack.js'
|
|
9
|
+
import { setupTouchClickWorkaround } from './utils/touchClickWorkaround.js'
|
|
10
|
+
import { applyTouchVertexColors } from './modes/editVertexMode/touchHandlers.js'
|
|
11
|
+
import { resolveColors } from '../../utils/resolveColors.js'
|
|
12
|
+
import { TOLERANCES, MAP_SIZE_SCALES } from './defaults.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates and manages a MapLibre/Mapbox Draw control instance configured for polygon editing.
|
|
16
|
+
* Returns an object with a `.remove()` cleanup function that removes all listeners
|
|
17
|
+
* and safely disposes of the Draw control.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Custom modes for editing and drawing vertices
|
|
21
|
+
* - Dynamic runtime style updates on `events.MAP_SET_STYLE` event
|
|
22
|
+
* - Safe reapplication of styles if map.setStyle is called
|
|
23
|
+
*
|
|
24
|
+
* @param {string} options.mapStyle - Map style object
|
|
25
|
+
* @param {Object} options.mapProvider - Object containing the map instance
|
|
26
|
+
* @param {Object} options.eventBus - Event bus for app-level events
|
|
27
|
+
* @param {Object} [options.pluginConfig] - Plugin-level colour/size overrides — see resolveColors()
|
|
28
|
+
* @returns {{ draw: MapboxDraw, remove: Function }} draw instance and cleanup function
|
|
29
|
+
*/
|
|
30
|
+
export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snapLayers, pluginConfig = {} }) => {
|
|
31
|
+
const { map } = mapProvider
|
|
32
|
+
|
|
33
|
+
// --- Configure MapLibre GL Draw CSS classes ---
|
|
34
|
+
MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl'
|
|
35
|
+
MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-'
|
|
36
|
+
MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group'
|
|
37
|
+
|
|
38
|
+
// --- Register custom modes ---
|
|
39
|
+
const modes = {
|
|
40
|
+
...MapboxDraw.modes,
|
|
41
|
+
disabled: DisabledMode,
|
|
42
|
+
edit_vertex: EditVertexMode,
|
|
43
|
+
draw_polygon: DrawPolygonMode,
|
|
44
|
+
draw_line: DrawLineMode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Create or reuse MapLibre Draw instance ---
|
|
48
|
+
let draw = mapProvider._mapboxDrawInstance
|
|
49
|
+
if (draw) {
|
|
50
|
+
// Update modes on existing draw instance when adapter is recreated
|
|
51
|
+
Object.assign(draw.modes, modes)
|
|
52
|
+
} else {
|
|
53
|
+
draw = new MapboxDraw({
|
|
54
|
+
modes,
|
|
55
|
+
styles: createDrawStyles(mapStyle, pluginConfig),
|
|
56
|
+
displayControlsDefault: false,
|
|
57
|
+
userProperties: true,
|
|
58
|
+
defaultMode: 'disabled'
|
|
59
|
+
})
|
|
60
|
+
map.addControl(draw)
|
|
61
|
+
mapProvider._mapboxDrawInstance = draw
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// mapbox-gl-draw swallows tap clicks in disabled mode — synthesize them
|
|
65
|
+
const touchClickWorkaround = setupTouchClickWorkaround(map, draw)
|
|
66
|
+
|
|
67
|
+
// We need a reference to this
|
|
68
|
+
mapProvider.draw = draw
|
|
69
|
+
map._drawCurrentMapStyle = mapStyle
|
|
70
|
+
// Stashed on the map (alongside _drawCurrentMapStyle) so mode code that only
|
|
71
|
+
// has `this.map` — e.g. touchHandlers.js's addTouchVertexTarget — can still
|
|
72
|
+
// resolve colour overrides without pluginConfig being threaded through every
|
|
73
|
+
// mode's option object.
|
|
74
|
+
map._drawPluginConfig = pluginConfig
|
|
75
|
+
// Initialize snap as disabled (matches initialState.snap = false)
|
|
76
|
+
mapProvider.snapEnabled = false
|
|
77
|
+
// Initialize undo stack (reuse if already exists)
|
|
78
|
+
let undoStack = mapProvider.undoStack
|
|
79
|
+
if (!undoStack) {
|
|
80
|
+
undoStack = createUndoStack((length) => map.fire('draw.undochange', { length }))
|
|
81
|
+
mapProvider.undoStack = undoStack
|
|
82
|
+
}
|
|
83
|
+
map._undoStack = undoStack
|
|
84
|
+
|
|
85
|
+
// --- Initialize MapboxSnap using external module ---
|
|
86
|
+
// Start with status: false to match initial snap disabled state
|
|
87
|
+
const snapColors = resolveColors(mapStyle, pluginConfig)
|
|
88
|
+
initMapLibreSnap(map, draw, {
|
|
89
|
+
layers: snapLayers,
|
|
90
|
+
radius: pluginConfig.snapRadius ?? TOLERANCES.snapRadius,
|
|
91
|
+
rules: ['vertex', 'edge'],
|
|
92
|
+
colors: { vertex: snapColors.snapVertex, edge: snapColors.snapEdge }
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// --- Update colour scheme ---
|
|
96
|
+
const handleSetMapStyle = (e) => {
|
|
97
|
+
map._drawCurrentMapStyle = e
|
|
98
|
+
map.once('idle', () => {
|
|
99
|
+
updateDrawStyles(map, e, pluginConfig)
|
|
100
|
+
const svg = map._drawEditContainer?.querySelector('[data-im-draw-touch-target]')
|
|
101
|
+
applyTouchVertexColors(svg, e, pluginConfig)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
eventBus.on(events.MAP_SET_STYLE, handleSetMapStyle)
|
|
105
|
+
|
|
106
|
+
// --- Update map scale ---
|
|
107
|
+
const handleSetMapSize = (e) => {
|
|
108
|
+
map.fire('draw.scalechange', { scale: MAP_SIZE_SCALES[e] })
|
|
109
|
+
}
|
|
110
|
+
eventBus.on(events.MAP_SET_SIZE, handleSetMapSize)
|
|
111
|
+
|
|
112
|
+
// --- Return instance and cleanup function ---
|
|
113
|
+
return {
|
|
114
|
+
draw,
|
|
115
|
+
remove () {
|
|
116
|
+
touchClickWorkaround.remove()
|
|
117
|
+
// Remove event listeners
|
|
118
|
+
eventBus.off(events.MAP_SET_STYLE, handleSetMapStyle)
|
|
119
|
+
eventBus.off(events.MAP_SET_SIZE, handleSetMapSize)
|
|
120
|
+
// Disable draw mode but keep control on map for reuse
|
|
121
|
+
draw.changeMode('disabled')
|
|
122
|
+
// Clear adapter reference (but not _mapboxDrawInstance so it persists)
|
|
123
|
+
mapProvider.draw = null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import MapboxDraw from '@mapbox/mapbox-gl-draw'
|
|
2
|
+
import { createDrawStyles, updateDrawStyles } from './styles.js'
|
|
3
|
+
import { initMapLibreSnap } from './mapboxSnap.js'
|
|
4
|
+
import { createUndoStack } from '../../utils/undoStack.js'
|
|
5
|
+
import { setupTouchClickWorkaround } from './utils/touchClickWorkaround.js'
|
|
6
|
+
import { applyTouchVertexColors } from './modes/editVertexMode/touchHandlers.js'
|
|
7
|
+
import { resolveColors } from '../../utils/resolveColors.js'
|
|
8
|
+
import { TOLERANCES, MAP_SIZE_SCALES } from './defaults.js'
|
|
9
|
+
import { createMapboxDraw } from './mapboxDraw.js'
|
|
10
|
+
|
|
11
|
+
jest.mock('@mapbox/mapbox-gl-draw', () => {
|
|
12
|
+
const MockDraw = jest.fn(function () {
|
|
13
|
+
this.modes = {}
|
|
14
|
+
this.changeMode = jest.fn()
|
|
15
|
+
})
|
|
16
|
+
MockDraw.constants = { classes: {} }
|
|
17
|
+
MockDraw.modes = { existing_mode: { id: 'existing' } }
|
|
18
|
+
return { __esModule: true, default: MockDraw }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
jest.mock('./modes/disabledMode.js', () => ({ DisabledMode: { id: 'disabled' } }))
|
|
22
|
+
jest.mock('./modes/editVertexMode.js', () => ({ EditVertexMode: { id: 'edit_vertex' } }))
|
|
23
|
+
jest.mock('./modes/drawPolygonMode.js', () => ({ DrawPolygonMode: { id: 'draw_polygon' } }))
|
|
24
|
+
jest.mock('./modes/drawLineMode.js', () => ({ DrawLineMode: { id: 'draw_line' } }))
|
|
25
|
+
jest.mock('./styles.js', () => ({
|
|
26
|
+
createDrawStyles: jest.fn(() => ['style']),
|
|
27
|
+
updateDrawStyles: jest.fn()
|
|
28
|
+
}))
|
|
29
|
+
jest.mock('./mapboxSnap.js', () => ({ initMapLibreSnap: jest.fn() }))
|
|
30
|
+
jest.mock('../../utils/undoStack.js', () => ({ createUndoStack: jest.fn(() => ({ id: 'undo-stack' })) }))
|
|
31
|
+
jest.mock('./utils/touchClickWorkaround.js', () => ({ setupTouchClickWorkaround: jest.fn() }))
|
|
32
|
+
jest.mock('./modes/editVertexMode/touchHandlers.js', () => ({ applyTouchVertexColors: jest.fn() }))
|
|
33
|
+
jest.mock('../../utils/resolveColors.js', () => ({
|
|
34
|
+
resolveColors: jest.fn(() => ({ snapVertex: 'resolved-vertex', snapEdge: 'resolved-edge' }))
|
|
35
|
+
}))
|
|
36
|
+
jest.mock('./defaults.js', () => ({
|
|
37
|
+
TOLERANCES: { snapRadius: 12 },
|
|
38
|
+
MAP_SIZE_SCALES: { medium: 1.5 }
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const EVENTS = { MAP_SET_STYLE: 'map:setstyle', MAP_SET_SIZE: 'map:setsize' }
|
|
42
|
+
|
|
43
|
+
const handlerFor = (mockFn, eventName) =>
|
|
44
|
+
mockFn.mock.calls.find(([name]) => name === eventName)?.[1]
|
|
45
|
+
|
|
46
|
+
const createMap = () => ({
|
|
47
|
+
addControl: jest.fn(),
|
|
48
|
+
once: jest.fn(),
|
|
49
|
+
on: jest.fn(),
|
|
50
|
+
off: jest.fn(),
|
|
51
|
+
fire: jest.fn()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const setup = ({ existingDraw, existingUndoStack, pluginConfig } = {}) => {
|
|
55
|
+
const map = createMap()
|
|
56
|
+
const mapProvider = {
|
|
57
|
+
map,
|
|
58
|
+
_mapboxDrawInstance: existingDraw,
|
|
59
|
+
undoStack: existingUndoStack
|
|
60
|
+
}
|
|
61
|
+
const eventBus = { on: jest.fn(), off: jest.fn(), emit: jest.fn() }
|
|
62
|
+
const removeWorkaround = jest.fn()
|
|
63
|
+
setupTouchClickWorkaround.mockReturnValue({ remove: removeWorkaround })
|
|
64
|
+
|
|
65
|
+
const result = createMapboxDraw({
|
|
66
|
+
mapStyle: 'light',
|
|
67
|
+
mapProvider,
|
|
68
|
+
events: EVENTS,
|
|
69
|
+
eventBus,
|
|
70
|
+
snapLayers: ['layer-a'],
|
|
71
|
+
...(pluginConfig !== undefined ? { pluginConfig } : {})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return { map, mapProvider, eventBus, removeWorkaround, result }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
jest.clearAllMocks()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('createMapboxDraw – instance creation', () => {
|
|
82
|
+
test('configures the MapLibre control CSS class constants', () => {
|
|
83
|
+
setup()
|
|
84
|
+
expect(MapboxDraw.constants.classes).toMatchObject({
|
|
85
|
+
CONTROL_BASE: 'maplibregl-ctrl',
|
|
86
|
+
CONTROL_PREFIX: 'maplibregl-ctrl-',
|
|
87
|
+
CONTROL_GROUP: 'maplibregl-ctrl-group'
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('creates a new draw instance with the custom modes and adds it to the map', () => {
|
|
92
|
+
const { map, mapProvider, result } = setup()
|
|
93
|
+
|
|
94
|
+
expect(MapboxDraw).toHaveBeenCalledTimes(1)
|
|
95
|
+
const options = MapboxDraw.mock.calls[0][0]
|
|
96
|
+
expect(options).toMatchObject({
|
|
97
|
+
styles: ['style'],
|
|
98
|
+
displayControlsDefault: false,
|
|
99
|
+
userProperties: true,
|
|
100
|
+
defaultMode: 'disabled'
|
|
101
|
+
})
|
|
102
|
+
expect(options.modes).toMatchObject({
|
|
103
|
+
existing_mode: { id: 'existing' },
|
|
104
|
+
disabled: { id: 'disabled' },
|
|
105
|
+
edit_vertex: { id: 'edit_vertex' },
|
|
106
|
+
draw_polygon: { id: 'draw_polygon' },
|
|
107
|
+
draw_line: { id: 'draw_line' }
|
|
108
|
+
})
|
|
109
|
+
expect(createDrawStyles).toHaveBeenCalledWith('light', {})
|
|
110
|
+
|
|
111
|
+
const draw = MapboxDraw.mock.instances[0]
|
|
112
|
+
expect(map.addControl).toHaveBeenCalledWith(draw)
|
|
113
|
+
expect(mapProvider._mapboxDrawInstance).toBe(draw)
|
|
114
|
+
expect(mapProvider.draw).toBe(draw)
|
|
115
|
+
expect(result.draw).toBe(draw)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('reuses an existing draw instance and merges modes instead of creating a new one', () => {
|
|
119
|
+
const existingDraw = { modes: { old: {} }, changeMode: jest.fn() }
|
|
120
|
+
const { map, mapProvider } = setup({ existingDraw })
|
|
121
|
+
|
|
122
|
+
expect(MapboxDraw).not.toHaveBeenCalled()
|
|
123
|
+
expect(map.addControl).not.toHaveBeenCalled()
|
|
124
|
+
expect(existingDraw.modes).toMatchObject({
|
|
125
|
+
old: {},
|
|
126
|
+
disabled: { id: 'disabled' },
|
|
127
|
+
edit_vertex: { id: 'edit_vertex' }
|
|
128
|
+
})
|
|
129
|
+
expect(mapProvider.draw).toBe(existingDraw)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('createMapboxDraw – setup side effects', () => {
|
|
134
|
+
test('sets up the touch-click workaround and records provider state', () => {
|
|
135
|
+
const { map, mapProvider } = setup()
|
|
136
|
+
|
|
137
|
+
expect(setupTouchClickWorkaround).toHaveBeenCalledWith(map, mapProvider.draw)
|
|
138
|
+
expect(map._drawCurrentMapStyle).toBe('light')
|
|
139
|
+
expect(mapProvider.snapEnabled).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('stashes pluginConfig on the map for mode code that only has `this.map`', () => {
|
|
143
|
+
const pluginConfig = { shapeStroke: '#custom' }
|
|
144
|
+
const { map } = setup({ pluginConfig })
|
|
145
|
+
|
|
146
|
+
expect(map._drawPluginConfig).toBe(pluginConfig)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('defaults pluginConfig to {} when not provided', () => {
|
|
150
|
+
const { map } = setup()
|
|
151
|
+
|
|
152
|
+
expect(map._drawPluginConfig).toEqual({})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('creates an undo stack when none exists and wires it to the map', () => {
|
|
156
|
+
const { map, mapProvider } = setup()
|
|
157
|
+
|
|
158
|
+
expect(createUndoStack).toHaveBeenCalledTimes(1)
|
|
159
|
+
expect(mapProvider.undoStack).toEqual({ id: 'undo-stack' })
|
|
160
|
+
expect(map._undoStack).toBe(mapProvider.undoStack)
|
|
161
|
+
|
|
162
|
+
// The callback passed to createUndoStack fires a draw.undochange event
|
|
163
|
+
const undoCallback = createUndoStack.mock.calls[0][0]
|
|
164
|
+
undoCallback(3)
|
|
165
|
+
expect(map.fire).toHaveBeenCalledWith('draw.undochange', { length: 3 })
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('reuses an existing undo stack', () => {
|
|
169
|
+
const existingUndoStack = { id: 'existing-stack' }
|
|
170
|
+
const { map } = setup({ existingUndoStack })
|
|
171
|
+
|
|
172
|
+
expect(createUndoStack).not.toHaveBeenCalled()
|
|
173
|
+
expect(map._undoStack).toBe(existingUndoStack)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('initializes snapping with the default radius, rules, and resolved colours', () => {
|
|
177
|
+
const { map, mapProvider } = setup()
|
|
178
|
+
|
|
179
|
+
expect(initMapLibreSnap).toHaveBeenCalledWith(map, mapProvider.draw, {
|
|
180
|
+
layers: ['layer-a'],
|
|
181
|
+
radius: TOLERANCES.snapRadius,
|
|
182
|
+
rules: ['vertex', 'edge'],
|
|
183
|
+
colors: { vertex: 'resolved-vertex', edge: 'resolved-edge' }
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('a pluginConfig.snapRadius overrides the default snap radius', () => {
|
|
188
|
+
const { map, mapProvider } = setup({ pluginConfig: { snapRadius: 20 } })
|
|
189
|
+
|
|
190
|
+
expect(initMapLibreSnap).toHaveBeenCalledWith(map, mapProvider.draw, expect.objectContaining({
|
|
191
|
+
radius: 20
|
|
192
|
+
}))
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('resolves snap colours from mapStyle + pluginConfig', () => {
|
|
196
|
+
const pluginConfig = { snapVertex: '#custom' }
|
|
197
|
+
setup({ pluginConfig })
|
|
198
|
+
|
|
199
|
+
expect(resolveColors).toHaveBeenCalledWith('light', pluginConfig)
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('createMapboxDraw – event handlers', () => {
|
|
204
|
+
test('MAP_SET_STYLE updates the current style and restyles on idle', () => {
|
|
205
|
+
const { map, eventBus } = setup()
|
|
206
|
+
|
|
207
|
+
const styleHandler = handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)
|
|
208
|
+
map._drawEditContainer = { querySelector: jest.fn(() => 'svg-el') }
|
|
209
|
+
styleHandler('dark')
|
|
210
|
+
|
|
211
|
+
expect(map._drawCurrentMapStyle).toBe('dark')
|
|
212
|
+
|
|
213
|
+
const idleCallback = handlerFor(map.once, 'idle')
|
|
214
|
+
idleCallback()
|
|
215
|
+
expect(updateDrawStyles).toHaveBeenCalledWith(map, 'dark', {})
|
|
216
|
+
expect(map._drawEditContainer.querySelector).toHaveBeenCalledWith('[data-im-draw-touch-target]')
|
|
217
|
+
expect(applyTouchVertexColors).toHaveBeenCalledWith('svg-el', 'dark', {})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('MAP_SET_STYLE idle handler tolerates a missing edit container', () => {
|
|
221
|
+
const { map, eventBus } = setup()
|
|
222
|
+
|
|
223
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
224
|
+
handlerFor(map.once, 'idle')()
|
|
225
|
+
|
|
226
|
+
expect(applyTouchVertexColors).toHaveBeenCalledWith(undefined, 'dark', {})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('MAP_SET_STYLE passes pluginConfig through to restyling and touch colours', () => {
|
|
230
|
+
const pluginConfig = { editStroke: '#custom' }
|
|
231
|
+
const { map, eventBus } = setup({ pluginConfig })
|
|
232
|
+
|
|
233
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
234
|
+
handlerFor(map.once, 'idle')()
|
|
235
|
+
|
|
236
|
+
expect(updateDrawStyles).toHaveBeenCalledWith(map, 'dark', pluginConfig)
|
|
237
|
+
expect(applyTouchVertexColors).toHaveBeenCalledWith(undefined, 'dark', pluginConfig)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('draw.interfacetypechange is not handled here — the adapter normalises it onto the bus', () => {
|
|
241
|
+
const { map } = setup()
|
|
242
|
+
expect(map.on).not.toHaveBeenCalledWith('draw.interfacetypechange', expect.any(Function))
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('MAP_SET_SIZE fires a scale change using the size lookup', () => {
|
|
246
|
+
const { map, eventBus } = setup()
|
|
247
|
+
|
|
248
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_SIZE)('medium')
|
|
249
|
+
|
|
250
|
+
expect(map.fire).toHaveBeenCalledWith('draw.scalechange', { scale: MAP_SIZE_SCALES.medium })
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('createMapboxDraw – cleanup', () => {
|
|
255
|
+
test('remove() detaches listeners, disables draw and clears the adapter reference', () => {
|
|
256
|
+
const { mapProvider, eventBus, removeWorkaround, result } = setup()
|
|
257
|
+
const draw = mapProvider.draw
|
|
258
|
+
|
|
259
|
+
result.remove()
|
|
260
|
+
|
|
261
|
+
expect(removeWorkaround).toHaveBeenCalledTimes(1)
|
|
262
|
+
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_SET_STYLE, expect.any(Function))
|
|
263
|
+
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_SET_SIZE, expect.any(Function))
|
|
264
|
+
expect(draw.changeMode).toHaveBeenCalledWith('disabled')
|
|
265
|
+
expect(mapProvider.draw).toBeNull()
|
|
266
|
+
expect(mapProvider._mapboxDrawInstance).toBe(draw)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { COLORS, TOLERANCES } from './defaults.js'
|
|
2
|
+
import { applyMapboxSnapPatches } from './snap/prototypePatches.js'
|
|
3
|
+
import { pollUntil } from './snap/sourceData.js'
|
|
4
|
+
import { createSnapInstance } from './snap/snapInstance.js'
|
|
5
|
+
import { registerStyleLoadHandler, registerZoomHandlers } from './snap/mapHandlers.js'
|
|
6
|
+
import { DRAW_HOT_SOURCE } from './snap/constants.js'
|
|
7
|
+
|
|
8
|
+
/** Initialize MapboxSnap with MapLibre + MapboxDraw */
|
|
9
|
+
export function initMapLibreSnap (map, draw, snapOptions = {}) {
|
|
10
|
+
// Prevent multiple initializations (causes event listener duplication)
|
|
11
|
+
if (map._snapInitialized) {
|
|
12
|
+
return map._snapInstance
|
|
13
|
+
}
|
|
14
|
+
map._snapInitialized = true
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
layers = [],
|
|
18
|
+
radius = TOLERANCES.snapRadius,
|
|
19
|
+
rules = ['vertex', 'midpoint', 'edge'],
|
|
20
|
+
status = false,
|
|
21
|
+
onSnapped = () => {},
|
|
22
|
+
colors = {}
|
|
23
|
+
} = snapOptions
|
|
24
|
+
const config = { layers, radius, rules, status, onSnapped }
|
|
25
|
+
|
|
26
|
+
// Apply global patches to the MapboxSnap prototype
|
|
27
|
+
applyMapboxSnapPatches({ vertex: COLORS.snapVertex, midpoint: COLORS.snapMidpoint, edge: COLORS.snapEdge, ...colors })
|
|
28
|
+
|
|
29
|
+
registerStyleLoadHandler(map, draw, config)
|
|
30
|
+
registerZoomHandlers(map)
|
|
31
|
+
|
|
32
|
+
// Initial setup - poll until the draw source exists
|
|
33
|
+
pollUntil(
|
|
34
|
+
() => map._removed ? null : map.getSource(DRAW_HOT_SOURCE),
|
|
35
|
+
(source) => createSnapInstance(map, draw, source, config)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return map._snapInstance
|
|
39
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { applyMapboxSnapPatches } from './snap/prototypePatches.js'
|
|
2
|
+
import { registerStyleLoadHandler, registerZoomHandlers } from './snap/mapHandlers.js'
|
|
3
|
+
import { createSnapInstance } from './snap/snapInstance.js'
|
|
4
|
+
import { initMapLibreSnap } from './mapboxSnap.js'
|
|
5
|
+
|
|
6
|
+
const DRAW_SOURCE = 'mapbox-gl-draw-hot'
|
|
7
|
+
|
|
8
|
+
jest.mock('./snap/prototypePatches.js', () => ({ applyMapboxSnapPatches: jest.fn() }))
|
|
9
|
+
jest.mock('./snap/mapHandlers.js', () => ({
|
|
10
|
+
registerStyleLoadHandler: jest.fn(),
|
|
11
|
+
registerZoomHandlers: jest.fn()
|
|
12
|
+
}))
|
|
13
|
+
jest.mock('./snap/snapInstance.js', () => ({
|
|
14
|
+
createSnapInstance: jest.fn((map) => {
|
|
15
|
+
map._snapInstance = { id: 'snap' }
|
|
16
|
+
return map._snapInstance
|
|
17
|
+
})
|
|
18
|
+
}))
|
|
19
|
+
jest.mock('./snap/sourceData.js', () => ({
|
|
20
|
+
pollUntil: jest.fn((checkFn, onSuccess) => {
|
|
21
|
+
const result = checkFn()
|
|
22
|
+
if (result) {
|
|
23
|
+
onSuccess(result)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}))
|
|
27
|
+
jest.mock('./defaults.js', () => ({
|
|
28
|
+
COLORS: { snapVertex: 'v', snapMidpoint: 'm', snapEdge: 'e' },
|
|
29
|
+
TOLERANCES: { snapRadius: 12 }
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
const makeMap = (overrides = {}) => ({
|
|
33
|
+
getSource: jest.fn(() => ({ id: 'hot' })),
|
|
34
|
+
...overrides
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jest.clearAllMocks()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('initMapLibreSnap', () => {
|
|
42
|
+
test('returns the existing instance and skips setup when already initialized', () => {
|
|
43
|
+
const map = { _snapInitialized: true, _snapInstance: { id: 'existing' } }
|
|
44
|
+
|
|
45
|
+
expect(initMapLibreSnap(map, {}, {})).toEqual({ id: 'existing' })
|
|
46
|
+
expect(applyMapboxSnapPatches).not.toHaveBeenCalled()
|
|
47
|
+
expect(registerStyleLoadHandler).not.toHaveBeenCalled()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('applies patches, registers handlers and creates the instance', () => {
|
|
51
|
+
const map = makeMap()
|
|
52
|
+
const draw = { id: 'draw' }
|
|
53
|
+
|
|
54
|
+
const result = initMapLibreSnap(map, draw, { layers: ['a'] })
|
|
55
|
+
|
|
56
|
+
expect(applyMapboxSnapPatches).toHaveBeenCalledWith({ vertex: 'v', midpoint: 'm', edge: 'e' })
|
|
57
|
+
expect(registerStyleLoadHandler).toHaveBeenCalledWith(map, draw, expect.objectContaining({ layers: ['a'], radius: 12 }))
|
|
58
|
+
expect(registerZoomHandlers).toHaveBeenCalledWith(map)
|
|
59
|
+
expect(createSnapInstance).toHaveBeenCalledWith(map, draw, { id: 'hot' }, expect.any(Object))
|
|
60
|
+
expect(map.getSource).toHaveBeenCalledWith(DRAW_SOURCE)
|
|
61
|
+
expect(result).toEqual({ id: 'snap' })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('merges custom snap colours over the defaults', () => {
|
|
65
|
+
initMapLibreSnap(makeMap(), {}, { colors: { vertex: 'custom' } })
|
|
66
|
+
expect(applyMapboxSnapPatches).toHaveBeenCalledWith({ vertex: 'custom', midpoint: 'm', edge: 'e' })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('uses the configured default radius when none is supplied', () => {
|
|
70
|
+
initMapLibreSnap(makeMap(), {}, {})
|
|
71
|
+
expect(registerStyleLoadHandler.mock.calls[0][2].radius).toBe(12)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('defaults snapOptions to an empty object when omitted', () => {
|
|
75
|
+
const map = makeMap()
|
|
76
|
+
expect(() => initMapLibreSnap(map, {})).not.toThrow()
|
|
77
|
+
expect(createSnapInstance).toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('provides a no-op default onSnapped handler', () => {
|
|
81
|
+
initMapLibreSnap(makeMap(), {}, {})
|
|
82
|
+
const passedConfig = registerStyleLoadHandler.mock.calls[0][2]
|
|
83
|
+
expect(passedConfig.onSnapped()).toBeUndefined()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('does not create an instance when the map has already been removed', () => {
|
|
87
|
+
const map = makeMap({ _removed: true })
|
|
88
|
+
initMapLibreSnap(map, {}, {})
|
|
89
|
+
expect(createSnapInstance).not.toHaveBeenCalled()
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createLifecycle } from './drawMode/lifecycle.js'
|
|
2
|
+
import { createClickHandlers } from './drawMode/clickHandlers.js'
|
|
3
|
+
import { createUndoHandlers } from './drawMode/undoHandlers.js'
|
|
4
|
+
import { createKeyboardHandlers } from './drawMode/keyboardHandlers.js'
|
|
5
|
+
import { createPointerHandlers } from './drawMode/pointerHandlers.js'
|
|
6
|
+
import { createRenderHelpers } from './drawMode/renderHelpers.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Factory function to create a draw mode for either polygons or lines.
|
|
10
|
+
* Shared behaviour (event handling, snap detection, undo, rubber band, keyboard
|
|
11
|
+
* drawing) is split across ./drawMode/* handler modules and composed here.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} ParentMode - DrawPolygon or DrawLineString from mapbox-gl-draw
|
|
14
|
+
* @param {Object} config - Configuration for the mode
|
|
15
|
+
* @param {string} config.featureProp - Property name on state ('polygon' or 'line')
|
|
16
|
+
* @param {string} config.geometryType - 'Polygon' or 'LineString'
|
|
17
|
+
* @param {Function} config.getCoords - Function to get coordinates from feature
|
|
18
|
+
* @param {Function} config.validateClick - Validation function for clicks
|
|
19
|
+
* @param {Function} config.getPlacedCoords - Function to get placed vertex coordinates from a display geojson
|
|
20
|
+
*/
|
|
21
|
+
export const createDrawMode = (ParentMode, config) => {
|
|
22
|
+
const {
|
|
23
|
+
featureProp,
|
|
24
|
+
geometryType,
|
|
25
|
+
getCoords,
|
|
26
|
+
validateClick,
|
|
27
|
+
getPlacedCoords,
|
|
28
|
+
excludeFeatureIdFromSetup = false,
|
|
29
|
+
finishOnInvalidClick = false // For lines: finish when clicking same spot (like double-click)
|
|
30
|
+
} = config
|
|
31
|
+
|
|
32
|
+
const deps = {
|
|
33
|
+
ParentMode,
|
|
34
|
+
featureProp,
|
|
35
|
+
geometryType,
|
|
36
|
+
getCoords,
|
|
37
|
+
validateClick,
|
|
38
|
+
getPlacedCoords,
|
|
39
|
+
excludeFeatureIdFromSetup,
|
|
40
|
+
finishOnInvalidClick,
|
|
41
|
+
getFeature: (state) => state[featureProp],
|
|
42
|
+
// ring is [...placed, last_placed, rubber_band]; splice(-2,1) removes last_placed
|
|
43
|
+
RUBBER_BAND_OFFSET: 2,
|
|
44
|
+
// Only these keys signal a switch to keyboard drawing (matches the OL adapter's
|
|
45
|
+
// drawInput) — modifier keys and shortcut chords like cmd+z must not show the crosshair
|
|
46
|
+
INTERFACE_KEYS: new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Enter'])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...ParentMode,
|
|
51
|
+
...createLifecycle(deps),
|
|
52
|
+
...createClickHandlers(deps),
|
|
53
|
+
...createUndoHandlers(deps),
|
|
54
|
+
...createKeyboardHandlers(deps),
|
|
55
|
+
...createPointerHandlers(deps),
|
|
56
|
+
...createRenderHelpers(deps)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createDrawMode } from './createDrawMode.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for the factory's own job: composing ParentMode with every handler group into a
|
|
5
|
+
* single mode object. The behaviour of each handler group is covered in its colocated
|
|
6
|
+
* test file under drawMode/ (lifecycle, click, keyboard, undo, pointer, render).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const POLYGON_CONFIG = {
|
|
10
|
+
featureProp: 'polygon',
|
|
11
|
+
geometryType: 'Polygon',
|
|
12
|
+
getCoords: (f) => f.coordinates[0],
|
|
13
|
+
validateClick: () => true,
|
|
14
|
+
getPlacedCoords: () => []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const build = (parent = {}, config = POLYGON_CONFIG) => createDrawMode(parent, config)
|
|
18
|
+
|
|
19
|
+
describe('createDrawMode composition', () => {
|
|
20
|
+
test('assembles a mode object with methods from every handler group', () => {
|
|
21
|
+
const mode = build()
|
|
22
|
+
const contributed = [
|
|
23
|
+
'onSetup', 'onStop', // lifecycle
|
|
24
|
+
'onClick', 'onTap', 'doClick', 'onVertexButtonClick', 'onCreate', // click
|
|
25
|
+
'pushDrawUndo', 'undoVertex', 'onUndo', // undo
|
|
26
|
+
'onKeydown', 'onKeyup', 'onKeyUp', // keyboard
|
|
27
|
+
'onMove', 'onMouseMove', 'onTouchStart', 'onBlur', // pointer
|
|
28
|
+
'toDisplayFeatures', '_showCrossHair', '_simulateMouse' // render
|
|
29
|
+
]
|
|
30
|
+
for (const method of contributed) {
|
|
31
|
+
expect(typeof mode[method]).toBe('function')
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('preserves non-overridden ParentMode members and overrides shared ones', () => {
|
|
36
|
+
const parent = { parentOnly () {}, onClick: 'parent-onclick' }
|
|
37
|
+
const mode = build(parent)
|
|
38
|
+
expect(mode.parentOnly).toBe(parent.parentOnly) // passed through
|
|
39
|
+
expect(typeof mode.onClick).toBe('function') // overridden by the click handlers
|
|
40
|
+
expect(mode.onClick).not.toBe(parent.onClick)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('passes config through so the getFeature accessor reads the configured feature prop', () => {
|
|
44
|
+
const polygonMode = build()
|
|
45
|
+
const lineMode = build({}, { ...POLYGON_CONFIG, featureProp: 'line' })
|
|
46
|
+
// dispatchVertexChange is config-agnostic, but pushDrawUndo reads getFeature(state).id
|
|
47
|
+
const map = { _undoStack: { push: jest.fn() }, _undoInProgress: false }
|
|
48
|
+
polygonMode.pushDrawUndo.call({ map }, { polygon: { id: 'p1' } })
|
|
49
|
+
lineMode.pushDrawUndo.call({ map }, { line: { id: 'l1' } })
|
|
50
|
+
expect(map._undoStack.push).toHaveBeenNthCalledWith(1, expect.objectContaining({ featureId: 'p1' }))
|
|
51
|
+
expect(map._undoStack.push).toHaveBeenNthCalledWith(2, expect.objectContaining({ featureId: 'l1' }))
|
|
52
|
+
})
|
|
53
|
+
})
|