@defra/interactive-map 0.0.36-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.
Files changed (248) hide show
  1. package/assets/templates/draw-tools.njk +4 -3
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api.md +32 -0
  8. package/docs/examples/draw-tools.mdx +0 -4
  9. package/docs/plugins/draw.md +482 -0
  10. package/docs/plugins.md +2 -6
  11. package/govuk-prototype-kit.config.json +3 -2
  12. package/package.json +2 -4
  13. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  14. package/plugins/beta/draw-es/src/events.js +1 -1
  15. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  16. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  17. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  18. package/plugins/beta/map-styles/dist/css/index.css +1 -97
  19. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  20. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  21. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  22. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  23. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +13 -0
  24. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
  25. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
  26. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
  27. package/plugins/draw/dist/esm/index.js +1 -0
  28. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
  29. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
  30. package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
  31. package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
  32. package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
  33. package/plugins/draw/dist/umd/index.js +2 -0
  34. package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
  35. package/plugins/draw/src/DrawInit.jsx +89 -0
  36. package/plugins/draw/src/DrawInit.test.jsx +184 -0
  37. package/plugins/draw/src/adapterEvents.js +127 -0
  38. package/plugins/draw/src/adapterEvents.test.js +20 -0
  39. package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
  40. package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
  41. package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
  42. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
  43. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
  44. package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
  45. package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
  46. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
  47. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
  48. package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
  49. package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
  50. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
  51. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
  52. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
  53. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
  54. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
  55. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
  56. package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
  57. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
  58. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
  59. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
  60. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
  61. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
  62. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
  63. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
  64. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
  65. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
  66. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
  67. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
  68. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
  69. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
  70. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
  71. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
  72. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
  74. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
  75. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
  90. package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
  91. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
  92. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
  93. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
  94. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
  95. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
  96. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
  97. package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
  98. package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
  99. package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
  100. package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
  101. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
  102. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
  103. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
  104. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
  105. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
  106. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
  107. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
  108. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
  109. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
  110. package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
  111. package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
  112. package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
  113. package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
  114. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
  115. package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
  116. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
  117. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
  118. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
  119. package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
  120. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
  121. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
  122. package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
  123. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
  124. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
  125. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
  126. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
  127. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
  128. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
  129. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
  130. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
  132. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
  134. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
  136. package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
  137. package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
  139. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
  140. package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
  141. package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
  142. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
  143. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
  144. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
  145. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
  146. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
  147. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
  148. package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
  149. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
  150. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
  151. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
  153. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
  154. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
  155. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
  156. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
  157. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
  158. package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
  159. package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
  160. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
  161. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
  162. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
  163. package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
  164. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
  165. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
  166. package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
  167. package/plugins/draw/src/api/addFeature.js +22 -0
  168. package/plugins/draw/src/api/addFeature.test.js +41 -0
  169. package/plugins/draw/src/api/deleteFeature.js +11 -0
  170. package/plugins/draw/src/api/deleteFeature.test.js +24 -0
  171. package/plugins/draw/src/api/editFeature.js +54 -0
  172. package/plugins/draw/src/api/editFeature.test.js +134 -0
  173. package/plugins/draw/src/api/merge.js +30 -0
  174. package/plugins/draw/src/api/merge.test.js +57 -0
  175. package/plugins/draw/src/api/newLine.js +41 -0
  176. package/plugins/draw/src/api/newLine.test.js +93 -0
  177. package/plugins/draw/src/api/newPolygon.js +41 -0
  178. package/plugins/draw/src/api/newPolygon.test.js +93 -0
  179. package/plugins/draw/src/api/split.js +117 -0
  180. package/plugins/draw/src/api/split.test.js +204 -0
  181. package/plugins/draw/src/defaults.js +52 -0
  182. package/plugins/draw/src/defaults.test.js +18 -0
  183. package/plugins/draw/src/draw.scss +43 -0
  184. package/plugins/draw/src/events.js +237 -0
  185. package/plugins/draw/src/events.test.js +369 -0
  186. package/plugins/draw/src/index.js +10 -0
  187. package/plugins/draw/src/index.test.js +24 -0
  188. package/plugins/draw/src/manifest.js +170 -0
  189. package/plugins/draw/src/manifest.test.js +147 -0
  190. package/plugins/draw/src/reducer.js +69 -0
  191. package/plugins/draw/src/reducer.test.js +85 -0
  192. package/plugins/draw/src/utils/debounce.js +16 -0
  193. package/plugins/draw/src/utils/debounce.test.js +40 -0
  194. package/plugins/draw/src/utils/eventBus.js +36 -0
  195. package/plugins/draw/src/utils/eventBus.test.js +47 -0
  196. package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
  197. package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
  198. package/plugins/draw/src/utils/getValueForStyle.js +33 -0
  199. package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
  200. package/plugins/draw/src/utils/resolveColors.js +39 -0
  201. package/plugins/draw/src/utils/resolveColors.test.js +34 -0
  202. package/plugins/draw/src/utils/spatial.js +243 -0
  203. package/plugins/draw/src/utils/spatial.test.js +243 -0
  204. package/plugins/draw/src/utils/touchTarget.js +95 -0
  205. package/plugins/draw/src/utils/touchTarget.test.js +107 -0
  206. package/plugins/draw/src/utils/undoStack.js +31 -0
  207. package/plugins/draw/src/utils/undoStack.test.js +49 -0
  208. package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
  209. package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
  210. package/plugins/draw/src/validation/liveStroke.js +85 -0
  211. package/plugins/draw/src/validation/liveStroke.test.js +150 -0
  212. package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
  213. package/plugins/draw/src/validation/rules.js +186 -0
  214. package/plugins/draw/src/validation/rules.test.js +120 -0
  215. package/plugins/draw/src/validation/validateGeometry.js +119 -0
  216. package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
  217. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  218. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  219. package/plugins/interact/src/hooks/useInteractionHandlers.js +1 -1
  220. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
  221. package/plugins/interact/src/utils/buildStylesMap.js +1 -0
  222. package/plugins/search/dist/css/index.css +1 -317
  223. package/rollup.esm.mjs +35 -28
  224. package/sonar-project.properties +2 -2
  225. package/src/App/components/Attributions/Attributions.module.scss +1 -0
  226. package/src/App/components/Hints/Hints.module.scss +5 -4
  227. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
  228. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
  229. package/src/App/components/MoveControl/MoveControl.jsx +73 -9
  230. package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
  231. package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
  232. package/src/App/hooks/useHintsAPI.js +67 -0
  233. package/src/App/hooks/useHintsAPI.test.js +145 -0
  234. package/src/App/renderer/PluginInits.jsx +4 -0
  235. package/src/App/renderer/PluginInits.test.jsx +7 -1
  236. package/src/InteractiveMap/InteractiveMap.js +20 -0
  237. package/src/InteractiveMap/InteractiveMap.test.js +7 -0
  238. package/src/config/events.js +4 -0
  239. package/src/scss/settings/_dimensions.scss +3 -0
  240. package/src/services/announcer.js +38 -3
  241. package/src/services/announcer.test.js +133 -1
  242. package/src/services/hints.js +3 -0
  243. package/src/services/hints.test.js +19 -0
  244. package/src/types.js +12 -0
  245. package/src/utils/detectInterfaceType.js +5 -1
  246. package/src/utils/isMac.js +15 -0
  247. package/src/utils/isMac.test.js +37 -0
  248. package/webpack.umd.mjs +1 -0
