@defra/interactive-map 0.0.39-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 (387) 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/api.md +4 -4
  9. package/docs/assets/images/slot-map.svg +61 -262
  10. package/docs/examples/draw-tools.mdx +3 -3
  11. package/docs/plugins/draw.md +80 -17
  12. package/docs/plugins/search.md +1 -1
  13. package/govuk-prototype-kit.config.json +2 -0
  14. package/jest.setup.js +5 -0
  15. package/package.json +9 -1
  16. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  17. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  18. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  19. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  20. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  21. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  22. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  23. package/plugins/datasets/dist/css/index.css +1 -1
  24. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  25. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  26. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  27. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  28. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  29. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  30. package/plugins/datasets/dist/umd/index.js +1 -1
  31. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +2 -3
  32. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
  33. package/plugins/datasets/src/adapters/esri/registry/esriDataset.js +1 -1
  34. package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
  35. package/plugins/datasets/src/adapters/loadLayerAdapter.js +1 -1
  36. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
  37. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +1 -3
  38. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +17 -3
  39. package/plugins/datasets/src/api/setOpacity.test.js +9 -0
  40. package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
  41. package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
  42. package/plugins/datasets/src/datasets.scss +0 -1
  43. package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
  44. package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
  45. package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
  46. package/plugins/datasets/src/index.test.js +36 -0
  47. package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
  48. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
  49. package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -5
  50. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
  51. package/plugins/datasets/src/manifest.js +0 -38
  52. package/plugins/datasets/src/manifest.test.js +49 -0
  53. package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
  54. package/plugins/datasets/src/reducers/pluginState.js +3 -16
  55. package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
  56. package/plugins/datasets/src/registry/dataset.js +15 -4
  57. package/plugins/datasets/src/registry/dataset.test.js +61 -0
  58. package/plugins/datasets/src/registry/datasetRegistry.js +12 -9
  59. package/plugins/datasets/src/registry/datasetRegistry.test.js +17 -5
  60. package/plugins/datasets/src/registry/globalDataset.js +0 -3
  61. package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
  62. package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
  63. package/plugins/datasets/src/utils/bbox.test.js +125 -0
  64. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
  65. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
  66. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
  67. package/plugins/draw/dist/esm/index.js +1 -1
  68. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
  69. package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
  70. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  71. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  72. package/plugins/draw/dist/umd/index.js +1 -1
  73. package/plugins/draw/src/DrawInit.jsx +12 -4
  74. package/plugins/draw/src/DrawInit.test.jsx +51 -10
  75. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +92 -12
  76. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
  77. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
  78. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
  79. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
  81. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
  82. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +1 -1
  83. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +1 -1
  84. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
  90. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
  91. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
  92. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
  93. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
  94. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
  95. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
  96. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
  97. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
  98. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
  99. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +5 -28
  100. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
  101. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
  102. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
  103. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
  104. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
  105. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
  106. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -54
  107. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
  108. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
  109. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +11 -4
  110. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
  111. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
  112. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
  113. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
  114. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
  115. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
  116. package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
  117. package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
  118. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
  119. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
  120. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
  121. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
  122. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
  123. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
  124. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
  125. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
  126. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
  127. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
  128. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
  129. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
  130. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
  131. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
  132. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
  133. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
  134. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
  135. package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
  136. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
  137. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +1 -0
  138. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +27 -1
  139. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +24 -4
  140. package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +47 -3
  141. package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +1 -1
  142. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +32 -1
  143. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
  144. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +34 -1
  145. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
  146. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
  147. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +12 -5
  148. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +26 -3
  149. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
  150. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
  151. package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
  152. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
  153. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
  154. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
  155. package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
  156. package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
  157. package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
  158. package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
  159. package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
  160. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
  161. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
  162. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
  163. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
  164. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
  165. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
  166. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
  167. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
  168. package/plugins/draw/src/api/addFeature.js +16 -1
  169. package/plugins/draw/src/api/addFeature.test.js +62 -5
  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/newPoint.js +42 -0
  173. package/plugins/draw/src/api/newPoint.test.js +117 -0
  174. package/plugins/draw/src/api/setStyle.js +43 -0
  175. package/plugins/draw/src/api/setStyle.test.js +75 -0
  176. package/plugins/draw/src/events.js +21 -14
  177. package/plugins/draw/src/events.test.js +15 -1
  178. package/plugins/draw/src/manifest.js +18 -6
  179. package/plugins/draw/src/manifest.test.js +32 -1
  180. package/plugins/draw/src/reducer.test.js +7 -0
  181. package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
  182. package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
  183. package/plugins/draw/src/utils/symbolKeys.js +5 -0
  184. package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
  185. package/plugins/draw/src/validation/liveStroke.test.js +64 -1
  186. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  187. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  188. package/plugins/interact/dist/umd/index.js +1 -1
  189. package/plugins/interact/src/hooks/useCrossHairVisibility.js +12 -4
  190. package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +88 -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/utils/featureQueries.js +18 -0
  194. package/plugins/interact/src/utils/featureQueries.test.js +15 -0
  195. package/plugins/interact/src/utils/spatial.js +9 -3
  196. package/plugins/interact/src/utils/spatial.test.js +12 -1
  197. package/plugins/map-key/dist/css/index.css +62 -0
  198. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
  199. package/plugins/map-key/dist/esm/index.js +1 -0
  200. package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
  201. package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
  202. package/plugins/map-key/dist/umd/index.js +2 -0
  203. package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
  204. package/plugins/map-key/package.json +28 -0
  205. package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
  206. package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
  207. package/plugins/map-key/src/components/Key/Key.jsx +41 -0
  208. package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
  209. package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
  210. package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
  211. package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
  212. package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
  213. package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
  214. package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
  215. package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
  216. package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
  217. package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
  218. package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
  219. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
  220. package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
  221. package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
  222. package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
  223. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
  224. package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
  225. package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
  226. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
  227. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
  228. package/plugins/map-key/src/index.js +13 -0
  229. package/plugins/map-key/src/index.test.js +31 -0
  230. package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
  231. package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
  232. package/plugins/map-key/src/manifest.js +30 -0
  233. package/plugins/map-key/src/manifest.test.js +63 -0
  234. package/plugins/map-key/src/mapKey.scss +4 -0
  235. package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
  236. package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
  237. package/plugins/map-key/src/registry/index.js +3 -0
  238. package/plugins/map-key/src/registry/index.test.js +15 -0
  239. package/plugins/menu/dist/css/index.css +80 -0
  240. package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
  241. package/plugins/menu/dist/esm/index.js +1 -0
  242. package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
  243. package/plugins/menu/dist/umd/index.js +2 -0
  244. package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
  245. package/plugins/menu/package.json +28 -0
  246. package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
  247. package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
  248. package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
  249. package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
  250. package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
  251. package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
  252. package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
  253. package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
  254. package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
  255. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
  256. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
  257. package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
  258. package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
  259. package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
  260. package/plugins/menu/src/index.js +13 -0
  261. package/plugins/menu/src/index.test.js +31 -0
  262. package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
  263. package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
  264. package/plugins/menu/src/initialise/createMenu.js +10 -0
  265. package/plugins/menu/src/initialise/createMenu.test.js +37 -0
  266. package/plugins/menu/src/manifest.js +35 -0
  267. package/plugins/menu/src/manifest.test.js +63 -0
  268. package/plugins/menu/src/menu.scss +4 -0
  269. package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
  270. package/plugins/menu/src/reducers/pluginState.js +35 -0
  271. package/plugins/menu/src/reducers/pluginState.test.js +57 -0
  272. package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
  273. package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
  274. package/plugins/menu/src/registry/index.js +3 -0
  275. package/plugins/menu/src/registry/index.test.js +15 -0
  276. package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
  277. package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
  278. package/plugins/search/dist/css/index.css +1 -1
  279. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  280. package/plugins/search/dist/esm/index.js +1 -1
  281. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  282. package/plugins/search/dist/umd/index.js +1 -1
  283. package/plugins/search/src/Search.jsx +7 -5
  284. package/plugins/search/src/components/Form/Form.module.scss +9 -20
  285. package/plugins/search/src/index.js +0 -1
  286. package/plugins/search/src/index.test.js +2 -4
  287. package/plugins/search/src/manifest.js +1 -1
  288. package/plugins/search/src/search.scss +17 -16
  289. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  290. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  291. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  292. package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
  293. package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
  294. package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
  295. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
  296. package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
  297. package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
  298. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  299. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  300. package/providers/maplibre/dist/umd/index.js +1 -1
  301. package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
  302. package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
  303. package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
  304. package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
  305. package/providers/maplibre/src/utils/hoverCursor.js +30 -14
  306. package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
  307. package/providers/maplibre/src/utils/patternImages.js +1 -1
  308. package/providers/maplibre/src/utils/symbolImages.js +41 -3
  309. package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
  310. package/rollup.esm.mjs +11 -1
  311. package/sonar-project.properties +7 -1
  312. package/src/App/components/Actions/Actions.test.jsx +47 -0
  313. package/src/App/components/Attributions/Attributions.module.scss +10 -0
  314. package/src/App/components/Hints/Hints.jsx +7 -2
  315. package/src/App/components/Hints/Hints.test.jsx +13 -0
  316. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +2 -3
  317. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +3 -6
  318. package/src/App/components/{MoveControl/MoveControl.jsx → MoveControls/MoveControls.jsx} +10 -10
  319. package/src/App/components/{MoveControl/MoveControl.module.scss → MoveControls/MoveControls.module.scss} +60 -29
  320. package/src/App/components/{MoveControl/MoveControl.test.jsx → MoveControls/MoveControls.test.jsx} +48 -48
  321. package/src/App/components/Panel/Panel.jsx +18 -3
  322. package/src/App/components/Panel/Panel.module.scss +18 -8
  323. package/src/App/components/Panel/Panel.test.jsx +34 -0
  324. package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
  325. package/src/App/components/Viewport/Viewport.jsx +9 -1
  326. package/src/App/components/Viewport/Viewport.test.jsx +11 -0
  327. package/src/App/hooks/useFeatureItems.js +16 -7
  328. package/src/App/hooks/useFeatureItems.test.js +57 -0
  329. package/src/App/hooks/useLayoutMeasurements.js +95 -84
  330. package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
  331. package/src/App/initialiseApp.js +11 -8
  332. package/src/App/initialiseApp.test.js +15 -3
  333. package/src/App/layout/Layout.jsx +12 -11
  334. package/src/App/layout/Layout.test.jsx +2 -0
  335. package/src/App/layout/layout.module.scss +31 -17
  336. package/src/App/registry/keyboardShortcutRegistry.js +25 -13
  337. package/src/App/registry/keyboardShortcutRegistry.test.js +49 -32
  338. package/src/App/registry/pluginRegistry.js +2 -3
  339. package/src/App/registry/pluginRegistry.test.js +16 -3
  340. package/src/App/renderer/HtmlElementHost.jsx +18 -5
  341. package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
  342. package/src/App/renderer/mapButtons.js +28 -12
  343. package/src/App/renderer/mapButtons.test.js +17 -4
  344. package/src/App/renderer/mapControls.js +3 -1
  345. package/src/App/renderer/mapControls.test.js +16 -0
  346. package/src/App/renderer/mapPanels.js +44 -9
  347. package/src/App/renderer/mapPanels.test.js +51 -2
  348. package/src/App/renderer/orderItems.js +28 -0
  349. package/src/App/renderer/orderItems.test.js +50 -0
  350. package/src/App/renderer/slotAggregator.js +2 -18
  351. package/src/App/renderer/slotHelpers.js +3 -1
  352. package/src/App/renderer/slotHelpers.test.js +5 -0
  353. package/src/App/renderer/slots.js +3 -0
  354. package/src/App/store/AppProvider.jsx +4 -2
  355. package/src/App/store/appDispatchMiddleware.js +1 -1
  356. package/src/App/store/appDispatchMiddleware.test.js +8 -0
  357. package/src/InteractiveMap/InteractiveMap.js +8 -2
  358. package/src/InteractiveMap/InteractiveMap.test.js +20 -3
  359. package/src/config/appConfig.js +17 -15
  360. package/src/config/appConfig.test.js +22 -22
  361. package/src/config/defaults.js +3 -1
  362. package/src/config/events.js +2 -0
  363. package/src/config/mergeConfig.js +7 -0
  364. package/src/config/mergeConfig.test.js +20 -0
  365. package/src/scss/main.scss +1 -1
  366. package/src/scss/settings/_colors.scss +1 -0
  367. package/src/scss/settings/_dimensions.scss +2 -3
  368. package/src/scss/settings/_transition.scss +1 -1
  369. package/src/services/eventBus.js +44 -0
  370. package/src/services/eventBus.test.js +83 -0
  371. package/src/services/symbolRegistry.js +1 -1
  372. package/src/test-utils.js +5 -0
  373. package/src/types.js +25 -7
  374. package/webpack.umd.mjs +2 -0
  375. package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
  376. package/plugins/datasets/src/components/Key/Key.jsx +0 -50
  377. package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
  378. package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
  379. package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
  380. package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
  381. package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
  382. package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
  383. package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
  384. package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
  385. /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
  386. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
  387. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  datasetsWithGroups, expectedDatasetsMenuConfigWithGroups
