@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,4 +1,13 @@
1
1
  import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
2
+ import { stripInternalProperties } from '../utils/stripInternalProperties.js'
3
+ import { logger } from '../../../../src/services/logger.js'
4
+
5
+ const SUPPORTED_GEOMETRY_TYPES = new Set(['Point', 'LineString', 'Polygon'])
6
+
7
+ // type: 'Feature' is defaulted below (both adapters mishandle it missing), but a missing or
8
+ // unsupported geometry is a genuine caller mistake, so that's rejected instead.
9
+ const isValidFeature = (feature) =>
10
+ SUPPORTED_GEOMETRY_TYPES.has(feature.geometry?.type) && feature.geometry?.coordinates !== undefined
2
11
 
3
12
  export const addFeature = ({ mapProvider, services }, feature) => {
4
13
  const { draw } = mapProvider
@@ -10,6 +19,7 @@ export const addFeature = ({ mapProvider, services }, feature) => {
10
19
 
11
20
  const { stroke, fill, strokeWidth, properties, ...featureRest } = feature
12
21
  const flatFeature = {
22
+ type: 'Feature',
13
23
  ...featureRest,
14
24
  properties: {
15
25
  ...properties,
@@ -17,6 +27,11 @@ export const addFeature = ({ mapProvider, services }, feature) => {
17
27
  }
18
28
  }
19
29
 
30
+ if (!isValidFeature(flatFeature)) {
31
+ logger.warn('addFeature: ignoring invalid GeoJSON feature', flatFeature)
32
+ return
33
+ }
34
+
20
35
  draw.add(flatFeature)
21
- eventBus.emit('draw:add', flatFeature)
36
+ eventBus.emit('draw:add', stripInternalProperties(flatFeature))
22
37
  }
@@ -1,9 +1,13 @@
1
1
  import { addFeature } from './addFeature.js'
2
2
  import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
3
+ import { logger } from '../../../../src/services/logger.js'
3
4
 
4
5
  jest.mock('../utils/flattenStyleProperties.js', () => ({
5
6
  flattenStyleProperties: jest.fn(() => ({ _flat: true }))
6
7
  }))
8
+ jest.mock('../../../../src/services/logger.js', () => ({
9
+ logger: { warn: jest.fn(), error: jest.fn() }
10
+ }))
7
11
 
8
12
  const setup = (draw) => {
9
13
  const eventBus = { emit: jest.fn() }
@@ -19,15 +23,27 @@ describe('addFeature', () => {
19
23
  expect(eventBus.emit).not.toHaveBeenCalled()
20
24
  })
21
25
 
22
- test('flattens style properties and adds the feature', () => {
26
+ test('flattens style properties, defaults type to Feature, and adds the feature', () => {
23
27
  const draw = { add: jest.fn() }
24
28
  const { context, eventBus } = setup(draw)
25
- const feature = { id: 'a', geometry: {}, stroke: 'red', fill: 'blue', strokeWidth: 2, properties: { name: 'x' } }
29
+ const feature = {
30
+ id: 'a',
31
+ geometry: { type: 'Point', coordinates: [0, 0] },
32
+ stroke: 'red',
33
+ fill: 'blue',
34
+ strokeWidth: 2,
35
+ properties: { name: 'x' }
36
+ }
26
37
 
27
38
  addFeature(context, feature)
28
39
 
29
40
  expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 2 })
30
- const expected = { id: 'a', geometry: {}, properties: { name: 'x', _flat: true } }
41
+ const expected = {
42
+ type: 'Feature',
43
+ id: 'a',
44
+ geometry: { type: 'Point', coordinates: [0, 0] },
45
+ properties: { name: 'x', _flat: true }
46
+ }
31
47
  expect(draw.add).toHaveBeenCalledWith(expected)
32
48
  expect(eventBus.emit).toHaveBeenCalledWith('draw:add', expected)
33
49
  })
@@ -35,7 +51,48 @@ describe('addFeature', () => {
35
51
  test('handles a feature without existing properties', () => {
36
52
  const draw = { add: jest.fn() }
37
53
  const { context } = setup(draw)
38
- addFeature(context, { id: 'b', geometry: {} })
39
- expect(draw.add).toHaveBeenCalledWith({ id: 'b', geometry: {}, properties: { _flat: true } })
54
+ addFeature(context, { id: 'b', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] } })
55
+ expect(draw.add).toHaveBeenCalledWith({
56
+ type: 'Feature',
57
+ id: 'b',
58
+ geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] },
59
+ properties: { _flat: true }
60
+ })
61
+ })
62
+
63
+ test('does not override an explicitly supplied type', () => {
64
+ const draw = { add: jest.fn() }
65
+ const { context } = setup(draw)
66
+ addFeature(context, { id: 'c', type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] } })
67
+ expect(draw.add).toHaveBeenCalledWith(expect.objectContaining({ type: 'Feature' }))
68
+ })
69
+
70
+ // A missing/unsupported geometry is a genuine caller mistake — warned and dropped rather
71
+ // than handed to an adapter that would throw or silently misread it.
72
+ describe('invalid geometry', () => {
73
+ test('rejects a feature with no geometry at all', () => {
74
+ const draw = { add: jest.fn() }
75
+ const { context, eventBus } = setup(draw)
76
+ addFeature(context, { id: 'd' })
77
+ expect(draw.add).not.toHaveBeenCalled()
78
+ expect(eventBus.emit).not.toHaveBeenCalled()
79
+ expect(logger.warn).toHaveBeenCalledWith('addFeature: ignoring invalid GeoJSON feature', expect.objectContaining({ id: 'd' }))
80
+ })
81
+
82
+ test('rejects an unsupported geometry type', () => {
83
+ const draw = { add: jest.fn() }
84
+ const { context } = setup(draw)
85
+ addFeature(context, { id: 'e', geometry: { type: 'MultiPoint', coordinates: [[0, 0]] } })
86
+ expect(draw.add).not.toHaveBeenCalled()
87
+ expect(logger.warn).toHaveBeenCalled()
88
+ })
89
+
90
+ test('rejects a geometry with no coordinates', () => {
91
+ const draw = { add: jest.fn() }
92
+ const { context } = setup(draw)
93
+ addFeature(context, { id: 'f', geometry: { type: 'Point' } })
94
+ expect(draw.add).not.toHaveBeenCalled()
95
+ expect(logger.warn).toHaveBeenCalled()
96
+ })
40
97
  })
