@defra/interactive-map 0.0.3-alpha → 0.0.4-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 (268) hide show
  1. package/README.md +16 -57
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/index.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/button-definition.md +176 -0
  7. package/docs/api/context.md +124 -0
  8. package/docs/api/control-definition.md +72 -0
  9. package/docs/api/icon-definition.md +28 -0
  10. package/docs/api/map-style-config.md +76 -0
  11. package/docs/api/marker-config.md +51 -0
  12. package/docs/api/panel-definition.md +117 -0
  13. package/docs/api/slots.md +25 -0
  14. package/docs/api.md +713 -0
  15. package/docs/architecture.md +139 -0
  16. package/docs/building-a-plugin.md +149 -0
  17. package/docs/getting-started.md +46 -0
  18. package/docs/govuk-prototype.md +0 -0
  19. package/docs/plugins/interact.md +205 -0
  20. package/docs/plugins/map-styles.md +84 -0
  21. package/docs/plugins/plugin-context.md +67 -0
  22. package/docs/plugins/plugin-descriptor.md +71 -0
  23. package/docs/plugins/plugin-manifest.md +118 -0
  24. package/docs/plugins/scale-bar.md +62 -0
  25. package/docs/plugins/search.md +162 -0
  26. package/docs/plugins.md +116 -0
  27. package/govuk-prototype-kit.config.json +7 -7
  28. package/package.json +14 -1
  29. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -0
  30. package/plugins/beta/datasets/dist/esm/index.js +2 -0
  31. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -0
  32. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  33. package/plugins/{datasets → beta/datasets}/src/manifest.js +1 -1
  34. package/plugins/{datasets → beta/datasets}/src/mapLayers.js +1 -1
  35. package/plugins/{datasets → beta/datasets}/src/panels/Key.jsx +1 -1
  36. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -0
  37. package/plugins/beta/draw-ml/dist/esm/index.js +2 -0
  38. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -0
  39. package/plugins/beta/draw-ml/dist/umd/index.js +2 -0
  40. package/plugins/{draw-ml → beta/draw-ml}/src/api/editFeature.js +1 -1
  41. package/plugins/beta/draw-ml/src/api/merge.js +9 -0
  42. package/plugins/beta/draw-ml/src/api/newLine.js +43 -0
  43. package/plugins/{draw-ml → beta/draw-ml}/src/api/newPolygon.js +5 -5
  44. package/plugins/beta/draw-ml/src/api/split.js +81 -0
  45. package/plugins/{draw-ml → beta/draw-ml}/src/defaults.js +2 -0
  46. package/plugins/{draw-ml → beta/draw-ml}/src/events.js +105 -3
  47. package/plugins/{draw-ml → beta/draw-ml}/src/manifest.js +17 -11
  48. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxDraw.js +51 -2
  49. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxSnap.js +2 -0
  50. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +496 -0
  51. package/plugins/beta/draw-ml/src/modes/drawLineMode.js +18 -0
  52. package/plugins/beta/draw-ml/src/modes/drawPolygonMode.js +16 -0
  53. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +792 -0
  54. package/plugins/{draw-ml → beta/draw-ml}/src/reducer.js +30 -2
  55. package/plugins/{draw-ml → beta/draw-ml}/src/styles.js +66 -36
  56. package/plugins/beta/draw-ml/src/undoStack.js +50 -0
  57. package/plugins/beta/draw-ml/src/utils/debounce.js +16 -0
  58. package/plugins/beta/draw-ml/src/utils/spatial.js +258 -0
  59. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -0
  60. package/plugins/beta/frame/dist/esm/index.js +2 -0
  61. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -0
  62. package/plugins/beta/frame/dist/umd/index.js +2 -0
  63. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -0
  64. package/plugins/beta/map-styles/dist/esm/index.js +2 -0
  65. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -0
  66. package/plugins/beta/map-styles/dist/umd/index.js +2 -0
  67. package/plugins/{map-styles → beta/map-styles}/src/MapStyles.jsx +1 -1
  68. package/plugins/{map-styles → beta/map-styles}/src/manifest.js +8 -10
  69. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -0
  70. package/plugins/beta/scale-bar/dist/esm/index.js +2 -0
  71. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -0
  72. package/plugins/beta/scale-bar/dist/umd/index.js +2 -0
  73. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -0
  74. package/plugins/beta/use-location/dist/esm/index.js +2 -0
  75. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -0
  76. package/plugins/beta/use-location/dist/umd/index.js +2 -0
  77. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  78. package/plugins/interact/dist/esm/index.js +1 -1
  79. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  80. package/plugins/interact/dist/umd/index.js +1 -1
  81. package/plugins/interact/src/InteractInit.test.js +97 -0
  82. package/plugins/interact/src/api/clear.test.js +18 -0
  83. package/plugins/interact/src/api/disable.test.js +12 -0
  84. package/plugins/interact/src/api/enable.test.js +48 -0
  85. package/plugins/interact/src/api/selectFeature.test.js +33 -0
  86. package/plugins/interact/src/api/unselectFeature.test.js +33 -0
  87. package/plugins/interact/src/defaults.js +1 -0
  88. package/plugins/interact/src/events.test.js +151 -0
  89. package/plugins/interact/src/hooks/useHighlightSync.test.js +212 -0
  90. package/plugins/interact/src/hooks/useInteractionHandlers.js +14 -7
  91. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +260 -0
  92. package/plugins/interact/src/index.test.js +22 -0
  93. package/plugins/interact/src/manifest.js +1 -1
  94. package/plugins/interact/src/manifest.test.js +94 -0
  95. package/plugins/interact/src/reducer.js +10 -1
  96. package/plugins/interact/src/reducer.test.js +142 -0
  97. package/plugins/interact/src/utils/buildStylesMap.test.js +57 -0
  98. package/plugins/interact/src/utils/featureQueries.test.js +56 -0
  99. package/plugins/interact/src/utils/turfHelpers.js +30 -0
  100. package/plugins/interact/src/utils/turfHelpers.test.js +30 -0
  101. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  102. package/plugins/search/dist/esm/index.js +1 -1
  103. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  104. package/plugins/search/dist/umd/index.js +1 -1
  105. package/plugins/search/src/utils/parseOsNamesResults.js +3 -7
  106. package/providers/{esri → beta/esri}/src/mapEvents.js +2 -2
  107. package/providers/beta/open-names/dist/esm/im-reverse-geocode.js +2 -0
  108. package/providers/beta/open-names/dist/esm/index.js +2 -0
  109. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js +2 -0
  110. package/providers/beta/open-names/dist/umd/index.js +2 -0
  111. package/providers/maplibre/dist/esm/im-maplibre-framework.js +1 -1
  112. package/providers/maplibre/dist/esm/im-maplibre-framework.js.LICENSE.txt +1 -1
  113. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +1 -1
  114. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  115. package/providers/maplibre/dist/esm/index.js +1 -1
  116. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  117. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  118. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +1 -1
  119. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  120. package/providers/maplibre/dist/umd/index.js +1 -1
  121. package/providers/maplibre/src/index.js +14 -1
  122. package/providers/maplibre/src/maplibreProvider.js +127 -0
  123. package/sonar-project.properties +1 -1
  124. package/src/App/hooks/useInterfaceAPI.js +24 -1
  125. package/src/App/renderer/mapButtons.js +1 -1
  126. package/src/App/store/appActionsMap.js +22 -1
  127. package/src/InteractiveMap/InteractiveMap.js +114 -6
  128. package/src/config/defaults.js +29 -20
  129. package/src/config/events.js +110 -4
  130. package/src/index.js +5 -2
  131. package/src/scss/tools/_border-focus.scss +20 -4
  132. package/src/types.js +585 -0
  133. package/webpack.esm.mjs +8 -8
  134. package/webpack.umd.mjs +8 -8
  135. package/plugins/datasets/dist/esm/im-datasets-plugin.js +0 -1
  136. package/plugins/datasets/dist/esm/index.js +0 -2
  137. package/plugins/datasets/dist/umd/im-datasets-plugin.js +0 -1
  138. package/plugins/datasets/dist/umd/index.js +0 -2
  139. package/plugins/draw-ml/dist/esm/im-draw-ml-plugin.js +0 -1
  140. package/plugins/draw-ml/dist/esm/index.js +0 -2
  141. package/plugins/draw-ml/dist/umd/im-draw-ml-plugin.js +0 -1
  142. package/plugins/draw-ml/dist/umd/index.js +0 -2
  143. package/plugins/draw-ml/src/modes/drawVertexMode.js +0 -275
  144. package/plugins/draw-ml/src/modes/editVertexMode.js +0 -426
  145. package/plugins/draw-ml/src/utils.js +0 -102
  146. package/plugins/frame/dist/esm/im-frame-plugin.js +0 -1
  147. package/plugins/frame/dist/esm/index.js +0 -2
  148. package/plugins/frame/dist/umd/im-frame-plugin.js +0 -1
  149. package/plugins/frame/dist/umd/index.js +0 -2
  150. package/plugins/map-styles/dist/esm/im-map-styles-plugin.js +0 -1
  151. package/plugins/map-styles/dist/esm/index.js +0 -2
  152. package/plugins/map-styles/dist/umd/im-map-styles-plugin.js +0 -1
  153. package/plugins/map-styles/dist/umd/index.js +0 -2
  154. package/plugins/scale-bar/dist/esm/im-scale-bar-plugin.js +0 -1
  155. package/plugins/scale-bar/dist/esm/index.js +0 -2
  156. package/plugins/scale-bar/dist/umd/im-scale-bar-plugin.js +0 -1
  157. package/plugins/scale-bar/dist/umd/index.js +0 -2
  158. package/plugins/use-location/dist/esm/im-use-location-plugin.js +0 -1
  159. package/plugins/use-location/dist/esm/index.js +0 -2
  160. package/plugins/use-location/dist/umd/im-use-location-plugin.js +0 -1
  161. package/plugins/use-location/dist/umd/index.js +0 -2
  162. package/providers/open-names/dist/esm/im-reverse-geocode.js +0 -2
  163. package/providers/open-names/dist/esm/index.js +0 -2
  164. package/providers/open-names/dist/umd/im-reverse-geocode.js +0 -2
  165. package/providers/open-names/dist/umd/index.js +0 -2
  166. /package/docs/architecture/{ARCHITECTURE_DIAGRAMS.md → architecture-diagrams.md} +0 -0
  167. /package/plugins/{datasets → beta/datasets}/dist/css/index.css +0 -0
  168. /package/plugins/{datasets → beta/datasets}/dist/esm/index.js.LICENSE.txt +0 -0
  169. /package/plugins/{datasets → beta/datasets}/dist/umd/index.js.LICENSE.txt +0 -0
  170. /package/plugins/{datasets → beta/datasets}/src/DatasetsInit.jsx +0 -0
  171. /package/plugins/{datasets → beta/datasets}/src/api/addDataset.js +0 -0
  172. /package/plugins/{datasets → beta/datasets}/src/api/hideDataset.js +0 -0
  173. /package/plugins/{datasets → beta/datasets}/src/api/hideFeatures.js +0 -0
  174. /package/plugins/{datasets → beta/datasets}/src/api/removeDataset.js +0 -0
  175. /package/plugins/{datasets → beta/datasets}/src/api/showDataset.js +0 -0
  176. /package/plugins/{datasets → beta/datasets}/src/api/showFeatures.js +0 -0
  177. /package/plugins/{datasets → beta/datasets}/src/datasets.js +0 -0
  178. /package/plugins/{datasets → beta/datasets}/src/datasets.scss +0 -0
  179. /package/plugins/{datasets → beta/datasets}/src/defaults.js +0 -0
  180. /package/plugins/{datasets → beta/datasets}/src/handleSetMapStyle.js +0 -0
  181. /package/plugins/{datasets → beta/datasets}/src/index.js +0 -0
  182. /package/plugins/{datasets → beta/datasets}/src/panels/Key.module.scss +0 -0
  183. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.jsx +0 -0
  184. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.module.scss +0 -0
  185. /package/plugins/{datasets → beta/datasets}/src/reducer.js +0 -0
  186. /package/plugins/{datasets → beta/datasets}/src/utils/bbox.js +0 -0
  187. /package/plugins/{datasets → beta/datasets}/src/utils/debounce.js +0 -0
  188. /package/plugins/{datasets → beta/datasets}/src/utils/filters.js +0 -0
  189. /package/plugins/{draw-es → beta/draw-es}/src/DrawInit.jsx +0 -0
  190. /package/plugins/{draw-es → beta/draw-es}/src/api/addFeature.js +0 -0
  191. /package/plugins/{draw-es → beta/draw-es}/src/api/deleteFeature.js +0 -0
  192. /package/plugins/{draw-es → beta/draw-es}/src/api/editFeature.js +0 -0
  193. /package/plugins/{draw-es → beta/draw-es}/src/api/newPolygon.js +0 -0
  194. /package/plugins/{draw-es → beta/draw-es}/src/events.js +0 -0
  195. /package/plugins/{draw-es → beta/draw-es}/src/graphic.js +0 -0
  196. /package/plugins/{draw-es → beta/draw-es}/src/index.js +0 -0
  197. /package/plugins/{draw-es → beta/draw-es}/src/manifest.js +0 -0
  198. /package/plugins/{draw-es → beta/draw-es}/src/reducer.js +0 -0
  199. /package/plugins/{draw-es → beta/draw-es}/src/sketchViewModel.js +0 -0
  200. /package/plugins/{draw-ml → beta/draw-ml}/dist/css/index.css +0 -0
  201. /package/plugins/{draw-ml → beta/draw-ml}/dist/esm/index.js.LICENSE.txt +0 -0
  202. /package/plugins/{draw-ml → beta/draw-ml}/dist/umd/index.js.LICENSE.txt +0 -0
  203. /package/plugins/{draw-ml → beta/draw-ml}/src/DrawInit.jsx +0 -0
  204. /package/plugins/{draw-ml → beta/draw-ml}/src/api/addFeature.js +0 -0
  205. /package/plugins/{draw-ml → beta/draw-ml}/src/api/deleteFeature.js +0 -0
  206. /package/plugins/{draw-ml → beta/draw-ml}/src/draw.scss +0 -0
  207. /package/plugins/{draw-ml → beta/draw-ml}/src/index.js +0 -0
  208. /package/plugins/{draw-ml → beta/draw-ml}/src/modes/disabledMode.js +0 -0
  209. /package/plugins/{draw-ml/src → beta/draw-ml/src/utils}/snapHelpers.js +0 -0
  210. /package/plugins/{frame → beta/frame}/dist/css/index.css +0 -0
  211. /package/plugins/{frame → beta/frame}/dist/esm/index.js.LICENSE.txt +0 -0
  212. /package/plugins/{frame → beta/frame}/dist/umd/index.js.LICENSE.txt +0 -0
  213. /package/plugins/{frame → beta/frame}/package.json +0 -0
  214. /package/plugins/{frame → beta/frame}/src/Frame.jsx +0 -0
  215. /package/plugins/{frame → beta/frame}/src/FrameInit.jsx +0 -0
  216. /package/plugins/{frame → beta/frame}/src/api/addFrame.js +0 -0
  217. /package/plugins/{frame → beta/frame}/src/api/editFeature.js +0 -0
  218. /package/plugins/{frame → beta/frame}/src/config.js +0 -0
  219. /package/plugins/{frame → beta/frame}/src/frame.scss +0 -0
  220. /package/plugins/{frame → beta/frame}/src/index.js +0 -0
  221. /package/plugins/{frame → beta/frame}/src/manifest.js +0 -0
  222. /package/plugins/{frame → beta/frame}/src/reducer.js +0 -0
  223. /package/plugins/{frame → beta/frame}/src/utils.js +0 -0
  224. /package/plugins/{map-styles → beta/map-styles}/dist/css/index.css +0 -0
  225. /package/plugins/{map-styles → beta/map-styles}/dist/esm/index.js.LICENSE.txt +0 -0
  226. /package/plugins/{map-styles → beta/map-styles}/dist/umd/index.js.LICENSE.txt +0 -0
  227. /package/plugins/{map-styles → beta/map-styles}/src/MapStylesInit.jsx +0 -0
  228. /package/plugins/{map-styles → beta/map-styles}/src/config.js +0 -0
  229. /package/plugins/{map-styles → beta/map-styles}/src/index.js +0 -0
  230. /package/plugins/{map-styles → beta/map-styles}/src/mapStyles.scss +0 -0
  231. /package/plugins/{scale-bar → beta/scale-bar}/dist/css/index.css +0 -0
  232. /package/plugins/{scale-bar → beta/scale-bar}/dist/esm/index.js.LICENSE.txt +0 -0
  233. /package/plugins/{scale-bar → beta/scale-bar}/dist/umd/index.js.LICENSE.txt +0 -0
  234. /package/plugins/{scale-bar → beta/scale-bar}/package.json +0 -0
  235. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.jsx +0 -0
  236. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.test.jsx +0 -0
  237. /package/plugins/{scale-bar → beta/scale-bar}/src/index.js +0 -0
  238. /package/plugins/{scale-bar → beta/scale-bar}/src/index.test.js +0 -0
  239. /package/plugins/{scale-bar → beta/scale-bar}/src/manifest.js +0 -0
  240. /package/plugins/{scale-bar → beta/scale-bar}/src/scaleBar.scss +0 -0
  241. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.js +0 -0
  242. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.test.js +0 -0
  243. /package/plugins/{use-location → beta/use-location}/dist/esm/index.js.LICENSE.txt +0 -0
  244. /package/plugins/{use-location → beta/use-location}/dist/umd/index.js.LICENSE.txt +0 -0
  245. /package/plugins/{use-location → beta/use-location}/src/UseLocation.jsx +0 -0
  246. /package/plugins/{use-location → beta/use-location}/src/UseLocationInit.jsx +0 -0
  247. /package/plugins/{use-location → beta/use-location}/src/defaults.js +0 -0
  248. /package/plugins/{use-location → beta/use-location}/src/events.js +0 -0
  249. /package/plugins/{use-location → beta/use-location}/src/index.js +0 -0
  250. /package/plugins/{use-location → beta/use-location}/src/manifest.js +0 -0
  251. /package/plugins/{use-location → beta/use-location}/src/reducer.js +0 -0
  252. /package/providers/{esri → beta/esri}/src/appEvents.js +0 -0
  253. /package/providers/{esri → beta/esri}/src/defaults.js +0 -0
  254. /package/providers/{esri → beta/esri}/src/esriProvider.js +0 -0
  255. /package/providers/{esri → beta/esri}/src/esriProvider.scss +0 -0
  256. /package/providers/{esri → beta/esri}/src/index.js +0 -0
  257. /package/providers/{esri → beta/esri}/src/utils/coords.js +0 -0
  258. /package/providers/{esri → beta/esri}/src/utils/detectWebGL.js +0 -0
  259. /package/providers/{esri → beta/esri}/src/utils/esriFixes.js +0 -0
  260. /package/providers/{esri → beta/esri}/src/utils/query.js +0 -0
  261. /package/providers/{esri → beta/esri}/src/utils/spatial.js +0 -0
  262. /package/providers/{open-names → beta/open-names}/dist/esm/im-reverse-geocode.js.LICENSE.txt +0 -0
  263. /package/providers/{open-names → beta/open-names}/dist/esm/index.js.LICENSE.txt +0 -0
  264. /package/providers/{open-names → beta/open-names}/dist/umd/im-reverse-geocode.js.LICENSE.txt +0 -0
  265. /package/providers/{open-names → beta/open-names}/dist/umd/index.js.LICENSE.txt +0 -0
  266. /package/providers/{open-names → beta/open-names}/src/index.js +0 -0
  267. /package/providers/{open-names → beta/open-names}/src/reverseGeocode.js +0 -0
  268. /package/providers/{open-names → beta/open-names}/src/utils/mapToLocationModel.js +0 -0
