@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,43 +1,43 @@
1
1
  import { render, screen, act } from '@testing-library/react'
2
2
  import { isVisibleWhen } from '../../registry/isVisibleWhen.js'
3
- import { LayersMenuRadio } from './LayersMenuRadio.jsx'
3
+ import { MenuRadio } from './MenuRadio.jsx'
4
4
 
5
5
  jest.mock('../../registry/isVisibleWhen.js', () => ({
6
6
  isVisibleWhen: jest.fn()
7
7
  }))
8
8
 
9
9
  const onChange = jest.fn()
10
- const baseItem = { id: 'radio-1', label: 'Option One' }
10
+ const baseItem = { id: 'option-1', label: 'Option One' }
11
11
 
12
12
  beforeEach(() => {
13
13
  isVisibleWhen.mockReturnValue(true)
14
14
  onChange.mockReset()
15
15
  })
16
16
 
17
- describe('LayersMenuRadio', () => {
17
+ describe('MenuRadio', () => {
18
18
  describe('visibility', () => {
19
19
  it('renders when visibleWhen is not set', () => {
20
20
  const { container } = render(
21
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
21
+ <MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
22
22
  )
23
23
  expect(container.querySelector('input[type="radio"]')).toBeTruthy()
24
24
  })
25
25
 
26
26
  it('does not call isVisibleWhen when visibleWhen is not set', () => {
27
- render(<LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />)
27
+ render(<MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />)
28
28
  expect(isVisibleWhen).not.toHaveBeenCalled()
29
29
  })
30
30
 
31
31
  it('calls isVisibleWhen with the visibleWhen value', () => {
32
32
  const visibleWhen = { menu: ['someValue'] }
33
- render(<LayersMenuRadio menuGroupItem={{ ...baseItem, visibleWhen }} name='group' checked={false} onChange={onChange} />)
33
+ render(<MenuRadio menuGroupItem={{ ...baseItem, visibleWhen }} name='group' checked={false} onChange={onChange} />)
34
34
  expect(isVisibleWhen).toHaveBeenCalledWith(visibleWhen)
35
35
  })
36
36
 
37
37
  it('returns null when isVisibleWhen resolves to false', () => {
38
38
  isVisibleWhen.mockReturnValue(false)
39
39
  const { container } = render(
40
- <LayersMenuRadio menuGroupItem={{ ...baseItem, visibleWhen: {} }} name='group' checked={false} onChange={onChange} />
40
+ <MenuRadio menuGroupItem={{ ...baseItem, visibleWhen: {} }} name='group' checked={false} onChange={onChange} />
41
41
  )
42
42
  expect(container.firstChild).toBeNull()
43
43
  })
@@ -45,7 +45,7 @@ describe('LayersMenuRadio', () => {
45
45
  it('renders when isVisibleWhen resolves to true', () => {
46
46
  isVisibleWhen.mockReturnValue(true)
47
47
  const { container } = render(
48
- <LayersMenuRadio menuGroupItem={{ ...baseItem, visibleWhen: {} }} name='group' checked={false} onChange={onChange} />
48
+ <MenuRadio menuGroupItem={{ ...baseItem, visibleWhen: {} }} name='group' checked={false} onChange={onChange} />
49
49
  )
50
50
  expect(container.querySelector('input[type="radio"]')).toBeTruthy()
51
51
  })
@@ -53,52 +53,52 @@ describe('LayersMenuRadio', () => {
53
53
 
54
54
  describe('rendered output', () => {
55
55
  it('renders the item label text', () => {
56
- render(<LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />)
56
+ render(<MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />)
57
57
  expect(screen.getByText('Option One')).toBeTruthy()
58
58
  })
59
59
 
60
60
  it('sets the name attribute on the input', () => {
61
61
  const { container } = render(
62
- <LayersMenuRadio menuGroupItem={baseItem} name='my-radio-group' checked={false} onChange={onChange} />
62
+ <MenuRadio menuGroupItem={baseItem} name='my-radio-group' checked={false} onChange={onChange} />
63
63
  )
64
64
  expect(container.querySelector('input').name).toBe('my-radio-group')
65
65
  })
66
66
 
67
67
  it('sets checked to true when checked prop is true', () => {
68
68
  const { container } = render(
69
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked onChange={onChange} />
69
+ <MenuRadio menuGroupItem={baseItem} name='group' checked onChange={onChange} />
70
70
  )
71
71
  expect(container.querySelector('input').checked).toBe(true)
72
72
  })
73
73
 
74
74
  it('sets checked to false when checked prop is false', () => {
75
75
  const { container } = render(
76
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
76
+ <MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
77
77
  )
78
78
  expect(container.querySelector('input').checked).toBe(false)
79
79
  })
80
80
 
81
81
  it('sets the input value to the item id', () => {
82
82
  const { container } = render(
83
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
83
+ <MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
84
84
  )
85
- expect(container.querySelector('input').value).toBe('radio-1')
85
+ expect(container.querySelector('input').value).toBe('option-1')
86
86
  })
87
87
 
88
88
  it('associates the label with the input via htmlFor', () => {
89
89
  const { container } = render(
90
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
90
+ <MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
91
91
  )
92
- expect(container.querySelector('label').htmlFor).toBe('radio-1')
92
+ expect(container.querySelector('label').htmlFor).toBe('map-menu-radio-option-1')
93
93
  })
94
94
 
95
95
  it('calls onChange when the radio input changes', () => {
96
96
  const { container } = render(
97
- <LayersMenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
97
+ <MenuRadio menuGroupItem={baseItem} name='group' checked={false} onChange={onChange} />
98
98
  )
99
99
  const input = container.querySelector('input')
100
100
  const propsKey = Object.keys(input).find(k => k.startsWith('__reactProps'))
101
- act(() => { input[propsKey].onChange({ target: { value: 'radio-1' } }) })
101
+ act(() => { input[propsKey].onChange({ target: { value: 'option-1' } }) })
102
102
  expect(onChange).toHaveBeenCalledTimes(1)
103
103
  })
104
104
  })
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+ import { isVisibleWhen } from '../../registry/isVisibleWhen.js'
3
+ import { MenuRadio } from './MenuRadio.jsx'
4
+ import { GroupLegend } from './GroupLegend.jsx'
5
+ export const RadioGroupWrapper = ({ menuState, dispatch, menuGroup }) => {
6
+ const { id, items, visibleWhen } = menuGroup
7
+ const visible = visibleWhen ? isVisibleWhen(visibleWhen) : true
8
+ if (!visible) {
9
+ return null
10
+ }
11
+
12
+ const value = menuState[id]
13
+ const handleChange = (event) => {
14
+ dispatch({ type: 'UPDATE_MENU_STATE', payload: { [id]: event.target.value } })
15
+ }
16
+
17
+ return (
18
+ <GroupLegend menuGroup={menuGroup}>
19
+ <div className='govuk-radios govuk-radios--small' data-module='govuk-radios'>
20
+ {items.map((menuGroupItem) =>
21
+ <MenuRadio
22
+ key={menuGroupItem.id}
23
+ menuGroupItem={menuGroupItem}
24
+ name={id}
25
+ checked={menuGroupItem.id === value}
26
+ onChange={handleChange}
27
+ />
28
+ )}
29
+ </div>
30
+ </GroupLegend>
31
+ )
32
+ }
@@ -1,6 +1,6 @@
1
1
  import { render, screen, act } from '@testing-library/react'
2
2
  import { isVisibleWhen } from '../../registry/isVisibleWhen.js'
3
- import { LayersRadioGroupWrapper } from './LayersRadioGroupWrapper.jsx'
3
+ import { RadioGroupWrapper } from './RadioGroupWrapper.jsx'
4
4
 
5
5
  jest.mock('../../registry/isVisibleWhen.js', () => ({
6
6
  isVisibleWhen: jest.fn()
@@ -8,8 +8,8 @@ jest.mock('../../registry/isVisibleWhen.js', () => ({
8
8
 
9
9
  let capturedOnChange = null
10
10
 
11
- jest.mock('./LayersMenuRadio.jsx', () => ({
12
- LayersMenuRadio: ({ menuGroupItem, checked, name, onChange }) => {
11
+ jest.mock('./MenuRadio.jsx', () => ({
12
+ MenuRadio: ({ menuGroupItem, checked, name, onChange }) => {
13
13
  capturedOnChange = onChange
14
14
  return (
15
15
  <div
@@ -22,7 +22,7 @@ jest.mock('./LayersMenuRadio.jsx', () => ({
22
22
  }
23
23
  }))
24
24
 
25
- const makePluginState = (menuState = {}) => ({
25
+ const makeProps = (menuState = {}) => ({
26
26
  menuState,
27
27
  dispatch: jest.fn()
28
28
  })
@@ -39,51 +39,51 @@ beforeEach(() => {
39
39
  capturedOnChange = null
40
40
  })
41
41
 
42
- describe('LayersRadioGroupWrapper', () => {
42
+ describe('RadioGroupWrapper', () => {
43
43
  describe('visibility', () => {
44
44
  it('returns null when isVisibleWhen resolves to false', () => {
45
45
  isVisibleWhen.mockReturnValue(false)
46
46
  const { container } = render(
47
- <LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={{ ...baseGroup, visibleWhen: {} }} />
47
+ <RadioGroupWrapper {...makeProps()} menuGroup={{ ...baseGroup, visibleWhen: {} }} />
48
48
  )
49
49
  expect(container.firstChild).toBeNull()
50
50
  })
51
51
 
52
52
  it('renders when visibleWhen is not set', () => {
53
53
  const { container } = render(
54
- <LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={baseGroup} />
54
+ <RadioGroupWrapper {...makeProps()} menuGroup={baseGroup} />
55
55
  )
56
56
  expect(container.querySelector('.govuk-form-group')).toBeTruthy()
57
57
  })
58
58
 
59
59
  it('does not call isVisibleWhen when visibleWhen is not set', () => {
60
- render(<LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={baseGroup} />)
60
+ render(<RadioGroupWrapper {...makeProps()} menuGroup={baseGroup} />)
61
61
  expect(isVisibleWhen).not.toHaveBeenCalled()
62
62
  })
63
63
 
64
64
  it('calls isVisibleWhen with the visibleWhen value', () => {
65
65
  const visibleWhen = { menu: ['someValue'] }
66
- render(<LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={{ ...baseGroup, visibleWhen }} />)
66
+ render(<RadioGroupWrapper {...makeProps()} menuGroup={{ ...baseGroup, visibleWhen }} />)
67
67
  expect(isVisibleWhen).toHaveBeenCalledWith(visibleWhen)
68
68
  })
69
69
  })
70
70
 
71
71
  describe('rendered output', () => {
72
72
  it('renders the group label as the legend', () => {
73
- render(<LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={baseGroup} />)
73
+ render(<RadioGroupWrapper {...makeProps()} menuGroup={baseGroup} />)
74
74
  expect(screen.getByText('My Group')).toBeTruthy()
75
75
  })
76
76
 
77
- it('renders a LayersMenuRadio for each item', () => {
77
+ it('renders a MenuRadio for each item', () => {
78
78
  const { getAllByTestId } = render(
79
- <LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={baseGroup} />
79
+ <RadioGroupWrapper {...makeProps()} menuGroup={baseGroup} />
80
80
  )
81
81
  expect(getAllByTestId('layers-menu-radio')).toHaveLength(2)
82
82
  })
83
83
 
84
84
  it('passes the group id as the name to each radio item', () => {
85
85
  const { getAllByTestId } = render(
86
- <LayersRadioGroupWrapper pluginState={makePluginState()} menuGroup={baseGroup} />
86
+ <RadioGroupWrapper {...makeProps()} menuGroup={baseGroup} />
87
87
  )
88
88
  getAllByTestId('layers-menu-radio').forEach(radio => {
89
89
  expect(radio.dataset.name).toBe('group-1')
@@ -94,7 +94,7 @@ describe('LayersRadioGroupWrapper', () => {
94
94
  describe('checked state', () => {
95
95
  it('marks the item matching menuState as checked', () => {
96
96
  const { getAllByTestId } = render(
97
- <LayersRadioGroupWrapper pluginState={makePluginState({ 'group-1': 'opt-a' })} menuGroup={baseGroup} />
97
+ <RadioGroupWrapper {...makeProps({ 'group-1': 'opt-a' })} menuGroup={baseGroup} />
98
98
  )
99
99
  const radios = getAllByTestId('layers-menu-radio')
100
100
  expect(radios[0].dataset.checked).toBe('true')
@@ -103,7 +103,7 @@ describe('LayersRadioGroupWrapper', () => {
103
103
 
104
104
  it('marks no item as checked when menuState has no value for the group', () => {
105
105
  const { getAllByTestId } = render(
106
- <LayersRadioGroupWrapper pluginState={makePluginState({})} menuGroup={baseGroup} />
106
+ <RadioGroupWrapper {...makeProps({})} menuGroup={baseGroup} />
107
107
  )
108
108
  getAllByTestId('layers-menu-radio').forEach(radio => {
109
109
  expect(radio.dataset.checked).toBe('false')
@@ -113,10 +113,10 @@ describe('LayersRadioGroupWrapper', () => {
113
113
 
114
114
  describe('handleChange', () => {
115
115
  it('dispatches UPDATE_MENU_STATE with the selected item id', () => {
116
- const pluginState = makePluginState({ 'group-1': 'opt-a' })
117
- render(<LayersRadioGroupWrapper pluginState={pluginState} menuGroup={baseGroup} />)
116
+ const props = makeProps({ 'group-1': 'opt-a' })
117
+ render(<RadioGroupWrapper {...props} menuGroup={baseGroup} />)
118
118
  act(() => { capturedOnChange({ target: { value: 'opt-b' } }) })
119
- expect(pluginState.dispatch).toHaveBeenCalledWith({
119
+ expect(props.dispatch).toHaveBeenCalledWith({
120
120
  type: 'UPDATE_MENU_STATE',
121
121
  payload: { 'group-1': 'opt-b' }
122
122
  })
@@ -124,10 +124,10 @@ describe('LayersRadioGroupWrapper', () => {
124
124
 
125
125
  it('dispatches using the group id as the payload key', () => {
126
126
  const group = { ...baseGroup, id: 'another-group' }
127
- const pluginState = makePluginState({ 'another-group': 'opt-a' })
128
- render(<LayersRadioGroupWrapper pluginState={pluginState} menuGroup={group} />)
127
+ const props = makeProps({ 'another-group': 'opt-a' })
128
+ render(<RadioGroupWrapper {...props} menuGroup={group} />)
129
129
  act(() => { capturedOnChange({ target: { value: 'opt-b' } }) })
130
- expect(pluginState.dispatch).toHaveBeenCalledWith({
130
+ expect(props.dispatch).toHaveBeenCalledWith({
131
131
  type: 'UPDATE_MENU_STATE',
132
132
  payload: { 'another-group': 'opt-b' }
133
133
  })
@@ -0,0 +1,4 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export const IdPrefixContext = createContext('map-menu')
4
+ export const useIdPrefix = (suffix) => `${useContext(IdPrefixContext)}-${suffix}`
@@ -0,0 +1,13 @@
1
+ import './menu.scss'
2
+
3
+ export default function createPlugin (options = {}) {
4
+ return {
5
+ noKeyItemText: 'No features displayed',
6
+ ...options,
7
+ id: 'menu',
8
+ load: async () => {
9
+ const module = (await import(/* webpackChunkName: "im-menu-plugin" */ './manifest.js')).manifest
10
+ return module
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,31 @@
1
+ import createPlugin from './index.js'
2
+
3
+ jest.mock('./menu.scss', () => {})
4
+ jest.mock('./manifest.js', () => ({ manifest: { InitComponent: 'MenuInit', panels: [] } }))
5
+
6
+ describe('createPlugin', () => {
7
+ it('returns the menu id', () => {
8
+ expect(createPlugin().id).toBe('menu')
9
+ })
10
+
11
+ it('id cannot be overridden by options', () => {
12
+ expect(createPlugin({ id: 'other' }).id).toBe('menu')
13
+ })
14
+
15
+ it('returns the default noKeyItemText', () => {
16
+ expect(createPlugin().noKeyItemText).toBe('No features displayed')
17
+ })
18
+
19
+ it('allows noKeyItemText to be overridden', () => {
20
+ expect(createPlugin({ noKeyItemText: 'Nothing here' }).noKeyItemText).toBe('Nothing here')
21
+ })
22
+
23
+ it('merges additional options into the returned object', () => {
24
+ expect(createPlugin({ custom: true }).custom).toBe(true)
25
+ })
26
+
27
+ it('load() resolves to the manifest', async () => {
28
+ const result = await createPlugin().load()
29
+ expect(result).toEqual({ InitComponent: 'MenuInit', panels: [] })
30
+ })
31
+ })
@@ -0,0 +1,24 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { createMenu } from './createMenu.js'
3
+ import { attachPluginStateRef } from '../registry/isVisibleWhen.js'
4
+
5
+ // additional possible params here are: appState, mapProvider,
6
+ export function MenuInit ({ pluginConfig, pluginState, mapState, services }) {
7
+ const { dispatch } = pluginState
8
+ const { eventBus } = services
9
+
10
+ const pluginStateRef = useRef(pluginState)
11
+ pluginStateRef.current = pluginState
12
+
13
+ useEffect(() => {
14
+ if (!mapState.isMapReady) {
15
+ return () => {}
16
+ }
17
+ attachPluginStateRef(pluginStateRef)
18
+ const { menu } = pluginConfig
19
+ return createMenu({ menu, eventBus, dispatch, pluginStateRef })
20
+ }, [mapState.isMapReady])
21
+
22
+ // Notify any plugins, that are listening, to the menu's state change
23
+ useEffect(() => eventBus.emit('menu:changed', pluginState.menuState), [pluginState.menuState])
24
+ }
@@ -0,0 +1,50 @@
1
+ import { render } from '@testing-library/react'
2
+ import { MenuInit } from './MenuInit.jsx'
3
+ import { createMenu } from './createMenu.js'
4
+
5
+ jest.mock('./createMenu.js', () => ({
6
+ createMenu: jest.fn()
7
+ }))
8
+
9
+ const mockRemove = jest.fn()
10
+ const mockDispatch = jest.fn()
11
+ const eventBus = { on: jest.fn(), off: jest.fn(), emit: jest.fn(), emitWhenRequested: jest.fn() }
12
+ const services = { eventBus }
13
+ const pluginState = { dispatch: mockDispatch, menuState: null }
14
+ const pluginConfig = { menu: [] }
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks()
18
+ createMenu.mockReturnValue(mockRemove)
19
+ })
20
+
21
+ describe('MenuInit', () => {
22
+ it('does not call createMenu when map is not ready', () => {
23
+ render(<MenuInit mapState={{ isMapReady: false }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
24
+ expect(createMenu).not.toHaveBeenCalled()
25
+ })
26
+
27
+ it('calls createMenu with eventBus when map is ready', () => {
28
+ render(<MenuInit mapState={{ isMapReady: true }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
29
+ expect(createMenu).toHaveBeenCalledWith(expect.objectContaining({ eventBus }))
30
+ })
31
+
32
+ it('calls remove on unmount when map was ready', () => {
33
+ const { unmount } = render(<MenuInit mapState={{ isMapReady: true }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
34
+ unmount()
35
+ expect(mockRemove).toHaveBeenCalled()
36
+ })
37
+
38
+ it('does not call remove on unmount when map was not ready', () => {
39
+ const { unmount } = render(<MenuInit mapState={{ isMapReady: false }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
40
+ unmount()
41
+ expect(mockRemove).not.toHaveBeenCalled()
42
+ })
43
+
44
+ it('calls createMenu again when isMapReady changes to true', () => {
45
+ const { rerender } = render(<MenuInit mapState={{ isMapReady: false }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
46
+ expect(createMenu).not.toHaveBeenCalled()
47
+ rerender(<MenuInit mapState={{ isMapReady: true }} services={services} pluginState={pluginState} pluginConfig={pluginConfig} />)
48
+ expect(createMenu).toHaveBeenCalledTimes(1)
49
+ })
50
+ })
@@ -0,0 +1,10 @@
1
+ import { setDatasetRegistry } from '../registry/getDatasetRegistry.js'
2
+
3
+ export const createMenu = ({ menu, eventBus, dispatch, pluginStateRef }) => {
4
+ dispatch({ type: 'SET_MENU', payload: { menu } })
5
+ // Request a handle on the datasetsRegistry singleton, so that the menu plugin can access it when needed
6
+ eventBus.requestOnce('datasets:registry', setDatasetRegistry)
7
+ // add a listener for the menu:state event, which will be emitted whenever requested by other plugins,
8
+ // e.g. the datasets plugin, so they can access the menu state.
9
+ return eventBus.emitWhenRequested('menu:state', pluginStateRef)
10
+ }
@@ -0,0 +1,37 @@
1
+ import { createMenu } from './createMenu.js'
2
+ import { setDatasetRegistry } from '../registry/getDatasetRegistry.js'
3
+
4
+ jest.mock('../registry/getDatasetRegistry.js', () => ({
5
+ setDatasetRegistry: jest.fn()
6
+ }))
7
+
8
+ const makeEventBus = () => ({
9
+ on: jest.fn(),
10
+ off: jest.fn(),
11
+ emit: jest.fn(),
12
+ requestOnce: jest.fn(),
13
+ emitWhenRequested: jest.fn()
14
+ })
15
+
16
+ describe('createMenu', () => {
17
+ it('dispatches SET_MENU with the menu', () => {
18
+ const eventBus = makeEventBus()
19
+ const dispatch = jest.fn()
20
+ const menu = [{ id: 'layer1' }]
21
+ createMenu({ menu, eventBus, dispatch, pluginStateRef: {} })
22
+ expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MENU', payload: { menu } })
23
+ })
24
+
25
+ it('calls requestOnce for the datasets registry', () => {
26
+ const eventBus = makeEventBus()
27
+ createMenu({ eventBus, dispatch: jest.fn(), pluginStateRef: {} })
28
+ expect(eventBus.requestOnce).toHaveBeenCalledWith('datasets:registry', setDatasetRegistry)
29
+ })
30
+
31
+ it('calls emitWhenRequested for menu:state', () => {
32
+ const eventBus = makeEventBus()
33
+ const pluginStateRef = { current: {} }
34
+ createMenu({ eventBus, dispatch: jest.fn(), pluginStateRef })
35
+ expect(eventBus.emitWhenRequested).toHaveBeenCalledWith('menu:state', pluginStateRef)
36
+ })
37
+ })
@@ -0,0 +1,35 @@
1
+ import { Menu } from './components/Menu/Menu.jsx'
2
+ import './registry/getDatasetRegistry.js'
3
+ import { MenuInit } from './initialise/MenuInit.jsx'
4
+ import { initialState, actions } from './reducers/pluginState.js'
5
+
6
+ export const manifest = {
7
+ InitComponent: MenuInit,
8
+ reducer: {
9
+ initialState,
10
+ actions
11
+ },
12
+ panels: [
13
+ {
14
+ id: 'menu',
15
+ label: 'Layers',
16
+ mobile: { slot: 'drawer', modal: true, dismissible: true },
17
+ tablet: { slot: 'left-top', dismissible: true, exclusive: true, width: '260px' },
18
+ desktop: { slot: 'left-top', modal: false, dismissible: true, exclusive: true, width: '280px' },
19
+ render: Menu
20
+ }],
21
+ buttons: [{
22
+ id: 'menuButton',
23
+ label: 'Layers',
24
+ panelId: 'menu',
25
+ iconId: 'layersMenu',
26
+ mobile: { slot: 'top-left', showLabel: true },
27
+ tablet: { slot: 'top-left', showLabel: true },
28
+ desktop: { slot: 'top-left', showLabel: true }
29
+ }],
30
+
31
+ icons: [{
32
+ id: 'layersMenu',
33
+ svgContent: '<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"></path><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"></path><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"></path>'
34
+ }]
35
+ }
@@ -0,0 +1,63 @@
1
+ import { manifest } from './manifest.js'
2
+ import { Menu } from './components/Menu/Menu.jsx'
3
+ import { MenuInit } from './initialise/MenuInit.jsx'
4
+
5
+ jest.mock('./components/Menu/Menu.jsx', () => ({ Menu: jest.fn() }))
6
+ jest.mock('./initialise/MenuInit.jsx', () => ({ MenuInit: jest.fn() }))
7
+ jest.mock('./registry/getDatasetRegistry.js', () => ({}))
8
+
9
+ describe('manifest', () => {
10
+ it('sets InitComponent to MenuInit', () => {
11
+ expect(manifest.InitComponent).toBe(MenuInit)
12
+ })
13
+
14
+ describe('panels', () => {
15
+ it('defines one panel', () => {
16
+ expect(manifest.panels).toHaveLength(1)
17
+ })
18
+
19
+ it('panel has id menu', () => {
20
+ expect(manifest.panels[0].id).toBe('menu')
21
+ })
22
+
23
+ it('panel render is Menu', () => {
24
+ expect(manifest.panels[0].render).toBe(Menu)
25
+ })
26
+
27
+ it('panel defines mobile, tablet and desktop slots', () => {
28
+ const { mobile, tablet, desktop } = manifest.panels[0]
29
+ expect(mobile.slot).toBe('drawer')
30
+ expect(tablet.slot).toBe('left-top')
31
+ expect(desktop.slot).toBe('left-top')
32
+ })
33
+ })
34
+
35
+ describe('buttons', () => {
36
+ it('defines one button', () => {
37
+ expect(manifest.buttons).toHaveLength(1)
38
+ })
39
+
40
+ it('button has id menuButton and links to the menu panel', () => {
41
+ expect(manifest.buttons[0].id).toBe('menuButton')
42
+ expect(manifest.buttons[0].panelId).toBe('menu')
43
+ })
44
+
45
+ it('button references the layersMenu icon', () => {
46
+ expect(manifest.buttons[0].iconId).toBe('layersMenu')
47
+ })
48
+ })
49
+
50
+ describe('icons', () => {
51
+ it('defines one icon', () => {
52
+ expect(manifest.icons).toHaveLength(1)
53
+ })
54
+
55
+ it('icon id is layersMenu', () => {
56
+ expect(manifest.icons[0].id).toBe('layersMenu')
57
+ })
58
+
59
+ it('icon has svgContent', () => {
60
+ expect(manifest.icons[0].svgContent).toBeTruthy()
61
+ })
62
+ })
63
+ })
@@ -0,0 +1,4 @@
1
+ // ===================================================
2
+ // Component: Menu
3
+ // ===================================================
4
+ @use './components/Menu/Menu.module';
@@ -0,0 +1,38 @@
1
+ import { buildMenuState } from './menuStateReducer.js'
2
+
3
+ describe('buildMenuState', () => {
4
+ it('returns an empty object for an empty menu', () => {
5
+ expect(buildMenuState([])).toEqual({})
6
+ })
7
+
8
+ it('ignores non-radio menu groups', () => {
9
+ const menu = [{ id: 'layer', type: 'checkbox', value: 'a' }]
10
+ expect(buildMenuState(menu)).toEqual({})
11
+ })
12
+
13
+ it('uses the explicit value when set', () => {
14
+ const menu = [{ id: 'datasets', type: 'radio', value: 'floodZones', items: [{ value: 'other' }] }]
15
+ expect(buildMenuState(menu)).toEqual({ datasets: 'floodZones' })
16
+ })
17
+
18
+ it('falls back to the first item value when no value is set', () => {
19
+ const menu = [{ id: 'datasets', type: 'radio', items: [{ value: 'floodZones' }, { value: 'other' }] }]
20
+ expect(buildMenuState(menu)).toEqual({ datasets: 'floodZones' })
21
+ })
22
+
23
+ it('handles multiple radio groups', () => {
24
+ const menu = [
25
+ { id: 'datasets', type: 'radio', value: 'floodZones' },
26
+ { id: 'timeframe', type: 'radio', items: [{ value: 'presentDay' }] }
27
+ ]
28
+ expect(buildMenuState(menu)).toEqual({ datasets: 'floodZones', timeframe: 'presentDay' })
29
+ })
30
+
31
+ it('mixes radio and non-radio groups, only including radios', () => {
32
+ const menu = [
33
+ { id: 'layer', type: 'checkbox', value: 'a' },
34
+ { id: 'datasets', type: 'radio', value: 'floodZones' }
35
+ ]
36
+ expect(buildMenuState(menu)).toEqual({ datasets: 'floodZones' })
37
+ })
38
+ })
@@ -0,0 +1,35 @@
1
+ import { buildMenuState } from './menuStateReducer.js'
2
+
3
+ const initialState = {
4
+ actionsArray: [],
5
+ menu: [],
6
+ menuState: {}
7
+ }
8
+
9
+ const updateMenuState = (state, payload) => {
10
+ return {
11
+ ...state,
12
+ menuState: { ...state.menuState, ...payload }
13
+ }
14
+ }
15
+
16
+ const setMenu = (state, payload) => {
17
+ const { menu } = payload
18
+ // build the initial menuState for radios from the menu
19
+ const menuState = buildMenuState(menu)
20
+ return {
21
+ ...state,
22
+ menu,
23
+ menuState
24
+ }
25
+ }
26
+
27
+ const actions = {
28
+ SET_MENU: setMenu,
29
+ UPDATE_MENU_STATE: updateMenuState
30
+ }
31
+
32
+ export {
33
+ initialState,
34
+ actions
35
+ }