@defra/interactive-map 0.0.16-alpha → 0.0.18-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 (224) hide show
  1. package/assets/images/slot-map.svg +264 -0
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/context.md +53 -7
  8. package/docs/api/map-style-config.md +41 -2
  9. package/docs/api/marker-config.md +53 -11
  10. package/docs/api/slots.md +16 -15
  11. package/docs/api/symbol-config.md +160 -0
  12. package/docs/api/symbol-registry.md +115 -0
  13. package/docs/api.md +25 -22
  14. package/docs/getting-started.md +4 -1
  15. package/docs/plugins/datasets.md +657 -0
  16. package/docs/plugins/interact.md +68 -43
  17. package/docs/plugins/search.md +15 -3
  18. package/docs/plugins.md +1 -1
  19. package/package.json +2 -2
  20. package/plugins/beta/datasets/dist/css/index.css +103 -15
  21. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  22. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  23. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  24. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  25. package/plugins/beta/datasets/src/DatasetsInit.jsx +29 -9
  26. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  27. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +159 -0
  28. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +75 -0
  29. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +440 -0
  30. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +27 -0
  31. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +31 -0
  32. package/plugins/beta/datasets/src/api/addDataset.js +2 -8
  33. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  34. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  35. package/plugins/beta/datasets/src/api/removeDataset.js +2 -44
  36. package/plugins/beta/datasets/src/api/setData.js +10 -0
  37. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  38. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  39. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  40. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  41. package/plugins/beta/datasets/src/components/EmptyKey.jsx +7 -0
  42. package/plugins/beta/datasets/src/components/EmptyKey.test.jsx +21 -0
  43. package/plugins/beta/datasets/src/components/KeySvg.jsx +24 -0
  44. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +19 -0
  45. package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +15 -0
  46. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +22 -0
  47. package/plugins/beta/datasets/src/components/KeySvgSymbol.jsx +16 -0
  48. package/plugins/beta/datasets/src/components/svgProperties.js +20 -0
  49. package/plugins/beta/datasets/src/datasets.js +39 -56
  50. package/plugins/beta/datasets/src/defaults.js +44 -8
  51. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +34 -25
  52. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  53. package/plugins/beta/datasets/src/index.js +2 -1
  54. package/plugins/beta/datasets/src/manifest.js +25 -17
  55. package/plugins/beta/datasets/src/panels/Key.jsx +51 -51
  56. package/plugins/beta/datasets/src/panels/Key.module.scss +59 -9
  57. package/plugins/beta/datasets/src/panels/Layers.jsx +132 -29
  58. package/plugins/beta/datasets/src/panels/Layers.module.scss +56 -8
  59. package/plugins/beta/datasets/src/reducer.js +134 -9
  60. package/plugins/beta/datasets/src/reducers/keyReducer.js +34 -0
  61. package/plugins/beta/datasets/src/utils/bbox.js +7 -5
  62. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  63. package/plugins/beta/datasets/src/utils/mergeSublayer.js +86 -0
  64. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  65. package/plugins/beta/draw-es/src/DrawInit.jsx +3 -2
  66. package/plugins/beta/draw-ml/dist/css/index.css +21 -1
  67. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  68. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  69. package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
  70. package/plugins/beta/draw-ml/src/DrawInit.jsx +4 -3
  71. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  72. package/plugins/beta/draw-ml/src/manifest.js +16 -16
  73. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  74. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  75. package/plugins/beta/frame/src/Frame.jsx +5 -5
  76. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  77. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  78. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  79. package/plugins/beta/map-styles/src/MapStyles.jsx +5 -4
  80. package/plugins/beta/map-styles/src/MapStylesInit.jsx +5 -4
  81. package/plugins/beta/map-styles/src/manifest.js +1 -1
  82. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  83. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  84. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  85. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  86. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  87. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  88. package/plugins/interact/dist/css/index.css +1 -1
  89. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  90. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  91. package/plugins/interact/dist/umd/index.js +1 -1
  92. package/plugins/interact/src/InteractInit.jsx +14 -5
  93. package/plugins/interact/src/InteractInit.test.js +26 -6
  94. package/plugins/interact/src/api/enable.test.js +7 -7
  95. package/plugins/interact/src/defaults.js +4 -6
  96. package/plugins/interact/src/events.js +9 -6
  97. package/plugins/interact/src/events.test.js +28 -4
  98. package/plugins/interact/src/hooks/useHighlightSync.js +3 -3
  99. package/plugins/interact/src/hooks/useHighlightSync.test.js +6 -6
  100. package/plugins/interact/src/hooks/useHoverCursor.js +10 -0
  101. package/plugins/interact/src/hooks/useHoverCursor.test.js +44 -0
  102. package/plugins/interact/src/hooks/useInteractionHandlers.js +111 -69
  103. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +147 -32
  104. package/plugins/interact/src/interact.scss +0 -7
  105. package/plugins/interact/src/manifest.js +14 -18
  106. package/plugins/interact/src/manifest.test.js +3 -1
  107. package/plugins/interact/src/reducer.js +23 -4
  108. package/plugins/interact/src/reducer.test.js +60 -11
  109. package/plugins/interact/src/utils/buildStylesMap.js +17 -4
  110. package/plugins/interact/src/utils/buildStylesMap.test.js +16 -2
  111. package/plugins/interact/src/utils/featureQueries.js +11 -6
  112. package/plugins/interact/src/utils/featureQueries.test.js +8 -1
  113. package/plugins/search/dist/css/index.css +1 -1
  114. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  115. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  116. package/plugins/search/src/Search.jsx +3 -1
  117. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  118. package/plugins/search/src/events/fetchSuggestions.js +6 -4
  119. package/plugins/search/src/events/fetchSuggestions.test.js +26 -4
  120. package/plugins/search/src/events/formHandlers.js +3 -3
  121. package/plugins/search/src/events/formHandlers.test.js +1 -1
  122. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  123. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  124. package/plugins/search/src/utils/updateMap.js +3 -3
  125. package/plugins/search/src/utils/updateMap.test.js +3 -3
  126. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  127. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  128. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  129. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  130. package/providers/maplibre/dist/umd/index.js +1 -1
  131. package/providers/maplibre/src/appEvents.js +7 -0
  132. package/providers/maplibre/src/appEvents.test.js +18 -4
  133. package/providers/maplibre/src/maplibreProvider.js +52 -0
  134. package/providers/maplibre/src/maplibreProvider.test.js +105 -1
  135. package/providers/maplibre/src/utils/highlightFeatures.js +37 -7
  136. package/providers/maplibre/src/utils/highlightFeatures.test.js +153 -95
  137. package/providers/maplibre/src/utils/hoverCursor.js +61 -0
  138. package/providers/maplibre/src/utils/hoverCursor.test.js +130 -0
  139. package/providers/maplibre/src/utils/patternImages.js +70 -0
  140. package/providers/maplibre/src/utils/patternImages.test.js +180 -0
  141. package/providers/maplibre/src/utils/queryFeatures.js +38 -16
  142. package/providers/maplibre/src/utils/queryFeatures.test.js +20 -3
  143. package/providers/maplibre/src/utils/rasteriseToImageData.js +30 -0
  144. package/providers/maplibre/src/utils/rasteriseToImageData.test.js +69 -0
  145. package/providers/maplibre/src/utils/symbolImages.js +147 -0
  146. package/providers/maplibre/src/utils/symbolImages.test.js +248 -0
  147. package/src/App/components/Actions/Actions.jsx +2 -2
  148. package/src/App/components/Actions/Actions.module.scss +0 -7
  149. package/src/App/components/Actions/Actions.test.jsx +1 -1
  150. package/src/App/components/Icon/Icon.jsx +3 -2
  151. package/src/App/components/Icon/Icon.module.scss +4 -0
  152. package/src/App/components/Icon/Icon.test.jsx +43 -4
  153. package/src/App/components/MapButton/MapButton.jsx +42 -17
  154. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  155. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  156. package/src/App/components/Markers/Markers.jsx +122 -27
  157. package/src/App/components/Markers/Markers.module.scss +0 -10
  158. package/src/App/components/Markers/Markers.test.jsx +246 -0
  159. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  160. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  161. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  162. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  163. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  164. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  165. package/src/App/hooks/useInterfaceAPI.js +6 -0
  166. package/src/App/hooks/useInterfaceAPI.test.js +156 -0
  167. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  168. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  169. package/src/App/hooks/useMarkersAPI.js +2 -5
  170. package/src/App/hooks/useMarkersAPI.test.js +4 -4
  171. package/src/App/layout/Layout.jsx +14 -9
  172. package/src/App/layout/Layout.test.jsx +6 -4
  173. package/src/App/layout/layout.module.scss +67 -29
  174. package/src/App/registry/pluginRegistry.js +1 -1
  175. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  176. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  177. package/src/App/renderer/mapButtons.js +1 -1
  178. package/src/App/renderer/mapPanels.test.js +2 -2
  179. package/src/App/renderer/slotHelpers.js +2 -2
  180. package/src/App/renderer/slotHelpers.test.js +5 -5
  181. package/src/App/renderer/slots.js +9 -5
  182. package/src/App/store/AppProvider.jsx +3 -1
  183. package/src/App/store/AppProvider.test.jsx +1 -1
  184. package/src/App/store/ServiceProvider.jsx +8 -4
  185. package/src/App/store/appActionsMap.js +16 -0
  186. package/src/App/store/appActionsMap.test.js +27 -0
  187. package/src/App/store/appDispatchMiddleware.js +1 -1
  188. package/src/App/store/appDispatchMiddleware.test.js +2 -2
  189. package/src/App/store/appReducer.js +2 -0
  190. package/src/App/store/mapActionsMap.js +4 -6
  191. package/src/App/store/mapActionsMap.test.js +3 -2
  192. package/src/App/store/mapReducer.js +2 -1
  193. package/src/InteractiveMap/InteractiveMap.js +4 -0
  194. package/src/config/appConfig.js +5 -8
  195. package/src/config/appConfig.test.js +1 -2
  196. package/src/config/defaults.js +0 -2
  197. package/src/config/events.js +28 -0
  198. package/src/config/mapTheme.js +56 -0
  199. package/src/config/patternConfig.js +16 -0
  200. package/src/config/symbolConfig.js +80 -0
  201. package/src/scss/main.scss +1 -0
  202. package/src/scss/settings/_colors.scss +0 -9
  203. package/src/scss/settings/_dimensions.scss +0 -1
  204. package/src/services/patternRegistry.js +40 -0
  205. package/src/services/patternRegistry.test.js +48 -0
  206. package/src/services/symbolRegistry.js +113 -0
  207. package/src/services/symbolRegistry.test.js +262 -0
  208. package/src/types.js +93 -11
  209. package/src/utils/getSafeZoneInset.js +9 -7
  210. package/src/utils/getSafeZoneInset.test.js +10 -10
  211. package/src/utils/patternUtils.js +94 -0
  212. package/src/utils/patternUtils.test.js +160 -0
  213. package/src/utils/symbolUtils.js +85 -0
  214. package/src/utils/symbolUtils.test.js +156 -0
  215. package/webpack.dev.mjs +1 -1
  216. package/docs/api/slot-map.svg +0 -1
  217. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  218. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  219. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  220. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  221. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  222. package/plugins/beta/datasets/src/mapLayers.js +0 -164
  223. /package/src/{utils → services}/logger.js +0 -0
  224. /package/src/{utils → services}/logger.test.js +0 -0