@@ -0,0 +1,249 @@
1
+ import MapboxDraw from '@mapbox/mapbox-gl-draw'
2
+ import { getSnapInstance, clearSnapIndicator } from '../utils/snapHelpers.js'
3
+ import { getCoords } from './editVertexMode/geometryHelpers.js'
4
+ import { scalePoint } from './editVertexMode/helpers.js'
5
+ import { undoHandlers } from './editVertexMode/undoHandlers.js'
6
+ import { touchHandlers } from './editVertexMode/touchHandlers.js'
7
+ import { vertexOperations } from './editVertexMode/vertexOperations.js'
8
+ import { vertexQueries } from './editVertexMode/vertexQueries.js'
9
+ import { keyboardHandlers } from './editVertexMode/keyboardHandlers.js'
10
+ import { pointerHandlers } from './editVertexMode/pointerHandlers.js'
11
+
12
+ const EVENT_VERTEX_SELECTION = 'draw.vertexselection'
13
+ const EVENT_NUDGE_VERTEX = 'draw.nudgevertex'
14
+
15
+ export const EditVertexMode = {
16
+ ...MapboxDraw.modes.direct_select,
17
+ ...undoHandlers,
18
+ ...touchHandlers,
19
+ ...vertexOperations,
20
+ ...vertexQueries,
21
+ ...keyboardHandlers,
22
+ ...pointerHandlers,
23
+
24
+ onSetup (options) {
25
+ const state = MapboxDraw.modes.direct_select.onSetup.call(this, options)
26
+ Object.assign(state, {
27
+ container: options.container,
28
+ interfaceType: options.interfaceType,
29
+ deleteVertexButtonId: options.deleteVertexButtonId,
30
+ undoButtonId: options.undoButtonId,
31
+ isPanEnabled: options.isPanEnabled,
32
+ getSnapEnabled: options.getSnapEnabled,
33
+ featureId: state.featureId,
34
+ selectedVertexIndex: options.selectedVertexIndex ?? -1,
35
+ selectedVertexType: options.selectedVertexType,
36
+ coordPath: options.coordPath,
37
+ scale: options.scale ?? 1
38
+ })
39
+
40
+ // Clear undo stack only on initial entry to edit mode for a feature
41
+ // Only clear if we're starting a new editing session (not already editing)
42
+ if (this.map._lastEditFeatureId !== state.featureId) {
43
+ this.map._undoStack?.clear()
44
+ this.map._lastEditFeatureId = state.featureId
45
+ }
46
+
47
+ // Get feature type for later reference
48
+ const feature = this.getFeature(state.featureId)
49
+ state.featureType = feature?.type
50
+
51
+ state.vertecies = this.getVerticies(state.featureId)
52
+ state.midpoints = this.getMidpoints(state.featureId)
53
+ this.setupEventListeners(state)
54
+
55
+ this.applyVertexSelection(state, options)
56
+ this.map._drawEditContainer = options.container
57
+ this.addTouchVertexTarget(state)
58
+
59
+ // Clear any snap indicator when entering edit mode
60
+ const snap = getSnapInstance(this.map)
61
+ if (snap) {
62
+ clearSnapIndicator(snap, this.map)
63
+ }
64
+
65
+ // Show touch target if entering with a selected vertex on touch interface
66
+ if (state.interfaceType === 'touch' && state.selectedVertexIndex >= 0 && state.selectedVertexType === 'vertex') {
67
+ const vertex = state.vertecies[state.selectedVertexIndex]
68
+ if (vertex) {
69
+ setTimeout(() => {
70
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
71
+ }, 0)
72
+ }
73
+ }
74
+
75
+ // Ignore pointermove deselection briefly after setup to let Safari settle
76
+ state._ignorePointermoveDeselect = true
77
+ setTimeout(() => { state._ignorePointermoveDeselect = false }, 100)
78
+
79
+ return state
80
+ },
81
+
82
+ setupEventListeners (state) {
83
+ const bind = (fn) => (e) => fn.call(this, state, e)
84
+ const h = this.handlers = {
85
+ keydown: bind(this.onKeydown),
86
+ keyup: bind(this.onKeyup),
87
+ pointerdown: bind(this.onPointerevent),
88
+ pointermove: bind(this.onPointerevent),
89
+ pointerup: bind(this.onPointerevent),
90
+ click: bind(this.onButtonClick),
91
+ touchstart: bind(this.onTouchstart),
92
+ touchmove: bind(this.onTouchmove),
93
+ touchend: bind(this.onTouchend),
94
+ selectionchange: bind(this.onSelectionChange),
95
+ scalechange: bind(this.onScaleChange),
96
+ update: bind(this.onUpdate),
97
+ move: bind(this.onMove),
98
+ interfacetypechange: bind(this.onInterfaceTypeChange),
99
+ nudgevertex: bind(this.onNudgeVertex)
100
+ }
101
+
102
+ window.addEventListener('keydown', h.keydown, { capture: true })
103
+ window.addEventListener('keyup', h.keyup, { capture: true })
104
+ window.addEventListener('click', h.click)
105
+ state.container.addEventListener('pointerdown', h.pointerdown)
106
+ state.container.addEventListener('pointermove', h.pointermove)
107
+ state.container.addEventListener('pointerup', h.pointerup)
108
+ state.container.addEventListener('touchstart', h.touchstart, { passive: false })
109
+ state.container.addEventListener('touchmove', h.touchmove, { passive: false })
110
+ state.container.addEventListener('touchend', h.touchend, { passive: false })
111
+ this.map.on('draw.selectionchange', h.selectionchange)
112
+ this.map.on('draw.scalechange', h.scalechange)
113
+ this.map.on('draw.update', h.update)
114
+ this.map.on('move', h.move)
115
+ this.map.on('draw.interfacetypechange', h.interfacetypechange)
116
+ this.map.on(EVENT_NUDGE_VERTEX, h.nudgevertex)
117
+ },
118
+
119
+ applyVertexSelection (state, options) {
120
+ if (options.selectedVertexType === 'midpoint') {
121
+ state.selectedCoordPaths = []
122
+ this.clearSelectedCoordinates()
123
+ state.feature.changed()
124
+ this._ctx.store.render()
125
+ this.updateMidpoint(state.midpoints[options.selectedVertexIndex - state.vertecies.length])
126
+ return
127
+ }
128
+ if (options.selectedVertexIndex === -1) {
129
+ state.selectedCoordPaths = []
130
+ this.clearSelectedCoordinates()
131
+ state.feature.changed()
132
+ this._ctx.store.render()
133
+ }
134
+ },
135
+
136
+ onSelectionChange (state, e) {
137
+ // Refresh vertex list so numVertecies reflects the latest geometry (e.g. after midpoint insertion)
138
+ this.syncVertices(state)
139
+
140
+ const vertexCoord = e.points[e.points.length - 1]?.geometry.coordinates
141
+
142
+ // Only update selectedVertexIndex from event if not keyboard mode AND event has valid vertex
143
+ // For keyboard mode or when we have coordPath, trust the existing selectedVertexIndex
144
+ if (state.interfaceType !== 'keyboard' && vertexCoord && !state.coordPath) {
145
+ // No coordPath available - need to search for vertex by coordinates
146
+ const geom = e.features[0]?.geometry
147
+ const coords = getCoords(geom)
148
+ state.selectedVertexIndex = this.findVertexIndex(coords, vertexCoord, state.selectedVertexIndex)
149
+ }
150
+ // If we have coordPath, selectedVertexIndex is already correct from onTap/changeMode
151
+
152
+ state.selectedVertexType ??= state.selectedVertexIndex >= 0 ? 'vertex' : null
153
+
154
+ this.map.fire(EVENT_VERTEX_SELECTION, {
155
+ index: state.selectedVertexType === 'vertex' ? state.selectedVertexIndex : -1,
156
+ numVertecies: state.vertecies.length
157
+ })
158
+
159
+ // Use vertex from event if available, otherwise fall back to state
160
+ const vertex = vertexCoord || (state.selectedVertexIndex >= 0 ? state.vertecies[state.selectedVertexIndex] : null)
161
+ this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
162
+ },
163
+
164
+ onScaleChange (state, e) {
165
+ state.scale = e.scale
166
+ },
167
+
168
+ onInterfaceTypeChange (state, e) {
169
+ state.interfaceType = e.interfaceType
170
+ const vertex = state.selectedVertexIndex >= 0 ? state.vertecies[state.selectedVertexIndex] : null
171
+ this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
172
+ },
173
+
174
+ onUpdate (state) {
175
+ const prev = new Set(state.vertecies.map(c => JSON.stringify(c)))
176
+ if (prev.size === state.vertecies.length) {
177
+ return
178
+ }
179
+ // Duplicate coordinates exist (e.g. a self-touching ring). Comparing the list
180
+ // against itself cannot surface a distinct new vertex, so clear the selection.
181
+ state.selectedVertexIndex = -1
182
+ state.selectedVertexType ??= null
183
+ },
184
+
185
+ onMove (state) {
186
+ const vertex = state.vertecies[state.selectedVertexIndex]
187
+ if (vertex) {
188
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
189
+ }
190
+ },
191
+
192
+ // Inbound signal from MaplibreDrawAdapter.nudgeSelectedVertex — bridges into the
193
+ // running mode the same way onInterfaceTypeChange does, since the adapter has no
194
+ // direct reference to this mode's live state. Unlike OL (where setState's shared
195
+ // onUpdate hook repositions the touch target for any vertex move), moveVertex
196
+ // here has no equivalent hook, so the reposition has to happen explicitly —
197
+ // same as onMove/onSelectionChange/onInterfaceTypeChange already do.
198
+ onNudgeVertex (state, e) {
199
+ this.nudgeVertexByDelta(state, e.dx, e.dy, e.isLargeStep)
200
+ const vertex = state.vertecies[state.selectedVertexIndex]
201
+ if (vertex) {
202
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
203
+ }
204
+ },
205
+
206
+ onButtonClick (state, e) {
207
+ if (e.target.closest(`#${state.deleteVertexButtonId}`) && state.selectedVertexType === 'vertex') {
208
+ this.deleteVertex(state)
209
+ }
210
+ if (e.target.closest(`#${state.undoButtonId}`)) {
211
+ this.handleUndo(state)
212
+ }
213
+ },
214
+
215
+ clickNoTarget (state) {
216
+ this.changeMode(state, { selectedVertexIndex: -1, selectedVertexType: null, isPanEnabled: true })
217
+ },
218
+
219
+ // Prevent selecting other features
220
+ changeMode (state, updates) {
221
+ if (!state.featureId) {
222
+ return
223
+ }
224
+ this._ctx.api.changeMode('edit_vertex', { ...state, ...updates })
225
+ },
226
+
227
+ onStop (state) {
228
+ this.map._drawEditContainer = null
229
+ this.map._editingFeatureId = null
230
+ const h = this.handlers
231
+ state.container.removeEventListener('pointerdown', h.pointerdown)
232
+ state.container.removeEventListener('pointermove', h.pointermove)
233
+ state.container.removeEventListener('pointerup', h.pointerup)
234
+ state.container.removeEventListener('touchstart', h.touchstart)
235
+ state.container.removeEventListener('touchmove', h.touchmove)
236
+ state.container.removeEventListener('touchend', h.touchend)
237
+ this.map.off('draw.selectionchange', h.selectionchange)
238
+ this.map.off('draw.scalechange', h.scalechange)
239
+ this.map.off('draw.update', h.update)
240
+ this.map.off('move', h.move)
241
+ this.map.off('draw.interfacetypechange', h.interfacetypechange)
242
+ this.map.off(EVENT_NUDGE_VERTEX, h.nudgevertex)
243
+ this.map.dragPan.enable()
244
+ window.removeEventListener('click', h.click)
245
+ window.removeEventListener('keydown', h.keydown, { capture: true })
246
+ window.removeEventListener('keyup', h.keyup, { capture: true })
247
+ this.hideTouchVertexIndicator(state)
248
+ }
249
+ }
@@ -0,0 +1,153 @@
1
+ import { createHarness, POLYGON } from './editVertexMode/__helpers__/harness.js'
2
+
3
+ /**
4
+ * Tests for EditVertexMode's own methods: setup/teardown lifecycle, selection/scale/update
5
+ * events, and the small move/button/changeMode routing. The keyboard, pointer, touch, undo,
6
+ * vertex-query, vertex-operation and geometry helpers are covered in their own colocated
7
+ * test files under editVertexMode/.
8
+ */
9
+
10
+ describe('onSetup / onStop lifecycle', () => {
11
+ test('populates state, clears the undo stack for a new feature and registers listeners', () => {
12
+ const { ctx, state, map } = createHarness()
13
+ expect(state.featureId).toBe('feat-1')
14
+ // Real PolygonFeature stores rings without the duplicate closing coordinate
15
+ expect(state.vertecies).toHaveLength(4)
16
+ expect(state.midpoints).toHaveLength(4)
17
+ expect(map._lastEditFeatureId).toBe('feat-1')
18
+ expect(map._drawEditContainer).toBe(state.container)
19
+ expect(ctx.map.on).toHaveBeenCalledWith('draw.update', expect.any(Function))
20
+ expect(ctx.map.on).toHaveBeenCalledWith('draw.nudgevertex', expect.any(Function))
21
+ })
22
+
23
+ test('does not clear the undo stack when re-entering the same feature', () => {
24
+ const { ctx, map, options } = createHarness()
25
+ map._undoStack.push({ type: 'move_vertex' })
26
+ // map._lastEditFeatureId is already 'feat-1' from the first setup
27
+ ctx.onSetup(options)
28
+ expect(map._undoStack.length).toBe(1)
29
+ })
30
+
31
+ test('onStop removes listeners and clears editing container; DirectSelect touch stubs are inert', () => {
32
+ const { ctx, state, map } = createHarness()
33
+ expect(() => { ctx.onTouchStart(); ctx.onTouchMove(); ctx.onTouchEnd() }).not.toThrow()
34
+ ctx.onStop(state)
35
+ expect(map._drawEditContainer).toBeNull()
36
+ expect(map.off).toHaveBeenCalledWith('draw.update', expect.any(Function))
37
+ expect(map.off).toHaveBeenCalledWith('draw.nudgevertex', expect.any(Function))
38
+ })
39
+
40
+ test('onSetup edge cases: clears an active snap indicator, positions or skips the touch target, clears an explicit -1 selection', () => {
41
+ jest.useFakeTimers()
42
+ const { ctx, map, options } = createHarness()
43
+ map._snapInstance = { status: true, snapStatus: false, snapCoords: null }
44
+ ctx.onSetup(options)
45
+ expect(map.getLayer).toHaveBeenCalledWith('snap-helper-circle')
46
+
47
+ const touch = createHarness(POLYGON(), { interfaceType: 'touch', selectedVertexIndex: 0, selectedVertexType: 'vertex' })
48
+ jest.runAllTimers()
49
+ expect(touch.state.touchVertexTarget.style.display).toBe('block')
50
+
51
+ const outOfRange = createHarness(POLYGON(), { interfaceType: 'touch', selectedVertexIndex: 99, selectedVertexType: 'vertex' })
52
+ jest.runAllTimers()
53
+ expect(outOfRange.state.touchVertexTarget.style.display).toBe('none')
54
+
55
+ const cleared = createHarness(POLYGON(), { selectedVertexIndex: -1 })
56
+ expect(cleared.ctx.clearSelectedCoordinates).toHaveBeenCalled()
57
+ })
58
+ })
59
+
60
+ describe('selection, scale and update events', () => {
61
+ test('applyVertexSelection handles midpoint entry and cleared selection', () => {
62
+ jest.useFakeTimers()
63
+ const midpoint = createHarness(POLYGON(), { selectedVertexType: 'midpoint', selectedVertexIndex: 4 })
64
+ jest.runAllTimers()
65
+ expect(midpoint.ctx.clearSelectedCoordinates).toHaveBeenCalled()
66
+ expect(midpoint.map.getSource).toHaveBeenCalledWith('mapbox-gl-draw-hot')
67
+ })
68
+
69
+ test('onSelectionChange searches by coordinate for mouse, trusts coordPath/keyboard otherwise', () => {
70
+ const { ctx, state, map } = createHarness()
71
+ const geom = ctx.getFeature('feat-1').toGeoJSON().geometry
72
+ ctx.onSelectionChange(state, { points: [{ geometry: { coordinates: [10, 0] } }], features: [{ geometry: geom }] })
73
+ expect(state.selectedVertexIndex).toBe(1)
74
+ expect(map.fire).toHaveBeenCalledWith('draw.vertexselection', expect.objectContaining({ numVertecies: 4 }))
75
+
76
+ state.coordPath = '0.2'
77
+ state.selectedVertexIndex = 2
78
+ ctx.onSelectionChange(state, { points: [{ geometry: { coordinates: [10, 10] } }], features: [{ geometry: geom }] })
79
+ expect(state.selectedVertexIndex).toBe(2) // trusted, not re-searched
80
+
81
+ // Keyboard mode trusts the selection; with no event vertex it falls back to state, then null
82
+ const kb = createHarness()
83
+ const g2 = kb.ctx.getFeature('feat-1').toGeoJSON().geometry
84
+ kb.state.interfaceType = 'keyboard'
85
+ kb.state.selectedVertexIndex = 2
86
+ kb.ctx.onSelectionChange(kb.state, { points: [{ geometry: { coordinates: [10, 10] } }], features: [{ geometry: g2 }] })
87
+ expect(kb.state.selectedVertexType).toBe('vertex')
88
+ kb.ctx.onSelectionChange({ ...kb.state, interfaceType: 'mouse', selectedVertexType: 'vertex', selectedVertexIndex: 0 }, { points: [], features: [{ geometry: g2 }] })
89
+ kb.ctx.onSelectionChange({ ...kb.state, interfaceType: 'mouse', selectedVertexType: null, selectedVertexIndex: -1 }, { points: [], features: [{ geometry: g2 }] })
90
+ })
91
+
92
+ test('onScaleChange and onInterfaceTypeChange update state and the touch target', () => {
93
+ const { ctx, state } = createHarness()
94
+ ctx.onScaleChange(state, { scale: 2 })
95
+ expect(state.scale).toBe(2)
96
+ state.selectedVertexIndex = 0
97
+ ctx.onInterfaceTypeChange(state, { interfaceType: 'touch' })
98
+ expect(state.interfaceType).toBe('touch')
99
+ ctx.onInterfaceTypeChange({ ...state, selectedVertexIndex: -1 }, { interfaceType: 'mouse' })
100
+ })
101
+
102
+ test('draw.nudgevertex moves the selected vertex and repositions the touch target — the inbound bridge for MoveControl.mapProvider.activeMoveTarget', () => {
103
+ jest.useFakeTimers()
104
+ const { state, map } = createHarness(POLYGON(), { interfaceType: 'touch', selectedVertexIndex: 0, selectedVertexType: 'vertex' })
105
+ jest.runAllTimers() // flush onSetup's deferred initial touch-target positioning
106
+ const before = [...state.vertecies[0]]
107
+ const targetBefore = { top: state.touchVertexTarget.style.top, left: state.touchVertexTarget.style.left }
108
+ map.fire('draw.nudgevertex', { dx: 1, dy: 0, isLargeStep: true })
109
+ expect(state.vertecies[0]).not.toEqual(before)
110
+ expect({ top: state.touchVertexTarget.style.top, left: state.touchVertexTarget.style.left }).not.toEqual(targetBefore)
111
+ })
112
+
113
+ test('onUpdate re-selects a changed vertex only when the vertex count is ambiguous', () => {
114
+ const { ctx, state } = createHarness()
115
+ ctx.onUpdate(state) // unique coords → no change
116
+ state.vertecies = [[0, 0], [0, 0], [1, 1]]
117
+ ctx.onUpdate(state)
118
+ expect(state.selectedVertexIndex).toBe(-1)
119
+ })
120
+ })
121
+
122
+ describe('move, button and changeMode routing', () => {
123
+ test('onMove keeps the touch target aligned with the selected vertex, ignoring an unselected vertex', () => {
124
+ const { ctx, state } = createHarness()
125
+ state.selectedVertexIndex = 0
126
+ state.interfaceType = 'touch'
127
+ ctx.onMove(state)
128
+ expect(state.touchVertexTarget.style.display).toBe('block')
129
+ expect(() => ctx.onMove({ ...state, selectedVertexIndex: -1 })).not.toThrow()
130
+ })
131
+
132
+ test('onButtonClick deletes or undoes based on the clicked control', () => {
133
+ const { ctx, state, container } = createHarness()
134
+ const del = document.createElement('button')
135
+ del.id = 'delete-vertex'
136
+ const undo = document.createElement('button')
137
+ undo.id = 'undo-vertex'
138
+ container.append(del, undo)
139
+ const deleteSpy = jest.spyOn(ctx, 'deleteVertex').mockImplementation(() => {})
140
+ const undoSpy = jest.spyOn(ctx, 'handleUndo').mockImplementation(() => {})
141
+
142
+ ctx.onButtonClick({ ...state, selectedVertexType: 'vertex' }, { target: del })
143
+ expect(deleteSpy).toHaveBeenCalled()
144
+ ctx.onButtonClick(state, { target: undo })
145
+ expect(undoSpy).toHaveBeenCalled()
146
+ })
147
+
148
+ test('changeMode is a no-op without a feature id', () => {
149
+ const { ctx, api } = createHarness()
150
+ ctx.changeMode({ featureId: null }, { selectedVertexIndex: -1 })
151
+ expect(api.changeMode).not.toHaveBeenCalled()
152
+ })
153
+ })
@@ -0,0 +1,2 @@
1
+ export const SNAP_HELPER_LAYER = 'snap-helper-circle'
2
+ export const DRAW_HOT_SOURCE = 'mapbox-gl-draw-hot'
@@ -0,0 +1,39 @@
1
+ import { pollUntil, patchSourceData } from './sourceData.js'
2
+ import { createSnapInstance, ensureSnapLayer } from './snapInstance.js'
3
+ import { SNAP_HELPER_LAYER, DRAW_HOT_SOURCE } from './constants.js'
4
+
5
+ /** Re-patch the source and restore the snap layer whenever the style reloads */
6
+ export function registerStyleLoadHandler (map, draw, config) {
7
+ map.on('style.load', () => {
8
+ pollUntil(
9
+ () => map._removed ? null : map.getSource(DRAW_HOT_SOURCE),
10
+ (source) => {
11
+ patchSourceData(source)
12
+ ensureSnapLayer(map)
13
+ if (!map._snapInstance) {
14
+ createSnapInstance(map, draw, source, config)
15
+ }
16
+ }
17
+ )
18
+ })
19
+ }
20
+
21
+ /** Suppress snap processing during zoom and reset the indicator afterwards */
22
+ export function registerZoomHandlers (map) {
23
+ // Suppress snap processing during zoom (indicator freezes in place)
24
+ map.on('zoomstart', () => {
25
+ map._isZooming = true
26
+ })
27
+
28
+ map.on('zoomend', () => {
29
+ map._isZooming = false
30
+ // Force hide then re-show to reset indicator at new zoom level (Safari fix)
31
+ if (map.getLayer(SNAP_HELPER_LAYER)) {
32
+ map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', 'none')
33
+ const snap = map._snapInstance
34
+ if (snap?.status) {
35
+ map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', 'visible')
36
+ }
37
+ }
38
+ })
39
+ }
@@ -0,0 +1,106 @@
1
+ import { registerStyleLoadHandler, registerZoomHandlers } from './mapHandlers.js'
2
+ import { createSnapInstance, ensureSnapLayer } from './snapInstance.js'
3
+ import { patchSourceData } from './sourceData.js'
4
+
5
+ const SNAP_LAYER = 'snap-helper-circle'
6
+ const DRAW_SOURCE = 'mapbox-gl-draw-hot'
7
+
8
+ jest.mock('./snapInstance.js', () => ({
9
+ createSnapInstance: jest.fn(),
10
+ ensureSnapLayer: jest.fn()
11
+ }))
12
+
13
+ jest.mock('./sourceData.js', () => ({
14
+ ...jest.requireActual('./sourceData.js'),
15
+ patchSourceData: jest.fn()
16
+ }))
17
+
18
+ const config = { layers: [], radius: 12, rules: [], status: false, onSnapped: () => {} }
19
+
20
+ const handlerFor = (map, name) => map.on.mock.calls.find(([event]) => event === name)?.[1]
21
+
22
+ const makeMap = (overrides = {}) => ({
23
+ on: jest.fn(),
24
+ getSource: jest.fn((id) => (id === DRAW_SOURCE ? { id: 'hot' } : null)),
25
+ getLayer: jest.fn(() => null),
26
+ setLayoutProperty: jest.fn(),
27
+ ...overrides
28
+ })
29
+
30
+ beforeEach(() => {
31
+ jest.clearAllMocks()
32
+ global.requestAnimationFrame = jest.fn()
33
+ })
34
+
35
+ describe('registerStyleLoadHandler', () => {
36
+ test('re-patches the source, ensures the layer and creates a missing instance', () => {
37
+ const map = makeMap()
38
+ const draw = { id: 'draw' }
39
+
40
+ registerStyleLoadHandler(map, draw, config)
41
+ handlerFor(map, 'style.load')()
42
+
43
+ expect(patchSourceData).toHaveBeenCalledWith({ id: 'hot' })
44
+ expect(ensureSnapLayer).toHaveBeenCalledWith(map)
45
+ expect(createSnapInstance).toHaveBeenCalledWith(map, draw, { id: 'hot' }, config)
46
+ })
47
+
48
+ test('does not recreate an existing instance', () => {
49
+ const map = makeMap({ _snapInstance: { id: 'exists' } })
50
+
51
+ registerStyleLoadHandler(map, {}, config)
52
+ handlerFor(map, 'style.load')()
53
+
54
+ expect(ensureSnapLayer).toHaveBeenCalledWith(map)
55
+ expect(createSnapInstance).not.toHaveBeenCalled()
56
+ })
57
+
58
+ test('stops when the map has been removed', () => {
59
+ const map = makeMap({ _removed: true })
60
+
61
+ registerStyleLoadHandler(map, {}, config)
62
+ handlerFor(map, 'style.load')()
63
+
64
+ expect(ensureSnapLayer).not.toHaveBeenCalled()
65
+ expect(createSnapInstance).not.toHaveBeenCalled()
66
+ })
67
+ })
68
+
69
+ describe('registerZoomHandlers', () => {
70
+ test('zoomstart sets the zooming flag', () => {
71
+ const map = makeMap()
72
+ registerZoomHandlers(map)
73
+ handlerFor(map, 'zoomstart')()
74
+ expect(map._isZooming).toBe(true)
75
+ })
76
+
77
+ test('zoomend clears the flag and re-shows the indicator when snapping is active', () => {
78
+ const map = makeMap({ getLayer: jest.fn(() => ({ id: 'layer' })), _snapInstance: { status: true } })
79
+
80
+ registerZoomHandlers(map)
81
+ handlerFor(map, 'zoomend')()
82
+
83
+ expect(map._isZooming).toBe(false)
84
+ expect(map.setLayoutProperty).toHaveBeenCalledWith(SNAP_LAYER, 'visibility', 'none')
85
+ expect(map.setLayoutProperty).toHaveBeenCalledWith(SNAP_LAYER, 'visibility', 'visible')
86
+ })
87
+
88
+ test('zoomend hides but does not re-show when snapping is disabled', () => {
89
+ const map = makeMap({ getLayer: jest.fn(() => ({ id: 'layer' })), _snapInstance: { status: false } })
90
+
91
+ registerZoomHandlers(map)
92
+ handlerFor(map, 'zoomend')()
93
+
94
+ expect(map.setLayoutProperty).toHaveBeenCalledTimes(1)
95
+ expect(map.setLayoutProperty).toHaveBeenCalledWith(SNAP_LAYER, 'visibility', 'none')
96
+ })
97
+
98
+ test('zoomend does nothing when the snap layer is absent', () => {
99
+ const map = makeMap({ getLayer: jest.fn(() => null) })
100
+
101
+ registerZoomHandlers(map)
102
+ handlerFor(map, 'zoomend')()
103
+
104
+ expect(map.setLayoutProperty).not.toHaveBeenCalled()
105
+ })
106
+ })