@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
@@ -10,227 +10,255 @@ const run = (action, state, panelConfig = {}) =>
10
10
 
11
11
  const flushMicrotasks = () => new Promise(queueMicrotask)
12
12
 
13
- describe('appDispatchMiddleware', () => {
14
- beforeEach(() => {
15
- jest.clearAllMocks()
16
- })
17
-
18
- describe('CLOSE_PANEL', () => {
19
- it('emits panel closed event', async () => {
20
- run(
21
- { type: 'CLOSE_PANEL', payload: 'testPanel' },
22
- { openPanels: {} }
23
- )
24
-
25
- await flushMicrotasks()
26
-
27
- expect(eventBus.emit).toHaveBeenCalledWith(
28
- events.APP_PANEL_CLOSED,
29
- { panelId: 'testPanel' }
30
- )
31
- })
32
- })
33
-
34
- describe('CLOSE_ALL_PANELS', () => {
35
- it('emits closed event for each panel', async () => {
36
- run(
37
- { type: 'CLOSE_ALL_PANELS' },
38
- { openPanels: { panel1: {}, panel2: {} } }
39
- )
40
-
41
- await flushMicrotasks()
42
-
43
- expect(eventBus.emit).toHaveBeenCalledTimes(2)
44
- expect(eventBus.emit).toHaveBeenCalledWith(
45
- events.APP_PANEL_CLOSED,
46
- { panelId: 'panel1' }
47
- )
48
- expect(eventBus.emit).toHaveBeenCalledWith(
49
- events.APP_PANEL_CLOSED,
50
- { panelId: 'panel2' }
51
- )
52
- })
53
- })
54
-
55
- describe('OPEN_PANEL', () => {
56
- it('emits panel opened event for regular panel', async () => {
57
- run(
58
- {
59
- type: 'OPEN_PANEL',
60
- payload: { panelId: 'newPanel', props: { foo: 'bar' } }
61
- },
62
- { openPanels: {}, breakpoint: 'md' },
63
- { newPanel: { md: { exclusive: false, modal: false } } }
64
- )
65
-
66
- await flushMicrotasks()
67
-
68
- expect(eventBus.emit).toHaveBeenCalledWith(
69
- events.APP_PANEL_OPENED,
70
- { panelId: 'newPanel', props: { foo: 'bar' } }
71
- )
72
- })
73
-
74
- it('closes all non-exclusive panels when opening exclusive non-modal panel', async () => {
75
- run(
76
- {
77
- type: 'OPEN_PANEL',
78
- payload: { panelId: 'exclusivePanel', props: {} }
79
- },
80
- {
81
- openPanels: {
82
- regularPanel1: {},
83
- regularPanel2: {},
84
- exclusivePanel2: {}
85
- },
86
- breakpoint: 'md'
87
- },
88
- {
89
- exclusivePanel: { md: { exclusive: true, modal: false } },
90
- regularPanel1: { md: { exclusive: false, modal: false } },
91
- regularPanel2: { md: { exclusive: false, modal: false } },
92
- exclusivePanel2: { md: { exclusive: true, modal: false } }
93
- }
94
- )
95
-
96
- await flushMicrotasks()
97
-
98
- expect(eventBus.emit).toHaveBeenCalledTimes(3)
99
- expect(eventBus.emit).toHaveBeenCalledWith(
100
- events.APP_PANEL_CLOSED,
101
- { panelId: 'regularPanel1' }
102
- )
103
- expect(eventBus.emit).toHaveBeenCalledWith(
104
- events.APP_PANEL_CLOSED,
105
- { panelId: 'regularPanel2' }
106
- )
107
- expect(eventBus.emit).toHaveBeenCalledWith(
108
- events.APP_PANEL_OPENED,
109
- { panelId: 'exclusivePanel', props: {} }
110
- )
111
- })
112
-
113
- it('closes all exclusive non-modal panels when opening regular panel', async () => {
114
- run(
115
- {
116
- type: 'OPEN_PANEL',
117
- payload: { panelId: 'regularPanel', props: {} }
118
- },
119
- {
120
- openPanels: {
121
- exclusivePanel1: {},
122
- exclusivePanel2: {},
123
- exclusiveModalPanel: {},
124
- regularPanel2: {}
125
- },
126
- breakpoint: 'md'
127
- },
128
- {
129
- regularPanel: { md: { exclusive: false, modal: false } },
130
- exclusivePanel1: { md: { exclusive: true, modal: false } },
131
- exclusivePanel2: { md: { exclusive: true, modal: false } },
132
- exclusiveModalPanel: { md: { exclusive: true, modal: true } },
133
- regularPanel2: { md: { exclusive: false, modal: false } }
134
- }
135
- )
136
-
137
- await flushMicrotasks()
138
-
139
- expect(eventBus.emit).toHaveBeenCalledTimes(3)
140
- expect(eventBus.emit).toHaveBeenCalledWith(
141
- events.APP_PANEL_CLOSED,
142
- { panelId: 'exclusivePanel1' }
143
- )
144
- expect(eventBus.emit).toHaveBeenCalledWith(
145
- events.APP_PANEL_CLOSED,
146
- { panelId: 'exclusivePanel2' }
147
- )
148
- expect(eventBus.emit).toHaveBeenCalledWith(
149
- events.APP_PANEL_OPENED,
150
- { panelId: 'regularPanel', props: {} }
151
- )
152
- })
153
-
154
- it('does not close any panels when opening modal', async () => {
155
- run(
156
- {
157
- type: 'OPEN_PANEL',
158
- payload: { panelId: 'modalPanel', props: {} }
159
- },
160
- {
161
- openPanels: { regularPanel: {}, exclusivePanel: {} },
162
- breakpoint: 'md'
163
- },
164
- {
165
- modalPanel: { md: { modal: true } },
166
- regularPanel: { md: { exclusive: false, modal: false } },
167
- exclusivePanel: { md: { exclusive: true, modal: false } }
168
- }
169
- )
170
-
171
- await flushMicrotasks()
172
-
173
- expect(eventBus.emit).toHaveBeenCalledTimes(1)
174
- expect(eventBus.emit).toHaveBeenCalledWith(
175
- events.APP_PANEL_OPENED,
176
- { panelId: 'modalPanel', props: {} }
177
- )
178
- })
179
- })
180
-
181
- describe('ADD_PANEL', () => {
182
- it('emits APP_PANEL_OPENED with slot when panel opens by default', async () => {
183
- run(
184
- { type: 'ADD_PANEL', payload: { id: 'newPanel', config: {} } },
185
- { breakpoint: 'desktop' }
186
- )
187
-
188
- await flushMicrotasks()
189
-
190
- expect(eventBus.emit).toHaveBeenCalledWith(
191
- events.APP_PANEL_OPENED,
192
- { panelId: 'newPanel', slot: 'left-top' }
193
- )
194
- })
195
-
196
- it('emits APP_PANEL_OPENED with visibleGeometry when provided in config', async () => {
197
- const visibleGeometry = { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: {} }
198
- run(
199
- { type: 'ADD_PANEL', payload: { id: 'geoPanel', config: { visibleGeometry } } },
200
- { breakpoint: 'desktop' }
201
- )
202
-
203
- await flushMicrotasks()
204
-
205
- expect(eventBus.emit).toHaveBeenCalledWith(
206
- events.APP_PANEL_OPENED,
207
- { panelId: 'geoPanel', slot: 'left-top', visibleGeometry }
208
- )
209
- })
210
-
211
- it('does not emit APP_PANEL_OPENED when breakpoint config sets open: false', async () => {
212
- run(
213
- { type: 'ADD_PANEL', payload: { id: 'hiddenPanel', config: { desktop: { open: false } } } },
214
- { breakpoint: 'desktop' }
215
- )
216
-
217
- await flushMicrotasks()
218
-
219
- expect(eventBus.emit).not.toHaveBeenCalled()
220
- })
221
-
222
- it('emits APP_PANEL_OPENED with slot for mobile breakpoint', async () => {
223
- run(
224
- { type: 'ADD_PANEL', payload: { id: 'mobilePanel', config: {} } },
225
- { breakpoint: 'mobile' }
226
- )
227
-
228
- await flushMicrotasks()
229
-
230
- expect(eventBus.emit).toHaveBeenCalledWith(
231
- events.APP_PANEL_OPENED,
232
- { panelId: 'mobilePanel', slot: 'bottom' }
233
- )
234
- })
13
+ const INVALID_SLOT = 'invalid-slot'
14
+ const LOG_PREFIX = '[interactive-map]'
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks()
18
+ })
19
+
20
+ describe('CLOSE_PANEL', () => {
21
+ it('emits panel closed event', async () => {
22
+ run(
23
+ { type: 'CLOSE_PANEL', payload: 'testPanel' },
24
+ { openPanels: {} }
25
+ )
26
+
27
+ await flushMicrotasks()
28
+
29
+ expect(eventBus.emit).toHaveBeenCalledWith(
30
+ events.APP_PANEL_CLOSED,
31
+ { panelId: 'testPanel' }
32
+ )
33
+ })
34
+ })
35
+
36
+ describe('CLOSE_ALL_PANELS', () => {
37
+ it('emits closed event for each panel', async () => {
38
+ run(
39
+ { type: 'CLOSE_ALL_PANELS' },
40
+ { openPanels: { panel1: {}, panel2: {} } }
41
+ )
42
+
43
+ await flushMicrotasks()
44
+
45
+ expect(eventBus.emit).toHaveBeenCalledTimes(2)
46
+ expect(eventBus.emit).toHaveBeenCalledWith(
47
+ events.APP_PANEL_CLOSED,
48
+ { panelId: 'panel1' }
49
+ )
50
+ expect(eventBus.emit).toHaveBeenCalledWith(
51
+ events.APP_PANEL_CLOSED,
52
+ { panelId: 'panel2' }
53
+ )
54
+ })
55
+ })
56
+
57
+ describe('OPEN_PANEL — regular panel', () => {
58
+ it('emits panel opened event', async () => {
59
+ run(
60
+ {
61
+ type: 'OPEN_PANEL',
62
+ payload: { panelId: 'newPanel', props: { foo: 'bar' } }
63
+ },
64
+ { openPanels: {}, breakpoint: 'md' },
65
+ { newPanel: { md: { exclusive: false, modal: false } } }
66
+ )
67
+
68
+ await flushMicrotasks()
69
+
70
+ expect(eventBus.emit).toHaveBeenCalledWith(
71
+ events.APP_PANEL_OPENED,
72
+ { panelId: 'newPanel', props: { foo: 'bar' } }
73
+ )
74
+ })
75
+ })
76
+
77
+ const CLOSED_PLUS_OPENED = 3
78
+
79
+ describe('OPEN_PANEL — opening exclusive closes non-exclusive', () => {
80
+ it('closes all non-exclusive panels', async () => {
81
+ run(
82
+ { type: 'OPEN_PANEL', payload: { panelId: 'exclusivePanel', props: {} } },
83
+ { openPanels: { regularPanel1: {}, regularPanel2: {}, exclusivePanel2: {} }, breakpoint: 'md' },
84
+ {
85
+ exclusivePanel: { md: { exclusive: true, modal: false } },
86
+ regularPanel1: { md: { exclusive: false, modal: false } },
87
+ regularPanel2: { md: { exclusive: false, modal: false } },
88
+ exclusivePanel2: { md: { exclusive: true, modal: false } }
89
+ }
90
+ )
91
+ await flushMicrotasks()
92
+ expect(eventBus.emit).toHaveBeenCalledTimes(CLOSED_PLUS_OPENED)
93
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_CLOSED, { panelId: 'regularPanel1' })
94
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_CLOSED, { panelId: 'regularPanel2' })
95
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_OPENED, { panelId: 'exclusivePanel', props: {} })
96
+ })
97
+ })
98
+
99
+ describe('OPEN_PANEL — opening regular closes exclusive non-modal', () => {
100
+ it('closes all exclusive non-modal panels', async () => {
101
+ run(
102
+ { type: 'OPEN_PANEL', payload: { panelId: 'regularPanel', props: {} } },
103
+ { openPanels: { exclusivePanel1: {}, exclusivePanel2: {}, exclusiveModalPanel: {}, regularPanel2: {} }, breakpoint: 'md' },
104
+ {
105
+ regularPanel: { md: { exclusive: false, modal: false } },
106
+ exclusivePanel1: { md: { exclusive: true, modal: false } },
107
+ exclusivePanel2: { md: { exclusive: true, modal: false } },
108
+ exclusiveModalPanel: { md: { exclusive: true, modal: true } },
109
+ regularPanel2: { md: { exclusive: false, modal: false } }
110
+ }
111
+ )
112
+ await flushMicrotasks()
113
+ expect(eventBus.emit).toHaveBeenCalledTimes(CLOSED_PLUS_OPENED)
114
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_CLOSED, { panelId: 'exclusivePanel1' })
115
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_CLOSED, { panelId: 'exclusivePanel2' })
116
+ expect(eventBus.emit).toHaveBeenCalledWith(events.APP_PANEL_OPENED, { panelId: 'regularPanel', props: {} })
117
+ })
118
+ })
119
+
120
+ describe('OPEN_PANEL — modal panel', () => {
121
+ it('does not close any panels when opening modal', async () => {
122
+ run(
123
+ {
124
+ type: 'OPEN_PANEL',
125
+ payload: { panelId: 'modalPanel', props: {} }
126
+ },
127
+ {
128
+ openPanels: { regularPanel: {}, exclusivePanel: {} },
129
+ breakpoint: 'md'
130
+ },
131
+ {
132
+ modalPanel: { md: { modal: true } },
133
+ regularPanel: { md: { exclusive: false, modal: false } },
134
+ exclusivePanel: { md: { exclusive: true, modal: false } }
135
+ }
136
+ )
137
+
138
+ await flushMicrotasks()
139
+
140
+ expect(eventBus.emit).toHaveBeenCalledTimes(1)
141
+ expect(eventBus.emit).toHaveBeenCalledWith(
142
+ events.APP_PANEL_OPENED,
143
+ { panelId: 'modalPanel', props: {} }
144
+ )
145
+ })
146
+ })
147
+
148
+ describe('ADD_BUTTON', () => {
149
+ beforeEach(() => jest.spyOn(console, 'warn').mockImplementation(() => {}))
150
+ afterEach(() => jest.restoreAllMocks())
151
+
152
+ it('warns when button has an invalid slot', () => {
153
+ run(
154
+ { type: 'ADD_BUTTON', payload: { id: 'myBtn', config: { desktop: { slot: INVALID_SLOT } } } },
155
+ { breakpoint: 'desktop' }
156
+ )
157
+ expect(console.warn).toHaveBeenCalledWith(LOG_PREFIX, expect.stringContaining(INVALID_SLOT))
158
+ })
159
+
160
+ it('does not warn when button has a valid slot', () => {
161
+ run(
162
+ { type: 'ADD_BUTTON', payload: { id: 'myBtn', config: { mobile: { slot: 'top-left' }, tablet: { slot: 'top-left' }, desktop: { slot: 'top-left' } } } },
163
+ { breakpoint: 'desktop' }
164
+ )
165
+ expect(console.warn).not.toHaveBeenCalled()
166
+ })
167
+ })
168
+
169
+ describe('ADD_CONTROL', () => {
170
+ beforeEach(() => jest.spyOn(console, 'warn').mockImplementation(() => {}))
171
+ afterEach(() => jest.restoreAllMocks())
172
+
173
+ it('warns when control has an invalid slot', () => {
174
+ run(
175
+ { type: 'ADD_CONTROL', payload: { id: 'myCtrl', config: { desktop: { slot: INVALID_SLOT } } } },
176
+ { breakpoint: 'desktop' }
177
+ )
178
+ expect(console.warn).toHaveBeenCalledWith(LOG_PREFIX, expect.stringContaining(INVALID_SLOT))
179
+ })
180
+
181
+ it('does not warn when control has a valid slot', () => {
182
+ run(
183
+ { type: 'ADD_CONTROL', payload: { id: 'myCtrl', config: { mobile: { slot: 'drawer' }, tablet: { slot: 'top-left' }, desktop: { slot: 'top-left' } } } },
184
+ { breakpoint: 'desktop' }
185
+ )
186
+ expect(console.warn).not.toHaveBeenCalled()
187
+ })
188
+ })
189
+
190
+ describe('ADD_PANEL', () => {
191
+ it('emits APP_PANEL_OPENED with slot when panel opens by default', async () => {
192
+ run(
193
+ { type: 'ADD_PANEL', payload: { id: 'newPanel', config: {} } },
194
+ { breakpoint: 'desktop' }
195
+ )
196
+
197
+ await flushMicrotasks()
198
+
199
+ expect(eventBus.emit).toHaveBeenCalledWith(
200
+ events.APP_PANEL_OPENED,
201
+ { panelId: 'newPanel', slot: 'left-top' }
202
+ )
203
+ })
204
+
205
+ it('emits APP_PANEL_OPENED with visibleGeometry when provided in config', async () => {
206
+ const visibleGeometry = { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: {} }
207
+ run(
208
+ { type: 'ADD_PANEL', payload: { id: 'geoPanel', config: { visibleGeometry } } },
209
+ { breakpoint: 'desktop' }
210
+ )
211
+
212
+ await flushMicrotasks()
213
+
214
+ expect(eventBus.emit).toHaveBeenCalledWith(
215
+ events.APP_PANEL_OPENED,
216
+ { panelId: 'geoPanel', slot: 'left-top', visibleGeometry }
217
+ )
218
+ })
219
+
220
+ it('does not emit APP_PANEL_OPENED when breakpoint config sets open: false', async () => {
221
+ run(
222
+ { type: 'ADD_PANEL', payload: { id: 'hiddenPanel', config: { desktop: { open: false } } } },
223
+ { breakpoint: 'desktop' }
224
+ )
225
+
226
+ await flushMicrotasks()
227
+
228
+ expect(eventBus.emit).not.toHaveBeenCalled()
229
+ })
230
+
231
+ it('emits APP_PANEL_OPENED with slot for mobile breakpoint', async () => {
232
+ run(
233
+ { type: 'ADD_PANEL', payload: { id: 'mobilePanel', config: {} } },
234
+ { breakpoint: 'mobile' }
235
+ )
236
+
237
+ await flushMicrotasks()
238
+
239
+ expect(eventBus.emit).toHaveBeenCalledWith(
240
+ events.APP_PANEL_OPENED,
241
+ { panelId: 'mobilePanel', slot: 'drawer' }
242
+ )
243
+ })
244
+
245
+ it('warns when panel has an invalid slot', () => {
246
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
247
+ run(
248
+ { type: 'ADD_PANEL', payload: { id: 'badPanel', config: { desktop: { slot: INVALID_SLOT, open: false } } } },
249
+ { breakpoint: 'desktop' }
250
+ )
251
+ expect(console.warn).toHaveBeenCalledWith(LOG_PREFIX, expect.stringContaining(INVALID_SLOT))
252
+ jest.restoreAllMocks()
253
+ })
254
+
255
+ it('does not warn for a panel with a button-adjacent slot', () => {
256
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
257
+ run(
258
+ { type: 'ADD_PANEL', payload: { id: 'btnPanel', config: { desktop: { slot: 'left-top-button', open: false } } } },
259
+ { breakpoint: 'desktop' }
260
+ )
261
+ expect(console.warn).not.toHaveBeenCalled()
262
+ jest.restoreAllMocks()
235
263
  })
236
264
  })