@@ -3,12 +3,15 @@ import { initialState, actions } from './reducer.js'
3
3
  import { DatasetsInit } from './DatasetsInit.jsx'
4
4
  import { Layers } from './panels/Layers.jsx'
5
5
  import { Key } from './panels/Key.jsx'
6
- import { showDataset } from './api/showDataset.js'
7
- import { hideDataset } from './api/hideDataset.js'
8
6
  import { addDataset } from './api/addDataset.js'
9
7
  import { removeDataset } from './api/removeDataset.js'
10
- import { showFeatures } from './api/showFeatures.js'
11
- import { hideFeatures } from './api/hideFeatures.js'
8
+ import { setDatasetVisibility } from './api/setDatasetVisibility.js'
9
+ import { setFeatureVisibility } from './api/setFeatureVisibility.js'
10
+ import { setStyle } from './api/setStyle.js'
11
+ import { getStyle } from './api/getStyle.js'
12
+ import { setOpacity } from './api/setOpacity.js'
13
+ import { getOpacity } from './api/getOpacity.js'
14
+ import { setData } from './api/setData.js'
12
15
 
13
16
  export const manifest = {
14
17
  InitComponent: DatasetsInit,
@@ -22,7 +25,7 @@ export const manifest = {
22
25
  id: 'datasetsLayers',
23
26
  label: 'Layers',
24
27
  mobile: {
25
- slot: 'bottom',
28
+ slot: 'drawer',
26
29
  modal: true,
27
30
  dismissible: true
28
31
  },
@@ -30,30 +33,30 @@ export const manifest = {
30
33
  slot: 'left-top',
31
34
  dismissible: true,
32
35
  exclusive: true,
33
- width: '300px'
36
+ width: '260px'
34
37
  },
35
38
  desktop: {
36
39
  slot: 'left-top',
37
40
  modal: false,
38
41
  dismissible: true,
39
42
  exclusive: true,
40
- width: '320px'
43
+ width: '280px'
41
44
  },
42
45
  render: Layers
43
46
  }, {
44
47
  id: 'datasetsKey',
45
48
  label: 'Key',
46
49
  mobile: {
47
- slot: 'bottom',
50
+ slot: 'drawer',
48
51
  modal: true
49
52
  },
50
53
  tablet: {
51
54
  slot: 'left-top',
52
- width: '300px'
55
+ width: '260px'
53
56
  },
54
57
  desktop: {
55
58
  slot: 'left-top',
56
- width: '320px'
59
+ width: '280px'
57
60
  },
58
61
  render: Key
59
62
  }],