4
4
  } from './__data__/demoDatasets.js'
5
5
 
6
- import { datasetsToMenu } from './datasetsToMenu'
6
+ import { datasetsToMenu, addDatasetToMenu, removeDatasetsFromMenu } from './datasetsToMenu'
7
7
 
8
8
  describe('datasetsToMenu', () => {
9
9
  describe('Missing or empty datasets', () => {
@@ -80,5 +80,58 @@ describe('datasetsToMenu', () => {
80
80
  const result = datasetsToMenu({ datasets: [sublayerDataset(true, false)] })
81
81
  expect(result).toEqual([{ id: 'test', items: [{ id: 'test', label: 'Test' }], type: 'checkbox', visibleWhen: true }])
82
82
  })
83
+
84
+ it('skips a parent with showInMenu:false when no sublayers are visible', () => {
85
+ // Contrived: a getter that returns true on the first access (outer filter's `some`)
86
+ // and false on subsequent accesses (inner filter), hitting the early-return guard.
87
+ let calls = 0
88
+ const dataset = {
89
+ id: 'tricky',
90
+ label: 'Tricky',
91
+ showInMenu: false,
92
+ sublayers: [{ id: 'tricky-a', label: 'A', get showInMenu () { return calls++ === 0 } }]
93
+ }
94
+ const result = datasetsToMenu({ datasets: [dataset] })
95
+ expect(result).toEqual([])
96
+ })
97
+ })
98
+ })
99
+
100
+ describe('addDatasetToMenu', () => {
101
+ it('appends to an existing menu group with matching groupLabel', () => {
102
+ const state = {
103
+ menu: [{ id: 'Layers', groupLabel: 'Layers', type: 'checkbox', visibleWhen: true, items: [{ id: 'roads', label: 'Roads' }] }]
104
+ }
105
+ const dataset = { id: 'rivers', label: 'Rivers', showInMenu: true, groupLabel: 'Layers' }
106
+ const result = addDatasetToMenu(state, dataset)
107
+ expect(result[0].items).toHaveLength(2)
108
+ expect(result[0].items[1]).toEqual({ id: 'rivers', label: 'Rivers' })
109
+ })
110
+
111
+ it('pushes a new group when no matching groupLabel exists', () => {
112
+ const state = { menu: [] }
113
+ const dataset = { id: 'parks', label: 'Parks', showInMenu: true }
114
+ const result = addDatasetToMenu(state, dataset)
115
+ expect(result).toHaveLength(1)
116
+ expect(result[0].items[0].id).toBe('parks')
117
+ })
118
+ })
119
+
120
+ describe('removeDatasetsFromMenu', () => {
121
+ it('keeps groups that still have items after filtering', () => {
122
+ const menu = [
123
+ { id: 'Layers', items: [{ id: 'roads' }, { id: 'rivers' }] }
124
+ ]
125
+ const result = removeDatasetsFromMenu(menu, ['roads'])
126
+ expect(result).toHaveLength(1)
127
+ expect(result[0].items).toEqual([{ id: 'rivers' }])
128
+ })
129
+
130
+ it('removes groups that are left with no items', () => {
131
+ const menu = [
132
+ { id: 'Layers', items: [{ id: 'roads' }] }
133
+ ]
134
+ const result = removeDatasetsFromMenu(menu, ['roads'])
135
+ expect(result).toHaveLength(0)
83
136
  })
84
137
  })