41
98
  })
@@ -0,0 +1,41 @@
1
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
2
+
3
+ // Shared body for newLine/newPolygon — the two are identical except which draw_* mode they
4
+ // enter, so that's the only thing parameterised here. Kept out of newPoint.js: a Point also
5
+ // needs symbol-key extraction (see newPoint.js), a genuine difference, not just the mode string.
6
+ export const createNewShape = (mode) => ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
7
+ const { dispatch } = pluginState
8
+ const { draw } = mapProvider
9
+ const { eventBus } = services
10
+
11
+ if (!draw) {
12
+ return
13
+ }
14
+
15
+ eventBus.emit('draw:started', { mode })
16
+
17
+ const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
18
+ draw.setSnapLayers(snapLayers)
19
+ dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
20
+
21
+ const { stroke, fill, strokeWidth, properties: customProperties, onGeometryChange, ...modeOptions } = options
22
+ // Per-call callback overrides the plugin-level one; events.js reads this on every commit.
23
+ draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
24
+ const properties = {
25
+ ...customProperties,
26
+ ...flattenStyleProperties({ stroke, fill, strokeWidth })
27
+ }
28
+
29
+ draw.changeMode(mode, {
30
+ container: appState.layoutRefs.viewportRef.current,
31
+ addVertexButtonId: `${appConfig.id}-draw-add-point`,
32
+ interfaceType: appState.interfaceType,
33
+ crossHair: mapState.crossHair,
34
+ getSnapEnabled: () => draw.isSnapEnabled(),
35
+ featureId,
36
+ ...modeOptions,
37
+ properties
38
+ })
39
+
40
+ dispatch({ type: 'SET_MODE', payload: mode })
41
+ }
@@ -0,0 +1,63 @@
1
+ import { createNewShape } from './createNewShape.js'
2
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
3
+
4
+ jest.mock('../utils/flattenStyleProperties.js', () => ({
5
+ flattenStyleProperties: jest.fn(() => ({ _flat: true }))
6
+ }))
7
+
8
+ const makeContext = (overrides = {}) => {
9
+ const dispatch = jest.fn()
10
+ const eventBus = { emit: jest.fn() }
11
+ const draw = { setSnapLayers: jest.fn(), changeMode: jest.fn(), isSnapEnabled: jest.fn(() => false) }
12
+ const context = {
13
+ appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
14
+ appConfig: { id: 'app' },
15
+ pluginConfig: {},
16
+ pluginState: { dispatch },
17
+ mapState: { crossHair: true },
18
+ mapProvider: { draw },
19
+ services: { eventBus },
20
+ ...overrides
21
+ }
22
+ return { context, dispatch, eventBus, draw }
23
+ }
24
+
25
+ beforeEach(() => jest.clearAllMocks())
26
+
27
+ describe('createNewShape', () => {
28
+ test('does nothing when there is no draw instance', () => {
29
+ const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
30
+ createNewShape('draw_line')(context, 'f1')
31
+ expect(eventBus.emit).not.toHaveBeenCalled()
32
+ })
33
+
34
+ // newLine.js/newPolygon.js only ever differ by this one argument — everything else about
35
+ // the returned function's behaviour is already covered end-to-end by their own tests.
36
+ test('threads the given mode through every call site: started event, changeMode, SET_MODE', () => {
37
+ const { context, dispatch, eventBus, draw } = makeContext()
38
+ createNewShape('draw_custom')(context, 'f1')
39
+
40
+ expect(eventBus.emit).toHaveBeenCalledWith('draw:started', { mode: 'draw_custom' })
41
+ expect(draw.changeMode).toHaveBeenCalledWith('draw_custom', expect.objectContaining({ featureId: 'f1' }))
42
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'draw_custom' })
43
+ })
44
+
45
+ test('flattens style properties into the mode-change payload', () => {
46
+ const { context, draw } = makeContext()
47
+ createNewShape('draw_line')(context, 'f1', { stroke: 'red', fill: 'blue', strokeWidth: 3, properties: { name: 'x' } })
48
+
49
+ expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 3 })
50
+ expect(draw.changeMode.mock.calls[0][1].properties).toEqual({ name: 'x', _flat: true })
51
+ })
52
+
53
+ test('two factory instances stay independent — one mode never leaks into the other', () => {
54
+ const { context: lineCtx, draw: lineDraw } = makeContext()
55
+ const { context: polyCtx, draw: polyDraw } = makeContext()
56
+
57
+ createNewShape('draw_line')(lineCtx, 'f1')
58
+ createNewShape('draw_polygon')(polyCtx, 'f2')
59
+
60
+ expect(lineDraw.changeMode).toHaveBeenCalledWith('draw_line', expect.anything())
61
+ expect(polyDraw.changeMode).toHaveBeenCalledWith('draw_polygon', expect.anything())
62
+ })
63
+ })
@@ -18,14 +18,17 @@ export const editFeature = ({ appState, appConfig, mapState, pluginConfig, plugi
18
18
  // Per-call callback overrides the plugin-level one; events.js reads this on every commit.
19
19
  draw._geometryValidator = options.onGeometryChange ?? pluginConfig.onGeometryChange
20
20
 
21
- const editModeMap = { LineString: 'edit_line', Polygon: 'edit_polygon' }
21
+ // A Point has no ring of vertices to edit — edit_point relocates the single coordinate
22
+ // instead of edit_vertex's ring-of-vertices machinery.
23
+ const editModeMap = { LineString: 'edit_line', Polygon: 'edit_polygon', Point: 'edit_point' }
24
+ const mode = existingFeature.geometry.type === 'Point' ? 'edit_point' : 'edit_vertex'
22
25
  eventBus.emit('draw:editstart', { mode: editModeMap[existingFeature.geometry.type] })
23
26
 
24
27
  const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
25
28
  draw.setSnapLayers(snapLayers)
26
29
  dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
27
30
 
28
- draw.changeMode('edit_vertex', {
31
+ draw.changeMode(mode, {
29
32
  container: appState.layoutRefs.viewportRef.current,
30
33
  deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
31
34
  undoButtonId: `${appConfig.id}-draw-undo`,
@@ -42,11 +45,11 @@ export const editFeature = ({ appState, appConfig, mapState, pluginConfig, plugi
42
45
  payload: { feature, tempFeature: feature }
43
46
  })
44
47
 
45
- dispatch({ type: 'SET_MODE', payload: 'edit_vertex' })
48
+ dispatch({ type: 'SET_MODE', payload: mode })
46
49
 
47
50
  // Seed the Done-button gate and the stroke from the feature's starting validity
48
51
  // so an already invalid feature opens dashed and cannot be "finished" until fixed.
49
- const { valid } = validateGeometry(feature, { phase: 'edit-start', mode: 'edit_vertex' }, { onGeometryChange: draw._geometryValidator })
52
+ const { valid } = validateGeometry(feature, { phase: 'edit-start', mode }, { onGeometryChange: draw._geometryValidator })
50
53
  dispatch({ type: 'SET_GEOMETRY_VALID', payload: valid })
51
54
  draw.setInvalid?.(!valid)
52
55
 
@@ -74,6 +74,22 @@ describe('editFeature', () => {
74
74
  expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_line' })
75
75
  })
76
76
 
77
+ // A Point has no ring of vertices — editing one goes through edit_point (relocate the
78
+ // single coordinate), not edit_vertex, across every mode-name site this function touches.
79
+ test('uses edit_point mode for a point feature', () => {
80
+ const { context, dispatch, eventBus, draw } = makeContext()
81
+ draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Point', coordinates: [0, 0] } })
82
+
83
+ editFeature(context, 'f1')
84
+
85
+ expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_point' })
86
+ expect(draw.changeMode).toHaveBeenCalledWith('edit_point', expect.objectContaining({ featureId: 'f1' }))
87
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'edit_point' })
88
+ // Point geometry is always valid (validation/rules.js has no Point branch) — the gate
89
+ // opens immediately, unlike a self-intersecting/zero-area polygon.
90
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_GEOMETRY_VALID', payload: true })
91
+ })
92
+
77
93
  test('stores a per-call onGeometryChange validator, overriding the plugin-level one', () => {
78
94
  const pluginOnGeometryChange = jest.fn()
79
95
  const onGeometryChange = jest.fn()
@@ -1,41 +1,3 @@
1
- import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
1
+ import { createNewShape } from './createNewShape.js'
2
2
 
3
- // Near-identical to newPolygon.js, differing only in the 'draw_line'/'draw_polygon'
4
- // mode string — kept as two small, explicit entry points rather than one
5
- // mode-parameterised factory. Revisit if a third geometry type is ever added.
6
- export const newLine = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
7
- const { dispatch } = pluginState
8
- const { draw } = mapProvider
9
- const { eventBus } = services
10
-
11
- if (!draw) {
12
- return
13
- }
14
-
15
- eventBus.emit('draw:started', { mode: 'draw_line' })
16
-
17
- const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
18
- draw.setSnapLayers(snapLayers)
19
- dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
20
-
21
- const { stroke, fill, strokeWidth, properties: customProperties, onGeometryChange, ...modeOptions } = options
22
- // Per-call callback overrides the plugin-level one; events.js reads this on every commit.
23
- draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
24
- const properties = {
25
- ...customProperties,
26
- ...flattenStyleProperties({ stroke, fill, strokeWidth })
27
- }
28
-
29
- draw.changeMode('draw_line', {
30
- container: appState.layoutRefs.viewportRef.current,
31
- addVertexButtonId: `${appConfig.id}-draw-add-point`,
32
- interfaceType: appState.interfaceType,
33
- crossHair: mapState.crossHair,
34
- getSnapEnabled: () => draw.isSnapEnabled(),
35
- featureId,
36
- ...modeOptions,
37
- properties
38
- })
39
-
40
- dispatch({ type: 'SET_MODE', payload: 'draw_line' })
41
- }
3
+ export const newLine = createNewShape('draw_line')
@@ -0,0 +1,42 @@
1
+ import { SYMBOL_KEYS } from '../utils/symbolKeys.js'
2
+
3
+ export const newPoint = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
4
+ const { dispatch } = pluginState
5
+ const { draw } = mapProvider
6
+ const { eventBus } = services
7
+
8
+ if (!draw) {
9
+ return
10
+ }
11
+
12
+ eventBus.emit('draw:started', { mode: 'draw_point' })
13
+
14
+ const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
15
+ draw.setSnapLayers(snapLayers)
16
+ dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
17
+
18
+ const { properties: customProperties, onGeometryChange, ...modeOptions } = options
19
+ // Per-call callback overrides the plugin-level one; events.js reads this on every commit.
20
+ draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
21
+
22
+ const symbolStyle = {}
23
+ SYMBOL_KEYS.forEach((key) => {
24
+ if (modeOptions[key] !== undefined) {
25
+ symbolStyle[key] = modeOptions[key]
26
+ delete modeOptions[key]
27
+ }
28
+ })
29
+
30
+ draw.changeMode('draw_point', {
31
+ container: appState.layoutRefs.viewportRef.current,
32
+ addVertexButtonId: `${appConfig.id}-draw-add-point`,
33
+ interfaceType: appState.interfaceType,
34
+ crossHair: mapState.crossHair,
35
+ getSnapEnabled: () => draw.isSnapEnabled(),
36
+ featureId,
37
+ ...modeOptions,
38
+ properties: { ...customProperties, ...symbolStyle }
39
+ })
40
+
41
+ dispatch({ type: 'SET_MODE', payload: 'draw_point' })
42
+ }
@@ -0,0 +1,117 @@
1
+ import { newPoint } from './newPoint.js'
2
+
3
+ const makeContext = (overrides = {}) => {
4
+ const dispatch = jest.fn()
5
+ const eventBus = { emit: jest.fn() }
6
+ const draw = { setSnapLayers: jest.fn(), changeMode: jest.fn(), isSnapEnabled: jest.fn(() => false) }
7
+ const context = {
8
+ appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
9
+ appConfig: { id: 'app' },
10
+ pluginConfig: { snapLayers: ['pc-layer'] },
11
+ pluginState: { dispatch },
12
+ mapState: { crossHair: true },
13
+ mapProvider: { draw },
14
+ services: { eventBus },
15
+ ...overrides
16
+ }
17
+ return { context, dispatch, eventBus, draw }
18
+ }
19
+
20
+ beforeEach(() => jest.clearAllMocks())
21
+
22
+ describe('newPoint', () => {
23
+ test('does nothing when there is no draw instance', () => {
24
+ const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
25
+ newPoint(context, 'f1')
26
+ expect(eventBus.emit).not.toHaveBeenCalled()
27
+ })
28
+
29
+ test('starts point placement, splitting symbol-config keys out of properties', () => {
30
+ const { context, dispatch, eventBus, draw } = makeContext()
31
+
32
+ newPoint(context, 'f1', {
33
+ symbol: 'pin',
34
+ symbolBackgroundColor: { outdoor: '#1d70b8' },
35
+ properties: { name: 'x' },
36
+ extra: 'opt'
37
+ })
38
+
39
+ expect(eventBus.emit).toHaveBeenCalledWith('draw:started', { mode: 'draw_point' })
40
+
41
+ expect(draw.changeMode).toHaveBeenCalledWith('draw_point', expect.objectContaining({
42
+ container: 'viewport',
43
+ addVertexButtonId: 'app-draw-add-point',
44
+ interfaceType: 'mouse',
45
+ crossHair: true,
46
+ featureId: 'f1',
47
+ extra: 'opt',
48
+ properties: { name: 'x', symbol: 'pin', symbolBackgroundColor: { outdoor: '#1d70b8' } }
49
+ }))
50
+
51
+ const opts = draw.changeMode.mock.calls[0][1]
52
+ expect(opts.getSnapEnabled()).toBe(false)
53
+ expect(draw.isSnapEnabled).toHaveBeenCalled()
54
+ // Symbol keys are pulled off modeOptions entirely, not just copied into properties.
55
+ expect(opts).not.toHaveProperty('symbol')
56
+ expect(opts).not.toHaveProperty('symbolBackgroundColor')
57
+
58
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'draw_point' })
59
+ })
60
+
61
+ test('carries every recognised symbol-config key through to properties', () => {
62
+ const { context, draw } = makeContext()
63
+ const symbolStyle = {
64
+ symbol: 'pin',
65
+ symbolSvgContent: '<path/>',
66
+ symbolBackgroundColor: '#fff',
67
+ symbolForegroundColor: '#000',
68
+ symbolHaloWidth: 2,
69
+ symbolGraphic: 'g',
70
+ symbolViewBox: '0 0 10 10',
71
+ symbolAnchor: [0.5, 0.9]
72
+ }
73
+ newPoint(context, 'f1', symbolStyle)
74
+ expect(draw.changeMode.mock.calls[0][1].properties).toEqual(symbolStyle)
75
+ })
76
+
77
+ test('omits symbol-config keys entirely when none are supplied', () => {
78
+ const { context, draw } = makeContext()
79
+ newPoint(context, 'f1')
80
+ expect(draw.changeMode.mock.calls[0][1].properties).toEqual({})
81
+ })
82
+
83
+ test('prefers explicit option snapLayers and flags them', () => {
84
+ const { context, draw, dispatch } = makeContext()
85
+ newPoint(context, 'f1', { snapLayers: ['opt'] })
86
+ expect(draw.setSnapLayers).toHaveBeenCalledWith(['opt'])
87
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
88
+ })
89
+
90
+ test('falls back to the plugin config snapLayers', () => {
91
+ const { context, draw } = makeContext()
92
+ newPoint(context, 'f1')
93
+ expect(draw.setSnapLayers).toHaveBeenCalledWith(['pc-layer'])
94
+ })
95
+
96
+ test('falls back to null when neither option nor config is set', () => {
97
+ const { context, draw, dispatch } = makeContext({ pluginConfig: {} })
98
+ newPoint(context, 'f1')
99
+ expect(draw.setSnapLayers).toHaveBeenCalledWith(null)
100
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: false })
101
+ })
102
+
103
+ test('stores a per-call onGeometryChange validator without leaking it into mode options', () => {
104
+ const { context, draw } = makeContext()
105
+ const onGeometryChange = jest.fn()
106
+ newPoint(context, 'f1', { onGeometryChange })
107
+ expect(draw._geometryValidator).toBe(onGeometryChange)
108
+ expect(draw.changeMode.mock.calls[0][1]).not.toHaveProperty('onGeometryChange')
109
+ })
110
+
111
+ test('falls back to the plugin-level onGeometryChange validator', () => {
112
+ const pluginOnGeometryChange = jest.fn()
113
+ const { context, draw } = makeContext({ pluginConfig: { onGeometryChange: pluginOnGeometryChange } })
114
+ newPoint(context, 'f1')
115
+ expect(draw._geometryValidator).toBe(pluginOnGeometryChange)
116
+ })
117
+ })
@@ -1,41 +1,3 @@
1
- import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
1
+ import { createNewShape } from './createNewShape.js'
2
2
 