@@ -29,7 +29,9 @@ export const initialState = (config) => {
29
29
  const openPanels = getInitialOpenPanels(panelConfig, initialBreakpoint)
30
30
 
31
31
  return {
32
+ appVisible: null,
32
33
  isLayoutReady: false,
34
+ arePluginsEvaluated: false,
33
35
  breakpoint: initialBreakpoint,
34
36
  interfaceType: initialInterfaceType,
35
37
  preferredColorScheme: autoColorScheme ? preferredColorScheme : appColorScheme,
@@ -245,6 +245,8 @@ export default class InteractiveMap {
245
245
  if (parts.length > 1) {
246
246
  document.title = parts[parts.length - 1]
247
247
  }
248
+
249
+ this.eventBus.emit(events.APP_HIDDEN)
248
250
  }
249
251
 
250
252
  /**
@@ -262,6 +264,8 @@ export default class InteractiveMap {
262
264
  }
263
265
 
264
266
  updateDOMState(this)
267
+
268
+ this.eventBus.emit(events.APP_VISIBLE)
265
269
  }
266
270
 
267
271
  /**
@@ -93,6 +93,9 @@ export const defaultAppConfig = {
93
93
  }, {
94
94
  id: 'minus',
95
95
  svgContent: '<path d="M5 12h14"/>'
96
+ }, {
97
+ id: 'chevron',
98
+ svgContent: '<path d="m6 9 6 6 6-6"/>'
96
99
  }]
97
100
  }
98
101
 
@@ -113,7 +116,7 @@ export const defaultButtonConfig = {
113
116
  export const defaultPanelConfig = {
114
117
  label: 'Panel',
115
118
  mobile: {
116
- slot: 'bottom',
119
+ slot: 'drawer',
117
120
  open: true,
118
121
  dismissible: true,
119
122
  modal: false,
@@ -141,13 +144,13 @@ export const defaultPanelConfig = {
141
144
  export const defaultControlConfig = {
142
145
  label: 'Control',
143
146
  mobile: {
144
- slot: 'bottom'
147
+ slot: 'drawer'
145
148
  },
146
149
  tablet: {
147
- slot: 'left-top'
150
+ slot: 'top-left'
148
151
  },
149
152
  desktop: {
150
- slot: 'left-top'
153
+ slot: 'top-left'
151
154
  }
152
155
  }
153
156
 
@@ -61,6 +61,34 @@ export const EVENTS = {
61
61
  */
62
62
  APP_READY: 'app:ready',
63
63
 
64
+ /**
65
+ * Emitted when the map application becomes visible after being hidden.
66
+ *
67
+ * This can occur in 'hybrid behaviour' responsive scenarios where the map is already initialized
68
+ * (e.g. initialized inline on desktop) but was hidden and then shown again
69
+ * (e.g. resizing to mobile and opening the map).
70
+ *
71
+ * @remarks
72
+ * - Only emitted when transitioning from hidden → visible.
73
+ * - Not fired on initial open.
74
+ * - The existing map state may be preserved depending on configuration.
75
+ */
76
+ APP_VISIBLE: 'app:visible',
77
+
78
+ /**
79
+ * Emitted when the map application becomes hidden.
80
+ *
81
+ * This can occur in 'hybrid behaviour' responsive scenarios where the map was initialized inline
82
+ * (e.g. visible on desktop) but then becomes hidden
83
+ * (e.g. resizing to mobile or closing the map view).
84
+ *
85
+ * @remarks
86
+ * - Only emitted when transitioning from visible → hidden.
87
+ * - Not fired on initial load if the map starts hidden.
88
+ * - The map state may be preserved depending on configuration.
89
+ */
90
+ APP_HIDDEN: 'app:hidden',
91
+
64
92
  /**
65
93
  * Emitted when a panel is opened.
66
94
  * Payload: { panelId: string }
@@ -19,6 +19,7 @@
19
19
  @use '../App/components/KeyboardHelp/KeyboardHelp.module';
20
20
  @use '../App/components/MapButton/MapButton.module';
21
21
  @use '../App/components/Panel/Panel.module';
22
+ @use '../App/components/Icon/Icon.module';
22
23
  @use '../App/components/Viewport/Viewport.module';
23
24
  @use '../App/components/Tooltip/Tooltip.module';
24
25
  @use '../App/components/PopupMenu/PopupMenu.module';
@@ -9,7 +9,6 @@
9
9
  --button-padding: 8px;
10
10
  --button-icon-label-offset: 7px;
11
11
  --label-button-padding: 10px;
12
- --touch-button-size: 50px;
13
12
 
14
13
  // Borders and focus
15
14
  --app-border-width: 1px;
@@ -0,0 +1,6 @@
1
+ const PREFIX = '[interactive-map]'
2
+
3
+ export const logger = {
4
+ warn: (...args) => console.warn(PREFIX, ...args),
5
+ error: (...args) => console.error(PREFIX, ...args)
6
+ }
@@ -0,0 +1,32 @@
1
+ import { logger } from './logger.js'
2
+
3
+ beforeEach(() => {
4
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
5
+ jest.spyOn(console, 'error').mockImplementation(() => {})
6
+ })
7
+
8
+ afterEach(() => {
9
+ jest.restoreAllMocks()
10
+ })
11
+
12
+ describe('logger', () => {
13
+ it('logger.warn calls console.warn with the [interactive-map] prefix', () => {
14
+ logger.warn('something went wrong')
15
+ expect(console.warn).toHaveBeenCalledWith('[interactive-map]', 'something went wrong')
16
+ })
17
+
18
+ it('logger.error calls console.error with the [interactive-map] prefix', () => {
19
+ logger.error('critical failure')
20
+ expect(console.error).toHaveBeenCalledWith('[interactive-map]', 'critical failure')
21
+ })
22
+
23
+ it('logger.warn forwards multiple arguments', () => {
24
+ logger.warn('slot', 'invalid', { detail: true })
25
+ expect(console.warn).toHaveBeenCalledWith('[interactive-map]', 'slot', 'invalid', { detail: true })
26
+ })
27
+
28
+ it('logger.error forwards multiple arguments', () => {
29
+ logger.error('code', 42, null)
30
+ expect(console.error).toHaveBeenCalledWith('[interactive-map]', 'code', 42, null)
31
+ })
32
+ })