@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.
Files changed (247) 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-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  15. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  16. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  17. package/plugins/beta/map-styles/dist/css/index.css +1 -97
  18. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  19. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  20. package/plugins/datasets/src/registry/dataset.js +3 -1
  21. package/plugins/datasets/src/registry/datasetRegistry.js +30 -24
  22. package/plugins/datasets/src/registry/datasetRegistry.test.js +25 -18
  23. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
  24. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
  25. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
  26. package/plugins/draw/dist/esm/index.js +1 -0
  27. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
  28. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
  29. package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
  30. package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
  31. package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
  32. package/plugins/draw/dist/umd/index.js +2 -0
  33. package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
  34. package/plugins/draw/src/DrawInit.jsx +89 -0
  35. package/plugins/draw/src/DrawInit.test.jsx +184 -0
  36. package/plugins/draw/src/adapterEvents.js +127 -0
  37. package/plugins/draw/src/adapterEvents.test.js +20 -0
  38. package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
  39. package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
  40. package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
  41. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
  42. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
  43. package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
  44. package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
  45. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
  46. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
  47. package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
  48. package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
  49. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
  50. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
  51. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
  52. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
  53. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
  54. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
  55. package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
  56. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
  57. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
  58. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
  59. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
  60. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
  61. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
  62. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
  63. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
  64. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
  65. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
  66. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
  67. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
  68. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
  69. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
  70. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
  71. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
  72. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
  74. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
  75. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
  89. package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
  90. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
  91. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
  92. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
  93. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
  94. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
  95. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
  96. package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
  97. package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
  98. package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
  99. package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
  100. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
  101. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
  102. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
  103. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
  104. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
  105. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
  106. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
  107. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
  108. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
  109. package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
  110. package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
  111. package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
  112. package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
  113. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
  114. package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
  115. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
  116. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
  117. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
  118. package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
  119. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
  120. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
  121. package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
  122. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
  123. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
  124. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
  125. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
  126. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
  127. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
  128. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
  129. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
  130. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
  132. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
  134. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
  136. package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
  137. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
  139. package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
  140. package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
  141. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
  142. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
  143. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
  144. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
  145. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
  146. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
  147. package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
  148. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
  149. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
  150. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
  151. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
  153. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
  154. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
  155. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
  156. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
  157. package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
  158. package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
  159. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
  160. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
  161. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
  162. package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
  163. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
  164. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
  165. package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
  166. package/plugins/draw/src/api/addFeature.js +22 -0
  167. package/plugins/draw/src/api/addFeature.test.js +41 -0
  168. package/plugins/draw/src/api/deleteFeature.js +11 -0
  169. package/plugins/draw/src/api/deleteFeature.test.js +24 -0
  170. package/plugins/draw/src/api/editFeature.js +54 -0
  171. package/plugins/draw/src/api/editFeature.test.js +134 -0
  172. package/plugins/draw/src/api/merge.js +30 -0
  173. package/plugins/draw/src/api/merge.test.js +57 -0
  174. package/plugins/draw/src/api/newLine.js +41 -0
  175. package/plugins/draw/src/api/newLine.test.js +93 -0
  176. package/plugins/draw/src/api/newPolygon.js +41 -0
  177. package/plugins/draw/src/api/newPolygon.test.js +93 -0
  178. package/plugins/draw/src/api/split.js +117 -0
  179. package/plugins/draw/src/api/split.test.js +204 -0
  180. package/plugins/draw/src/defaults.js +52 -0
  181. package/plugins/draw/src/defaults.test.js +18 -0
  182. package/plugins/draw/src/draw.scss +43 -0
  183. package/plugins/draw/src/events.js +237 -0
  184. package/plugins/draw/src/events.test.js +369 -0
  185. package/plugins/draw/src/index.js +10 -0
  186. package/plugins/draw/src/index.test.js +24 -0
  187. package/plugins/draw/src/manifest.js +170 -0
  188. package/plugins/draw/src/manifest.test.js +147 -0
  189. package/plugins/draw/src/reducer.js +69 -0
  190. package/plugins/draw/src/reducer.test.js +85 -0
  191. package/plugins/draw/src/utils/debounce.js +16 -0
  192. package/plugins/draw/src/utils/debounce.test.js +40 -0
  193. package/plugins/draw/src/utils/eventBus.js +36 -0
  194. package/plugins/draw/src/utils/eventBus.test.js +47 -0
  195. package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
  196. package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
  197. package/plugins/draw/src/utils/getValueForStyle.js +33 -0
  198. package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
  199. package/plugins/draw/src/utils/resolveColors.js +39 -0
  200. package/plugins/draw/src/utils/resolveColors.test.js +34 -0
  201. package/plugins/draw/src/utils/spatial.js +243 -0
  202. package/plugins/draw/src/utils/spatial.test.js +243 -0
  203. package/plugins/draw/src/utils/touchTarget.js +95 -0
  204. package/plugins/draw/src/utils/touchTarget.test.js +107 -0
  205. package/plugins/draw/src/utils/undoStack.js +31 -0
  206. package/plugins/draw/src/utils/undoStack.test.js +49 -0
  207. package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
  208. package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
  209. package/plugins/draw/src/validation/liveStroke.js +85 -0
  210. package/plugins/draw/src/validation/liveStroke.test.js +150 -0
  211. package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
  212. package/plugins/draw/src/validation/rules.js +186 -0
  213. package/plugins/draw/src/validation/rules.test.js +120 -0
  214. package/plugins/draw/src/validation/validateGeometry.js +119 -0
  215. package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
  216. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  217. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  218. package/plugins/interact/src/hooks/useInteractionHandlers.js +1 -1
  219. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
  220. package/plugins/interact/src/utils/buildStylesMap.js +1 -0
  221. package/plugins/search/dist/css/index.css +1 -317
  222. package/rollup.esm.mjs +35 -28
  223. package/sonar-project.properties +2 -2
  224. package/src/App/components/Attributions/Attributions.module.scss +1 -0
  225. package/src/App/components/Hints/Hints.module.scss +5 -4
  226. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
  227. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
  228. package/src/App/components/MoveControl/MoveControl.jsx +73 -9
  229. package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
  230. package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
  231. package/src/App/hooks/useHintsAPI.js +67 -0
  232. package/src/App/hooks/useHintsAPI.test.js +145 -0
  233. package/src/App/renderer/PluginInits.jsx +4 -0
  234. package/src/App/renderer/PluginInits.test.jsx +7 -1
  235. package/src/InteractiveMap/InteractiveMap.js +20 -0
  236. package/src/InteractiveMap/InteractiveMap.test.js +7 -0
  237. package/src/config/events.js +4 -0
  238. package/src/scss/settings/_dimensions.scss +3 -0
  239. package/src/services/announcer.js +38 -3
  240. package/src/services/announcer.test.js +133 -1
  241. package/src/services/hints.js +3 -0
  242. package/src/services/hints.test.js +19 -0
  243. package/src/types.js +12 -0
  244. package/src/utils/detectInterfaceType.js +5 -1
  245. package/src/utils/isMac.js +15 -0
  246. package/src/utils/isMac.test.js +37 -0
  247. package/webpack.umd.mjs +1 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * What stage of the draw/edit lifecycle a geometry validation call is running at.
