@defra/interactive-map 0.0.40-alpha → 0.0.42-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 (374) 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/button-definition.md +7 -8
  7. package/docs/api/control-definition.md +17 -1
  8. package/docs/api/panel-definition.md +5 -0
  9. package/docs/api/slots.md +50 -6
  10. package/docs/assets/images/slot-map.svg +61 -262
  11. package/docs/examples/add-polygons.mdx +8 -2
  12. package/docs/examples/add-symbols.mdx +8 -2
  13. package/docs/examples/draw-tools.mdx +3 -3
  14. package/docs/plugins/datasets.md +23 -15
  15. package/docs/plugins/draw.md +79 -16
  16. package/docs/plugins/map-key.md +147 -0
  17. package/docs/plugins/search.md +1 -1
  18. package/docs/plugins.md +5 -1
  19. package/govuk-prototype-kit.config.json +2 -0
  20. package/jest.setup.js +5 -0
  21. package/package.json +9 -1
  22. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  23. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  24. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  25. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  26. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  27. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  28. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
  29. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
  30. package/plugins/beta/use-location/src/manifest.js +1 -1
  31. package/plugins/datasets/dist/css/index.css +1 -1
  32. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  33. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  34. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  35. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  36. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  37. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  38. package/plugins/datasets/dist/umd/index.js +1 -1
  39. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
  40. package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
  41. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
  42. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
  43. package/plugins/datasets/src/api/setOpacity.test.js +9 -0
  44. package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
  45. package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
  46. package/plugins/datasets/src/datasets.scss +0 -1
  47. package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
  48. package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
  49. package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
  50. package/plugins/datasets/src/index.test.js +36 -0
  51. package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
  52. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
  53. package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
  54. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
  55. package/plugins/datasets/src/manifest.js +0 -38
  56. package/plugins/datasets/src/manifest.test.js +49 -0
  57. package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
  58. package/plugins/datasets/src/reducers/pluginState.js +3 -16
  59. package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
  60. package/plugins/datasets/src/registry/dataset.js +13 -1
  61. package/plugins/datasets/src/registry/dataset.test.js +61 -0
  62. package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
  63. package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
  64. package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
  65. package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
  66. package/plugins/datasets/src/utils/bbox.test.js +125 -0
  67. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
  68. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
  69. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
  70. package/plugins/draw/dist/esm/index.js +1 -1
  71. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
  72. package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
  73. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  74. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  75. package/plugins/draw/dist/umd/index.js +1 -1
  76. package/plugins/draw/src/DrawInit.jsx +10 -2
  77. package/plugins/draw/src/DrawInit.test.jsx +51 -10
  78. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
  79. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
  80. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
  81. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
  82. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
  84. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
  85. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
  90. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
  91. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
  92. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
  93. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
  94. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
  95. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
  96. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
  97. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
  98. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
  99. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
  100. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
  101. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
  102. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
  103. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
  104. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
  105. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
  106. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
  107. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
  108. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
  109. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
  110. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
  111. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
  112. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
  113. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
  114. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
  115. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
  116. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
  117. package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
  118. package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
  119. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
  120. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
  121. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
  122. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
  123. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
  124. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
  125. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
  126. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
  127. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
  128. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
  129. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
  130. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
  131. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
  132. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
  133. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
  134. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
  135. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
  136. package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
  137. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
  138. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
  139. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
  140. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
  141. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
  142. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
  143. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
  144. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
  145. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
  146. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
  147. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
  148. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
  149. package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
  150. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
  151. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
  152. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
  153. package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
  154. package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
  155. package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
  156. package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
  157. package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
  158. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
  159. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
  160. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
  161. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
  162. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
  163. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
  164. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
  165. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
  166. package/plugins/draw/src/api/addFeature.js +16 -1
  167. package/plugins/draw/src/api/addFeature.test.js +62 -5
  168. package/plugins/draw/src/api/createNewShape.js +41 -0
  169. package/plugins/draw/src/api/createNewShape.test.js +63 -0
  170. package/plugins/draw/src/api/editFeature.js +7 -4
  171. package/plugins/draw/src/api/editFeature.test.js +16 -0
  172. package/plugins/draw/src/api/newLine.js +2 -40
  173. package/plugins/draw/src/api/newPoint.js +42 -0
  174. package/plugins/draw/src/api/newPoint.test.js +117 -0
  175. package/plugins/draw/src/api/newPolygon.js +2 -40
  176. package/plugins/draw/src/api/setStyle.js +43 -0
  177. package/plugins/draw/src/api/setStyle.test.js +75 -0
  178. package/plugins/draw/src/events.js +17 -10
  179. package/plugins/draw/src/events.test.js +14 -0
  180. package/plugins/draw/src/manifest.js +18 -6
  181. package/plugins/draw/src/manifest.test.js +32 -1
  182. package/plugins/draw/src/reducer.test.js +7 -0
  183. package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
  184. package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
  185. package/plugins/draw/src/utils/symbolKeys.js +5 -0
  186. package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
  187. package/plugins/draw/src/validation/liveStroke.test.js +64 -1
  188. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  189. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  190. package/plugins/interact/dist/umd/index.js +1 -1
  191. package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
  192. package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
  193. package/plugins/interact/src/manifest.js +1 -1
  194. package/plugins/interact/src/utils/featureQueries.js +18 -0
  195. package/plugins/interact/src/utils/featureQueries.test.js +15 -0
  196. package/plugins/interact/src/utils/spatial.js +9 -3
  197. package/plugins/interact/src/utils/spatial.test.js +12 -1
  198. package/plugins/map-key/dist/css/index.css +68 -0
  199. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
  200. package/plugins/map-key/dist/esm/index.js +1 -0
  201. package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
  202. package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
  203. package/plugins/map-key/dist/umd/index.js +2 -0
  204. package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
  205. package/plugins/map-key/package.json +28 -0
  206. package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
  207. package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
  208. package/plugins/map-key/src/components/Key/Key.jsx +43 -0
  209. package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +21 -14
  210. package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
  211. package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
  212. package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
  213. package/plugins/map-key/src/components/Key/KeyItem.jsx +26 -0
  214. package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
  215. package/plugins/map-key/src/components/Key/KeySvg.jsx +41 -0
  216. package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
  217. package/plugins/map-key/src/components/Key/KeySvgLine.jsx +22 -0
  218. package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
  219. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.jsx +7 -2
  220. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +8 -2
  221. package/plugins/map-key/src/components/Key/KeySvgRect.jsx +26 -0
  222. package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
  223. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.jsx +8 -3
  224. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +17 -6
  225. package/plugins/map-key/src/components/Key/MapKey.jsx +53 -0
  226. package/plugins/map-key/src/components/Key/MapKey.test.jsx +147 -0
  227. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
  228. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
  229. package/plugins/map-key/src/index.js +13 -0
  230. package/plugins/map-key/src/index.test.js +31 -0
  231. package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
  232. package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
  233. package/plugins/map-key/src/manifest.js +30 -0
  234. package/plugins/map-key/src/manifest.test.js +63 -0
  235. package/plugins/map-key/src/mapKey.scss +4 -0
  236. package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
  237. package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
  238. package/plugins/map-key/src/registry/index.js +3 -0
  239. package/plugins/map-key/src/registry/index.test.js +15 -0
  240. package/plugins/menu/dist/css/index.css +80 -0
  241. package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
  242. package/plugins/menu/dist/esm/index.js +1 -0
  243. package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
  244. package/plugins/menu/dist/umd/index.js +2 -0
  245. package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
  246. package/plugins/menu/package.json +28 -0
  247. package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
  248. package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
  249. package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
  250. package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
  251. package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
  252. package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
  253. package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
  254. package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
  255. package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
  256. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
  257. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
  258. package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
  259. package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
  260. package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
  261. package/plugins/menu/src/index.js +13 -0
  262. package/plugins/menu/src/index.test.js +31 -0
  263. package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
  264. package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
  265. package/plugins/menu/src/initialise/createMenu.js +10 -0
  266. package/plugins/menu/src/initialise/createMenu.test.js +37 -0
  267. package/plugins/menu/src/manifest.js +35 -0
  268. package/plugins/menu/src/manifest.test.js +63 -0
  269. package/plugins/menu/src/menu.scss +4 -0
  270. package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
  271. package/plugins/menu/src/reducers/pluginState.js +35 -0
  272. package/plugins/menu/src/reducers/pluginState.test.js +57 -0
  273. package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
  274. package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
  275. package/plugins/menu/src/registry/index.js +3 -0
  276. package/plugins/menu/src/registry/index.test.js +15 -0
  277. package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
  278. package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
  279. package/plugins/search/dist/css/index.css +1 -1
  280. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  281. package/plugins/search/dist/esm/index.js +1 -1
  282. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  283. package/plugins/search/dist/umd/index.js +1 -1
  284. package/plugins/search/src/Search.jsx +2 -3
  285. package/plugins/search/src/components/Form/Form.module.scss +9 -20
  286. package/plugins/search/src/index.js +0 -1
  287. package/plugins/search/src/index.test.js +2 -4
  288. package/plugins/search/src/manifest.js +1 -1
  289. package/plugins/search/src/search.scss +17 -16
  290. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  291. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  292. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  293. package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
  294. package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
  295. package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
  296. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
  297. package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
  298. package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
  299. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  300. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  301. package/providers/maplibre/dist/umd/index.js +1 -1
  302. package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
  303. package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
  304. package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
  305. package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
  306. package/providers/maplibre/src/utils/hoverCursor.js +30 -14
  307. package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
  308. package/providers/maplibre/src/utils/patternImages.js +1 -1
  309. package/providers/maplibre/src/utils/symbolImages.js +41 -3
  310. package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
  311. package/rollup.esm.mjs +11 -1
  312. package/src/App/components/Actions/Actions.test.jsx +47 -0
  313. package/src/App/components/Hints/Hints.test.jsx +13 -0
  314. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +25 -23
  315. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +22 -5
  316. package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
  317. package/src/App/components/Panel/Panel.jsx +65 -8
  318. package/src/App/components/Panel/Panel.module.scss +49 -15
  319. package/src/App/components/Panel/Panel.test.jsx +114 -1
  320. package/src/App/components/Tabs/Tabs.jsx +43 -17
  321. package/src/App/components/Tabs/Tabs.module.scss +32 -5
  322. package/src/App/components/Tabs/Tabs.test.jsx +40 -10
  323. package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
  324. package/src/App/hooks/useFeatureItems.js +16 -7
  325. package/src/App/hooks/useFeatureItems.test.js +57 -0
  326. package/src/App/hooks/useLayoutMeasurements.js +95 -84
  327. package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
  328. package/src/App/layout/Layout.jsx +9 -10
  329. package/src/App/layout/Layout.test.jsx +2 -0
  330. package/src/App/layout/layout.module.scss +31 -17
  331. package/src/App/registry/pluginRegistry.js +1 -1
  332. package/src/App/registry/pluginRegistry.test.js +12 -0
  333. package/src/App/renderer/HtmlElementHost.jsx +18 -5
  334. package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
  335. package/src/App/renderer/groupByKey.js +28 -0
  336. package/src/App/renderer/groupByKey.test.js +39 -0
  337. package/src/App/renderer/groupIntoTabs.js +42 -0
  338. package/src/App/renderer/groupIntoTabs.test.js +95 -0
  339. package/src/App/renderer/mapButtons.js +97 -129
  340. package/src/App/renderer/mapButtons.test.js +67 -83
  341. package/src/App/renderer/mapControls.js +4 -1
  342. package/src/App/renderer/mapControls.test.js +32 -0
  343. package/src/App/renderer/mapPanels.js +53 -11
  344. package/src/App/renderer/mapPanels.test.js +95 -2
  345. package/src/App/renderer/orderItems.js +28 -0
  346. package/src/App/renderer/orderItems.test.js +50 -0
  347. package/src/App/renderer/slotAggregator.js +2 -18
  348. package/src/App/renderer/slotHelpers.js +3 -1
  349. package/src/App/renderer/slotHelpers.test.js +5 -0
  350. package/src/App/renderer/slots.js +3 -0
  351. package/src/App/store/AppProvider.jsx +1 -0
  352. package/src/App/store/appDispatchMiddleware.js +1 -1
  353. package/src/App/store/appDispatchMiddleware.test.js +8 -0
  354. package/src/config/appConfig.js +2 -2
  355. package/src/config/events.js +2 -0
  356. package/src/scss/settings/_colors.scss +1 -0
  357. package/src/scss/settings/_dimensions.scss +2 -3
  358. package/src/scss/settings/_transition.scss +1 -1
  359. package/src/services/eventBus.js +44 -0
  360. package/src/services/eventBus.test.js +83 -0
  361. package/src/services/symbolRegistry.js +1 -1
  362. package/src/types.js +31 -3
  363. package/webpack.umd.mjs +2 -0
  364. package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
  365. package/plugins/datasets/src/components/Key/Key.jsx +0 -50
  366. package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
  367. package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
  368. package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
  369. package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
  370. package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
  371. package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
  372. /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
  373. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
  374. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