@@ -1,5 +1,4 @@
1
1
  import { addDatasetToMenu, removeDatasetsFromMenu } from './datasetsToMenu.js'
2
- import { buildMenuState } from './menuStateReducer.js'
3
2
  import { mappedDatasetsReducer } from './mappedDatasetsReducer.js'
4
3
  import { logger } from '../../../../src/services/logger.js'
5
4
 
@@ -19,8 +18,7 @@ const initialState = {
19
18
  mappedDatasets: {},
20
19
  orderedDatasets: [],
21
20
  actionsArray: [],
22
- menu: [],
23
- menuState: {}
21
+ menu: []
24
22
  }
25
23
 
26
24
  const validateDatasetExists = (state, datasetId, prefix, suffix = 'not found') => {
@@ -46,21 +44,11 @@ const setGlobalState = (state, payload) => {
46
44
  })
47
45
  }
48
46
 
49
- const updateMenuState = (state, payload) => {
50
- return addAction('applyGlobalVisibility', [], {
51
- ...state,
52
- menuState: { ...state.menuState, ...payload }
53
- })
54
- }
55
-
56
47
  const setMenu = (state, payload) => {
57
48
  const { menu } = payload
58
- // build the initial menuState for radios from the menu
59
- const menuState = buildMenuState(menu)
60
49
  return {
61
50
  ...state,
62
- menu,
63
- menuState
51
+ menu
64
52
  }
65
53
  }
