@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,62 @@
|
|
|
1
|
+
import { pollUntil, patchSourceData } from './sourceData.js'
|
|
2
|
+
|
|
3
|
+
describe('pollUntil', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
global.requestAnimationFrame = jest.fn()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('stops when checkFn returns null', () => {
|
|
9
|
+
const onSuccess = jest.fn()
|
|
10
|
+
pollUntil(() => null, onSuccess)
|
|
11
|
+
expect(onSuccess).not.toHaveBeenCalled()
|
|
12
|
+
expect(global.requestAnimationFrame).not.toHaveBeenCalled()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('calls onSuccess when checkFn returns a truthy value', () => {
|
|
16
|
+
const onSuccess = jest.fn()
|
|
17
|
+
pollUntil(() => 'ready', onSuccess)
|
|
18
|
+
expect(onSuccess).toHaveBeenCalledWith('ready')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('reschedules via requestAnimationFrame until the value becomes available', () => {
|
|
22
|
+
const values = [undefined, 'ready']
|
|
23
|
+
let i = 0
|
|
24
|
+
global.requestAnimationFrame = jest.fn((cb) => cb())
|
|
25
|
+
const onSuccess = jest.fn()
|
|
26
|
+
|
|
27
|
+
pollUntil(() => values[i++], onSuccess)
|
|
28
|
+
|
|
29
|
+
expect(global.requestAnimationFrame).toHaveBeenCalledTimes(1)
|
|
30
|
+
expect(onSuccess).toHaveBeenCalledWith('ready')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('patchSourceData', () => {
|
|
35
|
+
test('ignores a nullish source', () => {
|
|
36
|
+
expect(() => patchSourceData(null)).not.toThrow()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('ignores a source that already exposes _data.features', () => {
|
|
40
|
+
const existing = { features: [] }
|
|
41
|
+
const source = { _data: existing }
|
|
42
|
+
patchSourceData(source)
|
|
43
|
+
expect(source._data).toBe(existing)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('installs a _data accessor that normalizes assigned values', () => {
|
|
47
|
+
const source = {}
|
|
48
|
+
patchSourceData(source)
|
|
49
|
+
|
|
50
|
+
expect(source._data).toEqual({ type: 'FeatureCollection', features: [] })
|
|
51
|
+
|
|
52
|
+
const fc = { type: 'FeatureCollection', features: [{ id: 1 }] }
|
|
53
|
+
source._data = fc
|
|
54
|
+
expect(source._data).toBe(fc)
|
|
55
|
+
|
|
56
|
+
source._data = 'nonsense'
|
|
57
|
+
expect(source._data).toEqual({ type: 'FeatureCollection', features: [] })
|
|
58
|
+
|
|
59
|
+
source._data = { features: 'not-array' }
|
|
60
|
+
expect(source._data).toEqual({ type: 'FeatureCollection', features: [] })
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// styles.js
|
|
2
|
+
import { SIZES } from './defaults.js'
|
|
3
|
+
import { resolveColors } from '../../utils/resolveColors.js'
|
|
4
|
+
|
|
5
|
+
// `defaultValue` is the already-resolved fallback (colors[defaultsKey] — the
|
|
6
|
+
// plugin-config override if one was set, else the built-in default), not the
|
|
7
|
+
// raw COLORS constant, so a shapeStroke/shapeFill override applies to every
|
|
8
|
+
// feature that doesn't set its own stroke/fill property.
|
|
9
|
+
const getUserProp = (mapStyle, prop, defaultValue) => [
|
|
10
|
+
'coalesce',
|
|
11
|
+
['get', `user_${prop}${mapStyle.id.charAt(0).toUpperCase() + mapStyle.id.slice(1)}`],
|
|
12
|
+
['get', `user_${prop}`],
|
|
13
|
+
defaultValue
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
// Inactive lines and fills
|
|
17
|
+
const fillInactive = (mapStyle, colors) => ({
|
|
18
|
+
id: 'fill-inactive',
|
|
19
|
+
type: 'fill',
|
|
20
|
+
filter: ['all', ['==', '$type', 'Polygon'], ['==', 'active', 'false']],
|
|
21
|
+
paint: { 'fill-color': getUserProp(mapStyle, 'fill', colors.shapeFill) }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const strokeInactive = (mapStyle, colors) => ({
|
|
25
|
+
id: 'stroke-inactive',
|
|
26
|
+
type: 'line',
|
|
27
|
+
filter: ['all', ['any', ['==', '$type', 'Polygon'], ['==', '$type', 'LineString']], ['==', 'active', 'false'], ['!has', 'user_splitter']],
|
|
28
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
29
|
+
paint: {
|
|
30
|
+
'line-color': getUserProp(mapStyle, 'stroke', colors.shapeStroke),
|
|
31
|
+
'line-width': colors.strokeWidth
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Active lines and fills (sketch during drawing)
|
|
36
|
+
const fillActive = (editFillColor) => ({
|
|
37
|
+
id: 'fill-active',
|
|
38
|
+
type: 'fill',
|
|
39
|
+
filter: ['all', ['==', '$type', 'Polygon'], ['==', 'active', 'true']],
|
|
40
|
+
paint: { 'fill-color': editFillColor }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const strokeActive = (editStrokeColor) => ({
|
|
44
|
+
id: 'stroke-active',
|
|
45
|
+
type: 'line',
|
|
46
|
+
filter: ['all', ['any', ['==', '$type', 'Polygon'], ['==', '$type', 'LineString']], ['==', 'active', 'true'], ['!has', 'user_splitter']],
|
|
47
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
48
|
+
paint: { 'line-color': editStrokeColor, 'line-width': 2, 'line-opacity': 1 }
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Dashed stroke shown in place of stroke-active while the shape is invalid. Same
|
|
52
|
+
// filter as stroke-active; the adapter's setInvalid() toggles the two layers'
|
|
53
|
+
// visibility (line-dasharray can't be data-driven per feature in MapLibre).
|
|
54
|
+
const strokeActiveInvalid = (invalidStrokeColor) => ({
|
|
55
|
+
id: 'stroke-active-invalid',
|
|
56
|
+
type: 'line',
|
|
57
|
+
filter: ['all', ['any', ['==', '$type', 'Polygon'], ['==', '$type', 'LineString']], ['==', 'active', 'true'], ['!has', 'user_splitter']],
|
|
58
|
+
layout: { 'line-cap': 'round', 'line-join': 'round', visibility: 'none' },
|
|
59
|
+
paint: {
|
|
60
|
+
'line-color': invalidStrokeColor,
|
|
61
|
+
'line-width': 2,
|
|
62
|
+
'line-dasharray': [0.2, 2], // NOSONAR
|
|
63
|
+
'line-opacity': 1
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Splitter line
|
|
68
|
+
const drawInvalidSplitter = (splitInvalidColor) => ({
|
|
69
|
+
id: 'stroke-invalid-splitter',
|
|
70
|
+
type: 'line',
|
|
71
|
+
filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true'], ['==', 'user_splitter', 'invalid']],
|
|
72
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
73
|
+
paint: {
|
|
74
|
+
'line-color': splitInvalidColor,
|
|
75
|
+
'line-width': 2,
|
|
76
|
+
'line-dasharray': [0.2, 2], // NOSONAR
|
|
77
|
+
'line-opacity': 1
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const drawValidSplitter = (splitValidColor) => ({
|
|
82
|
+
id: 'stroke-valid-splitter',
|
|
83
|
+
type: 'line',
|
|
84
|
+
filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true'], ['==', 'user_splitter', 'valid']],
|
|
85
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
86
|
+
paint: {
|
|
87
|
+
'line-color': splitValidColor,
|
|
88
|
+
'line-width': 2,
|
|
89
|
+
'line-opacity': 1
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Dashed preview line
|
|
94
|
+
const drawPreviewLine = (editStrokeColor) => ({
|
|
95
|
+
id: 'stroke-preview-line',
|
|
96
|
+
type: 'line',
|
|
97
|
+
filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true'], ['!has', 'user_splitter']],
|
|
98
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
99
|
+
paint: { 'line-color': editStrokeColor, 'line-width': 2, 'line-dasharray': [0.2, 2], 'line-opacity': 1 } // NOSONAR
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Vertex layers ('draw-vertex' = display-only markers on placed vertices while drawing)
|
|
103
|
+
const vertex = (editVertexColor, vertexRadius) => ({
|
|
104
|
+
id: 'vertex',
|
|
105
|
+
type: 'circle',
|
|
106
|
+
filter: ['all', ['==', '$type', 'Point'], ['in', 'meta', 'vertex', 'draw-vertex']],
|
|
107
|
+
paint: { 'circle-radius': vertexRadius, 'circle-color': editVertexColor }
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const vertexHalo = (editHaloColor, editActiveColor, vertexHaloRadius) => ({
|
|
111
|
+
id: 'vertex-halo',
|
|
112
|
+
type: 'circle',
|
|
113
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'vertex'], ['==', 'active', 'true']],
|
|
114
|
+
paint: { 'circle-radius': vertexHaloRadius, 'circle-stroke-width': 3, 'circle-color': editHaloColor, 'circle-stroke-color': editActiveColor }
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const vertexActive = (editVertexColor, vertexRadius) => ({
|
|
118
|
+
id: 'vertex-active',
|
|
119
|
+
type: 'circle',
|
|
120
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'vertex'], ['==', 'active', 'true']],
|
|
121
|
+
paint: { 'circle-radius': vertexRadius, 'circle-color': editVertexColor }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Midpoints
|
|
125
|
+
const midpoint = (editMidpointColor, midpointRadius) => ({
|
|
126
|
+
id: 'midpoint',
|
|
127
|
+
type: 'circle',
|
|
128
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint']],
|
|
129
|
+
paint: { 'circle-radius': midpointRadius, 'circle-color': editMidpointColor }
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const midpointHalo = (editHaloColor, editActiveColor, midpointHaloRadius) => ({
|
|
133
|
+
id: 'midpoint-halo',
|
|
134
|
+
type: 'circle',
|
|
135
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint'], ['==', 'active', 'true']],
|
|
136
|
+
paint: { 'circle-radius': midpointHaloRadius, 'circle-stroke-width': 3, 'circle-color': editHaloColor, 'circle-stroke-color': editActiveColor }
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const midpointActive = (editMidpointColor, midpointRadius) => ({
|
|
140
|
+
id: 'midpoint-active',
|
|
141
|
+
type: 'circle',
|
|
142
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint'], ['==', 'active', 'true']],
|
|
143
|
+
paint: { 'circle-radius': midpointRadius, 'circle-color': editMidpointColor }
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const circle = (editStrokeColor) => ({
|
|
147
|
+
id: 'circle',
|
|
148
|
+
type: 'line',
|
|
149
|
+
filter: ['==', 'id', 'circle'],
|
|
150
|
+
paint: { 'line-color': editStrokeColor, 'line-width': 2, 'line-opacity': 0.8 }
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const touchVertexIndicator = () => ({
|
|
154
|
+
id: 'touch-vertex-indicator',
|
|
155
|
+
type: 'circle',
|
|
156
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'touch-vertex-indicator']],
|
|
157
|
+
paint: { 'circle-radius': 30, 'circle-color': '#3bb2d0', 'circle-stroke-width': 3, 'circle-stroke-color': '#ffffff', 'circle-opacity': 0.9 }
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// `pluginConfig` lets callers override any of these colours/strokeWidth — see
|
|
161
|
+
// resolveColors() and the draw plugin's own options docs. Unaffected keys
|
|
162
|
+
// still resolve to the built-in COLORS/SIZES defaults.
|
|
163
|
+
const createDrawStyles = (mapStyle, pluginConfig = {}) => {
|
|
164
|
+
const colors = resolveColors(mapStyle, pluginConfig)
|
|
165
|
+
const { vertexRadius, midpointRadius, vertexHaloRadius, midpointHaloRadius } = SIZES
|
|
166
|
+
|
|
167
|
+
return [
|
|
168
|
+
fillInactive(mapStyle, colors),
|
|
169
|
+
fillActive(colors.editFill),
|
|
170
|
+
strokeActive(colors.editStroke),
|
|
171
|
+
strokeActiveInvalid(colors.invalidStroke),
|
|
172
|
+
strokeInactive(mapStyle, colors),
|
|
173
|
+
drawInvalidSplitter(colors.splitInvalid),
|
|
174
|
+
drawValidSplitter(colors.splitValid),
|
|
175
|
+
drawPreviewLine(colors.editStroke),
|
|
176
|
+
midpoint(colors.editMidpoint, midpointRadius),
|
|
177
|
+
midpointHalo(colors.editHalo, colors.editActive, midpointHaloRadius),
|
|
178
|
+
midpointActive(colors.editMidpoint, midpointRadius),
|
|
179
|
+
vertex(colors.editVertex, vertexRadius),
|
|
180
|
+
vertexHalo(colors.editHalo, colors.editActive, vertexHaloRadius),
|
|
181
|
+
vertexActive(colors.editVertex, vertexRadius),
|
|
182
|
+
circle(colors.editStroke),
|
|
183
|
+
touchVertexIndicator()
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Helper to iterate over a MapLibre map and apply new paint properties
|
|
189
|
+
*/
|
|
190
|
+
const updateDrawStyles = (map, mapStyle, pluginConfig = {}) => {
|
|
191
|
+
const layers = createDrawStyles(mapStyle, pluginConfig)
|
|
192
|
+
layers.forEach(layer => {
|
|
193
|
+
Object.entries(layer.paint).forEach(([prop, value]) => {
|
|
194
|
+
if (map.getLayer(`${layer.id}.cold`)) {
|
|
195
|
+
map.setPaintProperty(`${layer.id}.cold`, prop, value)
|
|
196
|
+
}
|
|
197
|
+
if (map.getLayer(`${layer.id}.hot`)) {
|
|
198
|
+
map.setPaintProperty(`${layer.id}.hot`, prop, value)
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
createDrawStyles,
|
|
206
|
+
updateDrawStyles
|
|
207
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createDrawStyles, updateDrawStyles } from './styles.js'
|
|
2
|
+
import { COLORS, SIZES } from './defaults.js'
|
|
3
|
+
import { getValueForStyle } from '../../utils/getValueForStyle.js'
|
|
4
|
+
|
|
5
|
+
const findLayer = (layers, id) => layers.find((l) => l.id === id)
|
|
6
|
+
|
|
7
|
+
describe('createDrawStyles', () => {
|
|
8
|
+
const mapStyle = { id: 'outdoor', mapColorScheme: 'light' }
|
|
9
|
+
|
|
10
|
+
test('returns every draw layer in order', () => {
|
|
11
|
+
const layers = createDrawStyles(mapStyle)
|
|
12
|
+
expect(layers.map((l) => l.id)).toEqual([
|
|
13
|
+
'fill-inactive', 'fill-active', 'stroke-active', 'stroke-active-invalid', 'stroke-inactive',
|
|
14
|
+
'stroke-invalid-splitter', 'stroke-valid-splitter', 'stroke-preview-line',
|
|
15
|
+
'midpoint', 'midpoint-halo', 'midpoint-active',
|
|
16
|
+
'vertex', 'vertex-halo', 'vertex-active', 'circle', 'touch-vertex-indicator'
|
|
17
|
+
])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('the invalid stroke layer is a hidden dashed line matching the active shape', () => {
|
|
21
|
+
const layers = createDrawStyles(mapStyle)
|
|
22
|
+
const invalid = findLayer(layers, 'stroke-active-invalid')
|
|
23
|
+
expect(invalid.layout.visibility).toBe('none') // hidden until the shape is invalid
|
|
24
|
+
expect(invalid.paint['line-dasharray']).toEqual([0.2, 2])
|
|
25
|
+
expect(invalid.paint['line-color']).toBe(getValueForStyle(COLORS.invalidStroke, 'light'))
|
|
26
|
+
expect(invalid.filter).toEqual(findLayer(layers, 'stroke-active').filter)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('resolves light-scheme colours', () => {
|
|
30
|
+
const layers = createDrawStyles({ id: 'outdoor', mapColorScheme: 'light' })
|
|
31
|
+
expect(findLayer(layers, 'stroke-active').paint['line-color']).toBe(getValueForStyle(COLORS.editStroke, 'light'))
|
|
32
|
+
expect(findLayer(layers, 'fill-active').paint['fill-color']).toBe(getValueForStyle(COLORS.editFill, 'light'))
|
|
33
|
+
expect(findLayer(layers, 'vertex').paint['circle-color']).toBe(getValueForStyle(COLORS.editVertex, 'light'))
|
|
34
|
+
expect(findLayer(layers, 'midpoint').paint['circle-color']).toBe(getValueForStyle(COLORS.editMidpoint, 'light'))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('resolves dark-scheme colours', () => {
|
|
38
|
+
const layers = createDrawStyles({ id: 'night', mapColorScheme: 'dark' })
|
|
39
|
+
expect(findLayer(layers, 'stroke-active').paint['line-color']).toBe(getValueForStyle(COLORS.editStroke, 'dark'))
|
|
40
|
+
expect(findLayer(layers, 'vertex-halo').paint['circle-color']).toBe(getValueForStyle(COLORS.editHalo, 'dark'))
|
|
41
|
+
expect(findLayer(layers, 'vertex-halo').paint['circle-stroke-color']).toBe(getValueForStyle(COLORS.editActive, 'dark'))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('defaults to the light scheme when none is provided', () => {
|
|
45
|
+
const layers = createDrawStyles({ id: 'outdoor' })
|
|
46
|
+
expect(findLayer(layers, 'stroke-active').paint['line-color']).toBe(getValueForStyle(COLORS.editStroke, 'light'))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('applies the configured sizes', () => {
|
|
50
|
+
const layers = createDrawStyles(mapStyle)
|
|
51
|
+
expect(findLayer(layers, 'vertex').paint['circle-radius']).toBe(SIZES.vertexRadius)
|
|
52
|
+
expect(findLayer(layers, 'vertex-halo').paint['circle-radius']).toBe(SIZES.vertexHaloRadius)
|
|
53
|
+
expect(findLayer(layers, 'midpoint').paint['circle-radius']).toBe(SIZES.midpointRadius)
|
|
54
|
+
expect(findLayer(layers, 'midpoint-halo').paint['circle-radius']).toBe(SIZES.midpointHaloRadius)
|
|
55
|
+
expect(findLayer(layers, 'stroke-inactive').paint['line-width']).toBe(SIZES.strokeWidth)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('builds per-style user-property coalesce expressions for inactive fill and stroke', () => {
|
|
59
|
+
const layers = createDrawStyles({ id: 'outdoor', mapColorScheme: 'light' })
|
|
60
|
+
|
|
61
|
+
expect(findLayer(layers, 'fill-inactive').paint['fill-color']).toEqual([
|
|
62
|
+
'coalesce',
|
|
63
|
+
['get', 'user_fillOutdoor'],
|
|
64
|
+
['get', 'user_fill'],
|
|
65
|
+
COLORS.shapeFill
|
|
66
|
+
])
|
|
67
|
+
expect(findLayer(layers, 'stroke-inactive').paint['line-color']).toEqual([
|
|
68
|
+
'coalesce',
|
|
69
|
+
['get', 'user_strokeOutdoor'],
|
|
70
|
+
['get', 'user_stroke'],
|
|
71
|
+
COLORS.shapeStroke
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('capitalises the style id in the user-property key', () => {
|
|
76
|
+
const layers = createDrawStyles({ id: 'satellite', mapColorScheme: 'light' })
|
|
77
|
+
expect(findLayer(layers, 'fill-inactive').paint['fill-color'][1]).toEqual(['get', 'user_fillSatellite'])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('colours the splitter and touch-indicator layers', () => {
|
|
81
|
+
const layers = createDrawStyles(mapStyle)
|
|
82
|
+
expect(findLayer(layers, 'stroke-invalid-splitter').paint['line-color']).toBe(getValueForStyle(COLORS.splitInvalid, 'light'))
|
|
83
|
+
expect(findLayer(layers, 'stroke-valid-splitter').paint['line-color']).toBe(getValueForStyle(COLORS.splitValid, 'light'))
|
|
84
|
+
expect(findLayer(layers, 'touch-vertex-indicator').paint['circle-color']).toBe('#3bb2d0')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('a pluginConfig override replaces the default colour/width everywhere it applies', () => {
|
|
88
|
+
const pluginConfig = { editStroke: '#custom-stroke', shapeStroke: '#custom-shape', strokeWidth: 9 }
|
|
89
|
+
const layers = createDrawStyles(mapStyle, pluginConfig)
|
|
90
|
+
|
|
91
|
+
expect(findLayer(layers, 'stroke-active').paint['line-color']).toBe('#custom-stroke')
|
|
92
|
+
expect(findLayer(layers, 'stroke-preview-line').paint['line-color']).toBe('#custom-stroke')
|
|
93
|
+
expect(findLayer(layers, 'circle').paint['line-color']).toBe('#custom-stroke')
|
|
94
|
+
expect(findLayer(layers, 'stroke-inactive').paint['line-color']).toEqual([
|
|
95
|
+
'coalesce',
|
|
96
|
+
['get', 'user_strokeOutdoor'],
|
|
97
|
+
['get', 'user_stroke'],
|
|
98
|
+
'#custom-shape'
|
|
99
|
+
])
|
|
100
|
+
expect(findLayer(layers, 'stroke-inactive').paint['line-width']).toBe(9)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('a per-feature stroke/fill property still wins over a pluginConfig override — coalesce checks it first', () => {
|
|
104
|
+
const layers = createDrawStyles(mapStyle, { shapeStroke: '#custom-shape' })
|
|
105
|
+
const [, userStyleKey, userKey, fallback] = findLayer(layers, 'stroke-inactive').paint['line-color']
|
|
106
|
+
expect(userStyleKey).toEqual(['get', 'user_strokeOutdoor'])
|
|
107
|
+
expect(userKey).toEqual(['get', 'user_stroke'])
|
|
108
|
+
expect(fallback).toBe('#custom-shape') // only reached when the feature sets neither
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('updateDrawStyles', () => {
|
|
113
|
+
const mapStyle = { id: 'outdoor', mapColorScheme: 'light' }
|
|
114
|
+
|
|
115
|
+
test('applies paint properties to both the cold and hot copies of each layer', () => {
|
|
116
|
+
const map = { getLayer: jest.fn(() => true), setPaintProperty: jest.fn() }
|
|
117
|
+
|
|
118
|
+
updateDrawStyles(map, mapStyle)
|
|
119
|
+
|
|
120
|
+
expect(map.setPaintProperty).toHaveBeenCalledWith('fill-inactive.cold', 'fill-color', expect.anything())
|
|
121
|
+
expect(map.setPaintProperty).toHaveBeenCalledWith('fill-inactive.hot', 'fill-color', expect.anything())
|
|
122
|
+
expect(map.setPaintProperty).toHaveBeenCalledWith('vertex.cold', 'circle-radius', SIZES.vertexRadius)
|
|
123
|
+
expect(map.setPaintProperty).toHaveBeenCalledWith('vertex.hot', 'circle-color', expect.anything())
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('skips layers that are not present on the map', () => {
|
|
127
|
+
const map = { getLayer: jest.fn(() => false), setPaintProperty: jest.fn() }
|
|
128
|
+
|
|
129
|
+
updateDrawStyles(map, mapStyle)
|
|
130
|
+
|
|
131
|
+
expect(map.setPaintProperty).not.toHaveBeenCalled()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('updates only the cold layer when the hot copy is absent', () => {
|
|
135
|
+
const map = {
|
|
136
|
+
getLayer: jest.fn((id) => id.endsWith('.cold')),
|
|
137
|
+
setPaintProperty: jest.fn()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
updateDrawStyles(map, mapStyle)
|
|
141
|
+
|
|
142
|
+
const targets = map.setPaintProperty.mock.calls.map(([id]) => id)
|
|
143
|
+
expect(targets.length).toBeGreaterThan(0)
|
|
144
|
+
expect(targets.every((id) => id.endsWith('.cold'))).toBe(true)
|
|
145
|
+
expect(targets.some((id) => id.endsWith('.hot'))).toBe(false)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('applies a pluginConfig override on the re-styled layers', () => {
|
|
149
|
+
const map = { getLayer: jest.fn(() => true), setPaintProperty: jest.fn() }
|
|
150
|
+
|
|
151
|
+
updateDrawStyles(map, mapStyle, { editStroke: '#custom' })
|
|
152
|
+
|
|
153
|
+
expect(map.setPaintProperty).toHaveBeenCalledWith('stroke-active.cold', 'line-color', '#custom')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snap helper utilities for draw modes
|
|
3
|
+
* Provides a consistent interface for snap detection and coordinate retrieval
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TOLERANCES } from '../../../defaults.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the snap instance from the map
|
|
10
|
+
* @param {maplibregl.Map} map - Map instance
|
|
11
|
+
* @returns {MapboxSnap|null} Snap instance or null
|
|
12
|
+
*/
|
|
13
|
+
export function getSnapInstance (map) {
|
|
14
|
+
return map?._snapInstance ?? null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if snapping is currently active
|
|
19
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
20
|
+
* @returns {boolean} True if snap is active with valid coordinates
|
|
21
|
+
*/
|
|
22
|
+
export function isSnapActive (snap) {
|
|
23
|
+
// Also check snap.status to ensure snap feature is enabled
|
|
24
|
+
return !!(snap?.status && snap?.snapStatus && snap.snapCoords?.length >= 2)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get snapped coordinates as lngLat object
|
|
29
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
30
|
+
* @returns {{ lng: number, lat: number }|null} Snapped coordinates or null
|
|
31
|
+
*/
|
|
32
|
+
export function getSnapLngLat (snap) {
|
|
33
|
+
if (!isSnapActive(snap)) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
lng: snap.snapCoords[0],
|
|
38
|
+
lat: snap.snapCoords[1]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get snapped coordinates as array [lng, lat]
|
|
44
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
45
|
+
* @returns {[number, number]|null} Snapped coordinates or null
|
|
46
|
+
*/
|
|
47
|
+
export function getSnapCoords (snap) {
|
|
48
|
+
if (!isSnapActive(snap)) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
return [snap.snapCoords[0], snap.snapCoords[1]]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Trigger snap detection at a given point
|
|
56
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
57
|
+
* @param {maplibregl.Map} map - Map instance
|
|
58
|
+
* @param {{ x: number, y: number }} point - Screen point
|
|
59
|
+
* @returns {boolean} True if snap was triggered
|
|
60
|
+
*/
|
|
61
|
+
export function triggerSnapAtPoint (snap, map, point) {
|
|
62
|
+
if (!snap || !map || !snap.status) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const lngLat = map.unproject(point)
|
|
67
|
+
snap.snapToClosestPoint({ point, lngLat })
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Trigger snap detection at map center (for touch/keyboard modes)
|
|
73
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
74
|
+
* @param {maplibregl.Map} map - Map instance
|
|
75
|
+
* @returns {boolean} True if snap was triggered
|
|
76
|
+
*/
|
|
77
|
+
export function triggerSnapAtCenter (snap, map) {
|
|
78
|
+
// Don't trigger snap if library is disabled
|
|
79
|
+
if (!snap || !map || !snap.status) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const center = map.getCenter()
|
|
84
|
+
const point = map.project(center)
|
|
85
|
+
snap.snapToClosestPoint({ point, lngLat: center })
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear the snap indicator circle and all internal snap state
|
|
91
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
92
|
+
* @param {maplibregl.Map} [map] - Optional map instance for direct layer control
|
|
93
|
+
*/
|
|
94
|
+
export function clearSnapIndicator (snap, map) {
|
|
95
|
+
if (snap) {
|
|
96
|
+
snap.snapStatus = false
|
|
97
|
+
snap.snapCoords = null
|
|
98
|
+
// Clear arrays in place (avoids creating new objects, reduces GC pressure)
|
|
99
|
+
if (snap.snappedFeatures?.length) {
|
|
100
|
+
snap.snappedFeatures.length = 0
|
|
101
|
+
}
|
|
102
|
+
if (snap.closeFeatures?.length) {
|
|
103
|
+
snap.closeFeatures.length = 0
|
|
104
|
+
}
|
|
105
|
+
if (snap.lines?.length) {
|
|
106
|
+
snap.lines.length = 0
|
|
107
|
+
}
|
|
108
|
+
// Note: Avoid calling setMapData here - it's expensive in Safari
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Just hide the layer - much cheaper than setData() in Safari
|
|
112
|
+
if (map?.getLayer('snap-helper-circle')) {
|
|
113
|
+
map.setLayoutProperty('snap-helper-circle', 'visibility', 'none')
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear all snap state (for use between drag operations)
|
|
119
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
120
|
+
*/
|
|
121
|
+
export function clearSnapState (snap) {
|
|
122
|
+
if (!snap) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
snap.snapStatus = false
|
|
126
|
+
snap.snapCoords = null
|
|
127
|
+
// Clear arrays in place (avoids creating new objects, reduces GC pressure)
|
|
128
|
+
if (snap.snappedFeatures?.length) {
|
|
129
|
+
snap.snappedFeatures.length = 0
|
|
130
|
+
}
|
|
131
|
+
if (snap.closeFeatures?.length) {
|
|
132
|
+
snap.closeFeatures.length = 0
|
|
133
|
+
}
|
|
134
|
+
if (snap.lines?.length) {
|
|
135
|
+
snap.lines.length = 0
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get snap radius in pixels
|
|
141
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
142
|
+
* @returns {number} Snap radius in pixels (falls back to the configured default)
|
|
143
|
+
*/
|
|
144
|
+
export function getSnapRadius (snap) {
|
|
145
|
+
return snap?.options?.radius ?? TOLERANCES.snapRadius
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if snapping is enabled for a given state
|
|
150
|
+
* @param {object} state - Mode state with optional getSnapEnabled function
|
|
151
|
+
* @returns {boolean} True if snapping is enabled
|
|
152
|
+
*/
|
|
153
|
+
export function isSnapEnabled (state) {
|
|
154
|
+
// Only return true if getSnapEnabled exists and explicitly returns true
|
|
155
|
+
if (typeof state?.getSnapEnabled !== 'function') {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
return state.getSnapEnabled() === true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a synthetic map event with snapped coordinates
|
|
163
|
+
* @param {object} e - Original event
|
|
164
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
165
|
+
* @returns {object} Event with snapped lngLat or original event
|
|
166
|
+
*/
|
|
167
|
+
export function createSnappedEvent (e, snap) {
|
|
168
|
+
const lngLat = getSnapLngLat(snap)
|
|
169
|
+
if (!lngLat) {
|
|
170
|
+
return e
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
...e,
|
|
175
|
+
lngLat
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a synthetic click event at snapped coordinates
|
|
181
|
+
* @param {maplibregl.Map} map - Map instance
|
|
182
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
183
|
+
* @returns {object|null} Synthetic event or null if no snap
|
|
184
|
+
*/
|
|
185
|
+
export function createSnappedClickEvent (map, snap) {
|
|
186
|
+
const lngLat = getSnapLngLat(snap)
|
|
187
|
+
if (!lngLat) {
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const point = map.project([lngLat.lng, lngLat.lat])
|
|
192
|
+
return {
|
|
193
|
+
lngLat,
|
|
194
|
+
point,
|
|
195
|
+
originalEvent: new MouseEvent('click', {
|
|
196
|
+
clientX: point.x,
|
|
197
|
+
clientY: point.y,
|
|
198
|
+
bubbles: true,
|
|
199
|
+
cancelable: true
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
}
|