@defra/interactive-map 0.0.15-alpha → 0.0.16-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 (176) 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/dist/esm/im-core.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/slot-map.svg +1 -0
  8. package/docs/api/slots.md +89 -6
  9. package/docs/api.md +1 -1
  10. package/docs/architecture.md +3 -1
  11. package/docs/{demo.mdx → examples.mdx} +1 -1
  12. package/docs/getting-started.md +1 -3
  13. package/docs/index.mdx +42 -0
  14. package/docs/plugins/interact.md +176 -55
  15. package/docs/plugins/map-styles.md +64 -7
  16. package/docs/plugins/search.md +207 -63
  17. package/docs/plugins.md +7 -15
  18. package/docusaurus.config.cjs +34 -34
  19. package/jest.setup.js +1 -1
  20. package/package.json +5 -4
  21. package/plugins/beta/datasets/src/DatasetsInit.jsx +1 -1
  22. package/plugins/beta/datasets/src/api/addDataset.js +1 -1
  23. package/plugins/beta/datasets/src/api/hideDataset.js +1 -1
  24. package/plugins/beta/datasets/src/api/hideFeatures.js +1 -1
  25. package/plugins/beta/datasets/src/api/removeDataset.js +1 -1
  26. package/plugins/beta/datasets/src/api/showDataset.js +1 -1
  27. package/plugins/beta/datasets/src/api/showFeatures.js +1 -1
  28. package/plugins/beta/datasets/src/datasets.js +4 -4
  29. package/plugins/beta/datasets/src/defaults.js +1 -1
  30. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +5 -5
  31. package/plugins/beta/datasets/src/handleSetMapStyle.js +1 -1
  32. package/plugins/beta/datasets/src/manifest.js +3 -3
  33. package/plugins/beta/datasets/src/mapLayers.js +2 -3
  34. package/plugins/beta/datasets/src/panels/Key.jsx +31 -29
  35. package/plugins/beta/datasets/src/panels/Layers.jsx +8 -9
  36. package/plugins/beta/datasets/src/utils/bbox.js +4 -4
  37. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  38. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  39. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  40. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  41. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  42. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  43. package/plugins/beta/draw-es/src/events.js +52 -20
  44. package/plugins/beta/draw-es/src/events.test.js +301 -0
  45. package/plugins/beta/draw-es/src/graphic.js +1 -1
  46. package/plugins/beta/draw-es/src/manifest.js +4 -4
  47. package/plugins/beta/draw-es/src/reducer.js +1 -1
  48. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  49. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  50. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  51. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  52. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  53. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  54. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  55. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  56. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  57. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  58. package/plugins/beta/draw-ml/src/events.js +8 -6
  59. package/plugins/beta/draw-ml/src/manifest.js +15 -15
  60. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  61. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  62. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  63. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  64. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  65. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  66. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  67. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  68. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  69. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  70. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  71. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  72. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  73. package/plugins/beta/frame/src/Frame.jsx +4 -4
  74. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  75. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  76. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  77. package/plugins/beta/frame/src/config.js +1 -1
  78. package/plugins/beta/frame/src/manifest.js +3 -3
  79. package/plugins/beta/frame/src/reducer.js +1 -1
  80. package/plugins/beta/frame/src/utils.js +1 -1
  81. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  82. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  83. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  84. package/plugins/beta/use-location/src/defaults.js +1 -1
  85. package/plugins/beta/use-location/src/events.js +3 -3
  86. package/plugins/interact/src/InteractInit.jsx +1 -2
  87. package/plugins/interact/src/api/enable.js +8 -5
  88. package/plugins/interact/src/api/enable.test.js +2 -2
  89. package/plugins/interact/src/api/selectFeature.js +4 -4
  90. package/plugins/interact/src/api/unselectFeature.js +5 -5
  91. package/plugins/interact/src/defaults.js +0 -1
  92. package/plugins/interact/src/events.test.js +15 -15
  93. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  94. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  95. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  96. package/plugins/interact/src/manifest.js +2 -2
  97. package/plugins/interact/src/manifest.test.js +3 -4
  98. package/plugins/interact/src/reducer.js +3 -3
  99. package/plugins/interact/src/reducer.test.js +0 -1
  100. package/plugins/interact/src/utils/spatial.js +10 -10
  101. package/plugins/interact/src/utils/spatial.test.js +14 -14
  102. package/plugins/search/dist/css/index.css +1 -1
  103. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  104. package/plugins/search/dist/esm/index.js +1 -1
  105. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  106. package/plugins/search/dist/umd/index.js +1 -1
  107. package/plugins/search/src/Search.jsx +7 -6
  108. package/plugins/search/src/Search.test.jsx +23 -23
  109. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  110. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  111. package/plugins/search/src/components/Form/Form.jsx +14 -14
  112. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  113. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  114. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  115. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  116. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  117. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  118. package/plugins/search/src/datasets.js +12 -13
  119. package/plugins/search/src/datasets.test.js +1 -1
  120. package/plugins/search/src/defaults.js +1 -1
  121. package/plugins/search/src/events/fetchSuggestions.js +3 -3
  122. package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
  123. package/plugins/search/src/events/formHandlers.js +3 -3
  124. package/plugins/search/src/events/formHandlers.test.js +1 -1
  125. package/plugins/search/src/events/index.js +2 -2
  126. package/plugins/search/src/events/index.test.js +2 -2
  127. package/plugins/search/src/events/inputHandlers.js +4 -4
  128. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  129. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  130. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  131. package/plugins/search/src/index.js +2 -1
  132. package/plugins/search/src/index.test.js +3 -3
  133. package/plugins/search/src/manifest.js +6 -4
  134. package/plugins/search/src/reducer.js +1 -2
  135. package/plugins/search/src/reducer.test.js +2 -2
  136. package/plugins/search/src/search.scss +10 -3
  137. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  138. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  139. package/plugins/search/src/utils/updateMap.js +1 -1
  140. package/plugins/search/src/utils/updateMap.test.js +5 -5
  141. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  142. package/providers/beta/esri/src/esriProvider.js +5 -5
  143. package/providers/beta/esri/src/utils/coords.js +1 -1
  144. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  145. package/providers/beta/esri/src/utils/query.js +4 -4
  146. package/providers/beta/esri/src/utils/spatial.js +1 -2
  147. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  148. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  149. package/providers/maplibre/src/appEvents.test.js +1 -1
  150. package/providers/maplibre/src/index.js +1 -1
  151. package/providers/maplibre/src/index.test.js +3 -5
  152. package/providers/maplibre/src/mapEvents.test.js +15 -5
  153. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  154. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  155. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  156. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  157. package/providers/maplibre/src/utils/highlightFeatures.js +2 -2
  158. package/providers/maplibre/src/utils/highlightFeatures.test.js +12 -6
  159. package/providers/maplibre/src/utils/labels.js +19 -20
  160. package/providers/maplibre/src/utils/labels.test.js +15 -13
  161. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  162. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  163. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  164. package/providers/maplibre/src/utils/spatial.js +0 -1
  165. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  166. package/src/App/registry/pluginRegistry.js +17 -0
  167. package/src/App/registry/pluginRegistry.test.js +33 -0
  168. package/src/App/renderer/mapButtons.js +3 -2
  169. package/src/App/store/appDispatchMiddleware.js +33 -1
  170. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  171. package/src/config/appConfig.js +2 -2
  172. package/src/utils/logger.js +6 -0
  173. package/src/utils/logger.test.js +32 -0
  174. package/webpack.dev.mjs +22 -18
  175. package/docs/govuk-prototype.md +0 -23
  176. package/docs/index.md +0 -19
@@ -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: 'bottom' }, 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: 'bottom' }
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
  })
@@ -144,10 +144,10 @@ export const defaultControlConfig = {
144
144
  slot: 'bottom'
145
145
  },
146
146
  tablet: {
147
- slot: 'left-top'
147
+ slot: 'top-left'
148
148
  },
149
149
  desktop: {
150
- slot: 'left-top'
150
+ slot: 'top-left'
151
151
  }
152
152
  }
153
153
 
@@ -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
+ })
package/webpack.dev.mjs CHANGED
@@ -12,6 +12,10 @@ 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
21
  forms: path.join(__dirname, 'demo/js/forms.js'),
@@ -21,7 +25,7 @@ export default {
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).