@defra/interactive-map 0.0.40-alpha → 0.0.41-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 (351) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/im-shell.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/control-definition.md +9 -1
  7. package/docs/api/slots.md +26 -5
  8. package/docs/assets/images/slot-map.svg +61 -262
  9. package/docs/examples/draw-tools.mdx +3 -3
  10. package/docs/plugins/draw.md +79 -16
  11. package/docs/plugins/search.md +1 -1
  12. package/govuk-prototype-kit.config.json +2 -0
  13. package/jest.setup.js +5 -0
  14. package/package.json +9 -1
  15. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  16. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  17. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  18. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  19. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  20. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  21. package/plugins/datasets/dist/css/index.css +1 -1
  22. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  23. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  24. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  25. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  26. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  27. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  28. package/plugins/datasets/dist/umd/index.js +1 -1
  29. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
  30. package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
  31. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
  32. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
  33. package/plugins/datasets/src/api/setOpacity.test.js +9 -0
  34. package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
  35. package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
  36. package/plugins/datasets/src/datasets.scss +0 -1
  37. package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
  38. package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
  39. package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
  40. package/plugins/datasets/src/index.test.js +36 -0
  41. package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
  42. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
  43. package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
  44. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
  45. package/plugins/datasets/src/manifest.js +0 -38
  46. package/plugins/datasets/src/manifest.test.js +49 -0
  47. package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
  48. package/plugins/datasets/src/reducers/pluginState.js +3 -16
  49. package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
  50. package/plugins/datasets/src/registry/dataset.js +13 -1
  51. package/plugins/datasets/src/registry/dataset.test.js +61 -0
  52. package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
  53. package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
  54. package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
  55. package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
  56. package/plugins/datasets/src/utils/bbox.test.js +125 -0
  57. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
  58. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
  59. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
  60. package/plugins/draw/dist/esm/index.js +1 -1
  61. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
  62. package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
  63. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  64. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  65. package/plugins/draw/dist/umd/index.js +1 -1
  66. package/plugins/draw/src/DrawInit.jsx +10 -2
  67. package/plugins/draw/src/DrawInit.test.jsx +51 -10
  68. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
  69. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
  70. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
  71. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
  72. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
  74. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
  75. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
  90. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
  91. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
  92. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
  93. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
  94. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
  95. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
  96. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
  97. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
  98. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
  99. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
  100. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
  101. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
  102. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
  103. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
  104. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
  105. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
  106. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
  107. package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
  108. package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
  109. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
  110. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
  111. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
  112. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
  113. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
  114. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
  115. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
  116. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
  117. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
  118. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
  119. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
  120. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
  121. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
  122. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
  123. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
  124. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
  125. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
  126. package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
  127. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
  128. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
  129. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
  130. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
  132. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
  134. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
  136. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
  137. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
  139. package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
  140. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
  141. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
  142. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
  143. package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
  144. package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
  145. package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
  146. package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
  147. package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
  148. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
  149. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
  150. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
  151. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
  153. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
  154. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
  155. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
  156. package/plugins/draw/src/api/addFeature.js +16 -1
  157. package/plugins/draw/src/api/addFeature.test.js +62 -5
  158. package/plugins/draw/src/api/editFeature.js +7 -4
  159. package/plugins/draw/src/api/editFeature.test.js +16 -0
  160. package/plugins/draw/src/api/newPoint.js +42 -0
  161. package/plugins/draw/src/api/newPoint.test.js +117 -0
  162. package/plugins/draw/src/api/setStyle.js +43 -0
  163. package/plugins/draw/src/api/setStyle.test.js +75 -0
  164. package/plugins/draw/src/events.js +17 -10
  165. package/plugins/draw/src/events.test.js +14 -0
  166. package/plugins/draw/src/manifest.js +18 -6
  167. package/plugins/draw/src/manifest.test.js +32 -1
  168. package/plugins/draw/src/reducer.test.js +7 -0
  169. package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
  170. package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
  171. package/plugins/draw/src/utils/symbolKeys.js +5 -0
  172. package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
  173. package/plugins/draw/src/validation/liveStroke.test.js +64 -1
  174. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  175. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  176. package/plugins/interact/dist/umd/index.js +1 -1
  177. package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
  178. package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
  179. package/plugins/interact/src/utils/featureQueries.js +18 -0
  180. package/plugins/interact/src/utils/featureQueries.test.js +15 -0
  181. package/plugins/interact/src/utils/spatial.js +9 -3
  182. package/plugins/interact/src/utils/spatial.test.js +12 -1
  183. package/plugins/map-key/dist/css/index.css +62 -0
  184. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
  185. package/plugins/map-key/dist/esm/index.js +1 -0
  186. package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
  187. package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
  188. package/plugins/map-key/dist/umd/index.js +2 -0
  189. package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
  190. package/plugins/map-key/package.json +28 -0
  191. package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
  192. package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
  193. package/plugins/map-key/src/components/Key/Key.jsx +41 -0
  194. package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
  195. package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
  196. package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
  197. package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
  198. package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
  199. package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
  200. package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
  201. package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
  202. package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
  203. package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
  204. package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
  205. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
  206. package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
  207. package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
  208. package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
  209. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
  210. package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
  211. package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
  212. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
  213. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
  214. package/plugins/map-key/src/index.js +13 -0
  215. package/plugins/map-key/src/index.test.js +31 -0
  216. package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
  217. package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
  218. package/plugins/map-key/src/manifest.js +30 -0
  219. package/plugins/map-key/src/manifest.test.js +63 -0
  220. package/plugins/map-key/src/mapKey.scss +4 -0
  221. package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
  222. package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
  223. package/plugins/map-key/src/registry/index.js +3 -0
  224. package/plugins/map-key/src/registry/index.test.js +15 -0
  225. package/plugins/menu/dist/css/index.css +80 -0
  226. package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
  227. package/plugins/menu/dist/esm/index.js +1 -0
  228. package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
  229. package/plugins/menu/dist/umd/index.js +2 -0
  230. package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
  231. package/plugins/menu/package.json +28 -0
  232. package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
  233. package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
  234. package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
  235. package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
  236. package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
  237. package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
  238. package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
  239. package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
  240. package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
  241. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
  242. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
  243. package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
  244. package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
  245. package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
  246. package/plugins/menu/src/index.js +13 -0
  247. package/plugins/menu/src/index.test.js +31 -0
  248. package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
  249. package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
  250. package/plugins/menu/src/initialise/createMenu.js +10 -0
  251. package/plugins/menu/src/initialise/createMenu.test.js +37 -0
  252. package/plugins/menu/src/manifest.js +35 -0
  253. package/plugins/menu/src/manifest.test.js +63 -0
  254. package/plugins/menu/src/menu.scss +4 -0
  255. package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
  256. package/plugins/menu/src/reducers/pluginState.js +35 -0
  257. package/plugins/menu/src/reducers/pluginState.test.js +57 -0
  258. package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
  259. package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
  260. package/plugins/menu/src/registry/index.js +3 -0
  261. package/plugins/menu/src/registry/index.test.js +15 -0
  262. package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
  263. package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
  264. package/plugins/search/dist/css/index.css +1 -1
  265. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  266. package/plugins/search/dist/esm/index.js +1 -1
  267. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  268. package/plugins/search/dist/umd/index.js +1 -1
  269. package/plugins/search/src/Search.jsx +2 -3
  270. package/plugins/search/src/components/Form/Form.module.scss +9 -20
  271. package/plugins/search/src/index.js +0 -1
  272. package/plugins/search/src/index.test.js +2 -4
  273. package/plugins/search/src/manifest.js +1 -1
  274. package/plugins/search/src/search.scss +17 -16
  275. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  276. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  277. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  278. package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
  279. package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
  280. package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
  281. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
  282. package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
  283. package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
  284. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  285. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  286. package/providers/maplibre/dist/umd/index.js +1 -1
  287. package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
  288. package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
  289. package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
  290. package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
  291. package/providers/maplibre/src/utils/hoverCursor.js +30 -14
  292. package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
  293. package/providers/maplibre/src/utils/patternImages.js +1 -1
  294. package/providers/maplibre/src/utils/symbolImages.js +41 -3
  295. package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
  296. package/rollup.esm.mjs +11 -1
  297. package/src/App/components/Actions/Actions.test.jsx +47 -0
  298. package/src/App/components/Hints/Hints.test.jsx +13 -0
  299. package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
  300. package/src/App/components/Panel/Panel.jsx +18 -3
  301. package/src/App/components/Panel/Panel.module.scss +18 -8
  302. package/src/App/components/Panel/Panel.test.jsx +34 -0
  303. package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
  304. package/src/App/hooks/useFeatureItems.js +16 -7
  305. package/src/App/hooks/useFeatureItems.test.js +57 -0
  306. package/src/App/hooks/useLayoutMeasurements.js +95 -84
  307. package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
  308. package/src/App/layout/Layout.jsx +9 -10
  309. package/src/App/layout/Layout.test.jsx +2 -0
  310. package/src/App/layout/layout.module.scss +31 -17
  311. package/src/App/registry/pluginRegistry.js +1 -1
  312. package/src/App/registry/pluginRegistry.test.js +12 -0
  313. package/src/App/renderer/HtmlElementHost.jsx +18 -5
  314. package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
  315. package/src/App/renderer/mapButtons.js +28 -12
  316. package/src/App/renderer/mapButtons.test.js +17 -4
  317. package/src/App/renderer/mapControls.js +3 -1
  318. package/src/App/renderer/mapControls.test.js +16 -0
  319. package/src/App/renderer/mapPanels.js +44 -9
  320. package/src/App/renderer/mapPanels.test.js +51 -2
  321. package/src/App/renderer/orderItems.js +28 -0
  322. package/src/App/renderer/orderItems.test.js +50 -0
  323. package/src/App/renderer/slotAggregator.js +2 -18
  324. package/src/App/renderer/slotHelpers.js +3 -1
  325. package/src/App/renderer/slotHelpers.test.js +5 -0
  326. package/src/App/renderer/slots.js +3 -0
  327. package/src/App/store/AppProvider.jsx +1 -0
  328. package/src/App/store/appDispatchMiddleware.js +1 -1
  329. package/src/App/store/appDispatchMiddleware.test.js +8 -0
  330. package/src/config/events.js +2 -0
  331. package/src/scss/settings/_colors.scss +1 -0
  332. package/src/scss/settings/_dimensions.scss +2 -3
  333. package/src/scss/settings/_transition.scss +1 -1
  334. package/src/services/eventBus.js +44 -0
  335. package/src/services/eventBus.test.js +83 -0
  336. package/src/services/symbolRegistry.js +1 -1
  337. package/src/types.js +11 -1
  338. package/webpack.umd.mjs +2 -0
  339. package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
  340. package/plugins/datasets/src/components/Key/Key.jsx +0 -50
  341. package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
  342. package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
  343. package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
  344. package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
  345. package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
  346. package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
  347. package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
  348. package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
  349. /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
  350. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
  351. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
