@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
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@defra/menu-plugin",
3
+ "version": "1.0.0-alpha.1",
4
+ "main": "dist/umd/index.js",
5
+ "module": "dist/esm/index.js",
6
+ "exports": {
7
+ "import": "./dist/esm/index.js",
8
+ "require": "./dist/umd/index.js"
9
+ },
10
+ "type": "module",
11
+ "peerDependencies": {
12
+ "react": "^19.2.0",
13
+ "react-dom": "^19.2.0",
14
+ "preact": "^10.27.0"
15
+ },
16
+ "peerDependenciesMeta": {
17
+ "react": { "optional": true },
18
+ "react-dom": { "optional": true }
19
+ },
20
+ "scripts": {
21
+ "build:umd": "webpack --config ../../../webpack.umd.js --mode production",
22
+ "build:esm": "webpack --config ../../../webpack.esm.mjs --mode production",
23
+ "build": "npm run build:umd && npm run build:esm",
24
+ "clean": "rm -rf dist"
25
+ },
26
+ "devDependencies": {},
27
+ "dependencies": {}
28
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+ import { MenuCheckbox } from './MenuCheckbox.jsx'
3
+ import { GroupLegend } from './GroupLegend.jsx'
4
+
5
+ export const CheckboxGroupWrapper = ({ dispatch, menuGroup, menuState }) => {
6
+ return (
7
+ <GroupLegend menuGroup={menuGroup}> {
8
+ // Each menuGroupItem
9
+ menuGroup.items.map(menuGroupItem => (
10
+ <MenuCheckbox
11
+ key={menuGroupItem.id}
12
+ dispatch={dispatch}
13
+ menuGroupItem={menuGroupItem}
14
+ checked={menuState[menuGroupItem.id]}
15
+ onChange={menuGroupItem.handleOnChange}
16
+ />)
17
+ )
18
+ }
19
+ </GroupLegend>
20
+ )
21
+ }
@@ -0,0 +1,17 @@
1
+ export const GroupLegend = ({ menuGroup, children }) => {
2
+ const label = menuGroup.groupLabel || menuGroup.label
3
+ if (!label) {
4
+ return <>{children}</>
5
+ }
6
+ const wrapperClass = 'govuk-form-group im-c-menu-group'
7
+ return (
8
+ <div key={menuGroup.id} className={wrapperClass}>
9
+ <fieldset className='im-c-menu-group__fieldset'>
10
+ <legend className='im-c-menu-group__legend'>
11
+ <h3> {label} </h3>
12
+ </legend>
13
+ {children}
14
+ </fieldset>
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,68 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { GroupLegend } from './GroupLegend.jsx'
3
+
4
+ const child = <span data-testid='child'>Child content</span>
5
+
6
+ describe('GroupLegend', () => {
7
+ describe('without a groupLabel', () => {
8
+ it('renders children directly without a wrapping div', () => {
9
+ const { getByTestId, container } = render(
10
+ <GroupLegend menuGroup={{ id: 'g1' }}>{child}</GroupLegend>
11
+ )
12
+ expect(getByTestId('child')).toBeTruthy()
13
+ expect(container.querySelector('.govuk-form-group')).toBeNull()
14
+ })
15
+
16
+ it('does not render a fieldset when groupLabel is absent', () => {
17
+ const { container } = render(
18
+ <GroupLegend menuGroup={{ id: 'g1' }}>{child}</GroupLegend>
19
+ )
20
+ expect(container.querySelector('fieldset')).toBeNull()
21
+ })
22
+
23
+ it('does not render a fieldset when groupLabel is an empty string', () => {
24
+ const { container } = render(
25
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: '' }}>{child}</GroupLegend>
26
+ )
27
+ expect(container.querySelector('fieldset')).toBeNull()
28
+ })
29
+ })
30
+
31
+ describe('with a groupLabel', () => {
32
+ it('renders the outer wrapper with the correct classes', () => {
33
+ const { container } = render(
34
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: 'My Group' }}>{child}</GroupLegend>
35
+ )
36
+ const wrapper = container.querySelector('.govuk-form-group.im-c-menu-group')
37
+ expect(wrapper).toBeTruthy()
38
+ })
39
+
40
+ it('renders a fieldset with the correct class', () => {
41
+ const { container } = render(
42
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: 'My Group' }}>{child}</GroupLegend>
43
+ )
44
+ expect(container.querySelector('fieldset.im-c-menu-group__fieldset')).toBeTruthy()
45
+ })
46
+
47
+ it('renders the groupLabel as the legend text', () => {
48
+ render(
49
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: 'My Group' }}>{child}</GroupLegend>
50
+ )
51
+ expect(screen.getByText('My Group')).toBeTruthy()
52
+ })
53
+
54
+ it('renders the legend with the correct class', () => {
55
+ const { container } = render(
56
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: 'My Group' }}>{child}</GroupLegend>
57
+ )
58
+ expect(container.querySelector('legend.im-c-menu-group__legend')).toBeTruthy()
59
+ })
60
+
61
+ it('renders children inside the fieldset', () => {
62
+ const { getByTestId } = render(
63
+ <GroupLegend menuGroup={{ id: 'g1', groupLabel: 'My Group' }}>{child}</GroupLegend>
64
+ )
65
+ expect(getByTestId('child')).toBeTruthy()
66
+ })
67
+ })
68
+ })
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import { MenuGroup } from './MenuGroup.jsx'
3
+ import { IdPrefixContext } from './useIdPrefix.jsx'
4
+
5
+ export const Menu = ({ pluginState, appConfig }) => {
6
+ const { menu = [], dispatch, menuState } = pluginState
7
+ const appId = appConfig?.id || 'map'
8
+ const hasGroups = menu.some(item => item.groupLabel)
9
+ const containerClass = `im-c-menu${hasGroups ? ' im-c-menu--has-groups' : ''}`
10
+ return (
11
+ <IdPrefixContext value={`${appId}-menu`}>
12
+ <div className={containerClass}> {
13
+ menu.map(menuGroup =>
14
+ <MenuGroup key={menuGroup.id} menuGroup={menuGroup} dispatch={dispatch} menuState={menuState} />)
15
+ }
16
+ </div>
17
+ </IdPrefixContext>
18
+ )
19
+ }
@@ -0,0 +1,90 @@
1
+ // When groups are present, every direct child except the first gets a border-top
2
+
3
+ .im-c-menu--has-groups > *:not(:first-child) {
4
+ border-top: 1px solid var(--button-hover-color);
5
+ }
6
+
7
+ .im-c-menu > *:first-child {
8
+ padding-top: 5px;
9
+ }
10
+
11
+ .im-c-menu > *:last-child {
12
+ margin-bottom: -5px;
13
+ }
14
+
15
+ .im-c-menu-group:not(:last-child) {
16
+ padding-bottom: 5px;
17
+ }
18
+
19
+ .im-c-menu__item {
20
+ padding-bottom: 5px;
21
+
22
+ &:first-child {
23
+ padding-top: 0;
24
+ }
25
+
26
+ .im-c-menu--has-groups > &:not(:first-child) {
27
+ padding-top: 5px;
28
+ }
29
+
30
+ // Items inside a group have no individual padding or spacing between them
31
+ .im-c-menu-group & {
32
+ padding-top: 0;
33
+ padding-bottom: 0;
34
+
35
+ &:not(:last-child) {
36
+ margin-bottom: 0;
37
+ }
38
+ }
39
+
40
+ .im-c-menu__item-label {
41
+ width: 100%;
42
+ max-width: none;
43
+ align-items: start;
44
+ padding-top: 12px;
45
+ padding-bottom: 10px;
46
+ align-self: auto;
47
+ font-size: 1rem;
48
+ line-height: 1.2;
49
+
50
+ color: var(--foreground-color);
51
+ }
52
+ }
53
+
54
+ // GovUK style overide
55
+ .im-c-menu__item .govuk-checkboxes__item,
56
+ .im-c-menu__item .govuk-radios__item {
57
+ flex-wrap: nowrap;
58
+
59
+ .govuk-checkboxes__input, .govuk-radios__input {
60
+ margin-left: -3px;
61
+ }
62
+ }
63
+
64
+ .im-c-menu-group__fieldset {
65
+ // Reset browser fieldset defaults
66
+ border: none;
67
+ padding: 0;
68
+ margin: 0;
69
+ min-width: 0;
70
+ }
71
+
72
+ .im-c-menu-group__legend {
73
+ padding-top: 15px;
74
+ padding-bottom: 10px;
75
+ padding-inline: 0;
76
+ font-size: 1rem;
77
+ font-weight: bold;
78
+ color: var(--foreground-color);
79
+ h3 {
80
+ font-size: inherit;
81
+ }
82
+ }
83
+
84
+ .im-c-menu-group:first-child .im-c-menu-group__legend {
85
+ padding-top: 0;
86
+ }
87
+
88
+ .im-c-menu .govuk-checkboxes__input[aria-disabled="true"] + .govuk-checkboxes__label {
89
+ opacity: 0.5;
90
+ }
@@ -0,0 +1,161 @@
1
+ import { render } from '@testing-library/react'
2
+ import { Menu } from './Menu.jsx'
3
+
4
+ let capturedOnChange = null
5
+
6
+ jest.mock('./MenuCheckbox.jsx', () => ({
7
+ MenuCheckbox: ({ menuGroupItem, onChange }) => {
8
+ capturedOnChange = onChange
9
+ return (
10
+ <div
11
+ data-testid='layers-menu-checkbox'
12
+ data-item-id={menuGroupItem.id}
13
+ />
14
+ )
15
+ }
16
+ }))
17
+
18
+ jest.mock('./RadioGroupWrapper.jsx', () => ({
19
+ RadioGroupWrapper: ({ menuGroup }) => (
20
+ <div data-testid='layers-radio-group-wrapper' data-group-id={menuGroup.id} />
21
+ )
22
+ }))
23
+
24
+ jest.mock('./GroupLegend.jsx', () => ({
25
+ GroupLegend: ({ menuGroup, children }) => (
26
+ <div data-testid='layers-menu-group-wrapper' data-group-id={menuGroup.id}>
27
+ {children}
28
+ </div>
29
+ )
30
+ }))
31
+
32
+ const makePluginState = (menu = [], extra = {}) => ({
33
+ menu,
34
+ menuState: {},
35
+ dispatch: jest.fn(),
36
+ ...extra
37
+ })
38
+
39
+ const appConfig = {
40
+ id: 'map'
41
+ }
42
+
43
+ describe('Menu', () => {
44
+ describe('container class', () => {
45
+ it('renders the base container class when no groups have a groupLabel', () => {
46
+ const pluginState = makePluginState([{ id: 'g1', type: 'checkbox', items: [] }])
47
+ const { container } = render(<Menu pluginState={pluginState} appConfig={appConfig} />)
48
+ const div = container.firstChild
49
+ expect(div.className).toBe('im-c-menu')
50
+ })
51
+
52
+ it('adds the --has-groups modifier when at least one group has a groupLabel', () => {
53
+ const pluginState = makePluginState([{ id: 'g1', type: 'checkbox', groupLabel: 'My Group', items: [] }])
54
+ const { container } = render(<Menu pluginState={pluginState} />)
55
+ const div = container.firstChild
56
+ expect(div.className).toBe('im-c-menu im-c-menu--has-groups')
57
+ })
58
+ })
59
+
60
+ describe('empty menu', () => {
61
+ it('renders the container with no children when menu is empty', () => {
62
+ const pluginState = makePluginState([])
63
+ const { container } = render(<Menu pluginState={pluginState} />)
64
+ expect(container.firstChild.children).toHaveLength(0)
65
+ })
66
+
67
+ it('uses an empty array when menu is not provided', () => {
68
+ const { container } = render(<Menu pluginState={{}} />)
69
+ expect(container.firstChild.children).toHaveLength(0)
70
+ })
71
+ })
72
+
73
+ describe('checkbox groups', () => {
74
+ it('renders a GroupLegend for each checkbox group', () => {
75
+ const pluginState = makePluginState([
76
+ { id: 'g1', type: 'checkbox', items: [] },
77
+ { id: 'g2', type: 'checkbox', items: [] }
78
+ ])
79
+ const { getAllByTestId } = render(<Menu pluginState={pluginState} />)
80
+ expect(getAllByTestId('layers-menu-group-wrapper')).toHaveLength(2)
81
+ })
82
+
83
+ it('renders a MenuCheckbox for each item in a checkbox group', () => {
84
+ const pluginState = makePluginState([
85
+ { id: 'g1', type: 'checkbox', items: [{ id: 'item-a' }, { id: 'item-b' }] }
86
+ ])
87
+ const { getAllByTestId } = render(<Menu pluginState={pluginState} />)
88
+ expect(getAllByTestId('layers-menu-checkbox')).toHaveLength(2)
89
+ })
90
+
91
+ it('passes the correct group to GroupLegend', () => {
92
+ const group = { id: 'group-x', type: 'checkbox', items: [] }
93
+ const pluginState = makePluginState([group])
94
+ const { getByTestId } = render(<Menu pluginState={pluginState} />)
95
+ expect(getByTestId('layers-menu-group-wrapper').dataset.groupId).toBe('group-x')
96
+ })
97
+
98
+ it('passes the correct item to MenuCheckbox', () => {
99
+ const pluginState = makePluginState([
100
+ { id: 'g1', type: 'checkbox', items: [{ id: 'item-1' }] }
101
+ ])
102
+ const { getByTestId } = render(<Menu pluginState={pluginState} />)
103
+ expect(getByTestId('layers-menu-checkbox').dataset.itemId).toBe('item-1')
104
+ })
105
+ })
106
+
107
+ describe('radio groups', () => {
108
+ it('renders a RadioGroupWrapper for non-checkbox groups', () => {
109
+ const pluginState = makePluginState([
110
+ { id: 'r1', type: 'radio', items: [{ id: 'opt1' }, { id: 'opt2' }] }
111
+ ])
112
+ const { getAllByTestId } = render(<Menu pluginState={pluginState} />)
113
+ expect(getAllByTestId('layers-radio-group-wrapper')).toHaveLength(1)
114
+ })
115
+
116
+ it('passes the correct group to RadioGroupWrapper', () => {
117
+ const pluginState = makePluginState([
118
+ { id: 'radio-group-1', type: 'radio', items: [] }
119
+ ])
120
+ const { getByTestId } = render(<Menu pluginState={pluginState} />)
121
+ expect(getByTestId('layers-radio-group-wrapper').dataset.groupId).toBe('radio-group-1')
122
+ })
123
+
124
+ it('does not render a GroupLegend for radio groups', () => {
125
+ const pluginState = makePluginState([{ id: 'r1', type: 'radio', items: [] }])
126
+ const { queryByTestId } = render(<Menu pluginState={pluginState} />)
127
+ expect(queryByTestId('layers-menu-group-wrapper')).toBeNull()
128
+ })
129
+ })
130
+
131
+ describe('mixed groups', () => {
132
+ it('renders both checkbox and radio wrappers when both types are present', () => {
133
+ const pluginState = makePluginState([
134
+ { id: 'c1', type: 'checkbox', items: [] },
135
+ { id: 'r1', type: 'radio', items: [] }
136
+ ])
137
+ const { getByTestId } = render(<Menu pluginState={pluginState} />)
138
+ expect(getByTestId('layers-menu-group-wrapper')).toBeTruthy()
139
+ expect(getByTestId('layers-radio-group-wrapper')).toBeTruthy()
140
+ })
141
+ })
142
+
143
+ describe('handleDatasetChange', () => {
144
+ it('passes handleOnChange from the config item as onChange', () => {
145
+ const handleOnChange = jest.fn()
146
+ const pluginState = makePluginState([
147
+ { id: 'g1', type: 'checkbox', items: [{ id: 'dataset-abc', handleOnChange }] }
148
+ ])
149
+ render(<Menu pluginState={pluginState} />)
150
+ expect(capturedOnChange).toBe(handleOnChange)
151
+ })
152
+
153
+ it('onChange is undefined when the config item has no handleOnChange', () => {
154
+ const pluginState = makePluginState([
155
+ { id: 'g1', type: 'checkbox', items: [{ id: 'dataset-abc' }] }
156
+ ])
157
+ render(<Menu pluginState={pluginState} />)
158
+ expect(capturedOnChange).toBeUndefined()
159
+ })
160
+ })
161
+ })
@@ -0,0 +1,35 @@
1
+ import { useIdPrefix } from './useIdPrefix.jsx'
2
+
3
+ export const MenuCheckbox = ({ dispatch, menuGroupItem, onChange, checked }) => {
4
+ const itemClass = `im-c-menu__item govuk-checkboxes govuk-checkboxes--small${checked ? '' : ' im-c-menu__item--checked'}`
5
+ const itemId = useIdPrefix(`checkbox-${menuGroupItem.id}`)
6
+
7
+ const handleOnChange = event => {
8
+ const _checked = Boolean(event.target?.checked)
9
+ if (onChange) {
10
+ // The menu config can optionally pass handleOnChange, which is passed in as onChange
11
+ // and called here
12
+ onChange(_checked)
13
+ }
14
+ // Also update the menu state, so any visibleWhen implementations can also handle it
15
+ dispatch({ type: 'UPDATE_MENU_STATE', payload: { [menuGroupItem.id]: _checked } })
16
+ }
17
+
18
+ return (
19
+ <div key={itemId} className={itemClass} data-module='govuk-checkboxes'>
20
+ <div className='govuk-checkboxes__item'>
21
+ <input
22
+ className='govuk-checkboxes__input'
23
+ id={itemId}
24
+ name={itemId}
25
+ type='checkbox'
26
+ checked={checked}
27
+ onChange={handleOnChange}
28
+ />
29
+ <label className='im-c-menu__item-label govuk-label govuk-checkboxes__label' htmlFor={itemId}>
30
+ {menuGroupItem.label}
31
+ </label>
32
+ </div>
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,93 @@
1
+ import { render, screen, act } from '@testing-library/react'
2
+ import { MenuCheckbox } from './MenuCheckbox.jsx'
3
+
4
+ const onChange = jest.fn()
5
+ const dispatch = jest.fn()
6
+ const menuGroupItem = { id: 'dataset-1', label: 'Dataset One' }
7
+
8
+ beforeEach(() => {
9
+ onChange.mockReset()
10
+ dispatch.mockReset()
11
+ })
12
+
13
+ const renderCheckbox = (props = {}) =>
14
+ render(<MenuCheckbox menuGroupItem={menuGroupItem} onChange={onChange} dispatch={dispatch} checked {...props} />)
15
+
16
+ describe('MenuCheckbox', () => {
17
+ describe('rendered output', () => {
18
+ it('renders a checkbox input', () => {
19
+ const { container } = renderCheckbox()
20
+ expect(container.querySelector('input[type="checkbox"]')).toBeTruthy()
21
+ })
22
+
23
+ it('renders the menuGroupItem label', () => {
24
+ renderCheckbox()
25
+ expect(screen.getByText('Dataset One')).toBeTruthy()
26
+ })
27
+
28
+ it('associates the label with the input via htmlFor', () => {
29
+ const { container } = renderCheckbox()
30
+ const label = container.querySelector('label')
31
+ const input = container.querySelector('input')
32
+ expect(label.htmlFor).toBe(input.id)
33
+ })
34
+ })
35
+
36
+ describe('checked state', () => {
37
+ it('is checked when checked prop is true', () => {
38
+ const { container } = renderCheckbox({ checked: true })
39
+ expect(container.querySelector('input').checked).toBe(true)
40
+ })
41
+
42
+ it('is unchecked when checked prop is false', () => {
43
+ const { container } = renderCheckbox({ checked: false })
44
+ expect(container.querySelector('input').checked).toBe(false)
45
+ })
46
+ })
47
+
48
+ describe('item class', () => {
49
+ it('does not include the --checked modifier when checked is true', () => {
50
+ const { container } = renderCheckbox({ checked: true })
51
+ expect(container.firstChild.className).not.toContain('im-c-menu__item--checked')
52
+ })
53
+
54
+ it('includes the --checked modifier when checked is false', () => {
55
+ const { container } = renderCheckbox({ checked: false })
56
+ expect(container.firstChild.className).toContain('im-c-menu__item--checked')
57
+ })
58
+ })
59
+
60
+ describe('handleOnChange', () => {
61
+ const triggerChange = (container, checked) => {
62
+ const input = container.querySelector('input')
63
+ const propsKey = Object.keys(input).find(k => k.startsWith('__reactProps'))
64
+ act(() => { input[propsKey].onChange({ target: { checked, value: input.value } }) })
65
+ }
66
+
67
+ it('calls onChange with true when the checkbox is checked', () => {
68
+ const { container } = renderCheckbox()
69
+ triggerChange(container, true)
70
+ expect(onChange).toHaveBeenCalledWith(true)
71
+ })
72
+
73
+ it('calls onChange with false when the checkbox is unchecked', () => {
74
+ const { container } = renderCheckbox()
75
+ triggerChange(container, false)
76
+ expect(onChange).toHaveBeenCalledWith(false)
77
+ })
78
+
79
+ it('dispatches UPDATE_MENU_STATE with the item id and checked value', () => {
80
+ const { container } = renderCheckbox()
81
+ triggerChange(container, true)
82
+ expect(dispatch).toHaveBeenCalledWith({
83
+ type: 'UPDATE_MENU_STATE',
84
+ payload: { 'dataset-1': true }
85
+ })
86
+ })
87
+
88
+ it('does not throw when onChange is not provided', () => {
89
+ const { container } = renderCheckbox({ onChange: undefined })
90
+ expect(() => triggerChange(container, true)).not.toThrow()
91
+ })
92
+ })
93
+ })
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import { RadioGroupWrapper } from './RadioGroupWrapper.jsx'
3
+ import { CheckboxGroupWrapper } from './CheckboxGroupWrapper.jsx'
4
+
5
+ export const MenuGroup = ({ menuGroup, dispatch, menuState }) => {
6
+ if (menuGroup.type === 'checkbox') {
7
+ return (<CheckboxGroupWrapper menuGroup={menuGroup} dispatch={dispatch} menuState={menuState} />)
8
+ } else {
9
+ return (<RadioGroupWrapper menuGroup={menuGroup} dispatch={dispatch} menuState={menuState} />)
10
+ }
11
+ }
@@ -1,7 +1,9 @@
1
1
  import { isVisibleWhen } from '../../registry/isVisibleWhen.js'
2
+ import { useIdPrefix } from './useIdPrefix.jsx'
2
3
 
3
- export const LayersMenuRadio = ({ menuGroupItem, name, checked, onChange }) => {
4
- const itemClass = 'im-c-datasets-layers__item govuk-radios govuk-radios--small"'
4
+ export const MenuRadio = ({ menuGroupItem, name, checked, onChange }) => {
5
+ const itemClass = 'im-c-menu__item govuk-radios govuk-radios--small"'
6
+ const itemId = useIdPrefix(`radio-${menuGroupItem.id}`)
5
7
  const { visibleWhen } = menuGroupItem
6
8
  const visible = visibleWhen ? isVisibleWhen(visibleWhen) : true
7
9
  if (!visible) {
@@ -11,7 +13,7 @@ export const LayersMenuRadio = ({ menuGroupItem, name, checked, onChange }) => {
11
13
  <div className={itemClass} data-module='govuk-radios'>
12
14
  <div className='govuk-radios__item'>
13
15
  <input
14
- id={menuGroupItem.id}
16
+ id={itemId}
15
17
  className='govuk-radios__input'
16
18
  type='radio'
17
19
  name={name}
@@ -19,7 +21,7 @@ export const LayersMenuRadio = ({ menuGroupItem, name, checked, onChange }) => {
19
21
  checked={checked}
20
22
  onChange={onChange}
21
23
  />
22
- <label className='im-c-datasets-layers__item-label govuk-label govuk-radios__label' htmlFor={menuGroupItem.id}>
24
+ <label className='im-c-menu__item-label govuk-label govuk-radios__label' htmlFor={itemId}>
23
25
  {menuGroupItem.label}
24
26
  </label>
25
27
  </div>