66
54
 
@@ -223,8 +211,7 @@ const actions = {
223
211
  SHOW_FEATURES: showFeatures,
224
212
  REMOVE_ADAPTER_ACTIONS: removeAdapterActions,
225
213
  SET_GLOBAL_STATE: setGlobalState,
226
- SET_MENU: setMenu,
227
- UPDATE_MENU_STATE: updateMenuState
214
+ SET_MENU: setMenu
228
215
  }
229
216
 
230
217
  export {
@@ -172,6 +172,29 @@ describe('SET_DATASET_VISIBILITY', () => {
172
172
  expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("'unknown'"))
173
173
  expect(result).toBe(state)
174
174
  })
175
+
176
+ it('returns unchanged state when visibility already matches', () => {
177
+ const state = makeStateWithDataset('roads')
178
+ const result = actions.SET_DATASET_VISIBILITY(state, { datasetId: 'roads', visible: true })
179
+ expect(result).toBe(state)
180
+ })
181
+ })
182
+
183
+ // ─── SET_MENU ────────────────────────────────────────────────────────────────
184
+
185
+ describe('SET_MENU', () => {
186
+ it('sets menu from payload', () => {
187
+ const state = makeState()
188
+ const menu = [{ id: 'layers', items: [] }]
189
+ const result = actions.SET_MENU(state, { menu })
190
+ expect(result.menu).toBe(menu)
191
+ })
192
+
193
+ it('does not add to actionsArray', () => {
194
+ const state = makeState()
195
+ const result = actions.SET_MENU(state, { menu: [] })
196
+ expect(result.actionsArray).toEqual([])
197
+ })
175
198
  })