@@ -0,0 +1,496 @@
1
+ import createVertex from '../../../../../node_modules/@mapbox/mapbox-gl-draw/src/lib/create_vertex.js'
2
+
3
+ import {
4
+ getSnapInstance,
5
+ isSnapActive,
6
+ isSnapEnabled,
7
+ getSnapLngLat,
8
+ triggerSnapAtPoint,
9
+ triggerSnapAtCenter,
10
+ createSnappedEvent,
11
+ createSnappedClickEvent
12
+ } from '../utils/snapHelpers.js'
13
+
14
+ /**
15
+ * Factory function to create a draw mode for either polygons or lines.
16
+ * Reduces duplication by sharing common event handling, snap detection, etc.
17
+ *
18
+ * @param {Object} ParentMode - DrawPolygon or DrawLineString from mapbox-gl-draw
19
+ * @param {Object} config - Configuration for the mode
20
+ * @param {string} config.featureProp - Property name on state ('polygon' or 'line')
21
+ * @param {string} config.geometryType - 'Polygon' or 'LineString'
22
+ * @param {Function} config.getCoords - Function to get coordinates from feature
23
+ * @param {Function} config.validateClick - Validation function for clicks
24
+ * @param {Function} config.createVertices - Function to create vertex display features
25
+ */
26
+ export const createDrawMode = (ParentMode, config) => {
27
+ const {
28
+ featureProp,
29
+ geometryType,
30
+ getCoords,
31
+ validateClick,
32
+ createVertices,
33
+ excludeFeatureIdFromSetup = false,
34
+ finishOnInvalidClick = false // For lines: finish when clicking same spot (like double-click)
35
+ } = config
36
+
37
+ const getFeature = (state) => state[featureProp]
38
+
39
+ return {
40
+ ...ParentMode,
41
+
42
+ onSetup(options) {
43
+ const { map } = this
44
+
45
+ // Some parent modes (DrawLineString) interpret featureId as "continue existing"
46
+ // rather than "use this ID for new feature"
47
+ const parentOptions = excludeFeatureIdFromSetup
48
+ ? { ...options, featureId: undefined }
49
+ : options
50
+
51
+ const state = {
52
+ ...ParentMode.onSetup.call(this, parentOptions),
53
+ ...options
54
+ }
55
+
56
+ // Add initial props
57
+ state[featureProp].properties = options.properties
58
+
59
+ const { container, interfaceType, vertexMarkerId } = state
60
+ const vertexMarker = container.querySelector(`#${vertexMarkerId}`)
61
+ vertexMarker.style.display = ['touch', 'keyboard'].includes(interfaceType) ? 'block' : 'none'
62
+ state.vertexMarker = vertexMarker
63
+
64
+ // Bind all handlers once
65
+ const bind = (name, fn) => (this[name] = fn.bind(this, state))
66
+ const handlers = {
67
+ keydownHandler: this.onKeydown,
68
+ keyupHandler: this.onKeyup,
69
+ focusHandler: this.onFocus,
70
+ blurHandler: this.onBlur,
71
+ createHandler: this.onCreate,
72
+ moveHandler: this.onMove,
73
+ pointerdownHandler: this.onPointerdown,
74
+ pointermoveHandler: this.onPointermove,
75
+ pointerupHandler: this.onPointerup,
76
+ vertexButtonClickHandler: this.onVertexButtonClick,
77
+ undoHandler: this.onUndo
78
+ }
79
+ Object.entries(handlers).forEach(([k, fn]) => bind(k, fn))
80
+
81
+ // Register events
82
+ this._listeners = [
83
+ [window, 'keydown', this.keydownHandler],
84
+ [window, 'keyup', this.keyupHandler],
85
+ [window, 'click', this.vertexButtonClickHandler],
86
+ [container, 'focus', this.focusHandler],
87
+ [container, 'blur', this.blurHandler],
88
+ [container, 'pointermove', this.pointermoveHandler],
89
+ [container, 'pointerup', this.pointerupHandler],
90
+ [map, 'pointerdown', this.pointerdownHandler],
91
+ [map, 'draw.create', this.createHandler],
92
+ [map, 'move', this.moveHandler],
93
+ [map, 'draw.undo', this.undoHandler]
94
+ ]
95
+ this._listeners.forEach(([t, e, h]) => t.addEventListener ? t.addEventListener(e, h) : t.on(e, h))
96
+
97
+ return state
98
+ },
99
+
100
+ onClick(state, e) {
101
+ // Skip non-primary clicks, undo operations, or clicks outside canvas
102
+ if (e.originalEvent.button > 0 || this.map._undoInProgress || e.originalEvent.target !== this.map.getCanvas()) {
103
+ return
104
+ }
105
+ const snap = getSnapInstance(this.map)
106
+ if (isSnapEnabled(state) && isSnapActive(snap)) {
107
+ e = createSnappedEvent(e, snap)
108
+ } else {
109
+ const coords = getCoords(getFeature(state))
110
+ if (coords.length > 0) {
111
+ coords[coords.length - 1] = [e.lngLat.lng, e.lngLat.lat]
112
+ }
113
+ }
114
+ const coordsBefore = getCoords(getFeature(state)).length
115
+ ParentMode.onClick.call(this, state, e)
116
+ // Push undo if a vertex was added
117
+ if (getCoords(getFeature(state)).length > coordsBefore) {
118
+ this.pushDrawUndo(state)
119
+ }
120
+ },
121
+
122
+ onTap() {
123
+ return
124
+ },
125
+
126
+ doClick(state) {
127
+ // Skip during undo operation
128
+ if (this.map._undoInProgress) {
129
+ return
130
+ }
131
+
132
+ const feature = getFeature(state)
133
+ const coords = getCoords(feature)
134
+ this.dispatchVertexChange(coords)
135
+
136
+ if (!validateClick(feature)) {
137
+ // For lines: clicking same spot (like double-click) should finish the line
138
+ if (finishOnInvalidClick && coords.length > 1) {
139
+ coords.pop()
140
+ this.map.fire('draw.create', {
141
+ features: [feature.toGeoJSON()]
142
+ })
143
+ this.changeMode('simple_select', { featureIds: [feature.id] })
144
+ }
145
+ return
146
+ }
147
+
148
+ const snap = getSnapInstance(this.map)
149
+ const snappedEvent = isSnapEnabled(state) && createSnappedClickEvent(this.map, snap)
150
+ const coordsBefore = coords.length
151
+
152
+ if (snappedEvent) {
153
+ ParentMode.onClick.call(this, state, snappedEvent)
154
+ this._ctx.store.render()
155
+ } else {
156
+ this._simulateMouse('click', ParentMode.onClick, state)
157
+ }
158
+
159
+ // Push undo if a vertex was added
160
+ if (getCoords(getFeature(state)).length > coordsBefore) {
161
+ this.pushDrawUndo(state)
162
+ }
163
+ },
164
+
165
+ dispatchVertexChange(coords) {
166
+ this.map.fire('draw.vertexchange', {
167
+ numVertecies: coords.length
168
+ })
169
+ },
170
+
171
+ /**
172
+ * Push an undo operation for the last added vertex
173
+ */
174
+ pushDrawUndo(state) {
175
+ const undoStack = this.map._undoStack
176
+ // Don't push during undo operations
177
+ if (!undoStack || this.map._undoInProgress) {
178
+ return
179
+ }
180
+ undoStack.push({
181
+ type: 'draw_vertex',
182
+ geometryType,
183
+ featureId: getFeature(state).id
184
+ })
185
+ },
186
+
187
+ /**
188
+ * Undo the last added vertex during drawing
189
+ */
190
+ undoVertex(state) {
191
+ const feature = getFeature(state)
192
+ const coords = getCoords(feature)
193
+
194
+ if (coords.length < 2) {
195
+ return false
196
+ }
197
+
198
+ // Undoing last vertex requires reinitializing the feature
199
+ if (coords.length === 2) {
200
+ return this._reinitializeFeature(state, feature)
201
+ }
202
+
203
+ this._removeLastVertex(state, feature, coords)
204
+ return true
205
+ },
206
+
207
+ /**
208
+ * Reinitialize feature when undoing to 0 vertices
209
+ * For Polygon: reinitialize in place
210
+ * For LineString: restart the draw mode with fresh state
211
+ */
212
+ _reinitializeFeature(state, feature) {
213
+ const featureId = feature.id
214
+ this._ctx.store.delete([featureId])
215
+
216
+ // LineString: restart the draw mode with fresh state but same feature ID
217
+ if (geometryType === 'LineString') {
218
+ const undoStack = this.map._undoStack
219
+ if (undoStack) {
220
+ undoStack.clear()
221
+ }
222
+ // Restart draw with same options (excludeFeatureIdFromSetup prevents "continue" mode)
223
+ this._ctx.api.changeMode('draw_line', {
224
+ featureId,
225
+ container: state.container,
226
+ interfaceType: state.interfaceType,
227
+ vertexMarkerId: state.vertexMarkerId,
228
+ addVertexButtonId: state.addVertexButtonId,
229
+ getSnapEnabled: state.getSnapEnabled
230
+ })
231
+ return true
232
+ }
233
+
234
+ // Polygon: reinitialize in place
235
+ const center = this.map.getCenter()
236
+ const initialCoords = [[center.lng, center.lat], [center.lng, center.lat]]
237
+ const newFeature = this.newFeature({
238
+ type: 'Feature',
239
+ properties: {},
240
+ geometry: {
241
+ type: geometryType,
242
+ coordinates: [initialCoords]
243
+ }
244
+ })
245
+ newFeature.id = featureId
246
+ this._ctx.store.add(newFeature)
247
+
248
+ state[featureProp] = newFeature
249
+ state.currentVertexPosition = 0
250
+
251
+ this._ctx.store.render()
252
+ this._simulateMouse('mousemove', ParentMode.onMouseMove, state)
253
+ this._ctx.store.render()
254
+
255
+ this.dispatchVertexChange(initialCoords)
256
+ return true
257
+ },
258
+
259
+ /**
260
+ * Remove the last committed vertex and update rubber band
261
+ */
262
+ _removeLastVertex(state, feature, coords) {
263
+ // Structure during drawing: [v1, v2, ..., vN, rubber_band]
264
+ const ring = geometryType === 'Polygon' ? feature.coordinates[0] : coords
265
+ ring.splice(ring.length - 2, 1)
266
+
267
+ // Snap rubber band to new last vertex position
268
+ const newLastVertex = ring[ring.length - 2]
269
+ if (newLastVertex) {
270
+ ring[ring.length - 1] = [...newLastVertex]
271
+ }
272
+
273
+ // Keep parent mode's vertex counter in sync (min 1 for rubber band)
274
+ state.currentVertexPosition = Math.max(1, state.currentVertexPosition - 1)
275
+
276
+ this._ctx.store.render()
277
+ this._updateRubberBand(state, getCoords(feature))
278
+ },
279
+
280
+ /**
281
+ * Update rubber band position based on interface type
282
+ */
283
+ _updateRubberBand(state, coords) {
284
+ if (['touch', 'keyboard'].includes(state.interfaceType)) {
285
+ // Touch/keyboard: move to map center for add point to work
286
+ this._simulateMouse('mousemove', ParentMode.onMouseMove, state)
287
+ this._ctx.store.render()
288
+ } else {
289
+ // Mouse: keep rubber band at current position
290
+ const rubberBandIndex = geometryType === 'Polygon' ? coords.length - 2 : coords.length - 1
291
+ const rubberBandPos = coords[rubberBandIndex]
292
+ if (rubberBandPos) {
293
+ const lngLat = { lng: rubberBandPos[0], lat: rubberBandPos[1] }
294
+ const point = this.map.project(lngLat)
295
+ ParentMode.onMouseMove.call(this, state, {
296
+ lngLat,
297
+ point,
298
+ originalEvent: new MouseEvent('mousemove', { clientX: point.x, clientY: point.y })
299
+ })
300
+ this._ctx.store.render()
301
+ }
302
+ }
303
+ this.dispatchVertexChange(coords)
304
+ },
305
+
306
+ /**
307
+ * Handle draw.undo event
308
+ */
309
+ onUndo(state, e) {
310
+ if (e.operation?.type === 'draw_vertex') {
311
+ this.undoVertex(state)
312
+ }
313
+ },
314
+
315
+ _simulateMouse(type, fn, state) {
316
+ const { map } = this
317
+ const center = map.getCenter()
318
+ const point = map.project(center)
319
+ fn.call(this, state, {
320
+ lngLat: center,
321
+ point,
322
+ originalEvent: new MouseEvent(type, {
323
+ clientX: point.x,
324
+ clientY: point.y,
325
+ bubbles: true,
326
+ cancelable: true
327
+ })
328
+ })
329
+ this._ctx.store.render()
330
+
331
+ this.map.fire('draw.geometrychange', state.polygon || state.line)
332
+ },
333
+
334
+ _setInterface(state, type, show = true) {
335
+ state.interfaceType = type
336
+ if (show) {
337
+ state.vertexMarker.style.display = 'block'
338
+ }
339
+ },
340
+
341
+ onCreate(state, e) {
342
+ const draw = this._ctx.api
343
+ const feature = e.features[0]
344
+ draw.delete(feature.id)
345
+ feature.id = state.featureId
346
+ draw.add(feature)
347
+ },
348
+
349
+ onVertexButtonClick(state, e) {
350
+ // Only trigger for the specific add vertex button, and skip during undo
351
+ if (state.addVertexButtonId && !this.map._undoInProgress && e.target.closest(`#${state.addVertexButtonId}`)) {
352
+ this.doClick(state)
353
+ }
354
+ },
355
+
356
+ onTouchStart(state, e) {
357
+ this._setInterface(state, 'touch')
358
+ this.onMove(state, e)
359
+ },
360
+
361
+ onTouchEnd(state, e) {
362
+ this._setInterface(state, 'touch')
363
+ this.onMove(state, e)
364
+ },
365
+
366
+ onKeydown(state, e) {
367
+ // Undo with Cmd/Ctrl+Z (works without viewport focus, but not in input fields)
368
+ if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
369
+ const tag = document.activeElement?.tagName
370
+ if (tag === 'INPUT' || tag === 'TEXTAREA') {
371
+ return
372
+ }
373
+ e.preventDefault()
374
+ e.stopPropagation()
375
+ const undoStack = this.map._undoStack
376
+ if (undoStack && undoStack.length > 0) {
377
+ const operation = undoStack.pop()
378
+ if (operation?.type === 'draw_vertex') {
379
+ // Set flag to prevent click interference during undo
380
+ this.map._undoInProgress = true
381
+ setTimeout(() => { this.map._undoInProgress = false }, 100)
382
+ this.undoVertex(state)
383
+ }
384
+ }
385
+ return
386
+ }
387
+ if (document.activeElement !== state.container) {
388
+ return
389
+ }
390
+ if (e.key === 'Escape') {
391
+ return e.preventDefault()
392
+ }
393
+ if (e.key === 'Enter') {
394
+ state.isActive = true
395
+ }
396
+ this._setInterface(state, 'keyboard')
397
+ this.onMove(state, e)
398
+ },
399
+
400
+ onKeyup(state, e) {
401
+ if (e.key === 'Escape' || document.activeElement !== state.container) {
402
+ return
403
+ }
404
+ this._setInterface(state, 'keyboard')
405
+ this.onMove(state, e)
406
+ if (e.key === 'Enter' && state.isActive) {
407
+ this.doClick(state)
408
+ }
409
+ },
410
+
411
+ onFocus(state) {
412
+ state.vertexMarker.style.display = ['touch', 'keyboard'].includes(state.interfaceType) ? 'block' : 'none'
413
+ },
414
+
415
+ onBlur(state, e) {
416
+ if (e.target !== state.container) {
417
+ state.vertexMarker.style.display = 'none'
418
+ }
419
+ },
420
+
421
+ onMouseMove(state, e) {
422
+ if (isSnapEnabled(state)) {
423
+ const snap = getSnapInstance(this.map)
424
+ triggerSnapAtPoint(snap, this.map, e.point)
425
+
426
+ const snappedLngLat = getSnapLngLat(snap)
427
+ if (snappedLngLat) {
428
+ e = { ...e, lngLat: snappedLngLat }
429
+ }
430
+ }
431
+
432
+ this.map.fire('draw.geometrychange', state.polygon || state.line)
433
+
434
+ ParentMode.onMouseMove.call(this, state, e)
435
+ },
436
+
437
+ onMove(state) {
438
+ if (['touch', 'keyboard'].includes(state.interfaceType)) {
439
+ if (isSnapEnabled(state)) {
440
+ triggerSnapAtCenter(getSnapInstance(this.map), this.map)
441
+ }
442
+
443
+ const snap = getSnapInstance(this.map)
444
+ const snappedLngLat = isSnapEnabled(state) && getSnapLngLat(snap)
445
+
446
+ if (snappedLngLat) {
447
+ const point = this.map.project([snappedLngLat.lng, snappedLngLat.lat])
448
+ ParentMode.onMouseMove.call(this, state, {
449
+ lngLat: snappedLngLat,
450
+ point,
451
+ originalEvent: new MouseEvent('mousemove', {
452
+ clientX: point.x,
453
+ clientY: point.y,
454
+ bubbles: true,
455
+ cancelable: true
456
+ })
457
+ })
458
+ this._ctx.store.render()
459
+ } else {
460
+ this._simulateMouse('mousemove', ParentMode.onMouseMove, state)
461
+ }
462
+ }
463
+ },
464
+
465
+ onPointerdown(state, e) {
466
+ if (e.pointerType !== 'touch') {
467
+ this._setInterface(state, 'pointer', false)
468
+ }
469
+ },
470
+
471
+ onPointermove(state, e) {
472
+ if (e.pointerType !== 'touch') {
473
+ state.vertexMarker.style.display = 'none'
474
+ }
475
+ },
476
+
477
+ onPointerup(state) {
478
+ this.dispatchVertexChange(getCoords(getFeature(state)))
479
+ },
480
+
481
+ toDisplayFeatures(state, geojson, display) {
482
+ ParentMode.toDisplayFeatures.call(this, state, geojson, display)
483
+
484
+ const feature = getFeature(state)
485
+ if (geojson.geometry.type === geometryType && geojson.id === feature.id) {
486
+ createVertices(geojson, display, createVertex)
487
+ }
488
+ },
489
+
490
+ onStop(state) {
491
+ ParentMode.onStop.call(this, state)
492
+ this._listeners.forEach(([t, e, h]) => t.removeEventListener ? t.removeEventListener(e, h) : t.off(e, h))
493
+ state.vertexMarker.style.display = 'none'
494
+ }
495
+ }
496
+ }
@@ -0,0 +1,18 @@
1
+ import DrawLineString from '../../../../../node_modules/@mapbox/mapbox-gl-draw/src/modes/draw_line_string.js'
2
+ import { isValidLineClick } from '../utils/spatial.js'
3
+ import { createDrawMode } from './createDrawMode.js'
4
+
5
+ export const DrawLineMode = createDrawMode(DrawLineString, {
6
+ featureProp: 'line',
7
+ geometryType: 'LineString',
8
+ getCoords: (feature) => feature.coordinates,
9
+ validateClick: (feature) => isValidLineClick(feature.coordinates),
10
+ excludeFeatureIdFromSetup: true, // DrawLineString interprets featureId as "continue existing"
11
+ finishOnInvalidClick: true, // Clicking same spot (like double-click) finishes the line
12
+ createVertices: (geojson, display, createVertex) => {
13
+ const coords = geojson.geometry.coordinates
14
+ for (let i = 1; i < coords.length - 1; i++) {
15
+ display(createVertex(geojson.id, coords[i], `${i}`))
16
+ }
17
+ }
18
+ })
@@ -0,0 +1,16 @@
1
+ import DrawPolygon from '../../../../../node_modules/@mapbox/mapbox-gl-draw/src/modes/draw_polygon.js'
2
+ import { isValidClick } from '../utils/spatial.js'
3
+ import { createDrawMode } from './createDrawMode.js'
4
+
5
+ export const DrawPolygonMode = createDrawMode(DrawPolygon, {
6
+ featureProp: 'polygon',
7
+ geometryType: 'Polygon',
8
+ getCoords: (feature) => feature.coordinates[0],
9
+ validateClick: (feature) => isValidClick(feature.coordinates),
10
+ createVertices: (geojson, display, createVertex) => {
11
+ const ring = geojson.geometry.coordinates[0]
12
+ for (let i = 1; i < ring.length - 2; i++) {
13
+ display(createVertex(geojson.id, ring[i], `0.${i}`))
14
+ }
15
+ }
16
+ })