@defra/interactive-map 0.0.37-alpha → 0.0.38-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/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,147 @@
|
|
|
1
|
+
import { manifest } from './manifest.js'
|
|
2
|
+
|
|
3
|
+
const findButton = (id) => manifest.buttons.find((b) => b.id === id)
|
|
4
|
+
const findMenuItem = (menuId, itemId) => findButton(menuId).menuItems.find((m) => m.id === itemId)
|
|
5
|
+
|
|
6
|
+
describe('manifest structure', () => {
|
|
7
|
+
test('exposes the reducer, init component and api surface', () => {
|
|
8
|
+
expect(manifest.reducer).toHaveProperty('initialState')
|
|
9
|
+
expect(manifest.reducer).toHaveProperty('actions')
|
|
10
|
+
expect(manifest.InitComponent).toBeDefined()
|
|
11
|
+
expect(Object.keys(manifest.api)).toEqual(expect.arrayContaining([
|
|
12
|
+
'newPolygon', 'newLine', 'editFeature', 'addFeature', 'deleteFeature', 'split', 'merge'
|
|
13
|
+
]))
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('drawCancel', () => {
|
|
18
|
+
test('is hidden only when there is no active mode', () => {
|
|
19
|
+
expect(findButton('drawCancel').hiddenWhen({ pluginState: { mode: null } })).toBe(true)
|
|
20
|
+
expect(findButton('drawCancel').hiddenWhen({ pluginState: { mode: 'draw_line' } })).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('drawAddPoint', () => {
|
|
25
|
+
const hidden = (interfaceType, mode) =>
|
|
26
|
+
findButton('drawAddPoint').hiddenWhen({ appState: { interfaceType }, pluginState: { mode } })
|
|
27
|
+
|
|
28
|
+
test('is shown only while drawing on a touch interface', () => {
|
|
29
|
+
expect(hidden('touch', 'draw_polygon')).toBe(false)
|
|
30
|
+
expect(hidden('mouse', 'draw_polygon')).toBe(true)
|
|
31
|
+
expect(hidden('touch', 'edit_vertex')).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('is disabled while placing at the crosshair would be vetoed', () => {
|
|
35
|
+
const btn = findButton('drawAddPoint')
|
|
36
|
+
expect(btn.enableWhen({ pluginState: { canAddPoint: true } })).toBe(true)
|
|
37
|
+
expect(btn.enableWhen({ pluginState: { canAddPoint: false } })).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('drawDone', () => {
|
|
42
|
+
const btn = () => findButton('drawDone')
|
|
43
|
+
|
|
44
|
+
test('is hidden outside draw/edit modes', () => {
|
|
45
|
+
expect(btn().hiddenWhen({ pluginState: { mode: null } })).toBe(true)
|
|
46
|
+
expect(btn().hiddenWhen({ pluginState: { mode: 'draw_polygon' } })).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('enables in a draw/edit mode only when the geometry is valid', () => {
|
|
50
|
+
expect(btn().enableWhen({ pluginState: { mode: 'draw_polygon', geometryValid: true } })).toBe(true)
|
|
51
|
+
expect(btn().enableWhen({ pluginState: { mode: 'draw_line', geometryValid: true } })).toBe(true)
|
|
52
|
+
expect(btn().enableWhen({ pluginState: { mode: 'edit_vertex', geometryValid: true } })).toBe(true)
|
|
53
|
+
expect(btn().enableWhen({ pluginState: { mode: 'disabled', geometryValid: true } })).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('stays disabled while the geometry is invalid', () => {
|
|
57
|
+
expect(btn().enableWhen({ pluginState: { mode: 'draw_polygon', geometryValid: false } })).toBe(false)
|
|
58
|
+
expect(btn().enableWhen({ pluginState: { mode: 'draw_line', geometryValid: false } })).toBe(false)
|
|
59
|
+
expect(btn().enableWhen({ pluginState: { mode: 'edit_vertex', geometryValid: false } })).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('drawMenu', () => {
|
|
64
|
+
test('is hidden outside draw/edit modes', () => {
|
|
65
|
+
expect(findButton('drawMenu').hiddenWhen({ pluginState: { mode: null } })).toBe(true)
|
|
66
|
+
expect(findButton('drawMenu').hiddenWhen({ pluginState: { mode: 'edit_vertex' } })).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('labels "Edit actions" in edit mode, "Draw actions" otherwise', () => {
|
|
70
|
+
expect(findButton('drawMenu').label({ pluginState: { mode: 'edit_vertex' } })).toBe('Edit actions')
|
|
71
|
+
expect(findButton('drawMenu').label({ pluginState: { mode: 'draw_polygon' } })).toBe('Draw actions')
|
|
72
|
+
expect(findButton('drawMenu').label({ pluginState: { mode: 'draw_line' } })).toBe('Draw actions')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('drawUndo', () => {
|
|
76
|
+
const item = () => findMenuItem('drawMenu', 'drawUndo')
|
|
77
|
+
|
|
78
|
+
test('is hidden outside draw/edit modes', () => {
|
|
79
|
+
expect(item().hiddenWhen({ pluginState: { mode: null } })).toBe(true)
|
|
80
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'draw_line' } })).toBe(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('enables from vertex count while drawing and from the undo stack while editing', () => {
|
|
84
|
+
expect(item().enableWhen({ pluginState: { mode: 'draw_polygon', numVertices: 1 } })).toBe(true)
|
|
85
|
+
expect(item().enableWhen({ pluginState: { mode: 'draw_polygon', numVertices: 0 } })).toBe(false)
|
|
86
|
+
expect(item().enableWhen({ pluginState: { mode: 'edit_vertex', undoStackLength: 2 } })).toBe(true)
|
|
87
|
+
expect(item().enableWhen({ pluginState: { mode: 'edit_vertex', undoStackLength: 0 } })).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('drawSnap', () => {
|
|
92
|
+
const item = () => findMenuItem('drawMenu', 'drawSnap')
|
|
93
|
+
|
|
94
|
+
test('is hidden without a mode or snap layers', () => {
|
|
95
|
+
expect(item().hiddenWhen({ pluginState: { mode: null, hasSnapLayers: true } })).toBe(true)
|
|
96
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'draw_line', hasSnapLayers: false } })).toBe(true)
|
|
97
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'draw_line', hasSnapLayers: true } })).toBe(false)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('is pressed when snapping is enabled', () => {
|
|
101
|
+
expect(item().pressedWhen({ pluginState: { snap: true } })).toBe(true)
|
|
102
|
+
expect(item().pressedWhen({ pluginState: { snap: false } })).toBe(false)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('drawDeletePoint', () => {
|
|
107
|
+
const item = () => findMenuItem('drawMenu', 'drawDeletePoint')
|
|
108
|
+
|
|
109
|
+
test('is hidden outside edit mode', () => {
|
|
110
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'draw_polygon' } })).toBe(true)
|
|
111
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'edit_vertex' } })).toBe(false)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('enables only with a selection and enough vertices (polygon vs line)', () => {
|
|
115
|
+
expect(item().enableWhen({ pluginState: { selectedVertexIndex: -1 } })).toBe(false)
|
|
116
|
+
expect(item().enableWhen({ pluginState: { selectedVertexIndex: 0, feature: { geometry: { type: 'Polygon' } }, numVertices: 4 } })).toBe(true)
|
|
117
|
+
expect(item().enableWhen({ pluginState: { selectedVertexIndex: 0, feature: { geometry: { type: 'Polygon' } }, numVertices: 3 } })).toBe(false)
|
|
118
|
+
expect(item().enableWhen({ pluginState: { selectedVertexIndex: 0, feature: { geometry: { type: 'LineString' } }, numVertices: 3 } })).toBe(true)
|
|
119
|
+
expect(item().enableWhen({ pluginState: { selectedVertexIndex: 0, feature: { geometry: { type: 'LineString' } }, numVertices: 2 } })).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('drawUndo keyboard shortcut (platform-specific command)', () => {
|
|
125
|
+
const loadUndoCommand = (mac) => {
|
|
126
|
+
let command
|
|
127
|
+
jest.isolateModules(() => {
|
|
128
|
+
jest.doMock('../../../src/utils/isMac.js', () => ({ isMac: () => mac }))
|
|
129
|
+
const { manifest: reloaded } = require('./manifest.js')
|
|
130
|
+
command = reloaded.keyboardShortcuts.find((s) => s.id === 'drawUndo').command
|
|
131
|
+
})
|
|
132
|
+
return command
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
jest.dontMock('../../../src/utils/isMac.js')
|
|
137
|
+
jest.resetModules()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('uses Command on macOS', () => {
|
|
141
|
+
expect(loadUndoCommand(true)).toBe('<kbd>Command</kbd> + <kbd>Z</kbd>')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('uses Ctrl on non-mac platforms', () => {
|
|
145
|
+
expect(loadUndoCommand(false)).toBe('<kbd>Ctrl</kbd> + <kbd>Z</kbd>')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const initialState = {
|
|
2
|
+
mode: null,
|
|
3
|
+
action: null,
|
|
4
|
+
actionValid: false,
|
|
5
|
+
feature: null,
|
|
6
|
+
tempFeature: null,
|
|
7
|
+
selectedVertexIndex: -1,
|
|
8
|
+
numVertices: null,
|
|
9
|
+
geometryValid: true,
|
|
10
|
+
canAddPoint: true,
|
|
11
|
+
snap: false,
|
|
12
|
+
hasSnapLayers: false,
|
|
13
|
+
undoStackLength: 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DRAW_MODES = new Set(['draw_polygon', 'draw_line'])
|
|
17
|
+
|
|
18
|
+
const setMode = (state, payload) => ({
|
|
19
|
+
...state,
|
|
20
|
+
mode: payload,
|
|
21
|
+
numVertices: DRAW_MODES.has(payload) ? 0 : state.numVertices,
|
|
22
|
+
// A new/empty shape is never valid; validation flips this true once the geometry
|
|
23
|
+
// passes all soft rules (edit mode seeds it explicitly in api/editFeature).
|
|
24
|
+
geometryValid: false,
|
|
25
|
+
// A fresh mode can always place; the live placement gate flips this on veto.
|
|
26
|
+
canAddPoint: true
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const setAction = (state, payload) => ({
|
|
30
|
+
...state,
|
|
31
|
+
action: payload.name,
|
|
32
|
+
actionValid: payload.isValid
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const setSelectedVertexIndex = (state, payload) => ({
|
|
36
|
+
...state,
|
|
37
|
+
selectedVertexIndex: payload.index,
|
|
38
|
+
numVertices: payload.numVertices
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const setFeature = (state, payload) => ({
|
|
42
|
+
...state,
|
|
43
|
+
feature: payload.feature === undefined ? state.feature : payload.feature,
|
|
44
|
+
tempFeature: payload.tempFeature === undefined ? state.tempFeature : payload.tempFeature
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const toggleSnap = (state) => ({ ...state, snap: !state.snap })
|
|
48
|
+
|
|
49
|
+
const setHasSnapLayers = (state, payload) => ({ ...state, hasSnapLayers: !!payload })
|
|
50
|
+
|
|
51
|
+
const setUndoStackLength = (state, payload) => ({ ...state, undoStackLength: payload })
|
|
52
|
+
|
|
53
|
+
const setGeometryValid = (state, payload) => ({ ...state, geometryValid: !!payload })
|
|
54
|
+
|
|
55
|
+
const setCanAddPoint = (state, payload) => ({ ...state, canAddPoint: !!payload })
|
|
56
|
+
|
|
57
|
+
const actions = {
|
|
58
|
+
SET_MODE: setMode,
|
|
59
|
+
SET_ACTION: setAction,
|
|
60
|
+
SET_FEATURE: setFeature,
|
|
61
|
+
SET_SELECTED_VERTEX_INDEX: setSelectedVertexIndex,
|
|
62
|
+
TOGGLE_SNAP: toggleSnap,
|
|
63
|
+
SET_HAS_SNAP_LAYERS: setHasSnapLayers,
|
|
64
|
+
SET_UNDO_STACK_LENGTH: setUndoStackLength,
|
|
65
|
+
SET_GEOMETRY_VALID: setGeometryValid,
|
|
66
|
+
SET_CAN_ADD_POINT: setCanAddPoint
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { initialState, actions }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { initialState, actions } from './reducer.js'
|
|
2
|
+
|
|
3
|
+
describe('initialState', () => {
|
|
4
|
+
test('has sensible defaults', () => {
|
|
5
|
+
expect(initialState).toMatchObject({
|
|
6
|
+
mode: null,
|
|
7
|
+
action: null,
|
|
8
|
+
actionValid: false,
|
|
9
|
+
feature: null,
|
|
10
|
+
tempFeature: null,
|
|
11
|
+
selectedVertexIndex: -1,
|
|
12
|
+
numVertices: null,
|
|
13
|
+
snap: false,
|
|
14
|
+
hasSnapLayers: false,
|
|
15
|
+
undoStackLength: 0,
|
|
16
|
+
canAddPoint: true
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('SET_MODE', () => {
|
|
22
|
+
test('resets numVertices to 0 for draw modes', () => {
|
|
23
|
+
expect(actions.SET_MODE(initialState, 'draw_polygon')).toMatchObject({ mode: 'draw_polygon', numVertices: 0 })
|
|
24
|
+
expect(actions.SET_MODE(initialState, 'draw_line')).toMatchObject({ mode: 'draw_line', numVertices: 0 })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('a fresh mode can always place a point', () => {
|
|
28
|
+
const state = { ...initialState, canAddPoint: false }
|
|
29
|
+
expect(actions.SET_MODE(state, 'draw_polygon')).toMatchObject({ canAddPoint: true })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('preserves numVertices for non-draw modes', () => {
|
|
33
|
+
const state = { ...initialState, numVertices: 5 }
|
|
34
|
+
expect(actions.SET_MODE(state, 'edit_vertex')).toMatchObject({ mode: 'edit_vertex', numVertices: 5 })
|
|
35
|
+
expect(actions.SET_MODE(state, null)).toMatchObject({ mode: null, numVertices: 5 })
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('SET_ACTION', () => {
|
|
40
|
+
test('sets the action name and validity', () => {
|
|
41
|
+
expect(actions.SET_ACTION(initialState, { name: 'split', isValid: true }))
|
|
42
|
+
.toMatchObject({ action: 'split', actionValid: true })
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('SET_FEATURE', () => {
|
|
47
|
+
test('updates provided fields and preserves undefined ones', () => {
|
|
48
|
+
const state = { ...initialState, feature: 'F', tempFeature: 'T' }
|
|
49
|
+
expect(actions.SET_FEATURE(state, { feature: 'F2' })).toMatchObject({ feature: 'F2', tempFeature: 'T' })
|
|
50
|
+
expect(actions.SET_FEATURE(state, { tempFeature: null })).toMatchObject({ feature: 'F', tempFeature: null })
|
|
51
|
+
expect(actions.SET_FEATURE(state, { feature: null, tempFeature: null })).toMatchObject({ feature: null, tempFeature: null })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('SET_SELECTED_VERTEX_INDEX', () => {
|
|
56
|
+
test('sets the index and numVertices', () => {
|
|
57
|
+
expect(actions.SET_SELECTED_VERTEX_INDEX(initialState, { index: 2, numVertices: 4 }))
|
|
58
|
+
.toMatchObject({ selectedVertexIndex: 2, numVertices: 4 })
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('snap actions', () => {
|
|
63
|
+
test('TOGGLE_SNAP flips the snap flag', () => {
|
|
64
|
+
expect(actions.TOGGLE_SNAP({ ...initialState, snap: false }).snap).toBe(true)
|
|
65
|
+
expect(actions.TOGGLE_SNAP({ ...initialState, snap: true }).snap).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('SET_HAS_SNAP_LAYERS coerces the payload to a boolean', () => {
|
|
69
|
+
expect(actions.SET_HAS_SNAP_LAYERS(initialState, ['x']).hasSnapLayers).toBe(true)
|
|
70
|
+
expect(actions.SET_HAS_SNAP_LAYERS(initialState, null).hasSnapLayers).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('SET_UNDO_STACK_LENGTH', () => {
|
|
75
|
+
test('sets the undo stack length', () => {
|
|
76
|
+
expect(actions.SET_UNDO_STACK_LENGTH(initialState, 3).undoStackLength).toBe(3)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('SET_CAN_ADD_POINT', () => {
|
|
81
|
+
test('coerces the payload to a boolean', () => {
|
|
82
|
+
expect(actions.SET_CAN_ADD_POINT(initialState, false).canAddPoint).toBe(false)
|
|
83
|
+
expect(actions.SET_CAN_ADD_POINT(initialState, 1).canAddPoint).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// --- Debounce Helper ---
|
|
2
|
+
export const debounce = (fn, delay) => {
|
|
3
|
+
let timeout
|
|
4
|
+
|
|
5
|
+
const debounced = (...args) => {
|
|
6
|
+
clearTimeout(timeout)
|
|
7
|
+
timeout = setTimeout(() => fn(...args), delay)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
debounced.cancel = () => {
|
|
11
|
+
clearTimeout(timeout)
|
|
12
|
+
timeout = null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return debounced
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { debounce } from './debounce.js'
|
|
2
|
+
|
|
3
|
+
jest.useFakeTimers()
|
|
4
|
+
|
|
5
|
+
describe('debounce', () => {
|
|
6
|
+
test('invokes the function after the delay with the latest arguments', () => {
|
|
7
|
+
const fn = jest.fn()
|
|
8
|
+
const debounced = debounce(fn, 100)
|
|
9
|
+
|
|
10
|
+
debounced('a')
|
|
11
|
+
expect(fn).not.toHaveBeenCalled()
|
|
12
|
+
|
|
13
|
+
jest.advanceTimersByTime(100)
|
|
14
|
+
expect(fn).toHaveBeenCalledWith('a')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('collapses rapid calls into a single trailing invocation', () => {
|
|
18
|
+
const fn = jest.fn()
|
|
19
|
+
const debounced = debounce(fn, 100)
|
|
20
|
+
|
|
21
|
+
debounced(1)
|
|
22
|
+
debounced(2)
|
|
23
|
+
debounced(3)
|
|
24
|
+
jest.advanceTimersByTime(100)
|
|
25
|
+
|
|
26
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
27
|
+
expect(fn).toHaveBeenCalledWith(3)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('cancel prevents a pending invocation', () => {
|
|
31
|
+
const fn = jest.fn()
|
|
32
|
+
const debounced = debounce(fn, 100)
|
|
33
|
+
|
|
34
|
+
debounced('x')
|
|
35
|
+
debounced.cancel()
|
|
36
|
+
jest.advanceTimersByTime(100)
|
|
37
|
+
|
|
38
|
+
expect(fn).not.toHaveBeenCalled()
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple pub-sub event bus for plugin-internal communication.
|
|
3
|
+
* Used to decouple components that need to communicate events.
|
|
4
|
+
*
|
|
5
|
+
* @returns {{on: Function, off: Function, emit: Function}}
|
|
6
|
+
*/
|
|
7
|
+
export const createEventBus = () => {
|
|
8
|
+
const listeners = new Map()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
on (type, handler) {
|
|
12
|
+
if (!listeners.has(type)) {
|
|
13
|
+
listeners.set(type, new Set())
|
|
14
|
+
}
|
|
15
|
+
listeners.get(type).add(handler)
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
off (type, handler) {
|
|
19
|
+
listeners.get(type)?.delete(handler)
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
emit (type, ...args) {
|
|
23
|
+
const handlers = listeners.get(type)
|
|
24
|
+
if (handlers) {
|
|
25
|
+
// Array.from, not [...handlers] — under Docusaurus's docs build, Babel
|
|
26
|
+
// compiles with `loose: true` (@docusaurus/babel/preset.js), which turns
|
|
27
|
+
// spread-of-non-array-iterable into `[].concat(handlers)`. concat() only
|
|
28
|
+
// flattens real arrays; for a Set it appends the whole Set as a single
|
|
29
|
+
// element, so every handler call becomes `set.apply(...)` and throws
|
|
30
|
+
// "h.apply is not a function". Array.from() is a plain function call, so
|
|
31
|
+
// it's untouched by that transform and always iterates correctly.
|
|
32
|
+
Array.from(handlers).forEach(h => h(...args))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createEventBus } from './eventBus.js'
|
|
2
|
+
|
|
3
|
+
describe('createEventBus', () => {
|
|
4
|
+
test('emits to a registered handler with the supplied arguments', () => {
|
|
5
|
+
const bus = createEventBus()
|
|
6
|
+
const handler = jest.fn()
|
|
7
|
+
bus.on('thing', handler)
|
|
8
|
+
|
|
9
|
+
bus.emit('thing', 1, 2)
|
|
10
|
+
|
|
11
|
+
expect(handler).toHaveBeenCalledWith(1, 2)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('supports multiple handlers for the same type', () => {
|
|
15
|
+
const bus = createEventBus()
|
|
16
|
+
const a = jest.fn()
|
|
17
|
+
const b = jest.fn()
|
|
18
|
+
bus.on('thing', a)
|
|
19
|
+
bus.on('thing', b)
|
|
20
|
+
|
|
21
|
+
bus.emit('thing')
|
|
22
|
+
|
|
23
|
+
expect(a).toHaveBeenCalled()
|
|
24
|
+
expect(b).toHaveBeenCalled()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('off removes a handler so it no longer fires', () => {
|
|
28
|
+
const bus = createEventBus()
|
|
29
|
+
const handler = jest.fn()
|
|
30
|
+
bus.on('thing', handler)
|
|
31
|
+
bus.off('thing', handler)
|
|
32
|
+
|
|
33
|
+
bus.emit('thing')
|
|
34
|
+
|
|
35
|
+
expect(handler).not.toHaveBeenCalled()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('emitting a type with no listeners is a no-op', () => {
|
|
39
|
+
const bus = createEventBus()
|
|
40
|
+
expect(() => bus.emit('nothing')).not.toThrow()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('off on an unknown type does not throw', () => {
|
|
44
|
+
const bus = createEventBus()
|
|
45
|
+
expect(() => bus.off('unknown', jest.fn())).not.toThrow()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const STYLE_PROPS = new Set(['stroke', 'fill', 'strokeWidth'])
|
|
2
|
+
|
|
3
|
+
export const flattenStyleProperties = (props) => {
|
|
4
|
+
if (!props) {
|
|
5
|
+
return {}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const result = {}
|
|
9
|
+
|
|
10
|
+
for (const [key, value] of Object.entries(props)) {
|
|
11
|
+
if (STYLE_PROPS.has(key) && typeof value === 'object' && value !== null) {
|
|
12
|
+
const entries = Object.entries(value)
|
|
13
|
+
if (entries.length > 0) {
|
|
14
|
+
result[key] = entries[0][1]
|
|
15
|
+
}
|
|
16
|
+
for (const [styleId, styleValue] of entries) {
|
|
17
|
+
result[`${key}${styleId.charAt(0).toUpperCase() + styleId.slice(1)}`] = styleValue
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
result[key] = value
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { flattenStyleProperties } from './flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
describe('flattenStyleProperties', () => {
|
|
4
|
+
test('returns an empty object for nullish input', () => {
|
|
5
|
+
expect(flattenStyleProperties(null)).toEqual({})
|
|
6
|
+
expect(flattenStyleProperties(undefined)).toEqual({})
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('flattens a style-variant object into base + per-style keys', () => {
|
|
10
|
+
const result = flattenStyleProperties({ stroke: { light: 'red', dark: 'blue' } })
|
|
11
|
+
expect(result).toEqual({
|
|
12
|
+
stroke: 'red', // base = first variant value
|
|
13
|
+
strokeLight: 'red',
|
|
14
|
+
strokeDark: 'blue'
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('passes through a scalar style value unchanged', () => {
|
|
19
|
+
expect(flattenStyleProperties({ stroke: 'red' })).toEqual({ stroke: 'red' })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('passes through a null style value unchanged', () => {
|
|
23
|
+
expect(flattenStyleProperties({ fill: null })).toEqual({ fill: null })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('passes through non-style properties untouched', () => {
|
|
27
|
+
expect(flattenStyleProperties({ name: 'field', count: 3 })).toEqual({ name: 'field', count: 3 })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('ignores an empty style-variant object (no base, no variants)', () => {
|
|
31
|
+
expect(flattenStyleProperties({ fill: {} })).toEqual({})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a configuration value which may be either a simple value (same for all styles)
|
|
3
|
+
* or an object with scheme/style variants.
|
|
4
|
+
*
|
|
5
|
+
* Useful for resolving any value (colors, sizes, etc.) across different map styles and color schemes.
|
|
6
|
+
* Can resolve based on both style ID and color scheme.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order for objects:
|
|
9
|
+
* 1. Exact style ID match (e.g., { outdoor: '...', dark: '...' })
|
|
10
|
+
* 2. Scheme match (e.g., { light: '...', dark: '...' })
|
|
11
|
+
* 3. Fallback to 'light' property
|
|
12
|
+
* 4. First value in object
|
|
13
|
+
*
|
|
14
|
+
* @param {any} value - Simple value or variant object
|
|
15
|
+
* @param {string} scheme - Current color scheme ('light' or 'dark')
|
|
16
|
+
* @param {string|null} styleId - Map style ID for per-style customization
|
|
17
|
+
* @returns {any} Resolved value
|
|
18
|
+
*/
|
|
19
|
+
export const getValueForStyle = (value, scheme, styleId = null) => {
|
|
20
|
+
if (typeof value !== 'object' || value === null) {
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
if (styleId && value[styleId] !== undefined) {
|
|
24
|
+
return value[styleId]
|
|
25
|
+
}
|
|
26
|
+
if (value[scheme] !== undefined) {
|
|
27
|
+
return value[scheme]
|
|
28
|
+
}
|
|
29
|
+
if (value.light !== undefined) {
|
|
30
|
+
return value.light
|
|
31
|
+
}
|
|
32
|
+
return Object.values(value)[0]
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getValueForStyle } from './getValueForStyle.js'
|
|
2
|
+
|
|
3
|
+
describe('getValueForStyle', () => {
|
|
4
|
+
test('returns primitive values unchanged', () => {
|
|
5
|
+
expect(getValueForStyle('red', 'light')).toBe('red')
|
|
6
|
+
expect(getValueForStyle(5, 'light')).toBe(5)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('returns null unchanged', () => {
|
|
10
|
+
expect(getValueForStyle(null, 'light')).toBeNull()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('prefers an exact style-id match', () => {
|
|
14
|
+
expect(getValueForStyle({ outdoor: 'green', light: 'red' }, 'light', 'outdoor')).toBe('green')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('falls back to the scheme match when the style id is absent', () => {
|
|
18
|
+
expect(getValueForStyle({ light: 'red', dark: 'blue' }, 'dark', 'outdoor')).toBe('blue')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('uses the scheme match when no style id is provided', () => {
|
|
22
|
+
expect(getValueForStyle({ light: 'red', dark: 'blue' }, 'dark')).toBe('blue')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('falls back to the light property when the scheme is missing', () => {
|
|
26
|
+
expect(getValueForStyle({ light: 'red', dark: 'blue' }, 'sepia')).toBe('red')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('falls back to the first value when neither scheme nor light exist', () => {
|
|
30
|
+
expect(getValueForStyle({ outdoor: 'green', satellite: 'grey' }, 'dark')).toBe('green')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { COLORS, SIZES } from '../defaults.js'
|
|
2
|
+
import { getValueForStyle } from './getValueForStyle.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve all draw colors for the given map style and plugin config overrides.
|
|
6
|
+
* Shared by both adapters (MapLibre and OpenLayers) — they draw from the same
|
|
7
|
+
* COLORS/SIZES palette (defaults.js), so a plugin-level override behaves
|
|
8
|
+
* identically regardless of which map provider is in use.
|
|
9
|
+
*
|
|
10
|
+
* Values in pluginConfig may be plain strings or variant objects (e.g. { light: '...', dark: '...' }).
|
|
11
|
+
* Variant resolution order: exact style ID match → color scheme → 'light' fallback → first value.
|
|
12
|
+
*
|
|
13
|
+
* @param {object|null} mapStyle - Current map style object (has .id and .mapColorScheme)
|
|
14
|
+
* @param {object} pluginConfig - Plugin-level user overrides (may override any COLORS key)
|
|
15
|
+
* @returns {object} Flat color values ready for use in each adapter's styles module
|
|
16
|
+
*/
|
|
17
|
+
export const resolveColors = (mapStyle, pluginConfig = {}) => {
|
|
18
|
+
const scheme = mapStyle?.mapColorScheme ?? 'light'
|
|
19
|
+
const styleId = mapStyle?.id ?? null
|
|
20
|
+
const resolveColor = (key) => getValueForStyle(pluginConfig[key] ?? COLORS[key], scheme, styleId)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
editStroke: resolveColor('editStroke'),
|
|
24
|
+
editFill: resolveColor('editFill'),
|
|
25
|
+
editVertex: resolveColor('editVertex'),
|
|
26
|
+
editMidpoint: resolveColor('editMidpoint'),
|
|
27
|
+
editActive: resolveColor('editActive'),
|
|
28
|
+
editHalo: resolveColor('editHalo'),
|
|
29
|
+
invalidStroke: resolveColor('invalidStroke'),
|
|
30
|
+
splitValid: resolveColor('splitValid'),
|
|
31
|
+
splitInvalid: resolveColor('splitInvalid'),
|
|
32
|
+
shapeStroke: resolveColor('shapeStroke'),
|
|
33
|
+
strokeWidth: pluginConfig.strokeWidth ?? SIZES.strokeWidth,
|
|
34
|
+
shapeFill: resolveColor('shapeFill'),
|
|
35
|
+
snapVertex: resolveColor('snapVertex'),
|
|
36
|
+
snapEdge: resolveColor('snapEdge'),
|
|
37
|
+
mapStyleId: styleId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveColors } from './resolveColors.js'
|
|
2
|
+
import { COLORS, SIZES } from '../defaults.js'
|
|
3
|
+
|
|
4
|
+
test('without a map style, colours resolve to their light variants and defaults', () => {
|
|
5
|
+
const colors = resolveColors(null)
|
|
6
|
+
expect(colors.editStroke).toBe(COLORS.editStroke.light)
|
|
7
|
+
expect(colors.shapeStroke).toBe(COLORS.shapeStroke) // plain value — no variants
|
|
8
|
+
expect(colors.strokeWidth).toBe(SIZES.strokeWidth)
|
|
9
|
+
expect(colors.mapStyleId).toBeNull()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('splitter colours are resolved', () => {
|
|
13
|
+
const colors = resolveColors(null)
|
|
14
|
+
expect(colors.splitValid).toBe(COLORS.splitValid.light)
|
|
15
|
+
expect(colors.splitInvalid).toBe(COLORS.splitInvalid.light)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('a dark map style resolves dark variants and carries its id through', () => {
|
|
19
|
+
const colors = resolveColors({ id: 'dark', mapColorScheme: 'dark' })
|
|
20
|
+
expect(colors.editStroke).toBe(COLORS.editStroke.dark)
|
|
21
|
+
expect(colors.mapStyleId).toBe('dark')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('plugin config overrides beat the defaults, as strings or scheme variants', () => {
|
|
25
|
+
const colors = resolveColors({ id: 'road', mapColorScheme: 'dark' }, {
|
|
26
|
+
editStroke: '#custom',
|
|
27
|
+
editFill: { light: '#l', dark: '#d' },
|
|
28
|
+
strokeWidth: 9
|
|
29
|
+
})
|
|
30
|
+
expect(colors.editStroke).toBe('#custom')
|
|
31
|
+
expect(colors.editFill).toBe('#d')
|
|
32
|
+
expect(colors.strokeWidth).toBe(9)
|
|
33
|
+
expect(colors.editVertex).toBe(COLORS.editVertex.dark) // untouched keys still resolve
|
|
34
|
+
})
|