176
199
 
177
200
  // ─── SET_GLOBAL_VISIBILITY ────────────────────────────────────────────────────
@@ -67,12 +67,11 @@ export class Dataset {
67
67
  get hasHiddenFeatures () { return Boolean(this.hiddenFeatures?.length > 0 || this.parent?.hasHiddenFeatures) }
68
68
 
69
69
  get filter () {
70
- return null // TODO - implement filter construction for esri and openLayers adapters
70
+ return null
71
71
  }
72
72
 
73
73
  // Returns true if either the parent (if it has one) or global visibility is hidden , otherwise returns true.
74
74
  // This is used to determine whether to show a tooltip
75
- // TODO: also work out how to convey datasets hidden by zoom filter.
76
75
  get isHiddenByInheritance () {
77
76
  if (this.isSublayer) {
78
77
  return !this.parent?.visible
@@ -109,7 +108,7 @@ export class Dataset {
109
108
  }
110
109
 
111
110
  get keyVisibility () {
112
- return this.visibility === 'visible' && this.showInKey
111
+ return this.showInKey && this.visibility === 'visible'
113
112
  }
114
113
 
115
114
  get symbolAnchor () {
@@ -165,7 +164,7 @@ export class Dataset {
165
164
  // any child with a parent opacity only would be multiplied by itself
166
165
  return {
167
166
  ...parentStyle,
168
- opacity: undefined,
167
+ opacity: undefined, // NOSONAR - we need to set this to undefined so that the opacity getter can calculate the correct opacity
169
168
  ...this._datasetDefinition.style,
170
169
  symbolDescription: this.symbolDescription
171
170
  }
@@ -202,4 +201,16 @@ export class Dataset {
202
201
  }
203
202
  return styleSymbols
204
203
  }
204
+
205
+ get keyDefinition () {
206
+ return {
207
+ id: this.id,
208
+ type: 'dataset',
209
+ label: this.label,
210
+ hasSymbol: this.hasSymbol,
211
+ hasPattern: this.hasPattern,
212
+ style: this.style,
213
+ symbolDescription: this.symbolDescription
214
+ }
215
+ }
205
216
  }
@@ -494,4 +494,65 @@ describe('Dataset class', () => {
494
494
  expect(new Dataset(childDef).sourceLayer).toBe('parent_layer')
495
495
  })
496
496
  })
497
+
498
+ describe('isHiddenByInheritance', () => {
499
+ it('returns true for a sublayer whose parent is not visible', () => {
500
+ const parentDef = { id: 'parent', visible: false, style: {} }
501
+ const childDef = { id: 'child', parentId: 'parent', visible: true, style: {} }
502
+ datasetRegistry.attach({ parent: parentDef, child: childDef })
503
+ expect(new Dataset(childDef).isHiddenByInheritance).toBe(true)
504
+ })
505
+
506
+ it('returns false for a sublayer whose parent is visible', () => {
507
+ const parentDef = { id: 'parent', visible: true, style: {} }
508
+ const childDef = { id: 'child', parentId: 'parent', visible: true, style: {} }
509
+ datasetRegistry.attach({ parent: parentDef, child: childDef })
510
+ expect(new Dataset(childDef).isHiddenByInheritance).toBe(false)
511
+ })
512
+
513
+ it('returns false for a top-level dataset when global visibility is true', () => {
514
+ const dataset = new Dataset({ id: 'top', visible: true, style: {} })
515
+ expect(dataset.isHiddenByInheritance).toBe(false)
516
+ })
517
+ })
518
+
519
+ describe('isLocallyVisible', () => {
520
+ it('returns the raw visible flag from the definition when not visible', () => {
521
+ const dataset = new Dataset({ visible: false })
522
+ expect(dataset.isLocallyVisible).toBe(false)
523
+ })
524
+
525
+ it('returns undefined when visible is not set and dataset is not visible', () => {
526
+ const dataset = new Dataset({})
527
+ expect(dataset.isLocallyVisible).toBeUndefined()
528
+ })
529
+
530
+ it('returns true when isVisible is truthy (subclass override scenario)', () => {
531
+ const dataset = new Dataset({ visible: false })
532
+ Object.defineProperty(dataset, 'isVisible', { get: () => true, configurable: true })
533
+ expect(dataset.isLocallyVisible).toBe(true)
534
+ })
535
+ })
536
+
537
+ describe('dynamicGeoJSON getter', () => {
538
+ it('returns a DynamicGeoJson with idProperty set', () => {
539
+ const dataset = new Dataset({
540
+ id: 'parcels',
541
+ dynamicGeoJSON: { url: 'http://example.com', idProperty: 'parcel_id' }
542
+ })
543
+ const dg = dataset.dynamicGeoJSON
544
+ expect(dg.source.promoteId).toBe('parcel_id')
545
+ expect(dg.source.generateId).toBeUndefined()
546
+ })
547
+
548
+ it('returns a DynamicGeoJson with generateId when no idProperty', () => {
549
+ const dataset = new Dataset({
550
+ id: 'parcels',
551
+ dynamicGeoJSON: { url: 'http://example.com' }
552
+ })
553
+ const dg = dataset.dynamicGeoJSON
554
+ expect(dg.source.generateId).toBe(true)
555
+ expect(dg.source.promoteId).toBeUndefined()
556
+ })
557
+ })
497
558
  })
