@defra/interactive-map 0.0.15-alpha → 0.0.17-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 (263) hide show
  1. package/assets/css/docusaurus.css +104 -0
  2. package/assets/images/favicon.svg +1 -0
  3. package/assets/images/hero.png +0 -0
  4. package/assets/images/slot-map.svg +264 -0
  5. package/dist/css/index.css +1 -1
  6. package/dist/esm/im-core.js +1 -1
  7. package/dist/esm/im-shell.js +1 -1
  8. package/dist/umd/im-core.js +1 -1
  9. package/dist/umd/index.js +1 -1
  10. package/docs/api/slots.md +90 -6
  11. package/docs/api.md +4 -4
  12. package/docs/architecture.md +3 -1
  13. package/docs/{demo.mdx → examples.mdx} +1 -1
  14. package/docs/getting-started.md +5 -4
  15. package/docs/index.mdx +42 -0
  16. package/docs/plugins/datasets.md +561 -0
  17. package/docs/plugins/interact.md +176 -55
  18. package/docs/plugins/map-styles.md +64 -7
  19. package/docs/plugins/search.md +207 -63
  20. package/docs/plugins.md +8 -16
  21. package/docusaurus.config.cjs +34 -34
  22. package/jest.setup.js +1 -1
  23. package/package.json +6 -5
  24. package/plugins/beta/datasets/dist/css/index.css +85 -15
  25. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  26. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  27. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  28. package/plugins/beta/datasets/src/DatasetsInit.jsx +24 -9
  29. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  30. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
  31. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
  32. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
  33. package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
  34. package/plugins/beta/datasets/src/api/addDataset.js +3 -9
  35. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  36. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  37. package/plugins/beta/datasets/src/api/removeDataset.js +3 -45
  38. package/plugins/beta/datasets/src/api/setData.js +8 -0
  39. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  40. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  41. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  42. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  43. package/plugins/beta/datasets/src/datasets.js +33 -59
  44. package/plugins/beta/datasets/src/defaults.js +43 -9
  45. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +39 -30
  46. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  47. package/plugins/beta/datasets/src/manifest.js +27 -19
  48. package/plugins/beta/datasets/src/panels/Key.jsx +129 -49
  49. package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
  50. package/plugins/beta/datasets/src/panels/Layers.jsx +131 -29
  51. package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
  52. package/plugins/beta/datasets/src/reducer.js +128 -9
  53. package/plugins/beta/datasets/src/styles/patterns.js +157 -0
  54. package/plugins/beta/datasets/src/utils/bbox.js +8 -6
  55. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  56. package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
  57. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  58. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  59. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  60. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  61. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  62. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  63. package/plugins/beta/draw-es/src/events.js +52 -20
  64. package/plugins/beta/draw-es/src/events.test.js +301 -0
  65. package/plugins/beta/draw-es/src/graphic.js +1 -1
  66. package/plugins/beta/draw-es/src/manifest.js +4 -4
  67. package/plugins/beta/draw-es/src/reducer.js +1 -1
  68. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  69. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  70. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  71. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  72. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  73. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  74. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  75. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  76. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  77. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  78. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  79. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  80. package/plugins/beta/draw-ml/src/events.js +8 -6
  81. package/plugins/beta/draw-ml/src/manifest.js +29 -29
  82. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  83. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  84. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  85. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  86. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  87. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  88. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  89. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  90. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  91. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  92. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  93. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  94. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  95. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  96. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  97. package/plugins/beta/frame/src/Frame.jsx +9 -9
  98. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  99. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  100. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  101. package/plugins/beta/frame/src/config.js +1 -1
  102. package/plugins/beta/frame/src/manifest.js +3 -3
  103. package/plugins/beta/frame/src/reducer.js +1 -1
  104. package/plugins/beta/frame/src/utils.js +1 -1
  105. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  106. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  107. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  108. package/plugins/beta/map-styles/src/manifest.js +1 -1
  109. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  110. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  111. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  112. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  113. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  114. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  115. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  116. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  117. package/plugins/beta/use-location/src/defaults.js +1 -1
  118. package/plugins/beta/use-location/src/events.js +3 -3
  119. package/plugins/interact/dist/css/index.css +1 -1
  120. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  121. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  122. package/plugins/interact/src/InteractInit.jsx +1 -2
  123. package/plugins/interact/src/api/enable.js +8 -5
  124. package/plugins/interact/src/api/enable.test.js +2 -2
  125. package/plugins/interact/src/api/selectFeature.js +4 -4
  126. package/plugins/interact/src/api/unselectFeature.js +5 -5
  127. package/plugins/interact/src/defaults.js +0 -1
  128. package/plugins/interact/src/events.test.js +15 -15
  129. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  130. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  131. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  132. package/plugins/interact/src/interact.scss +0 -7
  133. package/plugins/interact/src/manifest.js +15 -19
  134. package/plugins/interact/src/manifest.test.js +6 -5
  135. package/plugins/interact/src/reducer.js +3 -3
  136. package/plugins/interact/src/reducer.test.js +0 -1
  137. package/plugins/interact/src/utils/spatial.js +10 -10
  138. package/plugins/interact/src/utils/spatial.test.js +14 -14
  139. package/plugins/search/dist/css/index.css +1 -1
  140. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  141. package/plugins/search/dist/esm/index.js +1 -1
  142. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  143. package/plugins/search/dist/umd/index.js +1 -1
  144. package/plugins/search/src/Search.jsx +7 -6
  145. package/plugins/search/src/Search.test.jsx +23 -23
  146. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  147. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  148. package/plugins/search/src/components/Form/Form.jsx +14 -14
  149. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  150. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  151. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  152. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  153. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  154. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  155. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  156. package/plugins/search/src/datasets.js +12 -13
  157. package/plugins/search/src/datasets.test.js +1 -1
  158. package/plugins/search/src/defaults.js +1 -1
  159. package/plugins/search/src/events/fetchSuggestions.js +3 -3
  160. package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
  161. package/plugins/search/src/events/formHandlers.js +3 -3
  162. package/plugins/search/src/events/formHandlers.test.js +1 -1
  163. package/plugins/search/src/events/index.js +2 -2
  164. package/plugins/search/src/events/index.test.js +2 -2
  165. package/plugins/search/src/events/inputHandlers.js +4 -4
  166. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  167. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  168. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  169. package/plugins/search/src/index.js +2 -1
  170. package/plugins/search/src/index.test.js +3 -3
  171. package/plugins/search/src/manifest.js +6 -4
  172. package/plugins/search/src/reducer.js +1 -2
  173. package/plugins/search/src/reducer.test.js +2 -2
  174. package/plugins/search/src/search.scss +10 -3
  175. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  176. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  177. package/plugins/search/src/utils/updateMap.js +1 -1
  178. package/plugins/search/src/utils/updateMap.test.js +5 -5
  179. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  180. package/providers/beta/esri/src/esriProvider.js +5 -5
  181. package/providers/beta/esri/src/utils/coords.js +1 -1
  182. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  183. package/providers/beta/esri/src/utils/query.js +4 -4
  184. package/providers/beta/esri/src/utils/spatial.js +1 -2
  185. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  186. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  187. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  188. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  189. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  190. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  191. package/providers/maplibre/src/appEvents.test.js +1 -1
  192. package/providers/maplibre/src/index.js +1 -1
  193. package/providers/maplibre/src/index.test.js +3 -5
  194. package/providers/maplibre/src/mapEvents.test.js +15 -5
  195. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  196. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  197. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  198. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  199. package/providers/maplibre/src/utils/highlightFeatures.js +3 -2
  200. package/providers/maplibre/src/utils/highlightFeatures.test.js +13 -6
  201. package/providers/maplibre/src/utils/labels.js +19 -20
  202. package/providers/maplibre/src/utils/labels.test.js +15 -13
  203. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  204. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  205. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  206. package/providers/maplibre/src/utils/spatial.js +0 -1
  207. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  208. package/src/App/components/Actions/Actions.jsx +2 -2
  209. package/src/App/components/Actions/Actions.module.scss +0 -7
  210. package/src/App/components/Actions/Actions.test.jsx +1 -1
  211. package/src/App/components/Icon/Icon.jsx +3 -2
  212. package/src/App/components/Icon/Icon.module.scss +4 -0
  213. package/src/App/components/Icon/Icon.test.jsx +43 -4
  214. package/src/App/components/MapButton/MapButton.jsx +42 -17
  215. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  216. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  217. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  218. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  219. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  220. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  221. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  222. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  223. package/src/App/hooks/useInterfaceAPI.js +6 -0
  224. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  225. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  226. package/src/App/layout/Layout.jsx +12 -7
  227. package/src/App/layout/Layout.test.jsx +2 -2
  228. package/src/App/layout/layout.module.scss +67 -29
  229. package/src/App/registry/pluginRegistry.js +17 -0
  230. package/src/App/registry/pluginRegistry.test.js +33 -0
  231. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  232. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  233. package/src/App/renderer/mapButtons.js +3 -2
  234. package/src/App/renderer/mapPanels.test.js +2 -2
  235. package/src/App/renderer/slotHelpers.js +2 -2
  236. package/src/App/renderer/slotHelpers.test.js +5 -5
  237. package/src/App/renderer/slots.js +9 -5
  238. package/src/App/store/AppProvider.jsx +3 -1
  239. package/src/App/store/AppProvider.test.jsx +1 -1
  240. package/src/App/store/ServiceProvider.jsx +3 -1
  241. package/src/App/store/appActionsMap.js +16 -0
  242. package/src/App/store/appActionsMap.test.js +27 -0
  243. package/src/App/store/appDispatchMiddleware.js +33 -1
  244. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  245. package/src/App/store/appReducer.js +2 -0
  246. package/src/InteractiveMap/InteractiveMap.js +4 -0
  247. package/src/config/appConfig.js +7 -4
  248. package/src/config/events.js +28 -0
  249. package/src/scss/main.scss +1 -0
  250. package/src/scss/settings/_dimensions.scss +0 -1
  251. package/src/services/logger.js +6 -0
  252. package/src/services/logger.test.js +32 -0
  253. package/src/utils/getSafeZoneInset.js +9 -7
  254. package/src/utils/getSafeZoneInset.test.js +10 -10
  255. package/webpack.dev.mjs +23 -19
  256. package/docs/govuk-prototype.md +0 -23
  257. package/docs/index.md +0 -19
  258. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  259. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  260. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  261. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  262. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  263. package/plugins/beta/datasets/src/mapLayers.js +0 -165
