@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
@@ -10,7 +10,8 @@ import {
10
10
  getSnapRadius,
11
11
  isSnapEnabled,
12
12
  createSnappedEvent,
13
- createSnappedClickEvent
13
+ createSnappedClickEvent,
14
+ setAutoSnapSuspended
14
15
  } from './snapHelpers.js'
15
16
  import { TOLERANCES } from '../../../defaults.js'
16
17
 
@@ -87,7 +88,10 @@ describe('triggerSnapAtPoint', () => {
87
88
 
88
89
  expect(triggerSnapAtPoint(snap, map, point)).toBe(true)
89
90
  expect(map.unproject).toHaveBeenCalledWith(point)
90
- expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat })
91
+ // _explicit: true is what lets this call through snap/prototypePatches.js's suspend gate
92
+ // (see setAutoSnapSuspended) even while an edit mode's own offset-aware drag has silenced
93
+ // the snap library's own uncontrolled mousemove-driven auto-query.
94
+ expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat, _explicit: true })
91
95
  })
92
96
 
93
97
  test('returns false when snap is missing', () => {
@@ -112,7 +116,7 @@ describe('triggerSnapAtCenter', () => {
112
116
 
113
117
  expect(triggerSnapAtCenter(snap, map)).toBe(true)
114
118
  expect(map.project).toHaveBeenCalledWith(center)
115
- expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat: center })
119
+ expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point, lngLat: center, _explicit: true })
116
120
  })
117
121
 
118
122
  test('returns false when snap is missing', () => {
@@ -196,6 +200,20 @@ describe('clearSnapState', () => {
196
200
  })
197
201
  })
198
202
 