3
+ * Passed as `context.phase` through validateGeometry (validation/validateGeometry.js)
4
+ * to the default rules (validation/rules.js) and the user's `onGeometryChange` callback.
5
+ *
6
+ * @typedef {'preview' | 'place' | 'create' | 'edit-start' |
7
+ * 'commit-add' | 'commit-move' | 'commit-insert' | 'commit-delete'} GeometryChangePhase
8
+ *
9
+ * 'preview' - live, in-progress feedback (drag / rubber-band); nothing
10
+ * has committed yet, and nothing can be vetoed at this stage.
11
+ * While drawing, this single check also drives the
12
+ * Add-point button (see liveDrawChecks.js) — it isn't only
13
+ * a display concern, just never a commit/veto one.
14
+ * 'place' - a candidate vertex is genuinely about to be committed
15
+ * (a real click/tap/Add-point press), evaluated once,
16
+ * synchronously; a HARD_RULES failure vetoes it outright
17
+ * and it never appears. Never fires continuously — that's
18
+ * 'preview''s job (see above).
19
+ * 'create' - a whole new feature just finished being drawn.
20
+ * 'edit-start' - an existing feature was just loaded into an edit session;
21
+ * nothing has changed yet, this is the baseline check.
22
+ * 'commit-add' - a vertex was added while drawing.
23
+ * 'commit-move' - a vertex was dragged/nudged while editing.
24
+ * 'commit-insert' - a vertex was inserted at a midpoint while editing.
25
+ * 'commit-delete' - a vertex was removed while editing.
26
+ */
27
+
28
+ /**
29
+ * The single event object passed to a user's `onGeometryChange` callback
30
+ * (validateGeometry.js) — `onGeometryChange({ feature, ...context })`. Not every
31
+ * field is present on every phase; see each field's own doc below.
32
+ *
33
+ * While drawing, every rubber-band mouse-move invokes this callback ONCE,
34
+ * throttled to one call per animation frame, always with `phase: 'preview'` —
35
+ * even before any vertex is committed, since a location-based rule can be
36
+ * meaningful against a single candidate point. Its verdict is combined
37
+ * (internally, in liveDrawChecks.js) with two independently-floored built-in
38
+ * checks to drive BOTH the dashed stroke / Done gate AND the Add-point button —
39
+ * you don't need to do anything to support both; they share this one call. A
40
+ * separate, genuinely momentary call with `phase: 'place'` (see
41
+ * GeometryChangePhase) fires only for a real placement attempt, evaluated
42
+ * synchronously, never continuously. Earlier versions of this plugin called
43
+ * the callback twice per mouse-move (once per phase, for the same instant) —
44
+ * that's been unified into the single 'preview' call described here.
45
+ *
46
+ * @typedef {object} GeometryChangeEvent
47
+ * @property {object} feature - GeoJSON feature representing the shape at this
48
+ * point. Always present. During 'preview' this is the in-progress feature
49
+ * (placed vertices + rubber-band cursor); at every other phase it's the
50
+ * committed/candidate feature for that specific change.
51
+ * @property {GeometryChangePhase} phase - what stage of the draw/edit lifecycle
52
+ * this is. Always present.
53
+ * @property {'draw_polygon' | 'draw_line' | 'edit_vertex'} mode - the active draw
54
+ * mode. Always present.
55
+ * @property {number} [vertexIndex] - index of the vertex being placed, added,
56
+ * moved, inserted, or deleted. Present on 'place' and every 'commit-*' phase;
57
+ * absent on 'preview', 'create', and 'edit-start' (those have no single vertex
58
+ * to point at).
59
+ * @property {number} [numVertices] - count of already-committed vertices,
60
+ * excluding any in-progress rubber-band cursor point. Present on every
61
+ * 'preview' call, with no minimum — including 0, against the very first
62
+ * candidate point, before any vertex is committed. Note this is a lower bar
63
+ * than the built-in live rules, which stay skipped until the geometry type's
64
+ * minimum vertex count is reached (self-intersection/area are meaningless
65
+ * below that).
66
+ */
67
+
68
+ /**
69
+ * Shared adapter event contract.
70
+ *
71
+ * Both draw adapters (MaplibreDrawAdapter, OLDrawAdapter) expose an on/off bus
72
+ * that emits these events. events.js and the api entry points consume them, so
73
+ * the names and payload shapes below are the contract between the adapters and
74
+ * the framework-agnostic plugin layer.
75
+ *
76
+ * Engine-specific event names stay with their adapter — e.g. the map-level
77
+ * `draw.*` events mapbox-gl-draw fires live in adapters/maplibre/drawEvents.js,
78
+ * and the MapLibre adapter normalises them onto this contract.
79
+ *
80
+ * Payloads:
81
+ * CREATE GeoJSON feature that was drawn
82
+ * EDIT_FINISH GeoJSON feature after editing
83
+ * CANCEL (none)
84
+ * VERTEX_SELECTION { index, numVertices }
85
+ * VERTEX_CHANGE { numVertices }
86
+ * UNDO_CHANGE number — current undo stack length
87
+ * UPDATE GeoJSON feature after a vertex operation
88
+ * GEOMETRY_CHANGE Two payload shapes share this event:
89
+ * - Preview (draw/split live update): the in-progress
90
+ * feature itself (has `coordinates`, no `phase`).
91
+ * - Commit-level validation: `{ feature, phase, vertexIndex }`
92
+ * where phase ∈ 'commit-add' | 'commit-move' |
93
+ * 'commit-insert' | 'commit-delete' (see GeometryChangePhase).
94
+ * events.js validates these and reverts invalid ones.
95
+ * Listeners must guard for the shape they expect.
96
+ * INTERFACE_TYPE_CHANGE { interfaceType: 'mouse' | 'touch' | 'keyboard' }
97
+ * PLACEMENT_BLOCKED { feature, reason, phase: 'place', mode, vertexIndex } —
98
+ * a REAL placement attempt (click/tap/Add-point press)
99
+ * was rejected by validatePlacement (hard rule or user
100
+ * callback), evaluated once, synchronously; feature is
101
+ * the candidate geometry that was refused. Distinct from
102
+ * CAN_PLACE_CHANGE below, which is a continuous forecast
103
+ * of this same outcome, not an attempt.
104
+ * VALIDITY_CHANGE { valid, reason } — the live validity of the displayed
105
+ * shape flipped while editing (drag/nudge in progress).
106
+ * Drives the Done gate in edit mode, where the displayed
107
+ * shape is exactly what Done finishes. Fires on flips only.
108
+ * CAN_PLACE_CHANGE { canPlace, reason } — whether placing a vertex at the
109
+ * crosshair would be vetoed flipped while drawing. Drives
110
+ * the Add-point button. Derived from the same 'preview'
111
+ * check that drives the dashed stroke (liveDrawChecks.js),
112
+ * not a separate attempt — fires on flips only.
113
+ */
114
+ export const ADAPTER_EVENTS = {
115
+ CREATE: 'create',
116
+ EDIT_FINISH: 'editfinish',
117
+ CANCEL: 'cancel',
118
+ VERTEX_SELECTION: 'vertexselection',
119
+ VERTEX_CHANGE: 'vertexchange',
120
+ UNDO_CHANGE: 'undochange',
121
+ UPDATE: 'update',
122
+ GEOMETRY_CHANGE: 'geometrychange',
123
+ INTERFACE_TYPE_CHANGE: 'interfacetypechange',
124
+ PLACEMENT_BLOCKED: 'placementblocked',
125
+ VALIDITY_CHANGE: 'validitychange',
126
+ CAN_PLACE_CHANGE: 'canplacechange'
127
+ }
@@ -0,0 +1,20 @@
1
+ import { ADAPTER_EVENTS } from './adapterEvents.js'
2
+
3
+ describe('adapter event contract', () => {
4
+ test('event names are stable — renaming a value breaks adapter consumers', () => {
5
+ expect(ADAPTER_EVENTS).toEqual({
6
+ CREATE: 'create',
7
+ EDIT_FINISH: 'editfinish',
8
+ CANCEL: 'cancel',
9
+ VERTEX_SELECTION: 'vertexselection',
10
+ VERTEX_CHANGE: 'vertexchange',
11
+ UNDO_CHANGE: 'undochange',
12
+ UPDATE: 'update',
13
+ GEOMETRY_CHANGE: 'geometrychange',
14
+ INTERFACE_TYPE_CHANGE: 'interfacetypechange',
15
+ PLACEMENT_BLOCKED: 'placementblocked',
16
+ VALIDITY_CHANGE: 'validitychange',
17
+ CAN_PLACE_CHANGE: 'canplacechange'
18
+ })
19
+ })
20
+ })
@@ -0,0 +1,48 @@
1
+ import { MaplibreDrawAdapter } from './maplibre/MaplibreDrawAdapter.js'
2
+ import { OLDrawAdapter } from './openlayers/OLDrawAdapter.js'
3
+
4
+ /**
5
+ * The shared adapter contract: events.js, DrawInit and the api entry points are
6
+ * written against this surface, so both adapters must implement all of it — even
7
+ * where an engine needs only a documented no-op (deleteVertex on MapLibre,
8
+ * setFeatureProperty on OpenLayers). A method added to one adapter but not the
9
+ * other fails here before it fails at runtime on the other engine.
10
+ */
11
+ const CONTRACT_METHODS = [
12
+ 'changeMode',
13
+ 'getMode',
14
+ 'setInterfaceType',
15
+ 'done',
16
+ 'cancel',
17
+ 'undo',
18
+ 'deleteVertex',
19
+ 'nudgeSelectedVertex',
20
+ 'get',
21
+ 'add',
22
+ 'delete',
23
+ 'deleteAll',
24
+ 'setSnapEnabled',
25
+ 'setSnapLayers',
26
+ 'isSnapEnabled',
27
+ 'setFeatureProperty',
28
+ 'setGeometryValid',
29
+ 'setInvalid',
30
+ 'on',
31
+ 'off',
32
+ 'remove'
33
+ ]
34
+
35
+ describe.each([
36
+ ['MaplibreDrawAdapter', MaplibreDrawAdapter],
37
+ ['OLDrawAdapter', OLDrawAdapter]
38
+ ])('%s adapter contract', (name, Adapter) => {
39
+ test.each(CONTRACT_METHODS)('implements %s()', (method) => {
40
+ expect(typeof Adapter.prototype[method]).toBe('function')
41
+ })
42
+
43
+ test('exposes the _geometryValidator accessor pair (user callback storage)', () => {
44
+ const descriptor = Object.getOwnPropertyDescriptor(Adapter.prototype, '_geometryValidator')
45
+ expect(typeof descriptor?.get).toBe('function')
46
+ expect(typeof descriptor?.set).toBe('function')
47
+ })
48
+ })
@@ -0,0 +1,14 @@
1
+ export const loadDrawAdapter = async (mapProvider, options) => {
2
+ switch (mapProvider.name) {
3
+ case 'MapLibreProvider': {
4
+ const { MaplibreDrawAdapter } = await import(/* webpackChunkName: "im-draw-ml-adapter" */ './maplibre/MaplibreDrawAdapter.js')
5
+ return new MaplibreDrawAdapter(mapProvider, options)
6
+ }
7
+ case 'OpenLayersProvider': {
8
+ const { OLDrawAdapter } = await import(/* webpackChunkName: "im-draw-ol-adapter" */ './openlayers/OLDrawAdapter.js')
9
+ return new OLDrawAdapter(mapProvider, options)
10
+ }
11
+ default:
12
+ throw new Error(`No draw adapter available for map provider "${mapProvider.name}"`)
13
+ }
14
+ }
@@ -0,0 +1,53 @@
1
+ import { loadDrawAdapter } from './loadDrawAdapter.js'
2
+ import { MaplibreDrawAdapter } from './maplibre/MaplibreDrawAdapter.js'
3
+ import { OLDrawAdapter } from './openlayers/OLDrawAdapter.js'
4
+
5
+ jest.mock('./maplibre/MaplibreDrawAdapter.js', () => ({
6
+ MaplibreDrawAdapter: jest.fn(function (mapProvider, options) {
7
+ this.mapProvider = mapProvider
8
+ this.options = options
9
+ })
10
+ }))
11
+
12
+ jest.mock('./openlayers/OLDrawAdapter.js', () => ({
13
+ OLDrawAdapter: jest.fn(function (mapProvider, options) {
14
+ this.mapProvider = mapProvider
15
+ this.options = options
16
+ })
17
+ }))
18
+
19
+ beforeEach(() => {
20
+ jest.clearAllMocks()
21
+ })
22
+
23
+ describe('loadDrawAdapter', () => {
24
+ test('loads the MapLibre adapter for a MapLibreProvider', async () => {
25
+ const mapProvider = { name: 'MapLibreProvider' }
26
+ const options = { mapStyle: 'light' }
27
+
28
+ const adapter = await loadDrawAdapter(mapProvider, options)
29
+
30
+ expect(MaplibreDrawAdapter).toHaveBeenCalledWith(mapProvider, options)
31
+ expect(OLDrawAdapter).not.toHaveBeenCalled()
32
+ expect(adapter).toBeInstanceOf(MaplibreDrawAdapter)
33
+ })
34
+
35
+ test('loads the OpenLayers adapter for an OpenLayersProvider', async () => {
36
+ const mapProvider = { name: 'OpenLayersProvider' }
37
+ const options = { mapStyle: 'dark' }
38
+
39
+ const adapter = await loadDrawAdapter(mapProvider, options)
40
+
41
+ expect(OLDrawAdapter).toHaveBeenCalledWith(mapProvider, options)
42
+ expect(MaplibreDrawAdapter).not.toHaveBeenCalled()
43
+ expect(adapter).toBeInstanceOf(OLDrawAdapter)
44
+ })
45
+
46
+ test('throws for an unsupported map provider', async () => {
47
+ await expect(loadDrawAdapter({ name: 'SomethingElse' }, {}))
48
+ .rejects.toThrow('No draw adapter available for map provider "SomethingElse"')
49
+
50
+ expect(MaplibreDrawAdapter).not.toHaveBeenCalled()
51
+ expect(OLDrawAdapter).not.toHaveBeenCalled()
52
+ })
53
+ })
@@ -0,0 +1,367 @@
1
+ import { createMapboxDraw } from './mapboxDraw.js'
2
+ import { getSnapInstance, clearSnapState, clearSnapIndicator } from './utils/snapHelpers.js'
3
+ import { createEventBus } from '../../utils/eventBus.js'
4
+ import { MAPBOX_DRAW_EVENTS, CUSTOM_DRAW_EVENTS, STYLE_DATA_EVENT } from './drawEvents.js'
5
+ import { ADAPTER_EVENTS } from '../../adapterEvents.js'
6
+ import { createLiveStroke } from '../../validation/liveStroke.js'
7
+ import { createLiveDrawChecks } from '../../validation/liveDrawChecks.js'
8
+
9
+ const polygonFeature = (coordinates) => ({ type: 'Feature', geometry: { type: 'Polygon', coordinates } })
10
+ const lineFeature = (coordinates) => ({ type: 'Feature', geometry: { type: 'LineString', coordinates } })
11
+
12
+ // The displayed feature + placed-vertex count for the live stroke check. MapLibre's
13
+ // fire() copies the payload onto an Event whose `type` is the event name, so the
14
+ // geometry type is clobbered — it comes from the draw mode, or (in edit, where the
15
+ // mode covers both shapes) from the coordinate nesting: a polygon's coordinates are
16
+ // rings (one level deeper than a line's). Draw-mode coordinates carry a trailing
17
+ // rubber-band point; edit-mode coordinates are all committed vertices.
18
+ export const displayedShape = (mode, coordinates) => {
19
+ if (mode === 'draw_polygon') {
20
+ return { feature: polygonFeature(coordinates), numVertices: (coordinates[0]?.length ?? 1) - 1 }
21
+ }
22
+ if (mode === 'draw_line') {
23
+ return { feature: lineFeature(coordinates), numVertices: (coordinates?.length ?? 1) - 1 }
24
+ }
25
+ if (mode === 'edit_vertex') {
26
+ return Array.isArray(coordinates[0]?.[0])
27
+ ? { feature: polygonFeature(coordinates), numVertices: coordinates[0]?.length ?? 0 }
28
+ : { feature: lineFeature(coordinates), numVertices: coordinates?.length ?? 0 }
29
+ }
30
+ return null
31
+ }
32
+
33
+ /**
34
+ * Draw adapter for MapLibre GL.
35
+ *
36
+ * Wraps the MapboxDraw instance and normalises its map-event-based API into the
37
+ * shared adapter interface consumed by events.js, DrawInit, and the api entry points.
38
+ *
39
+ * Adapter interface (also implemented by OLDrawAdapter):
40
+ * changeMode(name, options)
41
+ * getMode()
42
+ * setInterfaceType(type)
43
+ * done() / cancel() / undo() / deleteVertex()
44
+ * nudgeSelectedVertex(dx, dy, isLargeStep)
45
+ * get(id) / add(feature) / delete(id) / deleteAll()
46
+ * setSnapEnabled(bool) / setSnapLayers(layers) / isSnapEnabled()
47
+ * setFeatureProperty(id, property, value) / setDrawingPreviewProperty(property, value)
48
+ * on(event, handler) / off(event, handler)
49
+ * remove()
50
+ */
51
+ export class MaplibreDrawAdapter {
52
+ constructor (mapProvider, options) {
53
+ this._mapProvider = mapProvider
54
+ this._map = mapProvider.map
55
+ this._bus = createEventBus()
56
+ this._editingFeatureId = null
57
+
58
+ const { draw, remove } = createMapboxDraw({
59
+ mapStyle: options.mapStyle,
60
+ mapProvider,
61
+ events: options.events,
62
+ eventBus: options.eventBus,
63
+ snapLayers: options.snapLayers,
64
+ pluginConfig: options.pluginConfig ?? {}
65
+ })
66
+
67
+ this._draw = draw
68
+ this._cleanupDraw = remove
69
+
70
+ // Single owner of the dashed-stroke state: live rubber-band / drag moves feed
71
+ // update() (default rules sync, user callback throttled) and committed verdicts
72
+ // (events.js) land via setInvalid → set(), so the cached state always mirrors
73
+ // the rendered layers. onChange does the actual layer toggle; in edit mode the
74
+ // displayed shape is exactly what Done finishes, so validity flips also gate
75
+ // the Done button (events.js dispatches them).
76
+ this._liveStroke = createLiveStroke({
77
+ onChange: (invalid, reason) => {
78
+ this._applyStrokeInvalid(invalid)
79
+ if (this._draw.getMode() === 'edit_vertex') {
80
+ this._bus.emit(ADAPTER_EVENTS.VALIDITY_CHANGE, { valid: !invalid, reason })
81
+ }
82
+ }
83
+ })
84
+
85
+ // Draw-mode-only: computes both the stroke/Done verdict and the Add-point
86
+ // verdict from a SINGLE throttled call to the user's callback per rubber-band
87
+ // move (see liveDrawChecks.js for why), and is flip-guarded itself. The
88
+ // stroke verdict still routes through _liveStroke.set() — that instance is
89
+ // also driven independently by edit mode and must stay the single source of
90
+ // truth for the rendered stroke across mode switches. The Add-point verdict
91
+ // has no such cross-mode instance to stay in sync with, so it emits directly.
92
+ this._liveDrawChecks = createLiveDrawChecks({
93
+ onStrokeChange: (invalid, reason) => this._liveStroke.set(invalid, reason),
94
+ onPlaceChange: (vetoed, reason) => this._bus.emit(ADAPTER_EVENTS.CAN_PLACE_CHANGE, { canPlace: !vetoed, reason })
95
+ })
96
+
97
+ // Normalise ML map events → the shared adapter event contract (adapterEvents.js).
98
+ // The OL adapter emits the same contract directly from OLDrawManager.
99
+ this._mapHandlers = {
100
+ create: (e) => this._bus.emit(ADAPTER_EVENTS.CREATE, e.features[0]),
101
+ editfinish: (e) => this._bus.emit(ADAPTER_EVENTS.EDIT_FINISH, e.features[0]),
102
+ cancel: () => this._bus.emit(ADAPTER_EVENTS.CANCEL),
103
+ // Normalise typo: the ML modes fire numVertecies, the contract uses numVertices
104
+ vertexselection: (e) => this._bus.emit(ADAPTER_EVENTS.VERTEX_SELECTION, { ...e, numVertices: e.numVertecies }),
105
+ vertexchange: (e) => this._bus.emit(ADAPTER_EVENTS.VERTEX_CHANGE, { ...e, numVertices: e.numVertecies }),
106
+ undochange: (e) => this._bus.emit(ADAPTER_EVENTS.UNDO_CHANGE, e.length),
107
+ update: (e) => this._bus.emit(ADAPTER_EVENTS.UPDATE, e.features[0]),
108
+ geometrychange: (e) => {
109
+ // Phase-less events are rubber-band moves carrying the displayed feature
110
+ // (placed vertices + cursor) — they drive the live invalid stroke, and are
111
+ // cached for setDrawingPreviewProperty (the in-progress feature has no
112
+ // stable id yet — only assigned once drawing actually finishes — so it
113
+ // can't be targeted via setFeatureProperty).
114
+ if (!e?.phase) {
115
+ this._updateLiveStroke(e)
116
+ this._currentDrawEvent = e
117
+ }
118
+ this._bus.emit(ADAPTER_EVENTS.GEOMETRY_CHANGE, e)
119
+ },
120
+ placementblocked: (e) => this._bus.emit(ADAPTER_EVENTS.PLACEMENT_BLOCKED, e),
121
+ interfacetypechange: (e) => this._bus.emit(ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE, { interfaceType: e.interfaceType }),
122
+ modechange: (e) => this._handleModeChange(e),
123
+ styledata: () => this._handleStyleData()
124
+ }
125
+
126
+ this._map.on(MAPBOX_DRAW_EVENTS.CREATE, this._mapHandlers.create)
127
+ this._map.on(CUSTOM_DRAW_EVENTS.EDIT_FINISH, this._mapHandlers.editfinish)
128
+ this._map.on(CUSTOM_DRAW_EVENTS.CANCEL, this._mapHandlers.cancel)
129
+ this._map.on(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, this._mapHandlers.vertexselection)
130
+ this._map.on(CUSTOM_DRAW_EVENTS.VERTEX_CHANGE, this._mapHandlers.vertexchange)
131
+ this._map.on(CUSTOM_DRAW_EVENTS.UNDO_CHANGE, this._mapHandlers.undochange)
132
+ this._map.on(MAPBOX_DRAW_EVENTS.UPDATE, this._mapHandlers.update)
133
+ this._map.on(CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE, this._mapHandlers.geometrychange)
134
+ this._map.on(CUSTOM_DRAW_EVENTS.PLACEMENT_BLOCKED, this._mapHandlers.placementblocked)
135
+ this._map.on(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, this._mapHandlers.interfacetypechange)
136
+ this._map.on(MAPBOX_DRAW_EVENTS.MODE_CHANGE, this._mapHandlers.modechange)
137
+ this._map.on(STYLE_DATA_EVENT, this._mapHandlers.styledata)
138
+ }
139
+
140
+ changeMode (name, options = {}) {
141
+ if (name === 'edit_vertex') {
142
+ this._editingFeatureId = options.featureId ?? null
143
+ }
144
+ // A fresh draw always starts with a solid stroke and a placeable crosshair;
145
+ // the live checks own both from here.
146
+ if (name === 'draw_polygon' || name === 'draw_line') {
147
+ this._liveStroke.set(false)
148
+ this._liveDrawChecks.reset()
149
+ }
150
+ this._draw.changeMode(name, options)
151
+ // The underlying mapbox-gl-draw control's public changeMode API is silent by
152
+ // default (it never fires 'draw.modechange'), so every mode change requested
153
+ // through this adapter must drive the same cleanup manually.
154
+ this._handleModeChange({ mode: name })
155
+ }
156
+
157
+ // Live invalid-stroke driver: called on every rubber-band move (draw) and vertex
158
+ // drag / nudge (edit) with the displayed feature. Edit mode has no Add-point
159
+ // gate, so it goes straight through the live-stroke controller as before. Draw
160
+ // mode routes through _liveDrawChecks instead, which computes both the stroke
161
+ // and Add-point verdicts from one throttled user-callback call (see
162
+ // liveDrawChecks.js).
163
+ _updateLiveStroke (e) {
164
+ if (!e?.coordinates) { return }
165
+ const mode = this._draw.getMode()
166
+ const shape = displayedShape(mode, e.coordinates)
167
+ if (!shape) { return }
168
+ if (mode === 'draw_polygon' || mode === 'draw_line') {
169
+ this._liveDrawChecks.update({ feature: shape.feature, numVertices: shape.numVertices, context: { mode }, onGeometryChange: this._geometryValidator })
170
+ } else {
171
+ this._liveStroke.update({ ...shape, context: { mode }, onGeometryChange: this._geometryValidator })
172
+ }
173
+ }
174
+
175
+ getMode () { return this._draw.getMode() }
176
+
177
+ setInterfaceType (type) {
178
+ this._map.fire(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, { interfaceType: type })
179
+ }
180
+
181
+ done () {
182
+ this._mapProvider.undoStack?.clear()
183
+ const mode = this._draw.getMode()
184
+ if (mode === 'edit_vertex' && this._editingFeatureId) {
185
+ // Leaving edit_vertex here — hide immediately rather than waiting on the
186
+ // async disable() the EDIT_FINISH handler fires later (see changeMode()).
187
+ this._handleModeChange({ mode: 'disabled' })
188
+ this._map.fire(CUSTOM_DRAW_EVENTS.EDIT_FINISH, { features: [this._draw.get(this._editingFeatureId)] })
189
+ return
190
+ }
191
+ if (mode === 'draw_polygon' || mode === 'draw_line') {
192
+ this._draw.changeMode('disabled')
193
+ this._handleModeChange({ mode: 'disabled' })
194
+ }
195
+ }
196
+
197
+ cancel () {
198
+ this._mapProvider.undoStack?.clear()
199
+ const mode = this._draw.getMode()
200
+ // trash() only belongs to an in-progress, never-committed draw — it deletes
201
+ // whatever's selected. For edit_vertex, events.js's handleCancel has already
202
+ // restored the original feature via draw.add() before this runs; trash()
203
+ // here would instead run mapbox-gl-draw's direct_select onTrash on the
204
+ // mode's own live state (removing the selected vertex, or — if the
205
+ // in-progress edit happened to leave the shape invalid — deleting the
206
+ // entire feature), silently discarding the just-restored original.
207
+ if (mode === 'draw_polygon' || mode === 'draw_line') {
208
+ this._draw.trash()
209
+ }
210
+ this._draw.changeMode('disabled')
211
+ this._handleModeChange({ mode: 'disabled' })
212
+ }
213
+
214
+ undo () {
215
+ this._map.fire(CUSTOM_DRAW_EVENTS.UNDO)
216
+ }
217
+
218
+ // MoveControl's D-pad, routed here via mapProvider.activeMoveTarget (see
219
+ // events.js) once a vertex is selected. Bridged into the running edit mode the
220
+ // same way setInterfaceType is, since the adapter has no direct reference to the
221
+ // mode's live state.
222
+ nudgeSelectedVertex (dx, dy, isLargeStep) {
223
+ this._map.fire(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, { dx, dy, isLargeStep })
224
+ }
225
+
226
+ // Record the current geometry validity so the draw mode can block finish gestures
227
+ // (double-click / click-to-close) while the in-progress shape is invalid.
228
+ setGeometryValid (valid) {
229
+ this._map._drawGeometryValid = valid
230
+ }
231
+
232
+ // The api entry points assign the active user validator to the adapter; store it
233
+ // on the map (like _drawGeometryValid) so modes can veto placements synchronously.
234
+ set _geometryValidator (fn) { this._map._drawGeometryValidator = fn }
235
+ get _geometryValidator () { return this._map._drawGeometryValidator }
236
+
237
+ // Committed-verdict write (events.js, edit mode): routed through the live-stroke
238
+ // controller so its cached state stays in sync with the rendered layers.
239
+ setInvalid (invalid) {
240
+ this._liveStroke.set(invalid)
241
+ }
242
+
243
+ // Toggle the active shape's stroke between solid (valid) and dashed (invalid) by
244
+ // swapping which of the two overlaid stroke layers is visible; the fill is hidden
245
+ // while invalid so the shape reads as an outline only. Only the live-stroke
246
+ // controller calls this — everything else goes through setInvalid.
247
+ _applyStrokeInvalid (invalid) {
248
+ this._setLayerVisibility('stroke-active', !invalid)
249
+ this._setLayerVisibility('stroke-active-invalid', invalid)
250
+ this._setLayerVisibility('fill-active', !invalid)
251
+ }
252
+
253
+ _setLayerVisibility (id, visible) {
254
+ ['hot', 'cold'].forEach((suffix) => {
255
+ const layerId = `${id}.${suffix}`
256
+ if (this._map.getLayer(layerId)) {
257
+ this._map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none')
258
+ }
259
+ })
260
+ }
261
+
262
+ deleteVertex () {
263
+ // Intentionally a no-op on MapLibre. The shared events layer calls draw.deleteVertex()
264
+ // from the delete-point button, but the ML edit mode already handles deletion itself:
265
+ // via the keyboard (Backspace/Delete) and its own window-click listener matching
266
+ // deleteVertexButtonId (see editVertexMode.onButtonClick). Deleting here too would
267
+ // double-delete. The OL adapter implements this for real; here it only satisfies the
268
+ // shared adapter interface.
269
+ }
270
+
271
+ get (id) { return this._draw.get(id) }
272
+ add (feature) { return this._draw.add(feature) }
273
+ delete (id) { this._draw.delete(id) }
274
+ deleteAll () { this._draw.deleteAll() }
275
+
276
+ setSnapEnabled (bool) {
277
+ this._mapProvider.snapEnabled = bool
278
+ const snap = getSnapInstance(this._map)
279
+ if (snap?.setSnapStatus) { snap.setSnapStatus(bool) }
280
+ if (!bool && snap) {
281
+ clearSnapState(snap)
282
+ if (this._map.getLayer('snap-helper-circle')) {
283
+ this._map.setLayoutProperty('snap-helper-circle', 'visibility', 'none')
284
+ }
285
+ }
286
+ }
287
+
288
+ setSnapLayers (layers) {
289
+ const snap = getSnapInstance(this._map)
290
+ if (snap?.setSnapLayers) {
291
+ snap.setSnapLayers(layers)
292
+ } else if (layers) {
293
+ this._map._pendingSnapLayers = layers
294
+ } else {
295
+ // No action
296
+ }
297
+ }
298
+
299
+ isSnapEnabled () {
300
+ return this._mapProvider.snapEnabled === true
301
+ }
302
+
303
+ setFeatureProperty (id, property, value) {
304
+ this._draw.setFeatureProperty(id, property, value)
305
+ }
306
+
307
+ // Tag the feature currently being drawn (rubber-band, not yet created — so it
308
+ // has no stable id to target via setFeatureProperty) with a property, and
309
+ // re-render so the change is visible immediately. Used for live preview styling
310
+ // while drawing, e.g. split's valid/invalid line colour. A no-op once nothing
311
+ // has been drawn yet this session, or outside draw_polygon/draw_line.
312
+ setDrawingPreviewProperty (property, value) {
313
+ const e = this._currentDrawEvent
314
+ if (e?.properties) {
315
+ e.properties[property] = value
316
+ }
317
+ e?.ctx?.store?.render()
318
+ }
319
+
320
+ on (type, handler) {
321
+ this._bus.on(type, handler)
322
+ }
323
+
324
+ off (type, handler) {
325
+ this._bus.off(type, handler)
326
+ }
327
+
328
+ _handleModeChange (e) {
329
+ const DRAW_MODES = new Set(['draw_polygon', 'draw_line', 'edit_vertex'])
330
+ if (!DRAW_MODES.has(e.mode)) {
331
+ clearSnapIndicator(getSnapInstance(this._map), this._map)
332
+ }
333
+ }
334
+
335
+ // Keeps draw layers on top after MapLibre style reloads
336
+ _handleStyleData () {
337
+ // A style reload re-adds the draw layers with their spec-default visibility
338
+ // (solid stroke shown, dashed hidden) — re-assert the cached stroke state so
339
+ // an invalid shape stays dashed across the reload.
340
+ this._liveStroke.refresh()
341
+ const layers = this._map.getStyle().layers || []
342
+ if (!layers.length || layers[layers.length - 1].source?.startsWith('mapbox-gl-draw')) {
343
+ return
344
+ }
345
+ layers
346
+ .filter(l => l.source?.startsWith('mapbox-gl-draw'))
347
+ .forEach(l => this._map.moveLayer(l.id))
348
+ }
349
+
350
+ remove () {
351
+ this._map.off(MAPBOX_DRAW_EVENTS.CREATE, this._mapHandlers.create)
352
+ this._map.off(CUSTOM_DRAW_EVENTS.EDIT_FINISH, this._mapHandlers.editfinish)
353
+ this._map.off(CUSTOM_DRAW_EVENTS.CANCEL, this._mapHandlers.cancel)
354
+ this._map.off(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, this._mapHandlers.vertexselection)
355
+ this._map.off(CUSTOM_DRAW_EVENTS.VERTEX_CHANGE, this._mapHandlers.vertexchange)
356
+ this._map.off(CUSTOM_DRAW_EVENTS.UNDO_CHANGE, this._mapHandlers.undochange)
357
+ this._map.off(MAPBOX_DRAW_EVENTS.UPDATE, this._mapHandlers.update)
358
+ this._map.off(CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE, this._mapHandlers.geometrychange)
359
+ this._map.off(CUSTOM_DRAW_EVENTS.PLACEMENT_BLOCKED, this._mapHandlers.placementblocked)
360
+ this._map.off(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, this._mapHandlers.interfacetypechange)
361
+ this._map.off(MAPBOX_DRAW_EVENTS.MODE_CHANGE, this._mapHandlers.modechange)
362
+ this._map.off(STYLE_DATA_EVENT, this._mapHandlers.styledata)
363
+ this._liveStroke.destroy()
364
+ this._liveDrawChecks.destroy()
365
+ this._cleanupDraw()
366
+ }
367
+ }