@@ -27,7 +27,7 @@
27
27
  * @param {React.RefObject} refs.leftRef - Left button column.
28
28
  * @param {React.RefObject} refs.rightRef - Right button column.
29
29
  * @param {React.RefObject} refs.actionsRef - Bottom action bar.
30
- * @param {React.RefObject} refs.footerRef - Footer (logo, copyright, etc).
30
+ * @param {React.RefObject} refs.bottomRef - Bottom row (logo, copyright, etc).
31
31
  * @param {React.RefObject} [refs.leftTopRef] - Top-left panel slot.
32
32
  * @param {React.RefObject} [refs.leftBottomRef] - Bottom-left panel slot.
33
33
  * @param {React.RefObject} [refs.rightTopRef] - Top-right panel slot.
@@ -107,15 +107,15 @@ const computeRow = (leftW, rightW, leftH, rightH, wThreshold, baseInset, gap) =>
107
107
  leftW + rightW > wThreshold ? baseInset + Math.max(leftH, rightH) + gap : 0
108
108
 
109
109
  export const getSafeZoneInset = ({
110
- mainRef, leftRef, rightRef, actionsRef, footerRef,
110
+ mainRef, leftRef, rightRef, actionsRef, bottomRef,
111
111
  leftTopRef, leftBottomRef, rightTopRef, rightBottomRef
112
112
  }) => {
113
- if ([mainRef, leftRef, rightRef, actionsRef, footerRef].some(ref => !ref?.current)) {
113
+ if ([mainRef, leftRef, rightRef, actionsRef, bottomRef].some(ref => !ref?.current)) {
114
114
  return undefined
115
115
  }
116
116
 
117
117
  const main = mainRef.current; const left = leftRef.current
118
- const actions = actionsRef.current; const footer = footerRef.current
118
+ const actions = actionsRef.current; const bottom = bottomRef.current
119
119
 
120
120
  const gap = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--divider-gap'), 10)
121
121
 
@@ -127,8 +127,10 @@ export const getSafeZoneInset = ({
127
127
  const baseLeft = main.offsetLeft + left.offsetLeft + colWidth + gap
128
128
  const baseRight = left.offsetLeft + colWidth + gap
129
129
  const baseTop = left.offsetTop
130
- const footerInset = main.offsetHeight - footer.offsetTop + gap // mirrors --left/right-offset-bottom CSS var
131
- const baseBottom = Math.max(main.offsetHeight - actions.offsetTop + gap, footerInset)
130
+ const bottomContainerPad = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
131
+ // Minimum: primary-gap above the bottom edge. Normally: divider-gap above the top of the bottom container.
132
+ const bottomInset = Math.max(bottomContainerPad, main.offsetHeight - bottom.offsetTop + gap)
133
+ const baseBottom = Math.max(main.offsetHeight - actions.offsetTop + gap, bottomInset)
132
134
 
133
135
  const availableH = main.offsetHeight - baseTop - baseBottom
134
136
  const availableW = main.offsetWidth - (baseLeft - main.offsetLeft) - baseRight
@@ -139,7 +141,7 @@ export const getSafeZoneInset = ({
139
141
  const leftPanelInset = leftCol.panelInset
140
142
  const rightPanelInset = rightCol.panelInset
141
143
  const topPanelInset = computeRow(leftCol.rowA.w, rightCol.rowA.w, leftCol.rowA.h, rightCol.rowA.h, availableW / RATIO, baseTop, gap)
142
- const bottomPanelInset = computeRow(leftCol.rowB.w, rightCol.rowB.w, leftCol.rowB.h, rightCol.rowB.h, availableW / RATIO, footerInset, gap)
144
+ const bottomPanelInset = computeRow(leftCol.rowB.w, rightCol.rowB.w, leftCol.rowB.h, rightCol.rowB.h, availableW / RATIO, bottomInset, gap)
143
145
 
144
146
  const usableW = main.offsetWidth - 2 * gap
145
147
  const usableH = main.offsetHeight - 2 * gap
@@ -7,7 +7,7 @@ const LEFT_WIDTH = 40
7
7
  const LEFT_TOP = 60
8
8
  const RIGHT_WIDTH = 40
9
9
  const ACTIONS_TOP = 540
10
- const FOOTER_TOP = 560
10
+ const BOTTOM_TOP = 560
11
11
  const EARLY_ACTIONS_TOP = 500
12
12
  const GAP = 8
13
13
 
@@ -31,7 +31,7 @@ const PANEL_H_TALL = 150
31
31
  const PANEL_H_SHORT = 100
32
32
 
33
33
  const ABOVE_CAP_TOP = 330 // 60+330+8=398 > CAP_HEIGHT ≈ 389.3 → capped
34
- const FOOTER_INSET = MAIN_HEIGHT - FOOTER_TOP + GAP // 48
34
+ const BOTTOM_INSET = MAIN_HEIGHT - BOTTOM_TOP + GAP // 48: divider-gap above top of bottom container
35
35
  const ABOVE_CAP_BOTTOM = 342 // 48+342+8=398 > CAP_HEIGHT → capped
36
36
 
37
37
  const PANEL_W_STANDARD = 200
@@ -49,7 +49,7 @@ const CAP_HEIGHT = (MAIN_HEIGHT - 2 * GAP) * (MAX_RATIO - 1) / MAX_RATIO
49
49
 
50
50
  // ─── Setup ──────────────────────────────────────────────────────────────────
51
51
 
52
- let mainRef, leftRef, rightRef, actionsRef, footerRef
52
+ let mainRef, leftRef, rightRef, actionsRef, bottomRef
53
53
 
54
54
  beforeAll(() => {
55
55
  globalThis.getComputedStyle = jest.fn().mockReturnValue({ getPropertyValue: () => String(GAP) })
@@ -72,10 +72,10 @@ beforeEach(() => {
72
72
  leftRef = colRef(LEFT_WIDTH, LEFT_LEFT, LEFT_TOP)
73
73
  rightRef = colRef(RIGHT_WIDTH)
74
74
  actionsRef = { current: { offsetTop: ACTIONS_TOP } }
75
- footerRef = { current: { offsetTop: FOOTER_TOP } }
75
+ bottomRef = { current: { offsetTop: BOTTOM_TOP, offsetHeight: 0 } }
76
76
  })
77
77
 
78
- const base = () => ({ mainRef, leftRef, rightRef, actionsRef, footerRef })
78
+ const base = () => ({ mainRef, leftRef, rightRef, actionsRef, bottomRef })
79
79
 
80
80
  // Slot container where an .im-c-panel is the first element child.
81
81
  const panel = (offsetWidth, offsetHeight) => {
@@ -96,7 +96,7 @@ describe('getSafeZoneInset — missing refs', () => {
96
96
  expect(getSafeZoneInset({ ...base(), leftRef: { current: null } })).toBeUndefined()
97
97
  })
98
98
  it('returns undefined when actionsRef is undefined', () => {
99
- expect(getSafeZoneInset({ mainRef, leftRef, rightRef, footerRef })).toBeUndefined()
99
+ expect(getSafeZoneInset({ mainRef, leftRef, rightRef, bottomRef })).toBeUndefined()
100
100
  })
101
101
  })
102
102
 
@@ -120,7 +120,7 @@ describe('getSafeZoneInset — base structural insets', () => {
120
120
  it('uses zero button width when column ref has no button group', () => {
121
121
  const noGroupLeft = { current: { offsetWidth: 0, offsetLeft: LEFT_LEFT, offsetTop: LEFT_TOP, querySelector: () => null } }
122
122
  const noGroupRight = { current: { offsetWidth: 0, offsetLeft: 0, offsetTop: 0, querySelector: () => null } }
123
- expect(getSafeZoneInset({ mainRef, leftRef: noGroupLeft, rightRef: noGroupRight, actionsRef, footerRef })).toEqual({
123
+ expect(getSafeZoneInset({ mainRef, leftRef: noGroupLeft, rightRef: noGroupRight, actionsRef, bottomRef })).toEqual({
124
124
  left: LEFT_LEFT + GAP, right: LEFT_LEFT + GAP, top: LEFT_TOP, bottom: BASE_BOTTOM
125
125
  })
126
126
  })
@@ -133,7 +133,7 @@ describe('getSafeZoneInset — base structural insets', () => {
133
133
  rightBottomRef: panel(PANEL_W_STANDARD, 0)
134
134
  })).toEqual({ left: BASE_LEFT, right: BASE_RIGHT, top: BASE_TOP, bottom: BASE_BOTTOM })
135
135
  })
136
- it('uses max of actions and footer for base bottom', () => {
136
+ it('uses max of actions and bottom for base bottom', () => {
137
137
  actionsRef.current.offsetTop = EARLY_ACTIONS_TOP
138
138
  expect(getSafeZoneInset(base()).bottom).toBe(MAIN_HEIGHT - EARLY_ACTIONS_TOP + GAP)
139
139
  })
@@ -244,7 +244,7 @@ describe('getSafeZoneInset — bottom edge', () => {
244
244
  })
245
245
  it('triggers when a bottom panel width exceeds threshold', () => {
246
246
  expect(getSafeZoneInset({ ...base(), leftBottomRef: panel(ABOVE_W_THRESHOLD, ABOVE_THRESHOLD) }).bottom)
247
- .toBe(Math.min(FOOTER_INSET + ABOVE_THRESHOLD + GAP, CAP_HEIGHT))
247
+ .toBe(Math.min(BOTTOM_INSET + ABOVE_THRESHOLD + GAP, CAP_HEIGHT))
248
248
  })
249
249
  it('column-primary wide-and-tall bottom panel triggers left inset, not bottom', () => {
250
250
  // panel(400,342): h/availableH≈0.724 > w/availableW≈0.510 → column-primary
@@ -257,7 +257,7 @@ describe('getSafeZoneInset — bottom edge', () => {
257
257
  ...base(),
258
258
  leftBottomRef: panel(COMBINED_ABOVE_W, PANEL_H_TALL),
259
259
  rightBottomRef: panel(COMBINED_ABOVE_W, PANEL_H_SHORT)
260
- }).bottom).toBe(Math.min(FOOTER_INSET + PANEL_H_TALL + GAP, CAP_HEIGHT))
260
+ }).bottom).toBe(Math.min(BOTTOM_INSET + PANEL_H_TALL + GAP, CAP_HEIGHT))
261
261
  })
262
262
  it('does not trigger when both bottom panels are below combined width threshold', () => {
263
263
  expect(getSafeZoneInset({
package/webpack.dev.mjs CHANGED
@@ -12,16 +12,20 @@ dotenv.config({ path: path.join(__dirname, './.env'), quiet: true })
12
12
  export default {
13
13
  mode: 'development',
14
14
  target: ['web', 'es5'],
15
+ devtool: [
16
+ // produce a source-map to aid debugging
17
+ { type: 'javascript', use: 'source-map' }
18
+ ],
15
19
  entry: {
16
20
  index: path.join(__dirname, 'demo/js/index.js'),
17
- forms: path.join(__dirname, 'demo/js/forms.js'),
21
+ draw: path.join(__dirname, 'demo/js/draw.js'),
18
22
  farming: path.join(__dirname, 'demo/js/farming.js'),
19
23
  planning: path.join(__dirname, 'demo/js/planning.js')
20
24
  },
21
25
  output: {
22
26
  path: path.resolve(__dirname, 'public'),
23
27
  filename: '[name].js',
24
- clean: true,
28
+ clean: true
25
29
  },
26
30
  resolve: {
27
31
  extensions: ['.tsx', '.ts', '.jsx', '.js'],
@@ -84,28 +88,28 @@ export default {
84
88
  test: /\.jsx?$/,
85
89
  loader: 'babel-loader',
86
90
  exclude: /node_modules/
87
- },{
91
+ }, {
88
92
  test: /\.s[ac]ss$/i,
89
- use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
90
- },{
93
+ use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
94
+ }, {
91
95
  test: /\.css$/i,
92
- use: [MiniCssExtractPlugin.loader, 'css-loader'],
96
+ use: [MiniCssExtractPlugin.loader, 'css-loader']
93
97
  }
94
- ],
98
+ ]
95
99
  },
96
100
  devServer: {
97
101
  static: [
98
- {
99
- directory: path.join(__dirname, 'demo'),
100
- },
101
- {
102
- directory: path.join(__dirname, 'public'),
103
- },
104
- {
105
- directory: path.join(__dirname, 'assets'),
106
- publicPath: '/assets' // Images served from here as used in both demo and prototype kit plugin
107
- }
108
- ],
102
+ {
103
+ directory: path.join(__dirname, 'demo')
104
+ },
105
+ {
106
+ directory: path.join(__dirname, 'public')
107
+ },
108
+ {
109
+ directory: path.join(__dirname, 'assets'),
110
+ publicPath: '/assets' // Images served from here as used in both demo and prototype kit plugin
111
+ }
112
+ ],
109
113
  compress: true,
110
114
  port: 8080,
111
115
  open: true,
@@ -120,4 +124,4 @@ export default {
120
124
  }
121
125
  }
122
126
  }
123
- }
127
+ }
@@ -1,23 +0,0 @@
1
- # GOV.UK Prototype Kit
2
-
3
- This guide explains how to use the Interactive Map component in a GOV.UK Prototype Kit project.
4
-
5
- ## Installation
6
-
7
- Install the package in your prototype kit project:
8
-
9
- ```shell
10
- npm install @defra/interactive-map
11
- ```
12
-
13
- The InteractiveMap plugin will be automatically added to your prototype.
14
-
15
- ## Usage
16
-
17
- For detailed usage instructions, see the [GOV.UK Prototype Kit documentation](https://prototype-kit.service.gov.uk/docs/install-and-use-plugins).
18
-
19
- ## Next Steps
20
-
21
- - [Getting Started](./getting-started.md) - Learn the basics
22
- - [API Reference](./api.md) - Explore the full API
23
- - [Plugins](./plugins.md) - Extend functionality
package/docs/index.md DELETED
@@ -1,19 +0,0 @@
1
- ## What's Inside
2
-
3
- - **[Getting Started](getting-started)** - Installation and basic usage
4
- - **[API Reference](api)** - Complete API documentation
5
- - **[Plugins](plugins)** - Extend functionality with plugins
6
- - **[Architecture](architecture)** - Learn about the design and structure
7
- - **[GOV.UK Prototype](govuk-prototype)** - Use with GOV.UK Prototype Kit
8
-
9
- ## Features
10
-
11
- - ✅ Accessible and keyboard navigable
12
- - 🗺️ Multiple map provider support (MapLibre, ESRI)
13
- - 🔌 Extensible plugin system
14
- - 🎨 Customizable styling and behaviors
15
- - 📱 Responsive design
16
-
17
- ## Support
18
-
19
- For issues and feature requests, visit our [GitHub repository](https://github.com/DEFRA/interactive-map).
@@ -1,14 +0,0 @@
1
- export const hideDataset = ({ mapProvider, pluginState }, datasetId) => {
2
- const map = mapProvider.map
3
-
4
- // Update map layer visibility
5
- if (map.getLayer(datasetId)) {
6
- map.setLayoutProperty(datasetId, 'visibility', 'none')
7
- }
8
- if (map.getLayer(`${datasetId}-stroke`)) {
9
- map.setLayoutProperty(`${datasetId}-stroke`, 'visibility', 'none')
10
- }
11
-
12
- // Update state
13
- pluginState.dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { id: datasetId, visibility: 'hidden' } })
14
- }
@@ -1,41 +0,0 @@
1
- import { applyExclusionFilter } from '../utils/filters.js'
2
-
3
- export const hideFeatures = ({ mapProvider, pluginState }, { featureIds, idProperty, datasetId }) => {
4
- const map = mapProvider.map
5
-
6
- // Get dataset to access original filter and determine layer IDs
7
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
8
- if (!dataset) {
9
- return
10
- }
11
-
12
- const originalFilter = dataset.filter || null
13
- const hasFill = !!dataset.fill
14
- const hasStroke = !!dataset.stroke
15
- const fillLayerId = hasFill ? datasetId : null
16
-
17
- let strokeLayerId = null
18
- if (hasStroke) {
19
- strokeLayerId = hasFill ? `${datasetId}-stroke` : datasetId
20
- }
21
-
22
- // Get current hidden state and calculate all hidden IDs
23
- const existingHidden = pluginState.hiddenFeatures[datasetId]
24
- const allHiddenIds = existingHidden
25
- ? [...new Set([...existingHidden.ids, ...featureIds])]
26
- : featureIds
27
-
28
- // Update state (store by datasetId, not individual layer IDs)
29
- pluginState.dispatch({
30
- type: 'HIDE_FEATURES',
31
- payload: { layerId: datasetId, idProperty, featureIds }
32
- })
33
-
34
- // Apply filter to both layers
35
- if (fillLayerId) {
36
- applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, allHiddenIds)
37
- }
38
- if (strokeLayerId) {
39
- applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, allHiddenIds)
40
- }
41
- }
@@ -1,14 +0,0 @@
1
- export const showDataset = ({ mapProvider, pluginState }, datasetId) => {
2
- const map = mapProvider.map
3
-
4
- // Update map layer visibility
5
- if (map.getLayer(datasetId)) {
6
- map.setLayoutProperty(datasetId, 'visibility', 'visible')
7
- }
8
- if (map.getLayer(`${datasetId}-stroke`)) {
9
- map.setLayoutProperty(`${datasetId}-stroke`, 'visibility', 'visible')
10
- }
11
-
12
- // Update state
13
- pluginState.dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { id: datasetId, visibility: 'visible' } })
14
- }
@@ -1,44 +0,0 @@
1
- import { applyExclusionFilter } from '../utils/filters.js'
2
-
3
- export const showFeatures = ({ mapProvider, pluginState }, { featureIds, idProperty, datasetId }) => {
4
- const map = mapProvider.map
5
-
6
- // Get current hidden state before update
7
- const existingHidden = pluginState.hiddenFeatures[datasetId]
8
- if (!existingHidden) {
9
- return
10
- }
11
-
12
- // Get dataset to access original filter and determine layer IDs
13
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
14
- if (!dataset) {
15
- return
16
- }
17
-
18
- const originalFilter = dataset.filter || null
19
- const hasFill = !!dataset.fill
20
- const hasStroke = !!dataset.stroke
21
- const fillLayerId = hasFill ? datasetId : null
22
-
23
- let strokeLayerId = null
24
- if (hasStroke) {
25
- strokeLayerId = hasFill ? `${datasetId}-stroke` : datasetId
26
- }
27
-
28
- // Calculate remaining hidden IDs
29
- const remainingHiddenIds = existingHidden.ids.filter(id => !featureIds.includes(id))
30
-
31
- // Update state
32
- pluginState.dispatch({
33
- type: 'SHOW_FEATURES',
34
- payload: { layerId: datasetId, featureIds }
35
- })
36
-
37
- // Apply filter to both layers (or restore original if nothing hidden)
38
- if (fillLayerId) {
39
- applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, remainingHiddenIds)
40
- }
41
- if (strokeLayerId) {
42
- applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, remainingHiddenIds)
43
- }
44
- }
@@ -1,54 +0,0 @@
1
- import { addMapLayers } from './mapLayers.js'
2
- import { applyExclusionFilter } from './utils/filters.js'
3
-
4
- export const handleSetMapStyle = ({
5
- map,
6
- events,
7
- eventBus,
8
- getDatasets,
9
- getHiddenFeatures,
10
- getDynamicSources
11
- }) => {
12
- const onSetStyle = (e) => {
13
- map.once('idle', () => {
14
- const newStyleId = e.id
15
- const datasets = getDatasets()
16
- const hiddenFeatures = getHiddenFeatures()
17
- const dynamicSources = getDynamicSources ? getDynamicSources() : new Map()
18
-
19
- // Re-add all layers with correct colors for new style
20
- datasets.forEach(dataset => {
21
- addMapLayers(map, newStyleId, dataset)
22
- })
23
-
24
- // Reapply cached data for dynamic sources
25
- dynamicSources.forEach(source => {
26
- source.reapply()
27
- })
28
-
29
- // Reapply hidden features filters
30
- Object.entries(hiddenFeatures).forEach(([datasetId, { idProperty, ids }]) => {
31
- const dataset = datasets.find(d => d.id === datasetId)
32
- if (!dataset) {
33
- return
34
- }
35
-
36
- const originalFilter = dataset.filter || null
37
- const hasFill = !!dataset.fill
38
- const hasStroke = !!dataset.stroke
39
- const fillLayerId = hasFill ? datasetId : null
40
- const strokeLayerId = hasStroke ? (hasFill ? `${datasetId}-stroke` : datasetId) : null
41
-
42
- if (fillLayerId) {
43
- applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, ids)
44
- }
45
- if (strokeLayerId) {
46
- applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, ids)
47
- }
48
- })
49
- })
50
- }
51
-
52
- eventBus.on(events.MAP_SET_STYLE, onSetStyle)
53
- return onSetStyle
54
- }
@@ -1,165 +0,0 @@
1
- import { getValueForStyle } from '../../../../src/utils/getValueForStyle.js'
2
-
3
- // Generate a hash for consistent source ID generation
4
- const hashString = (str) => {
5
- const HASH_BASE = 36
6
- let hash = 0
7
- for (const ch of str) {
8
- hash = ((hash << 5) - hash) + ch.codePointAt(0)
9
- hash = hash & hash
10
- }
11
- return Math.abs(hash).toString(HASH_BASE)
12
- }
13
-
14
- // Generate a consistent source ID for source sharing
15
- const getSourceId = (dataset) => {
16
- if (dataset.tiles) {
17
- const tilesKey = Array.isArray(dataset.tiles) ? dataset.tiles.join(',') : dataset.tiles
18
- return `tiles-${hashString(tilesKey)}`
19
- }
20
- if (dataset.geojson) {
21
- // Dynamic sources get unique IDs per dataset (no sharing)
22
- if (isDynamicSource(dataset)) {
23
- return `geojson-dynamic-${dataset.id}`
24
- }
25
- // URL strings can be shared, inline GeoJSON objects get unique IDs per dataset
26
- if (typeof dataset.geojson === 'string') {
27
- return `geojson-${hashString(dataset.geojson)}`
28
- }
29
- // Inline GeoJSON - use dataset ID since object identity can't be shared
30
- return `geojson-${dataset.id}`
31
- }
32
- // Fallback to dataset ID
33
- return `source-${dataset.id}`
34
- }
35
-
36
- /**
37
- * Check if a dataset uses dynamic fetching (bbox-based)
38
- * Dynamic sources require: URL geojson, idProperty for deduplication, and transformRequest for URL building
39
- * @param {Object} dataset
40
- * @returns {boolean}
41
- */
42
- const isDynamicSource = (dataset) => {
43
- return (
44
- typeof dataset.geojson === 'string' &&
45
- !!dataset.idProperty &&
46
- typeof dataset.transformRequest === 'function'
47
- )
48
- }
49
-
50
- /**
51
- * Update the data for a GeoJSON source
52
- * @param {Object} map - Map instance
53
- * @param {string} sourceId - Source ID
54
- * @param {Object} geojson - GeoJSON FeatureCollection
55
- */
56
- const updateSourceData = (map, sourceId, geojson) => {
57
- const source = map.getSource(sourceId)
58
- if (source && typeof source.setData === 'function') {
59
- source.setData(geojson)
60
- }
61
- }
62
-
63
- /**
64
- * Get all layer IDs that use a given source
65
- * @param {string} sourceId
66
- * @returns {string[]} Array of layer IDs using the source
67
- */
68
- const getLayersUsingSource = (map, sourceId) => {
69
- const style = map.getStyle()
70
- if (!style?.layers) {
71
- return []
72
- }
73
-
74
- return style.layers
75
- .filter(layer => layer.source === sourceId)
76
- .map(layer => layer.id)
77
- }
78
-
79
- const addMapLayers = (map, mapStyleId, dataset) => {
80
- const sourceId = getSourceId(dataset)
81
-
82
- // --- Add source (shared across datasets with same tiles/data URL) ---
83
- if (!map.getSource(sourceId)) {
84
-
85
- if (dataset.tiles) {
86
- // Tiles
87
- map.addSource(sourceId, {
88
- type: 'vector',
89
- tiles: dataset.tiles,
90
- minzoom: dataset.minZoom || 0,
91
- maxzoom: dataset.maxZoom || 22
92
- })
93
- } else if (dataset.geojson) {
94
- // Dynamic source - start with empty FeatureCollection, will be populated by createDynamicSource
95
- // Static source - use URL or inline GeoJSON directly
96
- const initialData = isDynamicSource(dataset)
97
- ? { type: 'FeatureCollection', features: [] }
98
- : dataset.geojson
99
-
100
- map.addSource(sourceId, {
101
- type: 'geojson',
102
- data: initialData
103
- })
104
- } else {
105
- // No action
106
- }
107
- }
108
-
109
- // --- Determine layer IDs ---
110
- const hasFill = !!dataset.fill
111
- const hasStroke = !!dataset.stroke
112
- const fillLayerId = hasFill ? dataset.id : null
113
- const strokeLayerId = hasStroke ? (hasFill ? `${dataset.id}-stroke` : dataset.id) : null
114
-
115
- // --- Determie visiblity ---
116
- const visibility = dataset.visibility === 'hidden' ? 'none' : 'visible'
117
-
118
- // --- Add fill layer ---
119
- if (hasFill && !map.getLayer(fillLayerId)) {
120
- const fillColor = getValueForStyle(dataset.fill, mapStyleId)
121
- map.addLayer({
122
- id: fillLayerId,
123
- type: 'fill',
124
- source: sourceId,
125
- 'source-layer': dataset?.tiles?.length ? dataset.sourceLayer : undefined,
126
- layout: {
127
- visibility
128
- },
129
- paint: {
130
- 'fill-color': fillColor,
131
- 'fill-opacity': dataset.opacity || 1
132
- },
133
- ...(dataset.filter ? { filter: dataset.filter } : {})
134
- })
135
- }
136
-
137
- // --- Add stroke layer ---
138
- if (hasStroke && !map.getLayer(strokeLayerId)) {
139
- const strokeColor = getValueForStyle(dataset.stroke, mapStyleId)
140
- map.addLayer({
141
- id: strokeLayerId,
142
- type: 'line',
143
- source: sourceId,
144
- 'source-layer': dataset?.tiles?.length ? dataset.sourceLayer : undefined,
145
- layout: {
146
- visibility
147
- },
148
- paint: {
149
- 'line-color': strokeColor,
150
- 'line-width': dataset.strokeWidth || 1,
151
- 'line-opacity': dataset.opacity || 1,
152
- ...(dataset.strokeDashArray ? { 'line-dasharray': dataset.strokeDashArray } : {})
153
- },
154
- ...(dataset.filter ? { filter: dataset.filter } : {})
155
- })
156
- }
157
- }
158
-
159
- export {
160
- getSourceId,
161
- getLayersUsingSource,
162
- addMapLayers,
163
- isDynamicSource,
164
- updateSourceData
165
- }