203
+ describe('setAutoSnapSuspended', () => {
204
+ test('sets the flag on the snap instance', () => {
205
+ const snap = {}
206
+ setAutoSnapSuspended(snap, true)
207
+ expect(snap._autoSnapSuspended).toBe(true)
208
+ setAutoSnapSuspended(snap, false)
209
+ expect(snap._autoSnapSuspended).toBe(false)
210
+ })
211
+
212
+ test('is a no-op when snap is nullish', () => {
213
+ expect(() => setAutoSnapSuspended(null, true)).not.toThrow()
214
+ })
215
+ })
216
+
199
217
  describe('getSnapRadius', () => {
200
218
  test('returns the configured radius', () => {
201
219
  expect(getSnapRadius({ options: { radius: 25 } })).toBe(25)
@@ -0,0 +1,59 @@
1
+ import { KEYBOARD } from '../defaults.js'
2
+ import {
3
+ getSnapInstance, isSnapActive, isSnapEnabled, getSnapLngLat,
4
+ getSnapRadius, triggerSnapAtPoint, clearSnapIndicator
5
+ } from './snapHelpers.js'
6
+ import { ARROW_OFFSETS } from './keyboardShortcuts.js'
7
+
8
+ // Shared by editPointMode/pointOperations.js and editVertexMode/vertexOperations.js — a
9
+ // keyboard/D-pad nudge resolves to the same pixel-offset-then-project-back coordinate math
10
+ // and the same snap/break-out-of-snap rule for both a single-coordinate Point and a ring
11
+ // vertex; only what happens to that resolved coordinate afterwards (moveVertex's ring
12
+ // splice vs movePoint's direct updateCoordinate) differs, so that part stays local to
13
+ // each mode's own operations file.
14
+ export const sharedSnapMovement = {
15
+ getOffset (coord, e) {
16
+ const pt = this.map.project(coord)
17
+ const offset = e?.shiftKey ? KEYBOARD.nudgeAmount : KEYBOARD.stepAmount
18
+ const [dx, dy] = e ? ARROW_OFFSETS[e.key].map(v => v * offset) : [0, 0]
19
+ return this.map.unproject({ x: pt.x + dx, y: pt.y + dy })
20
+ },
21
+
22
+ // Explicit-delta counterpart to getOffset, driven by a unit direction vector and
23
+ // MoveControls' own Precision toggle rather than a KeyboardEvent — used by
24
+ // nudgeVertexByDelta/nudgePointByDelta.
25
+ getOffsetByDelta (coord, dx, dy, isLargeStep) {
26
+ const pt = this.map.project(coord)
27
+ const amount = isLargeStep ? KEYBOARD.stepAmount : KEYBOARD.nudgeAmount
28
+ return this.map.unproject({ x: pt.x + dx * amount, y: pt.y + dy * amount })
29
+ },
30
+
31
+ // Resolves the destination coordinate for a nudge by (dx, dy) unit direction, applying
32
+ // snap or breaking out of an already-active one — shared by the keyboard arrow-key path
33
+ // and MoveControls' explicit-delta path so both snap identically. dx/dy are only needed
34
+ // for the snap-escape offset; getCandidate computes the raw, un-snapped destination the
35
+ // caller would otherwise have used.
36
+ resolveSnapTarget (state, dx, dy, currentCoord, getCandidate) {
37
+ const snap = getSnapInstance(this.map)
38
+
39
+ // Break out of an active snap by moving beyond the snap radius
40
+ if (isSnapEnabled(state) && state._isSnapped && snap) {
41
+ const offset = getSnapRadius(snap) + 1
42
+ const pt = this.map.project(currentCoord)
43
+ state._isSnapped = false
44
+ clearSnapIndicator(snap, this.map)
45
+ return this.map.unproject({ x: pt.x + dx * offset, y: pt.y + dy * offset })
46
+ }
47
+
48
+ const newCoord = getCandidate()
49
+ if (isSnapEnabled(state) && snap) {
50
+ triggerSnapAtPoint(snap, this.map, this.map.project(newCoord))
51
+ if (isSnapActive(snap)) {
52
+ state._isSnapped = true
53
+ return getSnapLngLat(snap)
54
+ }
55
+ }
56
+ state._isSnapped = false
57
+ return newCoord
58
+ }
59
+ }
@@ -0,0 +1,73 @@
1
+ import { sharedSnapMovement } from './snapMovement.js'
2
+
3
+ // Deterministic projection: lngLat <-> pixel is a x10 scale (same convention as the mode
4
+ // test harnesses this logic was extracted from).
5
+ const project = (p) => {
6
+ const [lng, lat] = Array.isArray(p) ? p : [p.lng, p.lat]
7
+ return { x: lng * 10, y: lat * 10 }
8
+ }
9
+ const unproject = ({ x, y }) => ({ lng: x / 10, lat: y / 10 })
10
+
11
+ const makeCtx = (snapOverrides) => ({
12
+ ...sharedSnapMovement,
13
+ map: {
14
+ project: jest.fn(project),
15
+ unproject: jest.fn(unproject),
16
+ getLayer: jest.fn(() => null),
17
+ setLayoutProperty: jest.fn(),
18
+ _snapInstance: snapOverrides
19
+ }
20
+ })
21
+
22
+ describe('getOffset', () => {
23
+ test('applies step/nudge amounts and returns the coord unchanged without an event', () => {
24
+ const ctx = makeCtx()
25
+ const stepped = ctx.getOffset([5, 5], { key: 'ArrowRight', shiftKey: false })
26
+ const nudged = ctx.getOffset([5, 5], { key: 'ArrowRight', shiftKey: true })
27
+ expect(stepped.lng).not.toBe(5)
28
+ expect(Math.abs(nudged.lng - 5)).toBeLessThan(Math.abs(stepped.lng - 5))
29
+ expect(ctx.getOffset([5, 5], null)).toEqual({ lng: 5, lat: 5 })
30
+ })
31
+ })
32
+
33
+ describe('getOffsetByDelta', () => {
34
+ test('applies step/nudge amounts along an explicit direction, mirroring getOffset\'s shiftKey polarity', () => {
35
+ const ctx = makeCtx()
36
+ const large = ctx.getOffsetByDelta([5, 5], 1, 0, true)
37
+ const small = ctx.getOffsetByDelta([5, 5], 1, 0, false)
38
+ expect(large.lng).not.toBe(5)
39
+ expect(Math.abs(small.lng - 5)).toBeLessThan(Math.abs(large.lng - 5))
40
+ expect(ctx.getOffsetByDelta([5, 5], 0, 0, true)).toEqual({ lng: 5, lat: 5 })
41
+ })
42
+ })
43
+
44
+ describe('resolveSnapTarget', () => {
45
+ test('is a no-op passthrough to the candidate coordinate when snap is disabled', () => {
46
+ const ctx = makeCtx()
47
+ const state = { getSnapEnabled: () => false }
48
+ const candidate = { lng: 3, lat: 4 }
49
+ expect(ctx.resolveSnapTarget(state, 1, 0, [0, 0], () => candidate)).toEqual(candidate)
50
+ })
51
+
52
+ test('snaps to a nearby target and breaks out of an active snap on the next call', () => {
53
+ const ctx = makeCtx({ status: true, snapStatus: true, snapCoords: [7, 8], snapToClosestPoint: jest.fn() })
54
+ const state = { getSnapEnabled: () => true }
55
+
56
+ const snapped = ctx.resolveSnapTarget(state, 0, -1, [5, 5], () => ({ lng: 5, lat: 4 }))
57
+ expect(state._isSnapped).toBe(true)
58
+ expect(snapped).toEqual({ lng: 7, lat: 8 })
59
+
60
+ // Already snapped → next call breaks out of the snap radius instead of re-snapping
61
+ const escaped = ctx.resolveSnapTarget(state, -1, 0, [7, 8], () => ({ lng: 6, lat: 8 }))
62
+ expect(state._isSnapped).toBe(false)
63
+ expect(escaped.lng).toBeLessThan(7)
64
+ })
65
+
66
+ test('falls back to the raw candidate when snap is enabled but inactive', () => {
67
+ const ctx = makeCtx({ status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() })
68
+ const state = { getSnapEnabled: () => true }
69
+ const candidate = { lng: 6, lat: 6 }
70
+ expect(ctx.resolveSnapTarget(state, 1, 0, [5, 5], () => candidate)).toEqual(candidate)
71
+ expect(state._isSnapped).toBe(false)
72
+ })
73
+ })
@@ -0,0 +1,33 @@
1
+ import { getSnapInstance, isSnapEnabled, triggerSnapAtPoint, getSnapLngLat } from './snapHelpers.js'
2
+
3
+ // Shared by editPointMode/touchHandlers.js and editVertexMode/touchHandlers.js — the offset-
4
+ // drag math (touch position → target/coordinate deltas, then delta → snapped map coordinate)
5
+ // is identical for both, since it's already single-coordinate, not ring-specific. Only what's
6
+ // dragged (state.touchPointTarget/movePoint vs state.touchVertexTarget/moveVertex) differs,
7
+ // which stays local to each mode's own touchHandlers.
8
+
9
+ export const getTouchPoint = (e) => ({ x: e.touches[0].clientX, y: e.touches[0].clientY })
10
+
11
+ // touchstart-time anchoring: the fixed offset between the finger and both the visible touch
12
+ // target and the underlying map coordinate, so onTouchmove can re-derive each from the
13
+ // finger's current position without drifting off its grab point.
14
+ export const computeTouchDragAnchors = (map, targetEl, touch, coord, scale) => {
15
+ const style = window.getComputedStyle(targetEl)
16
+ const deltaTarget = { x: touch.x - Number.parseFloat(style.left), y: touch.y - Number.parseFloat(style.top) }
17
+ const coordPt = map.project(coord)
18
+ const deltaVertex = { x: (touch.x / scale) - coordPt.x, y: (touch.y / scale) - coordPt.y }
19
+ return { deltaTarget, deltaVertex }
20
+ }
21
+
22
+ // Resolves the current touch position (given the deltas computed above) to a map coordinate,
23
+ // honouring snap — the mid-drag counterpart to computeTouchDragAnchors' touchstart-time setup.
24
+ export const resolveTouchDragCoord = (map, state, touch) => {
25
+ const screenPt = { x: (touch.x / state.scale) - state.deltaVertex.x, y: (touch.y / state.scale) - state.deltaVertex.y }
26
+ let finalCoord = map.unproject(screenPt)
27
+ if (isSnapEnabled(state)) {
28
+ const snap = getSnapInstance(map)
29
+ triggerSnapAtPoint(snap, map, screenPt)
30
+ finalCoord = getSnapLngLat(snap) || finalCoord
31
+ }
32
+ return finalCoord
33
+ }
@@ -0,0 +1,70 @@
1
+ import { getTouchPoint, computeTouchDragAnchors, resolveTouchDragCoord } from './touchDragMath.js'
2
+
3
+ // Deterministic projection: lngLat <-> pixel is a x10 scale (same convention as the mode
4
+ // test harnesses this logic was extracted from).
5
+ const project = (p) => {
6
+ const [lng, lat] = Array.isArray(p) ? p : [p.lng, p.lat]
7
+ return { x: lng * 10, y: lat * 10 }
8
+ }
9
+ const unproject = ({ x, y }) => ({ lng: x / 10, lat: y / 10 })
10
+
11
+ afterEach(() => {
12
+ document.body.innerHTML = ''
13
+ })
14
+
15
+ describe('getTouchPoint', () => {
16
+ test('reads clientX/clientY off the first touch', () => {
17
+ expect(getTouchPoint({ touches: [{ clientX: 12, clientY: 34 }] })).toEqual({ x: 12, y: 34 })
18
+ })
19
+ })
20
+
21
+ describe('computeTouchDragAnchors', () => {
22
+ test('anchors both the visible target and the map coordinate to the touchstart position', () => {
23
+ const map = { project: jest.fn(project) }
24
+ const targetEl = document.createElement('div')
25
+ document.body.appendChild(targetEl)
26
+ targetEl.style.left = '15px'
27
+ targetEl.style.top = '25px'
28
+
29
+ const { deltaTarget, deltaVertex } = computeTouchDragAnchors(map, targetEl, { x: 20, y: 40 }, [5, 5], 1)
30
+
31
+ expect(deltaTarget).toEqual({ x: 5, y: 15 }) // touch position minus the target's current CSS position
32
+ expect(deltaVertex).toEqual({ x: -30, y: -10 }) // touch/scale minus the projected coord
33
+ expect(map.project).toHaveBeenCalledWith([5, 5])
34
+ })
35
+
36
+ test('divides the touch position by scale before anchoring the coordinate delta', () => {
37
+ const map = { project: jest.fn(project) }
38
+ const targetEl = document.createElement('div')
39
+ document.body.appendChild(targetEl)
40
+
41
+ const { deltaVertex } = computeTouchDragAnchors(map, targetEl, { x: 100, y: 100 }, [5, 5], 2)
42
+ expect(deltaVertex).toEqual({ x: 0, y: 0 }) // (100 / 2) - (5 * 10)
43
+ })
44
+ })
45
+
46
+ describe('resolveTouchDragCoord', () => {
47
+ test('unprojects the delta-adjusted touch position when snap is disabled', () => {
48
+ const map = { unproject: jest.fn(unproject) }
49
+ const state = { scale: 1, deltaVertex: { x: 10, y: 10 }, getSnapEnabled: () => false }
50
+ expect(resolveTouchDragCoord(map, state, { x: 30, y: 40 })).toEqual({ lng: 2, lat: 3 })
51
+ })
52
+
53
+ test('snaps to a nearby target when active', () => {
54
+ const map = {
55
+ unproject: jest.fn(unproject),
56
+ _snapInstance: { status: true, snapStatus: true, snapCoords: [7, 8], snapToClosestPoint: jest.fn() }
57
+ }
58
+ const state = { scale: 1, deltaVertex: { x: 0, y: 0 }, getSnapEnabled: () => true }
59
+ expect(resolveTouchDragCoord(map, state, { x: 10, y: 10 })).toEqual({ lng: 7, lat: 8 })
60
+ })
61
+
62
+ test('falls back to the raw coordinate when snap is enabled but inactive', () => {
63
+ const map = {
64
+ unproject: jest.fn(unproject),
65
+ _snapInstance: { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
66
+ }
67
+ const state = { scale: 1, deltaVertex: { x: 0, y: 0 }, getSnapEnabled: () => true }
68
+ expect(resolveTouchDragCoord(map, state, { x: 10, y: 10 })).toEqual({ lng: 1, lat: 1 })
69
+ })
70
+ })
@@ -1,4 +1,5 @@
1
1
  import { createOLDraw } from './olDraw.js'
2
+ import { resolvePointSymbol, hasSymbolStyle } from './point/pointSymbolImages.js'
2
3
 
3
4
  // split.js passes this literal — MapLibre's own always-present "already-drawn
4
5
  // shapes" style layer id — as a snapLayers entry so the split line snaps to the
@@ -25,7 +26,7 @@ const DRAW_OUTLINE_STYLE_LAYER = 'stroke-inactive.cold'
25
26
  * setInterfaceType(type)
26
27
  * done() / cancel() / undo() / deleteVertex()
27
28
  * nudgeSelectedVertex(dx, dy, isLargeStep)
28
- * get(id) / add(feature) / delete(id) / deleteAll()
29
+ * get(id) / add(feature) / setStyle(id, properties) / delete(id) / deleteAll()
29
30
  * setSnapEnabled(bool) / setSnapLayers(layers) / isSnapEnabled()
30
31
  * setFeatureProperty(id, property, value) / setDrawingPreviewProperty(property, value)
31
32
  * on(event, handler) / off(event, handler)
@@ -89,7 +90,31 @@ export class OLDrawAdapter {
89
90
  setInvalid (invalid) { this._manager.setInvalid(invalid) }
90
91
 
91
92
  get (id) { return this._manager.get(id) }
92
- add (feature) { return this._manager.add(feature) }
93
+
94
+ // A directly-added Point (e.g. api/addFeature.js) skips draw_point's own icon-resolving
95
+ // drawend handler, so it must be resolved here instead — mirrors MaplibreDrawAdapter.js.
96
+ add (feature) {
97
+ const olFeature = this._manager.add(feature)
98
+ if (feature.geometry?.type === 'Point' && hasSymbolStyle(feature.properties)) {
99
+ resolvePointSymbol({ manager: this._manager, mapProvider: this._mapProvider, olFeature })
100
+ }
101
+ return olFeature
102
+ }
103
+
104
+ // Patches an existing feature's style properties (stroke/fill/strokeWidth or symbol-family
105
+ // keys) and re-renders — setProperties() must not be called silently, since VectorSource
106
+ // only redraws in response to the propertychange event that triggers.
107
+ setStyle (id, properties) {
108
+ const olFeature = this._manager.store.getOL(id)
109
+ if (!olFeature) {
110
+ return
111
+ }
112
+ olFeature.setProperties(properties)
113
+ if (olFeature.getGeometry()?.getType() === 'Point' && hasSymbolStyle(olFeature.getProperties())) {
114
+ resolvePointSymbol({ manager: this._manager, mapProvider: this._mapProvider, olFeature })
115
+ }
116
+ }
117
+
93
118
  delete (id) { return this._manager.delete(id) }
94
119
  deleteAll () { return this._manager.deleteAll() }
95
120
 
@@ -1,5 +1,6 @@
1
1
  import { OLDrawAdapter } from './OLDrawAdapter.js'
2
2
  import { createOLDraw } from './olDraw.js'
3
+ import { resolvePointSymbol, hasSymbolStyle } from './point/pointSymbolImages.js'
3
4
 
4
5
  // The literal split.js passes — see OLDrawAdapter.js's DRAW_OUTLINE_STYLE_LAYER comment.
5
6
  const DRAW_OUTLINE_STYLE_LAYER = 'stroke-inactive.cold'
@@ -20,6 +21,7 @@ const fakeManager = () => ({
20
21
  setDrawingPreviewProperty: jest.fn(),
21
22
  get: jest.fn(() => 'feature'),
22
23
  add: jest.fn(),
24
+ store: { getOL: jest.fn() },
23
25
  delete: jest.fn(),
24
26
  deleteAll: jest.fn(),
25
27
  on: jest.fn(),
@@ -31,6 +33,10 @@ const fakeManager = () => ({
31
33
  jest.mock('./olDraw.js', () => ({
32
34
  createOLDraw: jest.fn(({ mapProvider }) => ({ manager: mapProvider._testManager, remove: jest.fn() }))
33
35
  }))
36
+ jest.mock('./point/pointSymbolImages.js', () => ({
37
+ resolvePointSymbol: jest.fn(),
38
+ hasSymbolStyle: jest.fn()
39
+ }))
34
40
 
35
41
  const setup = () => {
36
42
  const manager = fakeManager()
@@ -132,6 +138,93 @@ test('remaining calls delegate straight through; setFeatureProperty is a deliber
132
138
  expect(manager.nudgeSelectedVertex).toHaveBeenCalledWith(1, 0, true)
133
139
  })
134
140
 
141
+ // A directly-added Point skips draw_point's own icon-resolving drawend handler.
142
+ describe('add() and point symbol resolution', () => {
143
+ test('resolves the symbol for a Point feature with symbol properties', () => {
144
+ const { manager, mapProvider, adapter } = setup()
145
+ const olFeature = {}
146
+ manager.add.mockReturnValue(olFeature)
147
+ hasSymbolStyle.mockReturnValue(true)
148
+ const feature = { geometry: { type: 'Point', coordinates: [0, 0] }, properties: { symbol: 'pin' } }
149
+
150
+ const result = adapter.add(feature)
151
+
152
+ expect(manager.add).toHaveBeenCalledWith(feature)
153
+ expect(hasSymbolStyle).toHaveBeenCalledWith({ symbol: 'pin' })
154
+ expect(resolvePointSymbol).toHaveBeenCalledWith({ manager, mapProvider, olFeature })
155
+ expect(result).toBe(olFeature)
156
+ })
157
+
158
+ test('does not attempt resolution for a Point with no symbol properties', () => {
159
+ const { manager, adapter } = setup()
160
+ manager.add.mockReturnValue({})
161
+ hasSymbolStyle.mockReturnValue(false)
162
+ adapter.add({ geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} })
163
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
164
+ })
165
+
166
+ test('does not attempt resolution for a non-Point geometry', () => {
167
+ const { manager, adapter } = setup()
168
+ manager.add.mockReturnValue({})
169
+ adapter.add({ geometry: { type: 'Polygon', coordinates: [[]] }, properties: { symbol: 'pin' } })
170
+ expect(hasSymbolStyle).not.toHaveBeenCalled()
171
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
172
+ })
173
+
174
+ test('does not attempt resolution for a feature with no geometry', () => {
175
+ const { manager, adapter } = setup()
176
+ manager.add.mockReturnValue({})
177
+ adapter.add({ id: 'b' })
178
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
179
+ })
180
+ })
181
+
182
+ describe('setStyle()', () => {
183
+ const fakeOLFeature = (geometryType) => ({
184
+ setProperties: jest.fn(),
185
+ getGeometry: jest.fn(() => ({ getType: () => geometryType })),
186
+ getProperties: jest.fn(() => ({}))
187
+ })
188
+
189
+ test('patches the feature\'s properties (not silently, so VectorSource still redraws)', () => {
190
+ const { manager, adapter } = setup()
191
+ const olFeature = fakeOLFeature('Polygon')
192
+ manager.store.getOL.mockReturnValue(olFeature)
193
+
194
+ adapter.setStyle('a', { stroke: 'blue' })
195
+
196
+ expect(manager.store.getOL).toHaveBeenCalledWith('a')
197
+ expect(olFeature.setProperties).toHaveBeenCalledWith({ stroke: 'blue' })
198
+ })
199
+
200
+ // setStyle re-resolves a Point's icon the same way add() does for a directly-added one.
201
+ test('re-resolves the icon for a Point patched with symbol properties', () => {
202
+ const { manager, mapProvider, adapter } = setup()
203
+ const olFeature = fakeOLFeature('Point')
204
+ manager.store.getOL.mockReturnValue(olFeature)
205
+ hasSymbolStyle.mockReturnValue(true)
206
+
207
+ adapter.setStyle('p1', { symbolBackgroundColor: '#ca3535' })
208
+
209
+ expect(resolvePointSymbol).toHaveBeenCalledWith({ manager, mapProvider, olFeature })
210
+ })
211
+
212
+ test('does not attempt resolution for a non-Point geometry', () => {
213
+ const { manager, adapter } = setup()
214
+ const olFeature = fakeOLFeature('Polygon')
215
+ manager.store.getOL.mockReturnValue(olFeature)
216
+ adapter.setStyle('a', { stroke: 'blue' })
217
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
218
+ })
219
+
220
+ test('does nothing for an id with no existing feature', () => {
221
+ const { manager, adapter } = setup()
222
+ manager.store.getOL.mockReturnValue(null)
223
+ expect(() => adapter.setStyle('missing', { stroke: 'blue' })).not.toThrow()
224
+ expect(resolvePointSymbol).not.toHaveBeenCalled()
225
+ })
226
+ })
227
+
135
228
  test('setGeometryValid records validity on the manager for finish gating', () => {
136
229
  const { adapter, manager } = setup()
137
230
  adapter.setGeometryValid(false)
@@ -1,6 +1,7 @@
1
1
  import Feature from 'ol/Feature.js'
2
2
  import Polygon from 'ol/geom/Polygon.js'
3
3
  import LineString from 'ol/geom/LineString.js'
4
+ import Point from 'ol/geom/Point.js'
4
5
  import Style from 'ol/style/Style.js'
5
6
  import { createUndoStack } from '../../../utils/undoStack.js'
6
7
 
@@ -41,6 +42,7 @@ export const createFakeMap = ({ center = [50, 50] } = {}) => {
41
42
  getCoordinateFromPixel: (p) => [p[0], p[1]],
42
43
  getEventPixel: (e) => [e.clientX, e.clientY],
43
44
  getView: () => view,
45
+ forEachFeatureAtPixel: jest.fn(() => undefined),
44
46
  render: jest.fn()
45
47
  }
46
48
  }
@@ -60,6 +62,7 @@ export const createFakeManager = () => {
60
62
  midpointStyle: new Style({}),
61
63
  selectedVertexStyle: new Style({}),
62
64
  selectedMidpointStyle: new Style({}),
65
+ selectedPointStyleFor: jest.fn(() => new Style({})),
63
66
  createSketchStyle: jest.fn(() => () => [])
64
67
  },
65
68
  colors: { editVertex: 'rgba(29,112,184,1)' },
@@ -79,6 +82,12 @@ export const lineFeature = (coords, id = 'f1') => {
79
82
  return feature
80
83
  }
81
84
 
85
+ export const pointFeature = (coord, id = 'f1') => {
86
+ const feature = new Feature(new Point(coord))
87
+ feature.setId(id)
88
+ return feature
89
+ }
90
+
82
91
  export const createContainer = () => {
83
92
  const container = document.createElement('div')
84
93
  container.tabIndex = 0
@@ -6,6 +6,9 @@ import { resolveColors } from '../../../utils/resolveColors.js'
6
6
  import { createSnapManager } from '../snap/snapManager.js'
7
7
  import { createDrawMode } from '../draw/DrawMode.js'
8
8
  import { createEditMode } from '../edit/EditMode.js'
9
+ import { createEditPointMode } from '../point/editPointMode.js'
10
+ import { createDrawPointMode } from '../point/drawPointMode.js'
11
+ import { resolvePointSymbol } from '../point/pointSymbolImages.js'
9
12
  import { TOLERANCES } from '../defaults.js'
10
13
  import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
11
14
  import { STYLES_CHANGED_EVENT } from './internalEvents.js'
@@ -31,6 +34,11 @@ export class OLDrawManager {
31
34
  this.store = createFeatureStore()
32
35
  this.undoStack = createUndoStack((length) => this.emit(ADAPTER_EVENTS.UNDO_CHANGE, length))
33
36
 
37
+ // Tracked (not just derived into colors/styles) so point/pointSymbolImages.js has
38
+ // something to pass to symbolRegistry.rasteriseSymbolImage() when resolving a point's
39
+ // icon on demand, independent of whatever triggered the resolve.
40
+ this.mapStyle = null
41
+
34
42
  this.colors = resolveColors(null, pluginConfig)
35
43
  this.styles = createStyles(this.colors)
36
44
  this.snap = createSnapManager(map, pluginConfig.snapLayers ?? null, this.colors, pluginConfig.snapRadius ?? TOLERANCES.snapRadius)
@@ -47,6 +55,7 @@ export class OLDrawManager {
47
55
  // --- Color / style updates ---
48
56
 
49
57
  setMapStyle (mapStyle) {
58
+ this.mapStyle = mapStyle
50
59
  this.colors = resolveColors(mapStyle, this._pluginConfig)
51
60
  this.styles = createStyles(this.colors)
52
61
  this._layer.setStyle(this.styles.createFeatureStyle())
@@ -84,15 +93,24 @@ export class OLDrawManager {
84
93
  this._modeInstance = null
85
94
  this._mode = modeName
86
95
 
87
- const isDrawMode = modeName === 'draw_polygon' || modeName === 'draw_line' || modeName === 'edit_vertex'
96
+ const isDrawMode = modeName === 'draw_polygon' || modeName === 'draw_line' || modeName === 'draw_point' || modeName === 'edit_vertex' || modeName === 'edit_point'
88
97
  this.snap?.setIndicatorActive(isDrawMode)
89
98
 
90
99
  const modeOptions = { ...options, snap: this.snap }
91
100
 
92
101
  if (modeName === 'draw_polygon' || modeName === 'draw_line') {
93
102
  this._modeInstance = createDrawMode({ map: this._map, manager: this, options: modeOptions })
103
+ } else if (modeName === 'draw_point') {
104
+ // Injected here (rather than imported directly by drawPointMode.js) so the mode only
105
+ // ever calls options.resolvePointSymbol(...) and stays free of a direct dependency on
106
+ // symbolRegistry/mapProvider — mirrors the ML adapter's own state.resolvePointSymbol
107
+ // convention (see MaplibreDrawAdapter.js's changeMode).
108
+ const pointOptions = { ...modeOptions, resolvePointSymbol: (olFeature) => resolvePointSymbol({ manager: this, mapProvider: options.mapProvider, olFeature }) }
109
+ this._modeInstance = createDrawPointMode({ map: this._map, manager: this, options: pointOptions })
94
110
  } else if (modeName === 'edit_vertex') {
95
111
  this._modeInstance = createEditMode({ map: this._map, manager: this, options: modeOptions })
112
+ } else if (modeName === 'edit_point') {
113
+ this._modeInstance = createEditPointMode({ map: this._map, manager: this, options: modeOptions })
96
114
  } else {
97
115
  // disabled — no mode instance needed
98
116
  }
@@ -2,6 +2,9 @@ import { OLDrawManager } from './OLDrawManager.js'
2
2
  import { STYLES_CHANGED_EVENT } from './internalEvents.js'
3
3
  import { createDrawMode } from '../draw/DrawMode.js'
4
4
  import { createEditMode } from '../edit/EditMode.js'
5
+ import { createEditPointMode } from '../point/editPointMode.js'
6
+ import { createDrawPointMode } from '../point/drawPointMode.js'
7
+ import { resolvePointSymbol } from '../point/pointSymbolImages.js'
5
8
  import { createSnapManager } from '../snap/snapManager.js'
6
9
  import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
7
10
  import { createFakeMap } from '../__helpers__/harness.js'
@@ -13,6 +16,15 @@ jest.mock('../draw/DrawMode.js', () => ({
13
16
  jest.mock('../edit/EditMode.js', () => ({
14
17
  createEditMode: jest.fn(() => ({ destroy: jest.fn(), done: jest.fn(), cancel: jest.fn(), undo: jest.fn(), deleteVertex: jest.fn(), nudgeSelectedVertex: jest.fn(), setInterfaceType: jest.fn(), setInvalid: jest.fn() }))
15
18
  }))
19
+ jest.mock('../point/editPointMode.js', () => ({
20
+ createEditPointMode: jest.fn(() => ({ destroy: jest.fn(), done: jest.fn(), cancel: jest.fn(), undo: jest.fn(), deleteVertex: jest.fn(), nudgeSelectedVertex: jest.fn(), setInterfaceType: jest.fn(), setInvalid: jest.fn() }))
21
+ }))
22
+ jest.mock('../point/drawPointMode.js', () => ({
23
+ createDrawPointMode: jest.fn(() => ({ destroy: jest.fn(), done: jest.fn(), cancel: jest.fn(), undo: jest.fn(), setInterfaceType: jest.fn() }))
24
+ }))
25
+ jest.mock('../point/pointSymbolImages.js', () => ({
26
+ resolvePointSymbol: jest.fn()
27
+ }))
16
28
  jest.mock('../snap/snapManager.js', () => ({
17
29
  createSnapManager: jest.fn(() => ({ setIndicatorActive: jest.fn(), reattach: jest.fn(), updateColors: jest.fn(), destroy: jest.fn() }))
18
30
  }))
@@ -52,7 +64,8 @@ describe('mode machine', () => {
52
64
  test.each([
53
65
  ['draw_polygon', createDrawMode],
54
66
  ['draw_line', createDrawMode],
55
- ['edit_vertex', createEditMode]
67
+ ['edit_vertex', createEditMode],
68
+ ['edit_point', createEditPointMode]
56
69
  ])('%s creates its mode with snap injected, activates the indicator and reattaches snap last', async (name, factory) => {
57
70
  const { map, manager } = setup()
58
71
  await manager.changeMode(name, { featureId: 'f1' })
@@ -62,6 +75,26 @@ describe('mode machine', () => {
62
75
  expect(manager.snap.reattach).toHaveBeenCalled()
63
76
  })
64
77
 
78
+ // draw_point gets an extra options key (resolvePointSymbol) the other modes don't — see
79
+ // OLDrawManager.js's changeMode comment — so it's asserted separately rather than folded
80
+ // into the table above.
81
+ test('draw_point creates its mode with snap and resolvePointSymbol injected, activates the indicator and reattaches snap last', async () => {
82
+ const { map, manager } = setup()
83
+ const mapProvider = { drawScale: 2 }
84
+ await manager.changeMode('draw_point', { featureId: 'f1', mapProvider })
85
+ expect(manager.getMode()).toBe('draw_point')
86
+ expect(createDrawPointMode).toHaveBeenCalledWith({ map, manager, options: { featureId: 'f1', mapProvider, snap: manager.snap, resolvePointSymbol: expect.any(Function) } })
87
+ expect(manager.snap.setIndicatorActive).toHaveBeenCalledWith(true)
88
+ expect(manager.snap.reattach).toHaveBeenCalled()
89
+
90
+ // The injected hook delegates to point/pointSymbolImages.js's resolvePointSymbol with
91
+ // this manager and the mapProvider changeMode was called with.
92
+ const injected = createDrawPointMode.mock.calls.at(-1)[0].options.resolvePointSymbol
93
+ const olFeature = { id: 'point-1' }
94
+ injected(olFeature)
95
+ expect(resolvePointSymbol).toHaveBeenCalledWith({ manager, mapProvider, olFeature })
96
+ })
97
+
65
98
  test('disabled destroys the previous mode and deactivates the indicator', async () => {
66
99
  const { manager } = setup()
67
100
  await manager.changeMode('draw_polygon')