@@ -3,8 +3,7 @@ import {
3
3
  getSnapInstance, isSnapEnabled, getSnapLngLat, triggerSnapAtPoint, clearSnapState
4
4
  } from '../../utils/snapHelpers.js'
5
5
  import { getCoords, coordPathToFlatIndex } from './geometryHelpers.js'
6
-
7
- const EVENT_VERTEX_SELECTION = 'draw.vertexselection'
6
+ import { CUSTOM_DRAW_EVENTS } from '../../drawEvents.js'
8
7
 
9
8
  /**
10
9
  * Mouse/pointer interaction for the vertex-edit mode: vertex/midpoint mouse-down,
@@ -48,7 +47,7 @@ export const pointerHandlers = {
48
47
  state.selectedVertexIndex = this.getVertexIndexFromMidpoint(state, coordPath)
49
48
  state.selectedVertexType = 'vertex'
50
49
  state.coordPath = null // Clear coordPath for midpoints
51
- this.map.fire(EVENT_VERTEX_SELECTION, { index: state.selectedVertexIndex, numVertecies: state.vertecies.length })
50
+ this.map.fire(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, { index: state.selectedVertexIndex, numVertecies: state.vertecies.length })
52
51
  }
53
52
  },
54
53
 
@@ -61,7 +60,7 @@ export const pointerHandlers = {
61
60
  state.selectedVertexType = 'vertex'
62
61
  state._isInsertingVertex = false
63
62
  state._insertedVertexIndex = null
64
- this.map.fire(EVENT_VERTEX_SELECTION, { index: insertedIndex, numVertecies: state.vertecies.length })
63
+ this.map.fire(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, { index: insertedIndex, numVertecies: state.vertecies.length })
65
64
  return
66
65
  }
67
66
  DirectSelect.onClick.call(this, state, e)
@@ -116,7 +115,7 @@ export const pointerHandlers = {
116
115
  state._isInsertingVertex = false
117
116
  state._insertedVertexIndex = null
118
117
  // DirectSelect.onMouseUp fires draw.update but not draw.selectionchange, so broadcast the count here
119
- this.map.fire(EVENT_VERTEX_SELECTION, { index: insertedIndex, numVertecies: state.vertecies.length })
118
+ this.map.fire(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, { index: insertedIndex, numVertecies: state.vertecies.length })
120
119
  },
121
120
 
122
121
  _recordMoveUndo (state) {
@@ -1,11 +1,9 @@
1
- import {
2
- getSnapInstance, isSnapEnabled, triggerSnapAtPoint, getSnapLngLat,
3
- clearSnapState, clearSnapIndicator
4
- } from '../../utils/snapHelpers.js'
1
+ import { getSnapInstance, clearSnapState, clearSnapIndicator } from '../../utils/snapHelpers.js'
5
2
  import { coordPathToFlatIndex } from './geometryHelpers.js'
6
- import { isOnSVG } from './helpers.js'
3
+ import { isOnSVG, scalePoint } from './helpers.js'
7
4
  import { createTouchTarget, applyTouchTargetColors } from '../../../../utils/touchTarget.js'
8
5
  import { resolveColors } from '../../../../utils/resolveColors.js'
6
+ import { getTouchPoint, computeTouchDragAnchors, resolveTouchDragCoord } from '../../utils/touchDragMath.js'
9
7
 
10
8
  export const applyTouchVertexColors = (el, mapStyle, pluginConfig = {}) => {
11
9
  if (!el) { return }
@@ -50,7 +48,7 @@ export const touchHandlers = {
50
48
  this.syncVertices(state)
51
49
 
52
50
  // Push undo for the move if touch actually moved
53
- if (state._touchMoved && state._moveStartPosition && state._moveStartIndex !== undefined) {
51
+ if (state._touchMoved && state._moveStartPosition && state._moveStartIndex != null) {
54
52
  this.pushUndo({
55
53
  type: 'move_vertex',
56
54
  featureId: state.featureId,
@@ -59,8 +57,19 @@ export const touchHandlers = {
59
57
  })
60
58
  }
61
59
  state._moveStartPosition = null
62
- state._moveStartIndex = undefined
60
+ state._moveStartIndex = null
63
61
  state._touchMoved = false
62
+
63
+ // Re-sync the target to the vertex's actual final coordinate. Mid-drag it tracks the
64
+ // raw finger 1:1 from its touchstart-time offset (see onTouchmove) — a mid-drag snap
65
+ // moves the vertex without moving the target by the same amount, leaving them out of
66
+ // alignment. Left uncorrected, the next onTouchstart's delta math re-anchors off that
67
+ // drifted position, compounding a little further with every snapped drag (same fix as
68
+ // editPointMode's own onTouchend, for the same reason).
69
+ const vertex = state.vertecies[state.selectedVertexIndex]
70
+ if (vertex) {
71
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
72
+ }
64
73
  }
65
74
  },
66
75
 
@@ -103,11 +112,8 @@ export const touchHandlers = {
103
112
  state._moveStartIndex = state.selectedVertexIndex
104
113
  state._touchMoved = false
105
114
 
106
- const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
107
- const style = window.getComputedStyle(state.touchVertexTarget)
108
- state.deltaTarget = { x: touch.x - Number.parseFloat(style.left), y: touch.y - Number.parseFloat(style.top) }
109
- const vertexPt = this.map.project(vertex)
110
- state.deltaVertex = { x: (touch.x / state.scale) - vertexPt.x, y: (touch.y / state.scale) - vertexPt.y }
115
+ const touch = getTouchPoint(e)
116
+ Object.assign(state, computeTouchDragAnchors(this.map, state.touchVertexTarget, touch, vertex, state.scale))
111
117
  },
112
118
 
113
119
  onTouchmove (state, e) {
@@ -117,17 +123,8 @@ export const touchHandlers = {
117
123
 
118
124
  state._touchMoved = true
119
125
 
120
- const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
121
- const screenPt = { x: (touch.x / state.scale) - state.deltaVertex.x, y: (touch.y / state.scale) - state.deltaVertex.y }
122
-
123
- let finalCoord = this.map.unproject(screenPt)
124
- if (isSnapEnabled(state)) {
125
- const snap = getSnapInstance(this.map)
126
- triggerSnapAtPoint(snap, this.map, screenPt)
127
- finalCoord = getSnapLngLat(snap) || finalCoord
128
- }
129
-
130
- this.moveVertex(state, finalCoord)
126
+ const touch = getTouchPoint(e)
127
+ this.moveVertex(state, resolveTouchDragCoord(this.map, state, touch))
131
128
  this.updateTouchVertexTarget(state, { x: touch.x - state.deltaTarget.x, y: touch.y - state.deltaTarget.y })
132
129
  }
133
130
  }
@@ -55,6 +55,27 @@ describe('touchHandlers', () => {
55
55
  expect(map._undoStack.pop()).toMatchObject({ type: 'move_vertex', vertexIndex: 1 })
56
56
  })
57
57
 
58
+ // Regression: mid-drag the target tracks the raw finger 1:1 from its touchstart-time offset
59
+ // (see onTouchmove), so a mid-drag snap — which moves the vertex without moving the target
60
+ // by the same amount — leaves the two out of alignment. Re-syncing on touchend to the
61
+ // vertex's actual final coordinate stops that drift from compounding on the next drag.
62
+ test('onTouchend re-syncs the target to the selected vertex\'s actual final coordinate', () => {
63
+ const { ctx, state } = createHarness()
64
+ state.interfaceType = 'touch'
65
+ state.selectedVertexIndex = 1
66
+ ctx.moveVertex(state, { lng: 9, lat: 9 }) // as onTouchmove would have left it mid-drag
67
+ ctx.onTouchend(state)
68
+ expect(state.touchVertexTarget.style.display).toBe('block')
69
+ expect(state.touchVertexTarget.style.left).toBe(`${9 * 10}px`) // harness project(): coord * 10
70
+ expect(state.touchVertexTarget.style.top).toBe(`${9 * 10}px`)
71
+ })
72
+
73
+ test('onTouchend skips repositioning when nothing is selected', () => {
74
+ const { ctx, state } = createHarness()
75
+ state.selectedVertexIndex = -1
76
+ expect(() => ctx.onTouchend(state)).not.toThrow()
77
+ })
78
+
58
79
  test('onTouchstart captures move start state, ignoring taps off the vertex/target', () => {
59
80
  const { ctx, state } = createHarness()
60
81
  ctx.onTouchstart(state, { target: { parentNode: document.createElement('div') }, touches: [{ clientX: 1, clientY: 1 }] })
@@ -66,6 +87,9 @@ describe('touchHandlers', () => {
66
87
  expect(state._touchMoved).toBe(false)
67
88
  })
68
89
 
90
+ // resolveTouchDragCoord's own snap/fallback branches are covered by
91
+ // utils/touchDragMath.test.js — this checks onTouchmove ignores off-target moves and wires
92
+ // an active snap through to the selected vertex.
69
93
  test('onTouchmove moves the selected vertex, honouring snap, and ignores non-target moves', () => {
70
94
  const { ctx, state, map } = createHarness()
71
95
  state.selectedVertexIndex = 1
@@ -74,16 +98,12 @@ describe('touchHandlers', () => {
74
98
  ctx.onTouchmove(state, { target: { parentNode: document.createElement('div') }, touches: [{ clientX: 5, clientY: 5 }] })
75
99
  expect(state.vertecies[1]).toEqual([10, 0]) // off-target move ignored
76
100
  ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 30, clientY: 40 }] })
101
+ expect(state._touchMoved).toBe(true)
77
102
 
78
103
  state.getSnapEnabled = () => true
79
104
  map._snapInstance = { status: true, snapStatus: true, snapCoords: [7, 8], snapToClosestPoint: jest.fn() }
80
105
  ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 50, clientY: 60 }] })
81
106
  expect(state.vertecies[1]).toEqual([7, 8])
82
-
83
- // Snap enabled but no snap point → falls back to the pointer position
84
- map._snapInstance = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
85
- ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 35, clientY: 45 }] })
86
- expect(state._touchMoved).toBe(true)
87
107
  })
88
108
 
89
109
  test('onTap clears the snap indicator, then selects a vertex, inserts on a midpoint, or clears with no target', () => {
@@ -4,8 +4,10 @@ import {
4
4
  getModifiableCoords
5
5
  } from './geometryHelpers.js'
6
6
  import { scalePoint } from './helpers.js'
7
+ import { sharedUndoHandlers } from '../../utils/geometryValidation.js'
7
8
 
8
- // Map an undo-stack op type onto the geometry-change `phase` consumed by validation.
9
+ // Map an undo-stack op type onto the geometry-change `phase` consumed by validation. Read off
10
+ // `this` by the shared pushUndo mixin (utils/geometryValidation.js) — see that file's comment.
9
11
  const UNDO_OP_PHASE = {
10
12
  move_vertex: 'commit-move',
11
13
  insert_vertex: 'commit-insert',
@@ -21,41 +23,9 @@ const UNDO_INVERSE_PHASE = {
21
23
  }
22
24
 
23
25
  export const undoHandlers = {
24
- // Fire geometry change event (for external listeners)
25
- fireGeometryChange (state) {
26
- const feature = this.getFeature(state.featureId)
27
- if (feature) {
28
- this.map.fire('draw.update', {
29
- features: [feature.toGeoJSON()],
30
- action: 'change_coordinates'
31
- })
32
- }
33
- },
34
-
35
- // Emit a commit-level geometrychange (feature + change phase + vertex index) so the
36
- // validation layer can accept or reject the change. Deferred a tick to avoid
37
- // re-entrancy: rejection calls draw.undo(), which must run after the current
38
- // mutation (and its undo bookkeeping) has fully settled.
39
- emitGeometryValidation (phase, vertexIndex, featureId) {
40
- if (!phase) { return }
41
- setTimeout(() => {
42
- const feature = this.getFeature(featureId)
43
- if (!feature) { return }
44
- this.map.fire('draw.geometrychange', { feature: feature.toGeoJSON(), phase, vertexIndex })
45
- }, 0)
46
- },
47
-
48
- // Undo support
49
- pushUndo (operation) {
50
- const undoStack = this.map._undoStack
51
- if (!undoStack) {
52
- return
53
- }
54
- undoStack.push(operation)
55
- // Every edit commit (move/insert/delete, via mouse or keyboard) records an undo
56
- // op here, so this is the single point that feeds commit-level validation.
57
- this.emitGeometryValidation(UNDO_OP_PHASE[operation.type], operation.vertexIndex, operation.featureId)
58
- },
26
+ ...sharedUndoHandlers,
27
+ UNDO_OP_PHASE,
28
+ UNDO_INVERSE_PHASE,
59
29
 
60
30
  handleUndo (state) {
61
31
  const undoStack = this.map._undoStack
@@ -1,52 +1,19 @@
1
1
  import { createHarness } from './__helpers__/harness.js'
2
- import { createUndoStack } from '../../../../utils/undoStack.js'
3
2
  import { getRingSegments } from './geometryHelpers.js'
4
3
 
4
+ // fireGeometryChange/emitGeometryValidation/pushUndo are the shared mixin from
5
+ // utils/geometryValidation.js — their own behaviour is covered by
6
+ // utils/geometryValidation.test.js. This file only tests what's specific to editVertexMode:
7
+ // pushUndo's UNDO_OP_PHASE wiring, handleUndo's per-op-type dispatch, and the
8
+ // undoMoveVertex/undoInsertVertex/undoDeleteVertex ring operations themselves.
5
9
  describe('undoHandlers', () => {
6
- test('pushUndo is a no-op without a stack and pushes otherwise', () => {
7
- const { ctx, map } = createHarness()
8
- map._undoStack = null
9
- expect(() => ctx.pushUndo({ type: 'move_vertex' })).not.toThrow()
10
- map._undoStack = createUndoStack(() => {})
11
- ctx.pushUndo({ type: 'move_vertex' })
12
- expect(map._undoStack.length).toBe(1)
13
- })
14
-
15
- test('pushUndo emits a deferred commit-level geometrychange with the change phase', () => {
10
+ test('pushUndo maps each op type to its commit phase', () => {
16
11
  jest.useFakeTimers()
17
12
  const { ctx, map } = createHarness()
18
13
  map.fire.mockClear()
19
-
20
14
  ctx.pushUndo({ type: 'move_vertex', featureId: 'feat-1', vertexIndex: 2 })
21
- // Deferred a tick to avoid re-entrancy — nothing fired synchronously.
22
- expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
23
-
24
- jest.runAllTimers()
25
- expect(map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({
26
- phase: 'commit-move',
27
- vertexIndex: 2,
28
- feature: expect.any(Object)
29
- }))
30
- jest.useRealTimers()
31
- })
32
-
33
- test('pushUndo does not emit a geometrychange for unmapped op types', () => {
34
- jest.useFakeTimers()
35
- const { ctx, map } = createHarness()
36
- map.fire.mockClear()
37
- ctx.pushUndo({ type: 'draw_vertex', featureId: 'feat-1' })
38
15
  jest.runAllTimers()
39
- expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
40
- jest.useRealTimers()
41
- })
42
-
43
- test('emitGeometryValidation does not fire once the feature is gone', () => {
44
- jest.useFakeTimers()
45
- const { ctx, map } = createHarness()
46
- map.fire.mockClear()
47
- ctx.emitGeometryValidation('commit-move', 0, 'missing-feature')
48
- jest.runAllTimers()
49
- expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
16
+ expect(map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({ phase: 'commit-move', vertexIndex: 2 }))
50
17
  jest.useRealTimers()
51
18
  })
52
19
 
@@ -82,15 +49,6 @@ describe('undoHandlers', () => {
82
49
  jest.useRealTimers()
83
50
  })
84
51
 
85
- test('fireGeometryChange fires draw.update only when the feature exists', () => {
86
- const { ctx, state, map } = createHarness()
87
- ctx.fireGeometryChange(state)
88
- expect(map.fire).toHaveBeenCalledWith('draw.update', expect.objectContaining({ action: 'change_coordinates' }))
89
- map.fire.mockClear()
90
- ctx.fireGeometryChange({ featureId: 'missing' })
91
- expect(map.fire).not.toHaveBeenCalled()
92
- })
93
-
94
52
  test('undoMoveVertex restores the previous position, guarding missing feature/segment', () => {
95
53
  const { ctx, state } = createHarness()
96
54
  ctx.undoMoveVertex({ ...state, featureId: 'missing' }, { vertexIndex: 0, previousPosition: [0, 0], featureId: 'missing' })
@@ -4,16 +4,12 @@ import {
4
4
  getSegmentForIndex,
5
5
  getModifiableCoords
6
6
  } from './geometryHelpers.js'
7
- import { KEYBOARD } from '../../defaults.js'
8
- import {
9
- getSnapInstance, isSnapActive, isSnapEnabled, getSnapLngLat,
10
- getSnapRadius, triggerSnapAtPoint, clearSnapIndicator
11
- } from '../../utils/snapHelpers.js'
7
+ import { sharedSnapMovement } from '../../utils/snapMovement.js'
12
8
  import { MIN_VERTICES } from '../../../../validation/rules.js'
13
9
 
14
- const ARROW_OFFSETS = { ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0] }
15
-
16
10
  export const vertexOperations = {
11
+ ...sharedSnapMovement,
12
+
17
13
  updateMidpoint (coordinates) {
18
14
  setTimeout(() => {
19
15
  this.map.getSource('mapbox-gl-draw-hot').setData({
@@ -32,56 +28,10 @@ export const vertexOperations = {
32
28
  this.changeMode(state, { selectedVertexIndex: idx, selectedVertexType: type, ...(type === 'vertex' && { coordPath: this.getCoordPath(state, idx) }) })
33
29
  },
34
30
 
35
- getOffset (coord, e) {
36
- const pt = this.map.project(coord)
37
- const offset = e?.shiftKey ? KEYBOARD.nudgeAmount : KEYBOARD.stepAmount
38
- const [dx, dy] = e ? ARROW_OFFSETS[e.key].map(v => v * offset) : [0, 0]
39
- return this.map.unproject({ x: pt.x + dx, y: pt.y + dy })
40
- },
41
-
42
31
  getNewCoord (state, e) {
43
32
  return this.getOffset(getCoords(this.getFeature(state.featureId))[state.selectedVertexIndex], e)
44
33
  },
45
34
 
46
- // Explicit-delta counterpart to getOffset, driven by a unit direction vector and
47
- // MoveControls' own Precision toggle rather than a KeyboardEvent — used by
48
- // nudgeVertexByDelta below.
49
- getOffsetByDelta (coord, dx, dy, isLargeStep) {
50
- const pt = this.map.project(coord)
51
- const amount = isLargeStep ? KEYBOARD.stepAmount : KEYBOARD.nudgeAmount
52
- return this.map.unproject({ x: pt.x + dx * amount, y: pt.y + dy * amount })
53
- },
54
-
55
- // Resolves the destination coordinate for a vertex nudge by (dx, dy) unit
56
- // direction, applying snap or breaking out of an already-active one — shared by
57
- // the keyboard arrow-key path (_keyboardMoveTarget) and MoveControls'
58
- // explicit-delta path (nudgeVertexByDelta) so both snap identically. dx/dy are
59
- // only needed for the snap-escape offset; getCandidate computes the raw,
60
- // un-snapped destination the caller would otherwise have used.
61
- resolveSnapTarget (state, dx, dy, currentCoord, getCandidate) {
62
- const snap = getSnapInstance(this.map)
63
-
64
- // Break out of an active snap by moving beyond the snap radius
65
- if (isSnapEnabled(state) && state._isSnapped && snap) {
66
- const offset = getSnapRadius(snap) + 1
67
- const pt = this.map.project(currentCoord)
68
- state._isSnapped = false
69
- clearSnapIndicator(snap, this.map)
70
- return this.map.unproject({ x: pt.x + dx * offset, y: pt.y + dy * offset })
71
- }
72
-
73
- const newCoord = getCandidate()
74
- if (isSnapEnabled(state) && snap) {
75
- triggerSnapAtPoint(snap, this.map, this.map.project(newCoord))
76
- if (isSnapActive(snap)) {
77
- state._isSnapped = true
78
- return getSnapLngLat(snap)
79
- }
80
- }
81
- state._isSnapped = false
82
- return newCoord
83
- },
84
-
85
35
  // Moves the selected vertex by an explicit (dx, dy) unit direction — the entry
86
36
  // point for MoveControls' D-pad (see mapProvider.activeMoveTarget in events.js),
87
37
  // as opposed to moveVertexByKey's KeyboardEvent-driven path. Each call is treated
@@ -1,5 +1,8 @@
1
1
  import { createHarness, LINE } from './__helpers__/harness.js'
2
2
 
3
+ // getOffset/getOffsetByDelta/resolveSnapTarget are the shared mixin from
4
+ // utils/snapMovement.js — their own behaviour is covered by utils/snapMovement.test.js.
5
+ // This file only tests what's specific to a ring/segment vertex.
3
6
  describe('vertexOperations', () => {
4
7
  test('updateMidpoint pushes point data to the hot source', () => {
5
8
  jest.useFakeTimers()
@@ -30,30 +33,12 @@ describe('vertexOperations', () => {
30
33
  expect(api.changeMode.mock.calls.at(-1)[1].coordPath).toBeUndefined()
31
34
  })
32
35
 
33
- test('getOffset applies step/nudge amounts and returns the coord unchanged without an event', () => {
34
- const { ctx } = createHarness()
35
- const stepped = ctx.getOffset([5, 5], { key: 'ArrowRight', shiftKey: false })
36
- const nudged = ctx.getOffset([5, 5], { key: 'ArrowRight', shiftKey: true })
37
- expect(stepped.lng).not.toBe(5)
38
- expect(Math.abs(nudged.lng - 5)).toBeLessThan(Math.abs(stepped.lng - 5))
39
- expect(ctx.getOffset([5, 5], null)).toEqual({ lng: 5, lat: 5 })
40
- })
41
-
42
36
  test('getNewCoord offsets the currently selected vertex', () => {
43
37
  const { ctx, state } = createHarness()
44
38
  state.selectedVertexIndex = 1
45
39
  expect(ctx.getNewCoord(state, { key: 'ArrowRight', shiftKey: false })).toHaveProperty('lng')
46
40
  })
47
41
 
48
- test('getOffsetByDelta applies step/nudge amounts along an explicit direction, mirroring getOffset\'s shiftKey polarity', () => {
49
- const { ctx } = createHarness()
50
- const large = ctx.getOffsetByDelta([5, 5], 1, 0, true)
51
- const small = ctx.getOffsetByDelta([5, 5], 1, 0, false)
52
- expect(large.lng).not.toBe(5)
53
- expect(Math.abs(small.lng - 5)).toBeLessThan(Math.abs(large.lng - 5))
54
- expect(ctx.getOffsetByDelta([5, 5], 0, 0, true)).toEqual({ lng: 5, lat: 5 })
55
- })
56
-
57
42
  test('nudgeVertexByDelta moves the selected vertex and pushes a single undo entry, or no-ops for a midpoint/no selection', () => {
58
43
  const { ctx, state, map } = createHarness()
59
44
  state.selectedVertexIndex = 0
@@ -73,29 +58,19 @@ describe('vertexOperations', () => {
73
58
  expect(map._undoStack).toHaveLength(0)
74
59
  })
75
60
 
76
- test('nudgeVertexByDelta snaps to a nearby target, breaks out of an active snap, and falls back to the raw coord when snap is inactive — same as keyboard nudging (regression: MoveControls bypassed snap entirely before resolveSnapTarget was shared)', () => {
61
+ // resolveSnapTarget's own snap/break-out-of-snap branches are covered by
62
+ // utils/snapMovement.test.js — this just checks nudgeVertexByDelta wires a snap hit
63
+ // through to the moved vertex, same as keyboard nudging (regression: MoveControls
64
+ // bypassed snap entirely before resolveSnapTarget was shared).
65
+ test('nudgeVertexByDelta honours an active snap', () => {
77
66
  const { ctx, state, map } = createHarness()
78
67
  state.selectedVertexIndex = 1
79
68
  state.selectedVertexType = 'vertex'
80
69
  state.getSnapEnabled = () => true
81
-
82
70
  map._snapInstance = { status: true, snapStatus: true, snapCoords: [7, 8], snapToClosestPoint: jest.fn() }
83
71
  ctx.nudgeVertexByDelta(state, 0, -1, true)
84
72
  expect(state._isSnapped).toBe(true)
85
73
  expect(state.vertecies[1]).toEqual([7, 8])
86
-
87
- ctx.nudgeVertexByDelta(state, -1, 0, true) // already snapped → breaks out of the snap radius
88
- expect(state._isSnapped).toBe(false)
89
-
90
- map._snapInstance = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
91
- ctx.nudgeVertexByDelta(state, 1, 0, true) // snap enabled but inactive → raw offset coord
92
- expect(state._isSnapped).toBe(false)
93
- })
94
-
95
- test('resolveSnapTarget is a no-op passthrough to the candidate coordinate when snap is disabled', () => {
96
- const { ctx, state } = createHarness()
97
- const candidate = { lng: 3, lat: 4 }
98
- expect(ctx.resolveSnapTarget(state, 1, 0, [0, 0], () => candidate)).toEqual(candidate)
99
74
  })
100
75
 
101
76
  test('insertVertex splits a midpoint into a new vertex, records undo and selects it', () => {
@@ -1,7 +1,8 @@
1
1
  import MapboxDraw from '@mapbox/mapbox-gl-draw'
2
- import { getSnapInstance, clearSnapIndicator } from '../utils/snapHelpers.js'
2
+ import { CUSTOM_DRAW_EVENTS } from '../drawEvents.js'
3
3
  import { getCoords } from './editVertexMode/geometryHelpers.js'
4
4
  import { scalePoint } from './editVertexMode/helpers.js'
5
+ import { bindEditModeListeners, unbindEditModeListeners, clearActiveSnapIndicator, buildEditModeHandlers } from '../utils/editModeEvents.js'
5
6
  import { undoHandlers } from './editVertexMode/undoHandlers.js'
6
7
  import { touchHandlers } from './editVertexMode/touchHandlers.js'
7
8
  import { vertexOperations } from './editVertexMode/vertexOperations.js'
@@ -9,9 +10,6 @@ import { vertexQueries } from './editVertexMode/vertexQueries.js'
9
10
  import { keyboardHandlers } from './editVertexMode/keyboardHandlers.js'
10
11
  import { pointerHandlers } from './editVertexMode/pointerHandlers.js'
11
12
 
12
- const EVENT_VERTEX_SELECTION = 'draw.vertexselection'
13
- const EVENT_NUDGE_VERTEX = 'draw.nudgevertex'
14
-
15
13
  export const EditVertexMode = {
16
14
  ...MapboxDraw.modes.direct_select,
17
15
  ...undoHandlers,
@@ -57,10 +55,7 @@ export const EditVertexMode = {
57
55
  this.addTouchVertexTarget(state)
58
56
 
59
57
  // Clear any snap indicator when entering edit mode
60
- const snap = getSnapInstance(this.map)
61
- if (snap) {
62
- clearSnapIndicator(snap, this.map)
63
- }
58
+ clearActiveSnapIndicator(this.map)
64
59
 
65
60
  // Show touch target if entering with a selected vertex on touch interface
66
61
  if (state.interfaceType === 'touch' && state.selectedVertexIndex >= 0 && state.selectedVertexType === 'vertex') {
@@ -80,40 +75,15 @@ export const EditVertexMode = {
80
75
  },
81
76
 
82
77
  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
- }
78
+ const handlers = this.handlers = buildEditModeHandlers(this, state, {
79
+ nudge: this.onNudgeVertex,
80
+ selectionchange: this.onSelectionChange,
81
+ update: this.onUpdate
82
+ })
101
83
 
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)
84
+ bindEditModeListeners(state, this.map, handlers)
85
+ this.map.on('draw.selectionchange', handlers.selectionchange)
86
+ this.map.on('draw.update', handlers.update)
117
87
  },
118
88
 
119
89
  applyVertexSelection (state, options) {
@@ -133,17 +103,17 @@ export const EditVertexMode = {
133
103
  }
134
104
  },
135
105
 
136
- onSelectionChange (state, e) {
106
+ onSelectionChange (state, event) {
137
107
  // Refresh vertex list so numVertecies reflects the latest geometry (e.g. after midpoint insertion)
138
108
  this.syncVertices(state)
139
109
 
140
- const vertexCoord = e.points[e.points.length - 1]?.geometry.coordinates
110
+ const vertexCoord = event.points[event.points.length - 1]?.geometry.coordinates
141
111
 
142
112
  // Only update selectedVertexIndex from event if not keyboard mode AND event has valid vertex
143
113
  // For keyboard mode or when we have coordPath, trust the existing selectedVertexIndex
144
114
  if (state.interfaceType !== 'keyboard' && vertexCoord && !state.coordPath) {
145
115
  // No coordPath available - need to search for vertex by coordinates
146
- const geom = e.features[0]?.geometry
116
+ const geom = event.features[0]?.geometry
147
117
  const coords = getCoords(geom)
148
118
  state.selectedVertexIndex = this.findVertexIndex(coords, vertexCoord, state.selectedVertexIndex)
149
119
  }
@@ -151,7 +121,7 @@ export const EditVertexMode = {
151
121
 
152
122
  state.selectedVertexType ??= state.selectedVertexIndex >= 0 ? 'vertex' : null
153
123
 
154
- this.map.fire(EVENT_VERTEX_SELECTION, {
124
+ this.map.fire(CUSTOM_DRAW_EVENTS.VERTEX_SELECTION, {
155
125
  index: state.selectedVertexType === 'vertex' ? state.selectedVertexIndex : -1,
156
126
  numVertecies: state.vertecies.length
157
127
  })
@@ -161,18 +131,18 @@ export const EditVertexMode = {
161
131
  this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
162
132
  },
163
133
 
164
- onScaleChange (state, e) {
165
- state.scale = e.scale
134
+ onScaleChange (state, event) {
135
+ state.scale = event.scale
166
136
  },
167
137
 
168
- onInterfaceTypeChange (state, e) {
169
- state.interfaceType = e.interfaceType
138
+ onInterfaceTypeChange (state, event) {
139
+ state.interfaceType = event.interfaceType
170
140
  const vertex = state.selectedVertexIndex >= 0 ? state.vertecies[state.selectedVertexIndex] : null
171
141
  this.updateTouchVertexTarget(state, vertex ? scalePoint(this.map.project(vertex), state.scale) : null)
172
142
  },
173
143
 
174
144
  onUpdate (state) {
175
- const prev = new Set(state.vertecies.map(c => JSON.stringify(c)))
145
+ const prev = new Set(state.vertecies.map(coordinate => JSON.stringify(coordinate)))
176
146
  if (prev.size === state.vertecies.length) {
177
147
  return
178
148
  }
@@ -195,19 +165,19 @@ export const EditVertexMode = {
195
165
  // onUpdate hook repositions the touch target for any vertex move), moveVertex
196
166
  // here has no equivalent hook, so the reposition has to happen explicitly —
197
167
  // same as onMove/onSelectionChange/onInterfaceTypeChange already do.
198
- onNudgeVertex (state, e) {
199
- this.nudgeVertexByDelta(state, e.dx, e.dy, e.isLargeStep)
168
+ onNudgeVertex (state, event) {
169
+ this.nudgeVertexByDelta(state, event.dx, event.dy, event.isLargeStep)
200
170
  const vertex = state.vertecies[state.selectedVertexIndex]
201
171
  if (vertex) {
202
172
  this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
203
173
  }
204
174
  },
205
175
 
206
- onButtonClick (state, e) {
207
- if (e.target.closest(`#${state.deleteVertexButtonId}`) && state.selectedVertexType === 'vertex') {
176
+ onButtonClick (state, event) {
177
+ if (event.target.closest(`#${state.deleteVertexButtonId}`) && state.selectedVertexType === 'vertex') {
208
178
  this.deleteVertex(state)
209
179
  }
210
- if (e.target.closest(`#${state.undoButtonId}`)) {
180
+ if (event.target.closest(`#${state.undoButtonId}`)) {
211
181
  this.handleUndo(state)
212
182
  }
213
183
  },
@@ -227,23 +197,10 @@ export const EditVertexMode = {
227
197
  onStop (state) {
228
198
  this.map._drawEditContainer = null
229
199
  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 })
200
+ const handlers = this.handlers
201
+ this.map.off('draw.selectionchange', handlers.selectionchange)
202
+ this.map.off('draw.update', handlers.update)
203
+ unbindEditModeListeners(state, this.map, handlers)
247
204
  this.hideTouchVertexIndicator(state)
248
205
  }
249
206
  }