3
- // Near-identical to newLine.js, differing only in the 'draw_polygon'/'draw_line'
4
- // mode string — kept as two small, explicit entry points rather than one
5
- // mode-parameterised factory. Revisit if a third geometry type is ever added.
6
- export const newPolygon = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
7
- const { dispatch } = pluginState
8
- const { draw } = mapProvider
9
- const { eventBus } = services
10
-
11
- if (!draw) {
12
- return
13
- }
14
-
15
- eventBus.emit('draw:started', { mode: 'draw_polygon' })
16
-
17
- const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
18
- draw.setSnapLayers(snapLayers)
19
- dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
20
-
21
- const { stroke, fill, strokeWidth, properties: customProperties, onGeometryChange, ...modeOptions } = options
22
- // Per-call callback overrides the plugin-level one; events.js reads this on every commit.
23
- draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
24
- const properties = {
25
- ...customProperties,
26
- ...flattenStyleProperties({ stroke, fill, strokeWidth })
27
- }
28
-
29
- draw.changeMode('draw_polygon', {
30
- container: appState.layoutRefs.viewportRef.current,
31
- addVertexButtonId: `${appConfig.id}-draw-add-point`,
32
- interfaceType: appState.interfaceType,
33
- crossHair: mapState.crossHair,
34
- getSnapEnabled: () => draw.isSnapEnabled(),
35
- featureId,
36
- ...modeOptions,
37
- properties
38
- })
39
-
40
- dispatch({ type: 'SET_MODE', payload: 'draw_polygon' })
41
- }
3
+ export const newPolygon = createNewShape('draw_polygon')
@@ -0,0 +1,43 @@
1
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
2
+ import { logger } from '../../../../src/services/logger.js'
3
+
4
+ const STYLE_KEYS = new Set(['stroke', 'fill', 'strokeWidth'])
5
+
6
+ // A partial patch — unlike addFeature.js building a whole new feature from scratch,
7
+ // flattenStyleProperties would otherwise write stroke/fill/strokeWidth as explicit
8
+ // `undefined` for any of the three the caller didn't mention, blanking out whatever that
9
+ // feature already had.
10
+ const definedOnly = (properties) => {
11
+ const result = {}
12
+ for (const [key, value] of Object.entries(properties)) {
13
+ if (value !== undefined) {
14
+ result[key] = value
15
+ }
16
+ }
17
+ return result
18
+ }
19
+
20
+ export const setStyle = ({ mapProvider, services }, featureId, styleChanges = {}) => {
21
+ const { draw } = mapProvider
22
+ const { eventBus } = services
23
+
24
+ if (!draw) {
25
+ return false
26
+ }
27
+
28
+ if (!draw.get(featureId)) {
29
+ logger.warn('setStyle: no feature found for id', featureId)
30
+ return false
31
+ }
32
+
33
+ const styleProps = {}
34
+ const symbolProps = {}
35
+ for (const [key, value] of Object.entries(styleChanges)) {
36
+ (STYLE_KEYS.has(key) ? styleProps : symbolProps)[key] = value
37
+ }
38
+ const properties = definedOnly({ ...flattenStyleProperties(styleProps), ...symbolProps })
39
+
40
+ draw.setStyle(featureId, properties)
41
+ eventBus.emit('draw:stylechange', { featureId, properties })
42
+ return true
43
+ }
@@ -0,0 +1,75 @@
1
+ import { setStyle } from './setStyle.js'
2
+ import { logger } from '../../../../src/services/logger.js'
3
+
4
+ jest.mock('../../../../src/services/logger.js', () => ({
5
+ logger: { warn: jest.fn(), error: jest.fn() }
6
+ }))
7
+
8
+ const setup = (draw) => {
9
+ const eventBus = { emit: jest.fn() }
10
+ return { context: { mapProvider: { draw }, services: { eventBus } }, eventBus }
11
+ }
12
+
13
+ beforeEach(() => jest.clearAllMocks())
14
+
15
+ describe('setStyle', () => {
16
+ test('does nothing when there is no draw instance', () => {
17
+ const { context, eventBus } = setup(undefined)
18
+ expect(setStyle(context, 'a', { stroke: 'red' })).toBe(false)
19
+ expect(eventBus.emit).not.toHaveBeenCalled()
20
+ })
21
+
22
+ test('warns and does nothing for an id with no existing feature', () => {
23
+ const draw = { get: jest.fn(() => null), setStyle: jest.fn() }
24
+ const { context, eventBus } = setup(draw)
25
+ expect(setStyle(context, 'missing', { stroke: 'red' })).toBe(false)
26
+ expect(draw.setStyle).not.toHaveBeenCalled()
27
+ expect(eventBus.emit).not.toHaveBeenCalled()
28
+ expect(logger.warn).toHaveBeenCalledWith('setStyle: no feature found for id', 'missing')
29
+ })
30
+
31
+ test('flattens stroke/fill/strokeWidth and forwards symbol keys as-is', () => {
32
+ const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
33
+ const { context, eventBus } = setup(draw)
34
+
35
+ const result = setStyle(context, 'a', {
36
+ stroke: { outdoor: '#1d70b8', dark: '#4c9ed9' },
37
+ symbol: 'square',
38
+ symbolBackgroundColor: { outdoor: '#ca3535', dark: '#ffffff' }
39
+ })
40
+
41
+ const expected = {
42
+ stroke: '#1d70b8',
43
+ strokeOutdoor: '#1d70b8',
44
+ strokeDark: '#4c9ed9',
45
+ symbol: 'square',
46
+ symbolBackgroundColor: { outdoor: '#ca3535', dark: '#ffffff' }
47
+ }
48
+ expect(draw.setStyle).toHaveBeenCalledWith('a', expected)
49
+ expect(eventBus.emit).toHaveBeenCalledWith('draw:stylechange', { featureId: 'a', properties: expected })
50
+ expect(result).toBe(true)
51
+ })
52
+
53
+ // A partial patch — omitting fill/strokeWidth must leave whatever the feature already has
54
+ // alone, not blank them out to undefined.
55
+ test('only includes the style keys actually supplied, dropping the unset ones', () => {
56
+ const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
57
+ const { context } = setup(draw)
58
+ setStyle(context, 'a', { stroke: '#e6c700' })
59
+ expect(draw.setStyle).toHaveBeenCalledWith('a', { stroke: '#e6c700' })
60
+ })
61
+
62
+ test('drops keys explicitly passed as undefined, from both style and symbol props', () => {
63
+ const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
64
+ const { context } = setup(draw)
65
+ setStyle(context, 'a', { stroke: 'red', fill: undefined, symbol: 'pin', symbolBackgroundColor: undefined })
66
+ expect(draw.setStyle).toHaveBeenCalledWith('a', { stroke: 'red', symbol: 'pin' })
67
+ })
68
+
69
+ test('defaults styleChanges to {} and still resolves cleanly', () => {
70
+ const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
71
+ const { context } = setup(draw)
72
+ setStyle(context, 'a')
73
+ expect(draw.setStyle).toHaveBeenCalledWith('a', {})
74
+ })
75
+ })