@@ -63,7 +66,9 @@ export const manifest = {
63
66
  label: 'Layers',
64
67
  panelId: 'datasetsLayers',
65
68
  iconId: 'layers',
66
- excludeWhen: ({ pluginConfig }) => !pluginConfig.datasets.find(l => l.showInLayers),
69
+ excludeWhen: ({ pluginConfig }) => !pluginConfig.datasets.some(l =>
70
+ l.toggleVisibility || l.sublayers?.some(r => r.toggleVisibility)
71
+ ),
67
72
  mobile: {
68
73
  slot: 'top-left',
69
74
  showLabel: true
@@ -81,7 +86,7 @@ export const manifest = {
81
86
  label: 'Key',
82
87
  panelId: 'datasetsKey',
83
88
  iconId: 'key',
84
- excludeWhen: ({ pluginConfig }) => !pluginConfig.datasets.find(l => l.showInKey),
89
+ excludeWhen: ({ pluginConfig }) => !pluginConfig.datasets.some(l => l.showInKey),
85
90
  mobile: {
86
91
  slot: 'top-left',
87
92
  showLabel: false
@@ -98,18 +103,21 @@ export const manifest = {
98
103
 
99
104
  icons: [{
100
105
  id: 'layers',
101
- svgContent: '<path d="M13 13.74a2 2 0 0 1-2 0L2.5 8.87a1 1 0 0 1 0-1.74L11 2.26a2 2 0 0 1 2 0l8.5 4.87a1 1 0 0 1 0 1.74z"></path><path d="m20 14.285 1.5.845a1 1 0 0 1 0 1.74L13 21.74a2 2 0 0 1-2 0l-8.5-4.87a1 1 0 0 1 0-1.74l1.5-.845"></path>'
106
+ svgContent: '<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"></path><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"></path><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"></path>'
102
107
  }, {
103
108
  id: 'key',
104
109
  svgContent: '<path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/>'
105
110
  }],
106
111
 
107
112
  api: {
108
- showDataset,
109
- hideDataset,
110
113
  addDataset,
111
114
  removeDataset,
112
- showFeatures,
113
- hideFeatures
115
+ setDatasetVisibility,
116
+ setFeatureVisibility,
117
+ setStyle,
118
+ getStyle,
119
+ setOpacity,
120
+ getOpacity,
121
+ setData
114
122
  }
115
123
  }
@@ -1,62 +1,62 @@
1
1
  import React from 'react'
2
2
  import { getValueForStyle } from '../../../../../src/utils/getValueForStyle'
3
+ import { mergeSublayer } from '../utils/mergeSublayer.js'
4
+ import { EmptyKey } from '../components/EmptyKey.jsx'
5
+ import { KeySvg } from '../components/KeySvg.jsx'
3
6
 
4
- export const Key = ({ mapState, pluginState }) => {
7
+ export const Key = ({ pluginConfig, mapState, pluginState, services }) => {
8
+ if (!pluginState?.key?.items?.length) {
9
+ return (<EmptyKey text={pluginConfig.noKeyItemText} />)
10
+ }
5
11
  const { mapStyle } = mapState
12
+ const { symbolRegistry, patternRegistry } = services
6
13
 
7
- const itemSymbol = (dataset) => (
8
- <svg
9
- xmlns='http://www.w3.org/2000/svg'
10
- width='20'
11
- height='20'
12
- viewBox='0 0 20 20'
13
- aria-hidden='true'
14
- focusable='false'
15
- >
16
- {dataset.keySymbolShape === 'line'
17
- ? (
18
- <line
19
- x1={dataset.strokeWidth / 2}
20
- y1='10'
21
- x2={20 - dataset.strokeWidth / 2}
22
- y2='10'
23
- stroke={getValueForStyle(dataset.stroke, mapStyle.id)}
24
- strokeWidth={dataset.strokeWidth}
25
- strokeLinecap='round'
26
- />
27
- )
28
- : (
29
- <rect
30
- x={dataset.strokeWidth / 2}
31
- y={dataset.strokeWidth / 2}
32
- width={20 - dataset.strokeWidth}
33
- height={20 - dataset.strokeWidth}
34
- rx={dataset.strokeWidth}
35
- ry={dataset.strokeWidth}
36
- fill={getValueForStyle(dataset.fill, mapStyle.id)}
37
- stroke={getValueForStyle(dataset.stroke, mapStyle.id)}
38
- strokeWidth={dataset.strokeWidth}
39
- strokeLinejoin='round'
40
- />
41
- )}
42
- </svg>
14
+ const renderEntry = (key, config) => (
15
+ <dl key={key} className='im-c-datasets-key__item'>
16
+ <dt className='im-c-datasets-key__item-symbol'>
17
+ <KeySvg {...config} symbolRegistry={symbolRegistry} patternRegistry={patternRegistry} mapStyle={mapStyle} />
18
+ </dt>
19
+ <dd className='im-c-datasets-key__item-label'>
20
+ {config.label}
21
+ {config.symbolDescription && (
22
+ <span className='govuk-visually-hidden'>
23
+ ({getValueForStyle(config.symbolDescription, mapStyle.id)})
24
+ </span>
25
+ )}
26
+ </dd>
27
+ </dl>
43
28
  )
44
29
 
30
+ const { items: keyGroups, hasGroups } = pluginState.key
31
+ const containerClass = `im-c-datasets-key${hasGroups ? ' im-c-datasets-key--has-groups' : ''}`
32
+
45
33
  return (
46
- <div className='im-c-datasets-key'>
47
- {(pluginState.datasets || []).filter(dataset => dataset.showInKey && dataset.visibility !== 'hidden').map(dataset => (
48
- <div key={dataset.id} className='im-c-datasets-key__item'>
49
- <div className='im-c-datasets-key__item-label'>
50
- {itemSymbol(dataset)}
51
- {dataset.label}
52
- {dataset.symbolDescription && (
53
- <span className='govuk-visually-hidden'>
54
- ({getValueForStyle(dataset.symbolDescription, mapStyle.id)})
55
- </span>
56
- )}
57
- </div>
58
- </div>
59
- ))}
34
+ <div className={containerClass}>
35
+ {keyGroups.map(item => {
36
+ if (item.type === 'sublayers') {
37
+ const headingId = `key-heading-${item.dataset.id}`
38
+ return (
39
+ <section key={item.dataset.id} className='im-c-datasets-key__group' aria-labelledby={headingId}>
40
+ <h3 id={headingId} className='im-c-datasets-key__group-heading'>{item.dataset.label}</h3>
41
+ {item.dataset.sublayers
42
+ .filter(sublayer => item.dataset.sublayerVisibility?.[sublayer.id] !== 'hidden')
43
+ .map(sublayer => renderEntry(`${item.dataset.id}-${sublayer.id}`, mergeSublayer(item.dataset, sublayer)))}
44
+ </section>
45
+ )
46
+ }
47
+
48
+ if (item.type === 'group') {
49
+ const headingId = `key-heading-${item.groupLabel.toLowerCase().replaceAll(/\s+/g, '-')}`
50
+ return (
51
+ <section key={item.groupLabel} className='im-c-datasets-key__group' aria-labelledby={headingId}>
52
+ <h3 id={headingId} className='im-c-datasets-key__group-heading'>{item.groupLabel}</h3>
53
+ {item.datasets.map(dataset => renderEntry(dataset.id, dataset))}
54
+ </section>
55
+ )
56
+ }
57
+
58
+ return renderEntry(item.dataset.id, item.dataset)
59
+ })}
60
60
  </div>
61
61
  )
62
62
  }
@@ -1,19 +1,69 @@
1
- .im-c-datasets-key {
1
+ // When groups are present, every direct child gets a border-top
2
+ .im-c-datasets-key > *:first-child {
3
+ padding-top: 5px;
2
4
 
5
+ .im-c-datasets-key__group-heading {
6
+ padding-top: 0;
7
+ }
3
8
  }
4
9
 
5
- .im-c-datasets-key__item-label {
10
+ .im-c-datasets-key--has-groups > *:not(:first-child) {
11
+ border-top: 1px solid var(--button-hover-color);
12
+ }
13
+
14
+ .im-c-datasets-key__group:not(:last-child) {
15
+ padding-bottom: 5px;
16
+ }
17
+
18
+ .im-c-datasets-key__group-heading {
19
+ padding-top: 15px;
20
+ padding-bottom: 10px;
21
+ margin: 0;
22
+ font-size: 1rem;
23
+ font-weight: bold;
24
+ color: var(--foreground-color);
25
+ }
26
+
27
+ .im-c-datasets-key__item {
6
28
  display: flex;
7
29
  align-items: start;
8
- padding-top: 12px;
9
- padding-bottom: 12px;
10
- align-self: auto;
30
+ padding-top: 10px;
31
+ padding-bottom: 10px;
11
32
  font-size: 1rem;
12
- line-height: 1.2;
33
+
34
+ // First item inside a group — heading already provides top spacing
35
+ .im-c-datasets-key__group &:first-child {
36
+ padding-top: 0;
37
+ }
38
+ }
39
+
40
+ .im-c-datasets-key--has-groups > .im-c-datasets-key__item:not(:first-child) {
41
+ padding-top: 15px;
13
42
  }
14
43
 
15
- .im-c-datasets-key__item-label svg {
44
+ .im-c-datasets-key--has-groups > .im-c-datasets-key__item {
45
+ padding-bottom: 15px;
46
+ }
47
+
48
+ .im-c-datasets-key__group .im-c-datasets-key__item {
49
+ padding-top: 10px;
50
+ padding-bottom: 10px;
51
+ }
52
+
53
+ // Last item in all scenarios — flat or inside the last group
54
+ .im-c-datasets-key > .im-c-datasets-key__item:last-child,
55
+ .im-c-datasets-key__group:last-child .im-c-datasets-key__item:last-child {
56
+ padding-bottom: 5px;
57
+ }
58
+
59
+ // Symbol
60
+
61
+ .am-c-datasets-key-symbol {
16
62
  position: relative;
17
63
  flex-shrink: 0;
18
- margin: 0px 13px 0 2px;
19
- }
64
+ margin: 0 13px 0 2px;
65
+ }
66
+
67
+ .am-c-datasets-key-symbol--point {
68
+ margin: -9px 3px -9px -7px;
69
+ }
@@ -1,38 +1,141 @@
1
1
  import React from 'react'
2
- import { showDataset } from '../api/showDataset'
3
- import { hideDataset } from '../api/hideDataset'
2
+ import { setDatasetVisibility } from '../api/setDatasetVisibility'
4
3
 
5
- export const Layers = ({ pluginState, mapProvider }) => {
6
- const handleChange = (e) => {
7
- const { value, checked } = e.target
8
- if (checked) {
9
- showDataset({ mapProvider, pluginState }, value)
10
- } else {
11
- hideDataset({ mapProvider, pluginState }, value)
4
+ const CHECKBOX_LABEL_CLASS = 'im-c-datasets-layers__item-label govuk-label govuk-checkboxes__label'
5
+
6
+ const hasToggleableSublayers = (dataset) => dataset.sublayers?.some(sublayer => sublayer.toggleVisibility)
7
+
8
+ /**
9
+ * Collapse the filtered dataset list into ordered render items:
10
+ * { type: 'sublayers', dataset } dataset with sublayers (takes precedence)
11
+ * { type: 'group', groupLabel, datasets } — datasets sharing a groupLabel
12
+ * { type: 'flat', dataset } — standalone dataset
13
+ */
14
+ const buildRenderItems = (datasets) => {
15
+ const seenGroups = new Set()
16
+ const items = []
17
+ datasets.forEach(dataset => {
18
+ if (hasToggleableSublayers(dataset)) {
19
+ items.push({ type: 'sublayers', dataset })
20
+ return
12
21
  }
22
+ if (dataset.groupLabel) {
23
+ if (seenGroups.has(dataset.groupLabel)) {
24
+ return
25
+ }
26
+ seenGroups.add(dataset.groupLabel)
27
+ items.push({
28
+ type: 'group',
29
+ groupLabel: dataset.groupLabel,
30
+ datasets: datasets.filter(d => !hasToggleableSublayers(d) && d.groupLabel === dataset.groupLabel)
31
+ })
32
+ return
33
+ }
34
+ items.push({ type: 'flat', dataset })
35
+ })
36
+ return items
37
+ }
38
+
39
+ export const Layers = ({ pluginState }) => {
40
+ const handleDatasetChange = (e) => {
41
+ const { value, checked } = e.target
42
+ setDatasetVisibility({ pluginState }, checked, { datasetId: value })
13
43
  }
14
44
 
15
- return (
16
- <div className='im-c-datasets-layers'>
17
- <div className='govuk-form-group'>
18
- <fieldset className='govuk-fieldset'>
19
- <legend className='govuk-visually-hidden'>
20
- Layers
21
- </legend>
22
- <div className='govuk-checkboxes govuk-checkboxes--small' data-module='govuk-checkboxes'>
23
- {(pluginState.datasets || []).filter(dataset => dataset.showInLayers).map(dataset => (
24
- <div key={dataset.id} className={`im-c-datasets-layers__item${dataset.visibility !== 'hidden' ? ' im-c-datasets-layers__item--checked' : ''}`}>
25
- <div className='govuk-checkboxes__item'>
26
- <input className='govuk-checkboxes__input' id={dataset.id} name='layers' type='checkbox' value={dataset.id} checked={dataset.visibility !== 'hidden'} onChange={handleChange} />
27
- <label className='im-c-datasets-layers__item-label govuk-label govuk-checkboxes__label' htmlFor={dataset.id}>
28
- {dataset.label}
29
- </label>
30
- </div>
31
- </div>
32
- ))}
33
- </div>
34
- </fieldset>
45
+ const handleSublayerChange = (e) => {
46
+ const { checked } = e.target
47
+ const datasetId = e.target.dataset.datasetId
48
+ const sublayerId = e.target.dataset.sublayerId
49
+ setDatasetVisibility({ pluginState }, checked, { datasetId, sublayerId })
50
+ }
51
+
52
+ const renderDatasetItem = (dataset) => {
53
+ const itemClass = `im-c-datasets-layers__item govuk-checkboxes govuk-checkboxes--small${dataset.visibility === 'hidden' ? '' : ' im-c-datasets-layers__item--checked'}`
54
+ return (
55
+ <div key={dataset.id} className={itemClass} data-module='govuk-checkboxes'>
56
+ <div className='govuk-checkboxes__item'>
57
+ <input
58
+ className='govuk-checkboxes__input'
59
+ id={dataset.id}
60
+ name='layers'
61
+ type='checkbox'
62
+ value={dataset.id}
63
+ checked={dataset.visibility !== 'hidden'}
64
+ onChange={handleDatasetChange}
65
+ />
66
+ <label className={CHECKBOX_LABEL_CLASS} htmlFor={dataset.id}>
67
+ {dataset.label}
68
+ </label>
69
+ </div>
35
70
  </div>
71
+ )
72
+ }
73
+
74
+ const visibleDatasets = (pluginState.datasets || [])
75
+ .filter(dataset => dataset.toggleVisibility || hasToggleableSublayers(dataset))
76
+
77
+ const renderItems = buildRenderItems(visibleDatasets)
78
+ const hasGroups = renderItems.some(item => item.type === 'sublayers' || item.type === 'group')
79
+ const containerClass = `im-c-datasets-layers${hasGroups ? ' im-c-datasets-layers--has-groups' : ''}`
80
+
81
+ return (
82
+ <div className={containerClass}>
83
+ {renderItems.map(item => {
84
+ if (item.type === 'sublayers') {
85
+ const { dataset } = item
86
+ const anySublayerChecked = dataset.sublayers
87
+ .filter(sublayer => sublayer.toggleVisibility)
88
+ .some(sublayer => dataset.sublayerVisibility?.[sublayer.id] !== 'hidden')
89
+ const wrapperClass = `govuk-form-group im-c-datasets-layers-group${anySublayerChecked ? ' im-c-datasets-layers-group--items-checked' : ''}`
90
+ return (
91
+ <div key={dataset.id} className={wrapperClass}>
92
+ <fieldset className='im-c-datasets-layers-group__fieldset'>
93
+ <legend className='im-c-datasets-layers-group__legend'>{dataset.label}</legend>
94
+ {dataset.sublayers
95
+ .filter(sublayer => sublayer.toggleVisibility)
96
+ .map(sublayer => {
97
+ const sublayerVisible = dataset.sublayerVisibility?.[sublayer.id] !== 'hidden'
98
+ const inputId = `${dataset.id}-${sublayer.id}`
99
+ const itemClass = `im-c-datasets-layers__item govuk-checkboxes govuk-checkboxes--small${sublayerVisible ? ' im-c-datasets-layers__item--checked' : ''}`
100
+ return (
101
+ <div key={sublayer.id} className={itemClass} data-module='govuk-checkboxes'>
102
+ <div className='govuk-checkboxes__item'>
103
+ <input
104
+ className='govuk-checkboxes__input'
105
+ id={inputId}
106
+ type='checkbox'
107
+ checked={sublayerVisible}
108
+ data-dataset-id={dataset.id}
109
+ data-sublayer-id={sublayer.id}
110
+ onChange={handleSublayerChange}
111
+ />
112
+ <label className={CHECKBOX_LABEL_CLASS} htmlFor={inputId}>
113
+ {sublayer.label}
114
+ </label>
115
+ </div>
116
+ </div>
117
+ )
118
+ })}
119
+ </fieldset>
120
+ </div>
121
+ )
122
+ }
123
+
124
+ if (item.type === 'group') {
125
+ const anyDatasetChecked = item.datasets.some(d => d.visibility !== 'hidden')
126
+ const wrapperClass = `govuk-form-group im-c-datasets-layers-group${anyDatasetChecked ? ' im-c-datasets-layers-group--items-checked' : ''}`
127
+ return (
128
+ <div key={item.groupLabel} className={wrapperClass}>
129
+ <fieldset className='im-c-datasets-layers-group__fieldset'>
130
+ <legend className='im-c-datasets-layers-group__legend'>{item.groupLabel}</legend>
131
+ {item.datasets.map(dataset => renderDatasetItem(dataset))}
132
+ </fieldset>
133
+ </div>
134
+ )
135
+ }
136
+
137
+ return renderDatasetItem(item.dataset)
138
+ })}
36
139
  </div>
37
140
  )
38
141
  }
@@ -1,8 +1,39 @@
1
+ // When groups are present, every direct child except the first gets a border-top
2
+ .im-c-datasets-layers--has-groups > *:not(:first-child) {
3
+ border-top: 1px solid var(--button-hover-color);
4
+ }
5
+
6
+ .im-c-datasets-layers > *:first-child {
7
+ padding-top: 5px;
8
+ }
9
+
10
+ .im-c-datasets-layers > *:last-child {
11
+ margin-bottom: -5px;
12
+ }
13
+
14
+ .im-c-datasets-layers-group:not(:last-child) {
15
+ padding-bottom: 5px;
16
+ }
17
+
1
18
  .im-c-datasets-layers__item {
2
- border: 1px solid var(--button-hover-color);
3
- padding-left: 10px;
4
- &:not(last-child) {
5
- margin-bottom: 5px;
19
+ padding-bottom: 5px;
20
+
21
+ &:first-child {
22
+ padding-top: 0;
23
+ }
24
+
25
+ .im-c-datasets-layers--has-groups > &:not(:first-child) {
26
+ padding-top: 5px;
27
+ }
28
+
29
+ // Items inside a group have no individual padding or spacing between them
30
+ .im-c-datasets-layers-group & {
31
+ padding-top: 0;
32
+ padding-bottom: 0;
33
+
34
+ &:not(:last-child) {
35
+ margin-bottom: 0;
36
+ }
6
37
  }
7
38
  }
8
39
 
@@ -19,10 +50,6 @@
19
50
  color: var(--foreground-color);
20
51
  }
21
52
 
22
- .im-c-datasets-layers__item--checked {
23
- border-color: var(--app-border-color);
24
- }
25
-
26
53
  // GovUK style overide
27
54
  .im-c-datasets-layers__item .govuk-checkboxes__item {
28
55
  flex-wrap: nowrap;
@@ -31,3 +58,24 @@
31
58
  margin-left: -3px;
32
59
  }
33
60
  }
61
+
62
+ .im-c-datasets-layers-group__fieldset {
63
+ // Reset browser fieldset defaults
64
+ border: none;
65
+ padding: 0;
66
+ margin: 0;
67
+ min-width: 0;
68
+ }
69
+
70
+ .im-c-datasets-layers-group__legend {
71
+ padding-top: 15px;
72
+ padding-bottom: 10px;
73
+ padding-inline: 0;
74
+ font-size: 1rem;
75
+ font-weight: bold;
76
+ color: var(--foreground-color);
77
+ }
78
+
79
+ .im-c-datasets-layers-group:first-child .im-c-datasets-layers-group__legend {
80
+ padding-top: 0;
81
+ }