@defra/interactive-map 0.0.40-alpha → 0.0.41-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/im-shell.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/control-definition.md +9 -1
  7. package/docs/api/slots.md +26 -5
  8. package/docs/assets/images/slot-map.svg +61 -262
  9. package/docs/examples/draw-tools.mdx +3 -3
  10. package/docs/plugins/draw.md +79 -16
  11. package/docs/plugins/search.md +1 -1
  12. package/govuk-prototype-kit.config.json +2 -0
  13. package/jest.setup.js +5 -0
  14. package/package.json +9 -1
  15. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  16. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  17. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  18. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  19. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  20. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  21. package/plugins/datasets/dist/css/index.css +1 -1
  22. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  23. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  24. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  25. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  26. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  27. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  28. package/plugins/datasets/dist/umd/index.js +1 -1
  29. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
  30. package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
  31. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
  32. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
  33. package/plugins/datasets/src/api/setOpacity.test.js +9 -0
  34. package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
  35. package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
  36. package/plugins/datasets/src/datasets.scss +0 -1
  37. package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
  38. package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
  39. package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
  40. package/plugins/datasets/src/index.test.js +36 -0
  41. package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
  42. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
  43. package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
  44. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
  45. package/plugins/datasets/src/manifest.js +0 -38
  46. package/plugins/datasets/src/manifest.test.js +49 -0
  47. package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
  48. package/plugins/datasets/src/reducers/pluginState.js +3 -16
  49. package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
  50. package/plugins/datasets/src/registry/dataset.js +13 -1
  51. package/plugins/datasets/src/registry/dataset.test.js +61 -0
  52. package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
  53. package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
  54. package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
  55. package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
  56. package/plugins/datasets/src/utils/bbox.test.js +125 -0
  57. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
  58. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
  59. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
  60. package/plugins/draw/dist/esm/index.js +1 -1
  61. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
  62. package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
  63. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  64. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  65. package/plugins/draw/dist/umd/index.js +1 -1
  66. package/plugins/draw/src/DrawInit.jsx +10 -2
  67. package/plugins/draw/src/DrawInit.test.jsx +51 -10
  68. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
  69. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
  70. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
  71. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
  72. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
  74. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
  75. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
  90. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
  91. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
  92. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
  93. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
  94. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
  95. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
  96. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
  97. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
  98. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
  99. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
  100. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
  101. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
  102. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
  103. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
  104. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
  105. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
  106. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
  107. package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
  108. package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
  109. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
  110. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
  111. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
  112. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
  113. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
  114. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
  115. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
  116. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
  117. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
  118. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
  119. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
  120. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
  121. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
  122. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
  123. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
  124. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
  125. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
  126. package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
  127. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
  128. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
  129. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
  130. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
  132. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
  134. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
  136. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
  137. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
  139. package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
  140. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
  141. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
  142. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
  143. package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
  144. package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
  145. package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
  146. package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
  147. package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
  148. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
  149. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
  150. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
  151. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
  153. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
  154. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
  155. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
  156. package/plugins/draw/src/api/addFeature.js +16 -1
  157. package/plugins/draw/src/api/addFeature.test.js +62 -5
  158. package/plugins/draw/src/api/editFeature.js +7 -4
  159. package/plugins/draw/src/api/editFeature.test.js +16 -0
  160. package/plugins/draw/src/api/newPoint.js +42 -0
  161. package/plugins/draw/src/api/newPoint.test.js +117 -0
  162. package/plugins/draw/src/api/setStyle.js +43 -0
  163. package/plugins/draw/src/api/setStyle.test.js +75 -0
  164. package/plugins/draw/src/events.js +17 -10
  165. package/plugins/draw/src/events.test.js +14 -0
  166. package/plugins/draw/src/manifest.js +18 -6
  167. package/plugins/draw/src/manifest.test.js +32 -1
  168. package/plugins/draw/src/reducer.test.js +7 -0
  169. package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
  170. package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
  171. package/plugins/draw/src/utils/symbolKeys.js +5 -0
  172. package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
  173. package/plugins/draw/src/validation/liveStroke.test.js +64 -1
  174. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  175. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  176. package/plugins/interact/dist/umd/index.js +1 -1
  177. package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
  178. package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
  179. package/plugins/interact/src/utils/featureQueries.js +18 -0
  180. package/plugins/interact/src/utils/featureQueries.test.js +15 -0
  181. package/plugins/interact/src/utils/spatial.js +9 -3
  182. package/plugins/interact/src/utils/spatial.test.js +12 -1
  183. package/plugins/map-key/dist/css/index.css +62 -0
  184. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
  185. package/plugins/map-key/dist/esm/index.js +1 -0
  186. package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
  187. package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
  188. package/plugins/map-key/dist/umd/index.js +2 -0
  189. package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
  190. package/plugins/map-key/package.json +28 -0
  191. package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
  192. package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
  193. package/plugins/map-key/src/components/Key/Key.jsx +41 -0
  194. package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
  195. package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
  196. package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
  197. package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
  198. package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
  199. package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
  200. package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
  201. package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
  202. package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
  203. package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
  204. package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
  205. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
  206. package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
  207. package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
  208. package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
  209. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
  210. package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
  211. package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
  212. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
  213. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
  214. package/plugins/map-key/src/index.js +13 -0
  215. package/plugins/map-key/src/index.test.js +31 -0
  216. package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
  217. package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
  218. package/plugins/map-key/src/manifest.js +30 -0
  219. package/plugins/map-key/src/manifest.test.js +63 -0
  220. package/plugins/map-key/src/mapKey.scss +4 -0
  221. package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
  222. package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
  223. package/plugins/map-key/src/registry/index.js +3 -0
  224. package/plugins/map-key/src/registry/index.test.js +15 -0
  225. package/plugins/menu/dist/css/index.css +80 -0
  226. package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
  227. package/plugins/menu/dist/esm/index.js +1 -0
  228. package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
  229. package/plugins/menu/dist/umd/index.js +2 -0
  230. package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
  231. package/plugins/menu/package.json +28 -0
  232. package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
  233. package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
  234. package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
  235. package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
  236. package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
  237. package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
  238. package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
  239. package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
  240. package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
  241. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
  242. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
  243. package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
  244. package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
  245. package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
  246. package/plugins/menu/src/index.js +13 -0
  247. package/plugins/menu/src/index.test.js +31 -0
  248. package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
  249. package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
  250. package/plugins/menu/src/initialise/createMenu.js +10 -0
  251. package/plugins/menu/src/initialise/createMenu.test.js +37 -0
  252. package/plugins/menu/src/manifest.js +35 -0
  253. package/plugins/menu/src/manifest.test.js +63 -0
  254. package/plugins/menu/src/menu.scss +4 -0
  255. package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
  256. package/plugins/menu/src/reducers/pluginState.js +35 -0
  257. package/plugins/menu/src/reducers/pluginState.test.js +57 -0
  258. package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
  259. package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
  260. package/plugins/menu/src/registry/index.js +3 -0
  261. package/plugins/menu/src/registry/index.test.js +15 -0
  262. package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
  263. package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
  264. package/plugins/search/dist/css/index.css +1 -1
  265. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  266. package/plugins/search/dist/esm/index.js +1 -1
  267. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  268. package/plugins/search/dist/umd/index.js +1 -1
  269. package/plugins/search/src/Search.jsx +2 -3
  270. package/plugins/search/src/components/Form/Form.module.scss +9 -20
  271. package/plugins/search/src/index.js +0 -1
  272. package/plugins/search/src/index.test.js +2 -4
  273. package/plugins/search/src/manifest.js +1 -1
  274. package/plugins/search/src/search.scss +17 -16
  275. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  276. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  277. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  278. package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
  279. package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
  280. package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
  281. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
  282. package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
  283. package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
  284. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  285. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  286. package/providers/maplibre/dist/umd/index.js +1 -1
  287. package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
  288. package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
  289. package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
  290. package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
  291. package/providers/maplibre/src/utils/hoverCursor.js +30 -14
  292. package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
  293. package/providers/maplibre/src/utils/patternImages.js +1 -1
  294. package/providers/maplibre/src/utils/symbolImages.js +41 -3
  295. package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
  296. package/rollup.esm.mjs +11 -1
  297. package/src/App/components/Actions/Actions.test.jsx +47 -0
  298. package/src/App/components/Hints/Hints.test.jsx +13 -0
  299. package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
  300. package/src/App/components/Panel/Panel.jsx +18 -3
  301. package/src/App/components/Panel/Panel.module.scss +18 -8
  302. package/src/App/components/Panel/Panel.test.jsx +34 -0
  303. package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
  304. package/src/App/hooks/useFeatureItems.js +16 -7
  305. package/src/App/hooks/useFeatureItems.test.js +57 -0
  306. package/src/App/hooks/useLayoutMeasurements.js +95 -84
  307. package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
  308. package/src/App/layout/Layout.jsx +9 -10
  309. package/src/App/layout/Layout.test.jsx +2 -0
  310. package/src/App/layout/layout.module.scss +31 -17
  311. package/src/App/registry/pluginRegistry.js +1 -1
  312. package/src/App/registry/pluginRegistry.test.js +12 -0
  313. package/src/App/renderer/HtmlElementHost.jsx +18 -5
  314. package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
  315. package/src/App/renderer/mapButtons.js +28 -12
  316. package/src/App/renderer/mapButtons.test.js +17 -4
  317. package/src/App/renderer/mapControls.js +3 -1
  318. package/src/App/renderer/mapControls.test.js +16 -0
  319. package/src/App/renderer/mapPanels.js +44 -9
  320. package/src/App/renderer/mapPanels.test.js +51 -2
  321. package/src/App/renderer/orderItems.js +28 -0
  322. package/src/App/renderer/orderItems.test.js +50 -0
  323. package/src/App/renderer/slotAggregator.js +2 -18
  324. package/src/App/renderer/slotHelpers.js +3 -1
  325. package/src/App/renderer/slotHelpers.test.js +5 -0
  326. package/src/App/renderer/slots.js +3 -0
  327. package/src/App/store/AppProvider.jsx +1 -0
  328. package/src/App/store/appDispatchMiddleware.js +1 -1
  329. package/src/App/store/appDispatchMiddleware.test.js +8 -0
  330. package/src/config/events.js +2 -0
  331. package/src/scss/settings/_colors.scss +1 -0
  332. package/src/scss/settings/_dimensions.scss +2 -3
  333. package/src/scss/settings/_transition.scss +1 -1
  334. package/src/services/eventBus.js +44 -0
  335. package/src/services/eventBus.test.js +83 -0
  336. package/src/services/symbolRegistry.js +1 -1
  337. package/src/types.js +11 -1
  338. package/webpack.umd.mjs +2 -0
  339. package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
  340. package/plugins/datasets/src/components/Key/Key.jsx +0 -50
  341. package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
  342. package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
  343. package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
  344. package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
  345. package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
  346. package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
  347. package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
  348. package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
  349. /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
  350. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
  351. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
@@ -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>
@@ -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
+ }