@@ -1,6 +1,7 @@
1
1
  import { createMapboxDraw } from './mapboxDraw.js'
2
2
  import { getSnapInstance, clearSnapState, clearSnapIndicator } from './utils/snapHelpers.js'
3
3
  import { createEventBus } from '../../utils/eventBus.js'
4
+ import { resolvePointSymbol, hasSymbolStyle } from './pointSymbolImages.js'
4
5
  import { MAPBOX_DRAW_EVENTS, CUSTOM_DRAW_EVENTS, STYLE_DATA_EVENT } from './drawEvents.js'
5
6
  import { MaplibreDrawAdapter, displayedShape } from './MaplibreDrawAdapter.js'
6
7
 
@@ -11,6 +12,11 @@ jest.mock('./utils/snapHelpers.js', () => ({
11
12
  clearSnapIndicator: jest.fn()
12
13
  }))
13
14
  jest.mock('../../utils/eventBus.js', () => ({ createEventBus: jest.fn() }))
15
+ jest.mock('./pointSymbolImages.js', () => ({
16
+ resolvePointSymbol: jest.fn(),
17
+ refreshAllPointSymbols: jest.fn(),
18
+ hasSymbolStyle: jest.fn()
19
+ }))
14
20
 
15
21
  const SNAP_LAYER = 'snap-helper-circle'