@@ -83,7 +83,12 @@ const datasetRegistry = {
83
83
  }, { patternConfigs: [], symbolConfigs: [] })
84
84
  },
85
85
 
86
- invalidateKeyItems () {
86
+ _lastMenuState: {},
87
+ invalidateKeyItemsOnMenuStateChange (menuState) {
88
+ if (menuState === this._lastMenuState) {
89
+ return
90
+ }
91
+ this._lastMenuState = menuState
87
92
  this._lastKeyItemsDatasets = null
88
93
  },
89
94
  _lastKeyItems: {},
@@ -101,8 +106,9 @@ const datasetRegistry = {
101
106
  }
102
107
  const groupObject = {
103
108
  type: 'group',
109
+ id: groupLabel.toLowerCase().replaceAll(/\s+/g, '-'),
104
110
  groupLabel,
105
- datasets: []
111
+ keyDefinitions: []
106
112
  }
107
113
  groups.set(groupLabel, groupObject)
108
114
  _items.push(groupObject)
@@ -110,22 +116,22 @@ const datasetRegistry = {
110
116
  }
111
117
 
112
118
  this.forEach((dataset) => {
113
- if (!(dataset.showInKey && dataset.keyVisibility)) {
119
+ if (!dataset.keyVisibility) {
114
120
  return
115
121
  }
116
122
  const isGroup = dataset.hasSublayers || dataset.groupLabel
117
123
  if (!isGroup) {
118
- _items.push({ type: 'flat', dataset })
124
+ _items.push({ type: 'flat', id: dataset.id, keyDefinition: dataset.keyDefinition })
119
125
  return
120
126
  }
121
127
 
122
128
  const groupLabel = dataset.groupLabel || dataset.label
123
129
  const groupObject = getOrCreateGroup(groupLabel)
124
130
  if (!dataset.hasSublayers) {
125
- groupObject.datasets.push(dataset)
131
+ groupObject.keyDefinitions.push(dataset.keyDefinition)
126
132
  }
127
133
  })
128
- const items = _items.filter((item) => item.type === 'flat' || Boolean(item.datasets?.length))
134
+ const items = _items.filter((item) => item.type === 'flat' || Boolean(item.keyDefinitions?.length))
129
135
  const hasGroups = items.some(item => item.type === 'group')
130
136
  this._lastKeyItems = { items, hasGroups }
131
137
  return this._lastKeyItems
@@ -134,7 +140,4 @@ const datasetRegistry = {
134
140
 
135
141
  Object.defineProperty(datasetRegistry, 'datasets', { get: () => datasetRegistry._datasets })
136
142
 
137
- // TODO remove this global reference once development is finished
138
- window.datasetRegistry = datasetRegistry
139
-
140
143
  export { datasetRegistry }
@@ -148,6 +148,7 @@ describe('datasetRegistry', () => {
148
148
  const { mappedDatasets: singleHiddenSublayer, orderedDatasets } = mappedDatasetsReducer({
149
149
  datasets: [{
150
150
  id: 'single-sublayer-dataset',
151
+ label: 'Single Layer',
151
152
  showInKey: true,
152
153
  visible: true,
153
154
  style: {},
@@ -162,7 +163,7 @@ describe('datasetRegistry', () => {
162
163
  it('only includes visible sublayers in group items', () => {
163
164
  const { items } = datasetRegistry.keyItems()
164
165
  const landCoversItem = items.find(item => item.type === 'group' && item.groupLabel === 'Land covers')
165
- const sublayerIds = landCoversItem.datasets.map(s => s.id)
166
+ const sublayerIds = landCoversItem.keyDefinitions.map(s => s.id)
166
167
  expect(sublayerIds).not.toContain('land-covers-379') // land-covers-379 has visible: false
167
168
  })
168
169
 
@@ -180,7 +181,7 @@ describe('datasetRegistry', () => {
180
181
  datasetRegistry.attach(grouped, orderedDatasets)
181
182
  const { items } = datasetRegistry.keyItems()
182
183
  const groupLabels = items.filter(i => i.type === 'group').map(i => i.groupLabel)
183
- expect(groupLabels.length).toBe(new Set(groupLabels).size)
184
+ expect(groupLabels).toHaveLength(new Set(groupLabels).size)
184
185
  })
185
186
 
186
187
  it('populates group item datasets with matching showInKey datasets', () => {
@@ -188,7 +189,7 @@ describe('datasetRegistry', () => {
188
189
  datasetRegistry.attach(grouped, orderedDatasets)
189
190
  const { items } = datasetRegistry.keyItems()
190
191
  const groupItem = items.find(item => item.type === 'group' && item.groupLabel === 'Test group')
191
- const ids = groupItem.datasets.map(d => d.id)
192
+ const ids = groupItem.keyDefinitions.map(d => d.id)
192
193
  expect(ids).toContain('existing-fields')
193
194
  expect(ids).toContain('hedge-control')
194
195
  })
@@ -199,13 +200,24 @@ describe('datasetRegistry', () => {
199
200
  expect(result1).toBe(result2)
200
201
  })
201
202
 
202
- it('recalculates caches the cached result when invalidateKeyItems is called', () => {
203
+ it('recalculates the keyItems when invalidateKeyItemsOnMenuStateChange is called with new menuState', () => {
204
+ const menuState = { someMenuKey: 'someValue' }
205
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange({})
203
206
  const result1 = datasetRegistry.keyItems()
204
- datasetRegistry.invalidateKeyItems()
207
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
205
208
  const result2 = datasetRegistry.keyItems()
206
209
  expect(result1).not.toBe(result2)
207
210
  })
208
211
 
212
+ it('keeps the cached keyItems when invalidateKeyItemsOnMenuStateChange is called with the same menuState', () => {
213
+ const menuState = { someMenuKey: 'someOtherValue' }
214
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
215
+ const result1 = datasetRegistry.keyItems()
216
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
217
+ const result2 = datasetRegistry.keyItems()
218
+ expect(result1).toBe(result2)
219
+ })
220
+
209
221
  it('recomputes when the datasets ref changes', () => {
210
222
  const result1 = datasetRegistry.keyItems()
211
223
  datasetRegistry.attach({ simple: { id: 'simple', showInKey: true, visible: true, style: {} } }, ['simple'])
@@ -4,9 +4,6 @@ export const attachGlobalState = (globalStateRef) => {
4
4
  globalState = globalStateRef
5
5
  }
6
6
 
7
- // TODO - remove this once testing finishes
8
- window.getGlobalState = () => Object.freeze(globalState)
9
-
10
7
  const calculateLocalOpacity = (opacityValue, parentOpacityValue) => {
11
8
  if (opacityValue === undefined) {
12
9
  if (parentOpacityValue === undefined) {
@@ -1,11 +1,14 @@
1
1
  import { datasetRegistry } from './datasetRegistry.js'
2
2
 
3
- let _menuState = {}
4
- export const setMenuState = (menuState) => { _menuState = menuState }
3
+ let _menuStateRef = {}
4
+ export const attachMenuStateRef = (menuStateRef) => {
5
+ _menuStateRef = menuStateRef
6
+ }
5
7
 
6
8
  const _isVisibleWhenMenuCheck = (menuVisibleWhen) => {
9
+ const menuState = _menuStateRef?.current?.menuState || {}
7
10
  for (const [key, valueArray] of Object.entries(menuVisibleWhen)) {
8
- const menuValue = _menuState[key]
11
+ const menuValue = menuState[key]
9
12
  if (!valueArray.includes(menuValue)) {
10
13
  return false
11
14
  }
@@ -1,6 +1,10 @@
1
- import { isVisibleWhen, setMenuState } from './isVisibleWhen.js'
1
+ import { isVisibleWhen, attachMenuStateRef } from './isVisibleWhen.js'
2
2
  import { datasetRegistry } from './datasetRegistry.js'
3
3
 
4
+ const setMenuState = (menuState) => {
5
+ attachMenuStateRef({ current: { menuState } })
6
+ }
7
+
4
8
  describe('isVisibleWhen', () => {
5
9
  beforeEach(() => {
6
10
  // reset menu state before each test
@@ -64,4 +68,10 @@ describe('isVisibleWhen', () => {
64
68
  const visibleWhen = 'incorrectly-configured'
65
69
  expect(isVisibleWhen(visibleWhen)).toBe(true)
66
70
  })
71
+
72
+ it('returns false when menu condition is checked and menuState has not been set up', () => {
73
+ attachMenuStateRef({}) // current is undefined — exercises the || {} fallback
74
+ const visibleWhen = { menu: { datasets: ['floodZones'] } }
75
+ expect(isVisibleWhen(visibleWhen)).toBe(false)
76
+ })
67
77
  })
@@ -0,0 +1,125 @@
1
+ import { getBboxArray, bboxContains, expandBbox, bboxIntersects, getGeometryBbox } from './bbox.js'
2
+
3
+ // ─── getBboxArray ─────────────────────────────────────────────────────────────
4
+
5
+ describe('getBboxArray', () => {
6
+ it('returns [west, south, east, north] from a map bounds object', () => {
7
+ const map = {
8
+ getBounds: () => ({
9
+ getWest: () => -1,
10
+ getSouth: () => 50,
11
+ getEast: () => 1,
12
+ getNorth: () => 52
13
+ })
14
+ }
15
+ expect(getBboxArray(map)).toEqual([-1, 50, 1, 52])
16
+ })
17
+ })
18
+
19
+ // ─── bboxContains ─────────────────────────────────────────────────────────────
20
+
21
+ describe('bboxContains', () => {
22
+ const outer = [-2, 49, 2, 53]
23
+
24
+ it('returns false when outer is null', () => {
25
+ expect(bboxContains(null, [-1, 50, 1, 52])).toBe(false)
26
+ })
27
+
28
+ it('returns false when inner is null', () => {
29
+ expect(bboxContains(outer, null)).toBe(false)
30
+ })
31
+
32
+ it('returns true when inner is fully inside outer', () => {
33
+ expect(bboxContains(outer, [-1, 50, 1, 52])).toBe(true)
34
+ })
35
+
36
+ it('returns false when inner extends outside outer', () => {
37
+ expect(bboxContains(outer, [-3, 50, 1, 52])).toBe(false)
38
+ })
39
+ })
40
+
41
+ // ─── expandBbox ───────────────────────────────────────────────────────────────
42
+
43
+ describe('expandBbox', () => {
44
+ it('returns a copy of addition when existing is null', () => {
45
+ expect(expandBbox(null, [1, 2, 3, 4])).toEqual([1, 2, 3, 4])
46
+ })
47
+
48
+ it('returns the bounding union of existing and addition', () => {
49
+ expect(expandBbox([-1, 49, 1, 51], [-2, 50, 2, 52])).toEqual([-2, 49, 2, 52])
50
+ })
51
+ })
52
+
53
+ // ─── bboxIntersects ───────────────────────────────────────────────────────────
54
+
55
+ describe('bboxIntersects', () => {
56
+ it('returns false when a is null', () => {
57
+ expect(bboxIntersects(null, [0, 0, 1, 1])).toBe(false)
58
+ })
59
+
60
+ it('returns false when b is null', () => {
61
+ expect(bboxIntersects([0, 0, 1, 1], null)).toBe(false)
62
+ })
63
+
64
+ it('returns true when bboxes overlap', () => {
65
+ expect(bboxIntersects([0, 0, 2, 2], [1, 1, 3, 3])).toBe(true)
66
+ })
67
+
68
+ it('returns false when bboxes do not overlap', () => {
69
+ expect(bboxIntersects([0, 0, 1, 1], [2, 2, 3, 3])).toBe(false)
70
+ })
71
+ })
72
+
73
+ // ─── getGeometryBbox ──────────────────────────────────────────────────────────
74
+
75
+ describe('getGeometryBbox', () => {
76
+ it('handles a Point', () => {
77
+ expect(getGeometryBbox({ type: 'Point', coordinates: [10, 20] }))
78
+ .toEqual([10, 20, 10, 20])
79
+ })
80
+
81
+ it('handles a LineString', () => {
82
+ expect(getGeometryBbox({ type: 'LineString', coordinates: [[0, 0], [2, 4]] }))
83
+ .toEqual([0, 0, 2, 4])
84
+ })
85
+
86
+ it('handles a MultiPoint', () => {
87
+ expect(getGeometryBbox({ type: 'MultiPoint', coordinates: [[1, 2], [3, 4]] }))
88
+ .toEqual([1, 2, 3, 4])
89
+ })
90
+
91
+ it('handles a Polygon', () => {
92
+ const ring = [[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]
93
+ expect(getGeometryBbox({ type: 'Polygon', coordinates: [ring] }))
94
+ .toEqual([0, 0, 5, 5])
95
+ })
96
+
97
+ it('handles a MultiLineString', () => {
98
+ expect(getGeometryBbox({
99
+ type: 'MultiLineString',
100
+ coordinates: [[[0, 0], [1, 1]], [[2, 2], [3, 3]]]
101
+ })).toEqual([0, 0, 3, 3])
102
+ })
103
+
104
+ it('handles a MultiPolygon', () => {
105
+ const poly1 = [[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]
106
+ const poly2 = [[[3, 3], [3, 5], [5, 5], [5, 3], [3, 3]]]
107
+ expect(getGeometryBbox({ type: 'MultiPolygon', coordinates: [poly1, poly2] }))
108
+ .toEqual([0, 0, 5, 5])
109
+ })
110
+
111
+ it('handles a GeometryCollection', () => {
112
+ expect(getGeometryBbox({
113
+ type: 'GeometryCollection',
114
+ geometries: [
115
+ { type: 'Point', coordinates: [1, 2] },
116
+ { type: 'Point', coordinates: [5, 6] }
117
+ ]
118
+ })).toEqual([1, 2, 5, 6])
119
+ })
120
+
121
+ it('throws for an unsupported geometry type', () => {
122
+ expect(() => getGeometryBbox({ type: 'Triangle', coordinates: [] }))
123
+ .toThrow('Unsupported geometry type: Triangle')
124
+ })
125
+ })