16
22
 
@@ -112,19 +118,61 @@ describe('construction', () => {
112
118
  })
113
119
 
114
120
  describe('map event normalisation', () => {
115
- test('create/editfinish/update forward the first feature', () => {
121
+ test('editfinish/update forward the first feature', () => {
116
122
  const { map, bus } = setup()
117
123
  const feature = { id: 'f1' }
118
124
 
119
- onHandler(map, MAPBOX_DRAW_EVENTS.CREATE)({ features: [feature] })
120
125
  onHandler(map, CUSTOM_DRAW_EVENTS.EDIT_FINISH)({ features: [feature] })
121
126
  onHandler(map, MAPBOX_DRAW_EVENTS.UPDATE)({ features: [feature] })
122
127
 
123
- expect(bus.emit).toHaveBeenCalledWith('create', feature)
124
128
  expect(bus.emit).toHaveBeenCalledWith('editfinish', feature)
125
129
  expect(bus.emit).toHaveBeenCalledWith('update', feature)
126
130
  })
127
131
 
132
+ // Drives fill/line/symbol-sort-key (styles.js) — a feature drawn interactively (not via the
133
+ // add() API) still needs a sort key assigned exactly once, at the point it's actually created.
134
+ // Deferred a tick: the owning draw mode's own 'draw.create' listener re-ids the feature to its
135
+ // final id in the same synchronous dispatch (see drawPointMode.js/clickHandlers.js), and this
136
+ // handler must run after that re-id, not before it.
137
+ test('create assigns an incrementing sortKey via setFeatureProperty before forwarding, after the mode\'s own re-id has run', () => {
138
+ jest.useFakeTimers()
139
+ const { map, draw, bus } = setup()
140
+ draw.get.mockImplementation((id) => ({ id, properties: { sortKey: id === 'f1' ? 1 : 2 } }))
141
+
142
+ onHandler(map, MAPBOX_DRAW_EVENTS.CREATE)({ features: [{ id: 'f1' }] })
143
+ onHandler(map, MAPBOX_DRAW_EVENTS.CREATE)({ features: [{ id: 'f2' }] })
144
+ expect(draw.setFeatureProperty).not.toHaveBeenCalled() // deferred past the mode's re-id
145
+ jest.runAllTimers()
146
+
147
+ expect(draw.setFeatureProperty).toHaveBeenNthCalledWith(1, 'f1', 'sortKey', 1)
148
+ expect(draw.setFeatureProperty).toHaveBeenNthCalledWith(2, 'f2', 'sortKey', 2)
149
+ // Forwards the freshly re-fetched feature (carrying the new property), not the stale event payload.
150
+ expect(bus.emit).toHaveBeenCalledWith('create', { id: 'f1', properties: { sortKey: 1 } })
151
+ expect(bus.emit).toHaveBeenCalledWith('create', { id: 'f2', properties: { sortKey: 2 } })
152
+ jest.useRealTimers()
153
+ })
154
+
155
+ // Reproduces the real integration hazard the deferral above guards against: a draw mode's
156
+ // 'draw.create' listener re-ids the feature (mutating the SAME event payload object) in the
157
+ // same synchronous dispatch, before this handler's deferred callback runs.
158
+ test('create picks up a feature id re-assigned synchronously by the owning draw mode', () => {
159
+ jest.useFakeTimers()
160
+ const { map, draw, bus } = setup()
161
+ draw.get.mockImplementation((id) => ({ id, properties: { sortKey: 1 } }))
162
+ const feature = { id: 'mapbox-temp-id' }
163
+
164
+ onHandler(map, MAPBOX_DRAW_EVENTS.CREATE)({ features: [feature] })
165
+ // Simulate the owning draw mode's own 'draw.create' listener re-idding this SAME feature
166
+ // object, synchronously, later in the same dispatch — before this handler's deferred
167
+ // callback below runs.
168
+ feature.id = 'caller-requested-id'
169
+ jest.runAllTimers()
170
+
171
+ expect(draw.setFeatureProperty).toHaveBeenCalledWith('caller-requested-id', 'sortKey', 1)
172
+ expect(bus.emit).toHaveBeenCalledWith('create', { id: 'caller-requested-id', properties: { sortKey: 1 } })
173
+ jest.useRealTimers()
174
+ })
175
+
128
176
  test('cancel forwards with no payload', () => {
129
177
  const { map, bus } = setup()
130
178
  onHandler(map, CUSTOM_DRAW_EVENTS.CANCEL)()
@@ -184,9 +232,41 @@ describe('displayedShape helper', () => {
184
232
  expect(line?.feature?.geometry?.type).toBe('LineString')
185
233
  })
186
234
 
235
+ test('builds a point feature from edit_point mode, with a flat (unnested) coordinate', () => {
236
+ const result = displayedShape('edit_point', [5, 5])
237
+ expect(result?.feature?.type).toBe('Feature')
238
+ expect(result?.feature?.geometry).toEqual({ type: 'Point', coordinates: [5, 5] })
239
+ expect(result?.numVertices).toBe(1)
240
+ })
241
+
187
242
  test('returns null for an unknown mode', () => {
188
243
  expect(displayedShape('unknown_mode', [[0, 0], [10, 0]])).toBeNull()
189
244
  })
245
+
246
+ // A ring/coordinate array can transiently be empty/absent right as a sketch starts —
247
+ // the `?? …` fallbacks keep numVertices a sane number instead of crashing on `undefined.length`.
248
+ test('falls back to numVertices 0 for a draw_polygon sketch with no ring yet', () => {
249
+ expect(displayedShape('draw_polygon', [])?.numVertices).toBe(0)
250
+ })
251
+
252
+ test('falls back to numVertices 0 for a draw_line sketch with no coordinates yet', () => {
253
+ expect(displayedShape('draw_line', undefined)?.numVertices).toBe(0)
254
+ })
255
+
256
+ test('falls back to numVertices 0 for an edit_vertex polygon with no ring yet', () => {
257
+ const result = displayedShape('edit_vertex', [])
258
+ expect(result?.feature?.geometry?.type).toBe('LineString') // coordinates[0] undefined → not detected as a polygon ring
259
+ expect(result?.numVertices).toBe(0)
260
+ })
261
+
262
+ // A plain object (not an array) still satisfies edit_vertex's own coordinates[0]?.[0]
263
+ // access without throwing, but has no .length of its own — the line branch's `?? 0`
264
+ // fallback the case above can't reach (an empty array's .length is 0, not nullish).
265
+ test('falls back to numVertices 0 for an edit_vertex line whose coordinates have no length at all', () => {
266
+ const result = displayedShape('edit_vertex', {})
267
+ expect(result?.feature?.geometry?.type).toBe('LineString')
268
+ expect(result?.numVertices).toBe(0)
269
+ })
190
270
  })
191
271
 
192
272
  describe('live invalid stroke (draw mode)', () => {
@@ -338,6 +418,18 @@ describe('live invalid stroke (edit mode)', () => {
338
418
  expect(bus.emit).not.toHaveBeenCalledWith('canplacechange', expect.anything())
339
419
  })
340
420
 
421
+ test('validity flips also gate the Done button while editing a point', () => {
422
+ jest.useFakeTimers()
423
+ const { map, draw, bus } = editSetup()
424
+ draw.getMode.mockReturnValue('edit_point')
425
+ const fire = onHandler(map, CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE)
426
+ map._drawGeometryValidator = () => ({ valid: false, reason: 'outside region' })
427
+ fire({ type: 'draw.geometrychange', coordinates: [5, 5] })
428
+ jest.runAllTimers()
429
+ expect(bus.emit).toHaveBeenCalledWith('validitychange', expect.objectContaining({ valid: false, reason: 'outside region' }))
430
+ jest.useRealTimers()
431
+ })
432
+
341
433
  test('lines never go dashed from the default rules while editing', () => {
342
434
  const { map } = editSetup()
343
435
  onHandler(map, CUSTOM_DRAW_EVENTS.GEOMETRY_CHANGE)({ type: 'draw.geometrychange', coordinates: [[0, 0], [10, 10], [10, 0], [0, 10]] })
@@ -376,6 +468,12 @@ describe('changeMode', () => {
376
468
  expect(draw.changeMode).toHaveBeenCalledWith('edit_vertex', { featureId: 'f9' })
377
469
  })
378
470
 
471
+ test('records the editing feature id when entering edit_point', () => {
472
+ const { adapter, draw } = setup()
473
+ adapter.changeMode('edit_point', { featureId: 'f9' })
474
+ expect(draw.changeMode).toHaveBeenCalledWith('edit_point', { featureId: 'f9' })
475
+ })
476
+
379
477
  test('defaults the editing feature id to null when omitted', () => {
380
478
  const { adapter, draw } = setup()
381
479
  adapter.changeMode('edit_vertex', {})
@@ -390,6 +488,20 @@ describe('changeMode', () => {
390
488
  adapter.changeMode('draw_polygon')
391
489
  expect(draw.changeMode).toHaveBeenCalledWith('draw_polygon', {})
392
490
  })
491
+
492
+ test('draw_point gets a resolvePointSymbol hook injected, delegating to pointSymbolImages.js', () => {
493
+ const { adapter, draw, map, mapProvider } = setup()
494
+ adapter.changeMode('draw_point', { featureId: 'p1' })
495
+
496
+ expect(draw.changeMode).toHaveBeenCalledWith('draw_point', {
497
+ featureId: 'p1',
498
+ resolvePointSymbol: expect.any(Function)
499
+ })
500
+
501
+ const injected = draw.changeMode.mock.calls[0][1].resolvePointSymbol
502
+ injected('p1', { symbol: 'pin' })
503
+ expect(resolvePointSymbol).toHaveBeenCalledWith({ draw, mapProvider, map, featureId: 'p1', properties: { symbol: 'pin' } })
504
+ })
393
505
  })
394
506
 
395
507
  describe('setGeometryValid', () => {
@@ -486,12 +598,122 @@ describe('simple delegations', () => {
486
598
  adapter.setFeatureProperty('d', 'p', 1)
487
599
 
488
600
  expect(draw.get).toHaveBeenCalledWith('a')
489
- expect(draw.add).toHaveBeenCalledWith({ id: 'b' })
601
+ expect(draw.add).toHaveBeenCalledWith({ id: 'b', properties: { sortKey: 1 } })
490
602
  expect(draw.delete).toHaveBeenCalledWith('c')
491
603
  expect(draw.deleteAll).toHaveBeenCalled()
492
604
  expect(draw.setFeatureProperty).toHaveBeenCalledWith('d', 'p', 1)
493
605
  })
494
606
 
607
+ // A directly-added Point skips draw_point's own icon-resolving drawend handler.
608
+ describe('add() and point symbol resolution', () => {
609
+ test('resolves the symbol for a Point feature with symbol properties, using the id draw.add() returns', () => {
610
+ const { adapter, draw, map, mapProvider } = setup()
611
+ draw.add.mockReturnValue(['generated-id'])
612
+ hasSymbolStyle.mockReturnValue(true)
613
+ const feature = { geometry: { type: 'Point', coordinates: [0, 0] }, properties: { symbol: 'pin' } }
614
+
615
+ const result = adapter.add(feature)
616
+
617
+ expect(hasSymbolStyle).toHaveBeenCalledWith({ symbol: 'pin', sortKey: 1 })
618
+ expect(resolvePointSymbol).toHaveBeenCalledWith({
619
+ draw, mapProvider, map, featureId: 'generated-id', properties: { symbol: 'pin', sortKey: 1 }
620
+ })
621
+ expect(result).toEqual(['generated-id'])
622
+ })
623
+
624
+ test('does not attempt resolution for a Point with no symbol properties', () => {
625
+ const { adapter, draw } = setup()
626
+ draw.add.mockReturnValue(['id-1'])
627
+ hasSymbolStyle.mockReturnValue(false)
628
+ adapter.add({ geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} })
629
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
630
+ })
631
+
632
+ test('does not attempt resolution for a non-Point geometry', () => {
633
+ const { adapter, draw } = setup()
634
+ draw.add.mockReturnValue(['id-1'])
635
+ adapter.add({ geometry: { type: 'Polygon', coordinates: [[]] }, properties: { symbol: 'pin' } })
636
+ expect(hasSymbolStyle).not.toHaveBeenCalled()
637
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
638
+ })
639
+
640
+ test('does not attempt resolution for a feature with no geometry', () => {
641
+ const { adapter, draw } = setup()
642
+ draw.add.mockReturnValue(['id-1'])
643
+ adapter.add({ id: 'b' })
644
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
645
+ })
646
+ })
647
+
648
+ describe('add() sort key assignment', () => {
649
+ test('assigns an incrementing sortKey to each genuinely new feature', () => {
650
+ const { adapter, draw } = setup()
651
+ adapter.add({ id: 'a' })
652
+ adapter.add({ id: 'b' })
653
+ expect(draw.add).toHaveBeenNthCalledWith(1, { id: 'a', properties: { sortKey: 1 } })
654
+ expect(draw.add).toHaveBeenNthCalledWith(2, { id: 'b', properties: { sortKey: 2 } })
655
+ })
656
+
657
+ test('preserves an existing sortKey rather than assigning a new one', () => {
658
+ const { adapter, draw } = setup()
659
+ adapter.add({ id: 'a', properties: { sortKey: 5 } })
660
+ expect(draw.add).toHaveBeenCalledWith({ id: 'a', properties: { sortKey: 5 } })
661
+ })
662
+ })
663
+
664
+ describe('setStyle()', () => {
665
+ test('merges the patch into the existing feature and re-adds it', () => {
666
+ const { adapter, draw } = setup()
667
+ const feature = { id: 'a', type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] }, properties: { stroke: 'red', name: 'x' } }
668
+ draw.get.mockReturnValue(feature)
669
+ draw.add.mockReturnValue(['a'])
670
+
671
+ adapter.setStyle('a', { stroke: 'blue' })
672
+
673
+ expect(draw.add).toHaveBeenCalledWith({
674
+ id: 'a',
675
+ type: 'Feature',
676
+ geometry: { type: 'Polygon', coordinates: [[]] },
677
+ properties: { stroke: 'blue', name: 'x', sortKey: 1 }
678
+ })
679
+ })
680
+
681
+ test('preserves an existing sortKey rather than assigning a new one', () => {
682
+ const { adapter, draw } = setup()
683
+ const feature = { id: 'a', type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] }, properties: { stroke: 'red', sortKey: 7 } }
684
+ draw.get.mockReturnValue(feature)
685
+ draw.add.mockReturnValue(['a'])
686
+
687
+ adapter.setStyle('a', { stroke: 'blue' })
688
+
689
+ expect(draw.add).toHaveBeenCalledWith(expect.objectContaining({ properties: { stroke: 'blue', sortKey: 7 } }))
690
+ })
691
+
692
+ // setStyle is routed through add() itself, so a Point patched with symbol properties gets
693
+ // its icon re-resolved the same way a directly-added one does.
694
+ test('re-resolves the icon for a Point patched with symbol properties', () => {
695
+ const { adapter, draw } = setup()
696
+ const feature = { id: 'p1', type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: { symbol: 'pin' } }
697
+ draw.get.mockReturnValue(feature)
698
+ draw.add.mockReturnValue(['p1'])
699
+ hasSymbolStyle.mockReturnValue(true)
700
+
701
+ adapter.setStyle('p1', { symbolBackgroundColor: '#ca3535' })
702
+
703
+ expect(resolvePointSymbol).toHaveBeenCalledWith(expect.objectContaining({
704
+ featureId: 'p1',
705
+ properties: { symbol: 'pin', symbolBackgroundColor: '#ca3535', sortKey: 1 }
706
+ }))
707
+ })
708
+
709
+ test('does nothing for an id with no existing feature', () => {
710
+ const { adapter, draw } = setup()
711
+ draw.get.mockReturnValue(undefined)
712
+ adapter.setStyle('missing', { stroke: 'blue' })
713
+ expect(draw.add).not.toHaveBeenCalled()
714
+ })
715
+ })
716
+
495
717
  test('setDrawingPreviewProperty tags the in-progress feature and re-renders', () => {
496
718
  const { adapter, map } = setup()
497
719
  const render = jest.fn()
@@ -570,6 +792,18 @@ describe('done', () => {
570
792
  expect(map.fire).not.toHaveBeenCalled()
571
793
  expect(draw.changeMode).not.toHaveBeenCalled()
572
794
  })
795
+
796
+ test('clears the undo stack and fires editfinish when editing a point', () => {
797
+ const { adapter, map, draw, undoStack } = setup()
798
+ adapter.changeMode('edit_point', { featureId: 'f1' })
799
+ draw.getMode.mockReturnValue('edit_point')
800
+
801
+ adapter.done()
802
+
803
+ expect(undoStack.clear).toHaveBeenCalled()
804
+ expect(map.fire).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.EDIT_FINISH, { features: [{ id: 'f1' }] })
805
+ expect(draw.changeMode).not.toHaveBeenCalledWith('disabled')
806
+ })
573
807
  })
574
808
 
575
809
  describe('cancel', () => {
@@ -719,6 +953,14 @@ describe('_handleModeChange', () => {
719
953
  })
720
954
 
721
955
  describe('_handleStyleData', () => {
956
+ test('does nothing when the map has no style yet (removed, or mid-teardown)', () => {
957
+ const { map } = setup()
958
+ map.getStyle.mockReturnValue(undefined)
959
+ expect(() => onHandler(map, STYLE_DATA_EVENT)()).not.toThrow()
960
+ expect(map.moveLayer).not.toHaveBeenCalled()
961
+ expect(map.setLayoutProperty).not.toHaveBeenCalled()
962
+ })
963
+
722
964
  test('does nothing when there are no layers', () => {
723
965
  const { map } = setup()
724
966
  map.getStyle.mockReturnValue({ layers: undefined })
@@ -1,8 +1,10 @@
1
1
  import MapboxDraw from '@mapbox/mapbox-gl-draw'
2
2
  import { DisabledMode } from './modes/disabledMode.js'
3
3
  import { EditVertexMode } from './modes/editVertexMode.js'
4
+ import { EditPointMode } from './modes/editPointMode.js'
4
5
  import { DrawPolygonMode } from './modes/drawPolygonMode.js'
5
6
  import { DrawLineMode } from './modes/drawLineMode.js'
7
+ import { DrawPointMode } from './modes/drawPointMode.js'
6
8
  import { createDrawStyles, updateDrawStyles } from './styles.js'
7
9
  import { initMapLibreSnap } from './mapboxSnap.js'
8
10
  import { createUndoStack } from '../../utils/undoStack.js'
@@ -10,6 +12,89 @@ import { setupTouchClickWorkaround } from './utils/touchClickWorkaround.js'
10
12
  import { applyTouchVertexColors } from './modes/editVertexMode/touchHandlers.js'
11
13
  import { resolveColors } from '../../utils/resolveColors.js'
12
14
  import { TOLERANCES, MAP_SIZE_SCALES } from './defaults.js'
15
+ import { refreshAllPointSymbols } from './pointSymbolImages.js'
16
+
17
+ // map.setStyle() discards MapLibre's entire previous Style, wiping every programmatically
18
+ // added source/layer/image — including mapbox-gl-draw's own, which it never re-adds itself.
19
+ // draw.options.styles already holds the fully processed cold+hot layer pairs, so re-adding
20
+ // needs no new computation.
21
+ const DRAW_SOURCES = { COLD: 'mapbox-gl-draw-cold', HOT: 'mapbox-gl-draw-hot' }
22
+
23
+ // Re-creates the (empty) sources/layers only — deliberately doesn't push draw.getAll() back
24
+ // in here. mapbox-gl-draw's own render() is requestAnimationFrame-debounced, so doing that
25
+ // too early would write each point's *previous* (not yet re-resolved) image ids into the
26
+ // fresh source, racing the correct write that refreshAllPointSymbols makes moments later.
27
+ // The caller pushes the final data back in exactly once, after that settles.
28
+ const ensureDrawSourcesAndLayers = (map, draw) => {
29
+ if (map.getSource(DRAW_SOURCES.COLD)) {
30
+ return false // survived (or already restored) — nothing to do
31
+ }
32
+ const empty = { type: 'FeatureCollection', features: [] }
33
+ map.addSource(DRAW_SOURCES.COLD, { data: empty, type: 'geojson' })
34
+ map.addSource(DRAW_SOURCES.HOT, { data: empty, type: 'geojson' })
35
+ draw.options.styles.forEach(style => map.addLayer(style))
36
+ return true
37
+ }
38
+
39
+ // Reacts to MAP_STYLE_CHANGE (fired off the native 'style.load' event, i.e. after setStyle()
40
+ // actually ran), not MAP_SET_STYLE (fired the instant a change is *requested*, before setStyle
41
+ // may have run) — registering map.once('idle', ...) too early can catch the map still idle
42
+ // from the *previous* style and consume itself before the real wipe happens.
43
+ const settleStyleChange = ({ map, draw, mapProvider, pluginConfig, onDone }) => {
44
+ const mapStyle = map._drawCurrentMapStyle
45
+ map.once('idle', () => {
46
+ const sourcesWereMissing = ensureDrawSourcesAndLayers(map, draw)
47
+ updateDrawStyles(map, mapStyle, pluginConfig)
48
+ const svg = map._drawEditContainer?.querySelector('[data-im-draw-touch-target]')
49
+ applyTouchVertexColors(svg, mapStyle, pluginConfig)
50
+ // Rasterised point symbol images are style-scoped (colours resolve per map style) —
51
+ // re-resolve every drawn point's icon now the new style has settled.
52
+ refreshAllPointSymbols({ draw, mapProvider, map }).then(() => {
53
+ if (sourcesWereMissing) {
54
+ // draw's own feature store still holds everything; push it back now the new style's
55
+ // image ids are resolved — exactly one write, with final data.
56
+ draw.set(draw.getAll())
57
+ }
58
+ // The highlight ring is a standalone layer maintained outside mapbox-gl-draw's own
59
+ // render cycle, so nothing guarantees MapLibre repaints it (or refreshes the hover
60
+ // cursor's queryRenderedFeatures results) right after a style reload — force it.
61
+ map.triggerRepaint()
62
+ onDone()
63
+ })
64
+ })
65
+ }
66
+
67
+ // Wires MAP_SET_STYLE (stashes the incoming style) and MAP_STYLE_CHANGE (settles it) onto the
68
+ // event bus. Returns both handlers so createMapboxDraw's remove() can unsubscribe them.
69
+ const wireStyleChangeHandling = ({ map, draw, mapProvider, pluginConfig, eventBus, events, notifyPointSymbolsRefreshed }) => {
70
+ const handleSetMapStyle = (e) => {
71
+ map._drawCurrentMapStyle = e
72
+ }
73
+ eventBus.on(events.MAP_SET_STYLE, handleSetMapStyle)
74
+
75
+ // MAP_STYLE_CHANGE fires twice per style change (two separate 'style.load' listeners
76
+ // elsewhere in the app) — this guard stops settleStyleChange's expensive work running twice.
77
+ let settlePending = false
78
+ const handleStyleChanged = () => {
79
+ if (settlePending) {
80
+ return
81
+ }
82
+ settlePending = true
83
+ settleStyleChange({
84
+ map,
85
+ draw,
86
+ mapProvider,
87
+ pluginConfig,
88
+ onDone: () => {
89
+ settlePending = false
90
+ notifyPointSymbolsRefreshed()
91
+ }
92
+ })
93
+ }
94
+ eventBus.on(events.MAP_STYLE_CHANGE, handleStyleChanged)
95
+
96
+ return { handleSetMapStyle, handleStyleChanged }
97
+ }
13
98
 
14
99
  /**
15
100
  * Creates and manages a MapLibre/Mapbox Draw control instance configured for polygon editing.
@@ -40,8 +125,10 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap
40
125
  ...MapboxDraw.modes,
41
126
  disabled: DisabledMode,
42
127
  edit_vertex: EditVertexMode,
128
+ edit_point: EditPointMode,
43
129
  draw_polygon: DrawPolygonMode,
44
- draw_line: DrawLineMode
130
+ draw_line: DrawLineMode,
131
+ draw_point: DrawPointMode
45
132
  }
46
133
 
47
134
  // --- Create or reuse MapLibre Draw instance ---
@@ -67,10 +154,7 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap
67
154
  // We need a reference to this
68
155
  mapProvider.draw = draw
69
156
  map._drawCurrentMapStyle = mapStyle
70
- // Stashed on the map (alongside _drawCurrentMapStyle) so mode code that only
71
- // has `this.map` — e.g. touchHandlers.js's addTouchVertexTarget — can still
72
- // resolve colour overrides without pluginConfig being threaded through every
73
- // mode's option object.
157
+ // Stashed on the map so mode code with only `this.map` can resolve colour overrides.
74
158
  map._drawPluginConfig = pluginConfig
75
159
  // Initialize snap as disabled (matches initialState.snap = false)
76
160
  mapProvider.snapEnabled = false
@@ -92,16 +176,14 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap
92
176
  colors: { vertex: snapColors.snapVertex, edge: snapColors.snapEdge }
93
177
  })
94
178
 
179
+ // Nudges plugins/interact's useHighlightSync to re-apply highlights once point symbols
180
+ // have actually finished re-resolving, rather than relying on a side-effect event.
181
+ const notifyPointSymbolsRefreshed = () => eventBus.emit(events.MAP_DATA_CHANGE)
182
+
95
183
  // --- Update colour scheme ---
96
- const handleSetMapStyle = (e) => {
97
- map._drawCurrentMapStyle = e
98
- map.once('idle', () => {
99
- updateDrawStyles(map, e, pluginConfig)
100
- const svg = map._drawEditContainer?.querySelector('[data-im-draw-touch-target]')
101
- applyTouchVertexColors(svg, e, pluginConfig)
102
- })
103
- }
104
- eventBus.on(events.MAP_SET_STYLE, handleSetMapStyle)
184
+ const { handleSetMapStyle, handleStyleChanged } = wireStyleChangeHandling({
185
+ map, draw, mapProvider, pluginConfig, eventBus, events, notifyPointSymbolsRefreshed
186
+ })
105
187
 
106
188
  // --- Update map scale ---
107
189
  const handleSetMapSize = (e) => {
@@ -109,6 +191,17 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap
109
191
  }
110
192
  eventBus.on(events.MAP_SET_SIZE, handleSetMapSize)
111
193
 
194
+ // --- Update point symbol resolution for the new pixel ratio ---
195
+ // MAP_SET_PIXEL_RATIO carries the freshly computed pixel ratio itself, fired right after
196
+ // MAP_SET_SIZE — that's the value this needs, not map.getPixelRatio() (unchanged since init).
197
+ const handleSetPixelRatio = (pixelRatio) => {
198
+ refreshAllPointSymbols({ draw, mapProvider, map, pixelRatioOverride: pixelRatio }).then(() => {
199
+ map.triggerRepaint()
200
+ notifyPointSymbolsRefreshed()
201
+ })
202
+ }
203
+ eventBus.on(events.MAP_SET_PIXEL_RATIO, handleSetPixelRatio)
204
+
112
205
  // --- Return instance and cleanup function ---
113
206
  return {
114
207
  draw,
@@ -116,7 +209,9 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap
116
209
  touchClickWorkaround.remove()
117
210
  // Remove event listeners
118
211
  eventBus.off(events.MAP_SET_STYLE, handleSetMapStyle)
212
+ eventBus.off(events.MAP_STYLE_CHANGE, handleStyleChanged)
119
213
  eventBus.off(events.MAP_SET_SIZE, handleSetMapSize)
214
+ eventBus.off(events.MAP_SET_PIXEL_RATIO, handleSetPixelRatio)
120
215
  // Disable draw mode but keep control on map for reuse
121
216
  draw.changeMode('disabled')
122
217
  // Clear adapter reference (but not _mapboxDrawInstance so it persists)