@defra/interactive-map 0.0.14-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.
- package/assets/css/docusaurus.css +104 -0
- package/assets/images/favicon.svg +1 -0
- package/assets/images/hero.png +0 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/slot-map.svg +1 -0
- package/docs/api/slots.md +89 -6
- package/docs/api.md +1 -1
- package/docs/architecture.md +3 -1
- package/docs/{demo.mdx → examples.mdx} +1 -1
- package/docs/getting-started.md +1 -3
- package/docs/index.mdx +42 -0
- package/docs/plugins/interact.md +176 -55
- package/docs/plugins/map-styles.md +64 -7
- package/docs/plugins/search.md +207 -63
- package/docs/plugins.md +7 -15
- package/docusaurus.config.cjs +34 -34
- package/jest.setup.js +1 -1
- package/package.json +5 -4
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/src/DatasetsInit.jsx +1 -1
- package/plugins/beta/datasets/src/api/addDataset.js +1 -1
- package/plugins/beta/datasets/src/api/hideDataset.js +1 -1
- package/plugins/beta/datasets/src/api/hideFeatures.js +1 -1
- package/plugins/beta/datasets/src/api/removeDataset.js +1 -1
- package/plugins/beta/datasets/src/api/showDataset.js +1 -1
- package/plugins/beta/datasets/src/api/showFeatures.js +1 -1
- package/plugins/beta/datasets/src/datasets.js +4 -4
- package/plugins/beta/datasets/src/defaults.js +1 -1
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +5 -5
- package/plugins/beta/datasets/src/handleSetMapStyle.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +7 -7
- package/plugins/beta/datasets/src/mapLayers.js +2 -3
- package/plugins/beta/datasets/src/panels/Key.jsx +31 -29
- package/plugins/beta/datasets/src/panels/Layers.jsx +8 -9
- package/plugins/beta/datasets/src/utils/bbox.js +4 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
- package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
- package/plugins/beta/draw-es/src/events.js +52 -20
- package/plugins/beta/draw-es/src/events.test.js +301 -0
- package/plugins/beta/draw-es/src/graphic.js +1 -1
- package/plugins/beta/draw-es/src/manifest.js +4 -4
- package/plugins/beta/draw-es/src/reducer.js +1 -1
- package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
- package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
- package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
- package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
- package/plugins/beta/draw-ml/src/api/split.js +4 -4
- package/plugins/beta/draw-ml/src/defaults.js +1 -1
- package/plugins/beta/draw-ml/src/events.js +8 -6
- package/plugins/beta/draw-ml/src/manifest.js +15 -15
- package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
- package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
- package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
- package/plugins/beta/draw-ml/src/reducer.js +1 -1
- package/plugins/beta/draw-ml/src/undoStack.js +4 -4
- package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
- package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
- package/plugins/beta/frame/src/Frame.jsx +4 -4
- package/plugins/beta/frame/src/FrameInit.jsx +4 -4
- package/plugins/beta/frame/src/api/addFrame.js +1 -1
- package/plugins/beta/frame/src/api/editFeature.js +1 -1
- package/plugins/beta/frame/src/config.js +1 -1
- package/plugins/beta/frame/src/manifest.js +3 -3
- package/plugins/beta/frame/src/reducer.js +1 -1
- package/plugins/beta/frame/src/utils.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
- package/plugins/beta/map-styles/src/manifest.js +2 -2
- package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
- package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
- package/plugins/beta/use-location/src/defaults.js +1 -1
- package/plugins/beta/use-location/src/events.js +3 -3
- package/plugins/interact/src/InteractInit.jsx +1 -2
- package/plugins/interact/src/api/enable.js +8 -5
- package/plugins/interact/src/api/enable.test.js +2 -2
- package/plugins/interact/src/api/selectFeature.js +4 -4
- package/plugins/interact/src/api/unselectFeature.js +5 -5
- package/plugins/interact/src/defaults.js +0 -1
- package/plugins/interact/src/events.test.js +15 -15
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
- package/plugins/interact/src/manifest.js +2 -2
- package/plugins/interact/src/manifest.test.js +3 -4
- package/plugins/interact/src/reducer.js +3 -3
- package/plugins/interact/src/reducer.test.js +0 -1
- package/plugins/interact/src/utils/spatial.js +10 -10
- package/plugins/interact/src/utils/spatial.test.js +14 -14
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +7 -6
- package/plugins/search/src/Search.test.jsx +23 -23
- package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
- package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
- package/plugins/search/src/components/Form/Form.jsx +14 -14
- package/plugins/search/src/components/Form/Form.test.jsx +11 -11
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
- package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
- package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
- package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
- package/plugins/search/src/datasets.js +12 -13
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +1 -1
- package/plugins/search/src/events/fetchSuggestions.js +4 -4
- package/plugins/search/src/events/fetchSuggestions.test.js +5 -5
- package/plugins/search/src/events/formHandlers.js +3 -3
- package/plugins/search/src/events/formHandlers.test.js +1 -1
- package/plugins/search/src/events/index.js +2 -2
- package/plugins/search/src/events/index.test.js +2 -2
- package/plugins/search/src/events/inputHandlers.js +4 -4
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -2
- package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
- package/plugins/search/src/index.js +2 -1
- package/plugins/search/src/index.test.js +3 -3
- package/plugins/search/src/manifest.js +6 -4
- package/plugins/search/src/reducer.js +1 -2
- package/plugins/search/src/reducer.test.js +2 -2
- package/plugins/search/src/search.scss +18 -6
- package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
- package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
- package/plugins/search/src/utils/updateMap.js +1 -1
- package/plugins/search/src/utils/updateMap.test.js +5 -5
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +5 -5
- package/providers/beta/esri/src/utils/coords.js +1 -1
- package/providers/beta/esri/src/utils/esriFixes.js +1 -1
- package/providers/beta/esri/src/utils/query.js +4 -4
- package/providers/beta/esri/src/utils/spatial.js +1 -2
- package/providers/beta/esri/src/utils/spatial.test.js +4 -1
- package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
- package/providers/maplibre/src/appEvents.test.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +3 -5
- package/providers/maplibre/src/mapEvents.test.js +15 -5
- package/providers/maplibre/src/maplibreProvider.test.js +6 -2
- package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
- package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
- package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +2 -2
- package/providers/maplibre/src/utils/highlightFeatures.test.js +12 -6
- package/providers/maplibre/src/utils/labels.js +19 -20
- package/providers/maplibre/src/utils/labels.test.js +15 -13
- package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
- package/providers/maplibre/src/utils/queryFeatures.js +6 -6
- package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
- package/providers/maplibre/src/utils/spatial.js +0 -1
- package/providers/maplibre/src/utils/spatial.test.js +26 -27
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/hooks/useLayoutMeasurements.js +1 -10
- package/src/App/hooks/useLayoutMeasurements.test.js +2 -5
- package/src/App/hooks/useVisibleGeometry.js +7 -13
- package/src/App/hooks/useVisibleGeometry.test.js +72 -47
- package/src/App/layout/Layout.jsx +0 -3
- package/src/App/layout/Layout.test.jsx +0 -1
- package/src/App/layout/layout.module.scss +11 -77
- package/src/App/registry/pluginRegistry.js +17 -0
- package/src/App/registry/pluginRegistry.test.js +33 -0
- package/src/App/renderer/HtmlElementHost.jsx +0 -1
- package/src/App/renderer/HtmlElementHost.test.jsx +20 -11
- package/src/App/renderer/mapButtons.js +3 -2
- package/src/App/renderer/mapPanels.test.js +3 -3
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +3 -3
- package/src/App/renderer/slots.js +0 -3
- package/src/App/store/AppProvider.jsx +0 -1
- package/src/App/store/appDispatchMiddleware.js +33 -1
- package/src/App/store/appDispatchMiddleware.test.js +250 -222
- package/src/config/appConfig.js +4 -4
- package/src/utils/getSafeZoneInset.js +139 -42
- package/src/utils/getSafeZoneInset.test.js +298 -122
- package/src/utils/logger.js +6 -0
- package/src/utils/logger.test.js +32 -0
- package/webpack.dev.mjs +22 -18
- package/docs/govuk-prototype.md +0 -23
- package/docs/index.md +0 -19
|
@@ -153,6 +153,11 @@
|
|
|
153
153
|
left: var(--primary-gap);
|
|
154
154
|
top: var(--left-offset-top);
|
|
155
155
|
bottom: var(--left-offset-bottom);
|
|
156
|
+
gap: var(--divider-gap);
|
|
157
|
+
|
|
158
|
+
& > *:empty {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
156
161
|
|
|
157
162
|
@media (prefers-reduced-motion: no-preference) {
|
|
158
163
|
transition: bottom 0.15s ease;
|
|
@@ -162,34 +167,21 @@
|
|
|
162
167
|
.im-o-app__left-top {
|
|
163
168
|
display: flex;
|
|
164
169
|
flex-direction: column;
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
align-items: flex-start;
|
|
171
|
+
min-height: 0;
|
|
167
172
|
position: relative;
|
|
168
173
|
gap: var(--divider-gap);
|
|
169
|
-
|
|
170
|
-
.im-c-panel {
|
|
171
|
-
position: absolute;
|
|
172
|
-
top: 0;
|
|
173
|
-
left: 0;
|
|
174
|
-
max-height: var(--left-top-panel-max-height);
|
|
175
|
-
}
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
.im-o-app__left-bottom {
|
|
179
177
|
display: flex;
|
|
180
178
|
flex-direction: column;
|
|
181
|
-
|
|
179
|
+
align-items: flex-start;
|
|
180
|
+
justify-content: flex-end;
|
|
181
|
+
min-height: 0;
|
|
182
182
|
margin-top: auto;
|
|
183
|
-
align-items: flex-end;
|
|
184
183
|
position: relative;
|
|
185
184
|
gap: var(--divider-gap);
|
|
186
|
-
|
|
187
|
-
.im-c-panel {
|
|
188
|
-
position: absolute;
|
|
189
|
-
bottom: 0;
|
|
190
|
-
left: 0;
|
|
191
|
-
max-height: var(--left-bottom-panel-max-height);
|
|
192
|
-
}
|
|
193
185
|
}
|
|
194
186
|
|
|
195
187
|
// ---------------------------------------------------
|
|
@@ -219,6 +211,7 @@
|
|
|
219
211
|
right: var(--primary-gap);
|
|
220
212
|
top: var(--right-offset-top);
|
|
221
213
|
bottom: var(--right-offset-bottom);
|
|
214
|
+
gap: var(--divider-gap);
|
|
222
215
|
|
|
223
216
|
@media (prefers-reduced-motion: no-preference) {
|
|
224
217
|
transition: bottom 0.15s ease;
|
|
@@ -232,13 +225,6 @@
|
|
|
232
225
|
align-items: flex-end;
|
|
233
226
|
position: relative;
|
|
234
227
|
gap: var(--divider-gap);
|
|
235
|
-
|
|
236
|
-
.im-c-panel {
|
|
237
|
-
position: absolute;
|
|
238
|
-
top: 0;
|
|
239
|
-
right: 0;
|
|
240
|
-
max-height: var(--right-top-panel-max-height);
|
|
241
|
-
}
|
|
242
228
|
}
|
|
243
229
|
|
|
244
230
|
.im-o-app__right-bottom {
|
|
@@ -249,57 +235,6 @@
|
|
|
249
235
|
align-items: flex-end;
|
|
250
236
|
position: relative;
|
|
251
237
|
gap: var(--divider-gap);
|
|
252
|
-
|
|
253
|
-
.im-c-panel {
|
|
254
|
-
position: absolute;
|
|
255
|
-
bottom: 0;
|
|
256
|
-
right: 0;
|
|
257
|
-
max-height: var(--right-bottom-panel-max-height);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// When both left sub-slots have panels, split the column proportionally
|
|
262
|
-
// so panels don't overlap. flex: 1 1 auto gives each sub-slot a share of the
|
|
263
|
-
// column; max-height: 100% caps the panel at its sub-slot height (not forced).
|
|
264
|
-
.im-o-app__left:has(.im-o-app__left-top > .im-c-panel:not([style*="display: none"])):has(.im-o-app__left-bottom > .im-c-panel:not([style*="display: none"])) {
|
|
265
|
-
gap: var(--divider-gap);
|
|
266
|
-
|
|
267
|
-
.im-o-app__left-top,
|
|
268
|
-
.im-o-app__left-bottom {
|
|
269
|
-
flex: 1 1 auto;
|
|
270
|
-
min-height: 0;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.im-o-app__left-bottom {
|
|
274
|
-
margin-top: 0;
|
|
275
|
-
justify-content: flex-end;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.im-o-app__left-top .im-c-panel,
|
|
279
|
-
.im-o-app__left-bottom .im-c-panel {
|
|
280
|
-
max-height: 100%;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Same for right column
|
|
285
|
-
.im-o-app__right:has(.im-o-app__right-top > .im-c-panel:not([style*="display: none"])):has(.im-o-app__right-bottom > .im-c-panel:not([style*="display: none"])) {
|
|
286
|
-
gap: var(--divider-gap);
|
|
287
|
-
|
|
288
|
-
.im-o-app__right-top,
|
|
289
|
-
.im-o-app__right-bottom {
|
|
290
|
-
flex: 1 1 auto;
|
|
291
|
-
min-height: 0;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.im-o-app__right-bottom {
|
|
295
|
-
margin-top: 0;
|
|
296
|
-
justify-content: flex-end;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.im-o-app__right-top .im-c-panel,
|
|
300
|
-
.im-o-app__right-bottom .im-c-panel {
|
|
301
|
-
max-height: 100%;
|
|
302
|
-
}
|
|
303
238
|
}
|
|
304
239
|
|
|
305
240
|
// ---------------------------------------------------
|
|
@@ -517,7 +452,6 @@
|
|
|
517
452
|
width: 100%;
|
|
518
453
|
left: 0;
|
|
519
454
|
bottom: calc(var(--primary-gap) * 2);
|
|
520
|
-
padding-left: var(--offset-left);
|
|
521
455
|
|
|
522
456
|
.im-c-panel {
|
|
523
457
|
max-width: var(--action-bar-max-width);
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
// src/core/registry/pluginRegistry.js
|
|
2
2
|
import { registerIcon } from './iconRegistry.js'
|
|
3
3
|
import { registerKeyboardShortcut } from './keyboardShortcutRegistry.js'
|
|
4
|
+
import { allowedSlots } from '../renderer/slots.js'
|
|
5
|
+
import { logger } from '../../utils/logger.js'
|
|
4
6
|
|
|
5
7
|
const asArray = (value) => Array.isArray(value) ? value : [value]
|
|
6
8
|
|
|
9
|
+
const BREAKPOINTS = ['mobile', 'tablet', 'desktop']
|
|
10
|
+
|
|
11
|
+
function validateSlots (item, type) {
|
|
12
|
+
const allowed = allowedSlots[type]
|
|
13
|
+
BREAKPOINTS.forEach(bp => {
|
|
14
|
+
const slot = item[bp]?.slot
|
|
15
|
+
if (slot && !allowed.includes(slot) && !(type === 'panel' && slot.endsWith('-button'))) {
|
|
16
|
+
logger.warn(`${type} "${item.id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowed.join(', ')}.`)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export function createPluginRegistry ({ registerButton, registerPanel, registerControl }) {
|
|
8
22
|
const registeredPlugins = []
|
|
9
23
|
|
|
@@ -18,6 +32,7 @@ export function createPluginRegistry ({ registerButton, registerPanel, registerC
|
|
|
18
32
|
|
|
19
33
|
if (manifest.buttons) {
|
|
20
34
|
asArray(manifest.buttons).forEach(button => {
|
|
35
|
+
validateSlots(button, 'button')
|
|
21
36
|
registerButton({ [button.id]: { ...pluginConfig, ...button } })
|
|
22
37
|
// Flat button registry including any menu items (isMenuItem prevents slot rendering)
|
|
23
38
|
button?.menuItems?.forEach(menuItem => {
|
|
@@ -28,12 +43,14 @@ export function createPluginRegistry ({ registerButton, registerPanel, registerC
|
|
|
28
43
|
|
|
29
44
|
if (manifest.panels) {
|
|
30
45
|
asArray(manifest.panels).forEach(panel => {
|
|
46
|
+
validateSlots(panel, 'panel')
|
|
31
47
|
registerPanel({ [panel.id]: { ...pluginConfig, ...panel } })
|
|
32
48
|
})
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
if (manifest.controls) {
|
|
36
52
|
asArray(manifest.controls).forEach(control => {
|
|
53
|
+
validateSlots(control, 'control')
|
|
37
54
|
registerControl({ [control.id]: { ...pluginConfig, ...control } })
|
|
38
55
|
})
|
|
39
56
|
}
|
|
@@ -139,6 +139,39 @@ describe('pluginRegistry', () => {
|
|
|
139
139
|
expect(pluginRegistry.registeredPlugins).toEqual([pluginA, pluginB])
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
+
describe('slot validation', () => {
|
|
143
|
+
const INVALID_SLOT = 'invalid-slot'
|
|
144
|
+
|
|
145
|
+
beforeEach(() => jest.spyOn(console, 'warn').mockImplementation(() => {}))
|
|
146
|
+
afterEach(() => jest.restoreAllMocks())
|
|
147
|
+
|
|
148
|
+
it('warns when a manifest item has an invalid slot', () => {
|
|
149
|
+
const plugin = {
|
|
150
|
+
id: 'bad-plugin',
|
|
151
|
+
config: {},
|
|
152
|
+
manifest: {
|
|
153
|
+
buttons: [{ id: 'btn1', desktop: { slot: INVALID_SLOT } }],
|
|
154
|
+
panels: [{ id: 'panel1', desktop: { slot: INVALID_SLOT } }],
|
|
155
|
+
controls: [{ id: 'ctrl1', desktop: { slot: INVALID_SLOT } }]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
pluginRegistry.registerPlugin(plugin)
|
|
159
|
+
expect(console.warn).toHaveBeenCalledWith('[interactive-map]', expect.stringContaining(INVALID_SLOT))
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('does not warn for a panel with a button-adjacent slot', () => {
|
|
163
|
+
const plugin = {
|
|
164
|
+
id: 'adj-plugin',
|
|
165
|
+
config: {},
|
|
166
|
+
manifest: {
|
|
167
|
+
panels: [{ id: 'panel1', desktop: { slot: 'left-top-button' } }]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
pluginRegistry.registerPlugin(plugin)
|
|
171
|
+
expect(console.warn).not.toHaveBeenCalled()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
142
175
|
it('clears all registered plugins', () => {
|
|
143
176
|
const pluginA = { id: 'A', config: {}, manifest: {} }
|
|
144
177
|
const pluginB = { id: 'B', config: {}, manifest: {} }
|
|
@@ -14,7 +14,6 @@ export const getSlotRef = (slot, layoutRefs) => {
|
|
|
14
14
|
banner: layoutRefs.bannerRef,
|
|
15
15
|
'top-left': layoutRefs.topLeftColRef,
|
|
16
16
|
'top-right': layoutRefs.topRightColRef,
|
|
17
|
-
inset: layoutRefs.insetRef,
|
|
18
17
|
'left-top': layoutRefs.leftTopRef,
|
|
19
18
|
'left-bottom': layoutRefs.leftBottomRef,
|
|
20
19
|
middle: layoutRefs.middleRef,
|
|
@@ -14,8 +14,8 @@ jest.mock('../components/Panel/Panel.jsx', () => ({
|
|
|
14
14
|
}))
|
|
15
15
|
jest.mock('./slots.js', () => ({
|
|
16
16
|
allowedSlots: {
|
|
17
|
-
panel: ['
|
|
18
|
-
control: ['
|
|
17
|
+
panel: ['left-top', 'side', 'modal', 'bottom'],
|
|
18
|
+
control: ['left-top', 'banner', 'bottom', 'actions']
|
|
19
19
|
}
|
|
20
20
|
}))
|
|
21
21
|
|
|
@@ -25,7 +25,7 @@ jest.mock('./slots.js', () => ({
|
|
|
25
25
|
*/
|
|
26
26
|
const SlotHarness = ({ layoutRefs, children }) => (
|
|
27
27
|
<div>
|
|
28
|
-
<div ref={layoutRefs.
|
|
28
|
+
<div ref={layoutRefs.leftTopRef} data-slot='left-top' />
|
|
29
29
|
<div ref={layoutRefs.sideRef} data-slot='side' />
|
|
30
30
|
<div ref={layoutRefs.modalRef} data-slot='modal' />
|
|
31
31
|
<div ref={layoutRefs.bottomRef} data-slot='bottom' />
|
|
@@ -45,7 +45,7 @@ describe('HtmlElementHost', () => {
|
|
|
45
45
|
bannerRef: { current: null },
|
|
46
46
|
topLeftColRef: { current: null },
|
|
47
47
|
topRightColRef: { current: null },
|
|
48
|
-
|
|
48
|
+
leftTopRef: { current: null },
|
|
49
49
|
middleRef: { current: null },
|
|
50
50
|
bottomRef: { current: null },
|
|
51
51
|
actionsRef: { current: null },
|
|
@@ -105,10 +105,10 @@ describe('HtmlElementHost', () => {
|
|
|
105
105
|
|
|
106
106
|
it('projects open panel into correct slot', () => {
|
|
107
107
|
const { container } = renderWithSlots({
|
|
108
|
-
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: '
|
|
108
|
+
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: 'left-top' } } },
|
|
109
109
|
openPanels: { p1: { props: {} } }
|
|
110
110
|
})
|
|
111
|
-
expect(container.querySelector('[data-slot="
|
|
111
|
+
expect(container.querySelector('[data-slot="left-top"] [data-testid="panel-p1"]')).toBeTruthy()
|
|
112
112
|
})
|
|
113
113
|
|
|
114
114
|
it('hides panel when closed and passes isOpen=false', () => {
|
|
@@ -130,19 +130,28 @@ describe('HtmlElementHost', () => {
|
|
|
130
130
|
|
|
131
131
|
it('hides panel with inline:false when not fullscreen', () => {
|
|
132
132
|
const { getByTestId } = renderWithSlots({
|
|
133
|
-
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: '
|
|
133
|
+
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: 'left-top' }, inline: false } },
|
|
134
134
|
openPanels: { p1: { props: {} } },
|
|
135
135
|
isFullscreen: false
|
|
136
136
|
})
|
|
137
137
|
expect(getByTestId('panel-p1').style.display).toBe('none')
|
|
138
138
|
})
|
|
139
139
|
|
|
140
|
-
it('
|
|
140
|
+
it('shows panel with inline:false when fullscreen', () => {
|
|
141
|
+
const { getByTestId } = renderWithSlots({
|
|
142
|
+
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: 'left-top' }, inline: false } },
|
|
143
|
+
openPanels: { p1: { props: {} } },
|
|
144
|
+
isFullscreen: true
|
|
145
|
+
})
|
|
146
|
+
expect(getByTestId('panel-p1').dataset.open).toBe('true')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('resolves bottom slot to left-top on desktop', () => {
|
|
141
150
|
const { container } = renderWithSlots({
|
|
142
151
|
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: 'bottom' } } },
|
|
143
152
|
openPanels: { p1: { props: {} } }
|
|
144
153
|
})
|
|
145
|
-
expect(container.querySelector('[data-slot="
|
|
154
|
+
expect(container.querySelector('[data-slot="left-top"] [data-testid="panel-p1"]')).toBeTruthy()
|
|
146
155
|
expect(container.querySelector('[data-slot="bottom"] [data-testid="panel-p1"]')).toBeNull()
|
|
147
156
|
})
|
|
148
157
|
|
|
@@ -169,9 +178,9 @@ describe('HtmlElementHost', () => {
|
|
|
169
178
|
|
|
170
179
|
it('projects visible control into correct slot', () => {
|
|
171
180
|
const { container } = renderWithSlots({
|
|
172
|
-
controlConfig: { c1: { id: 'c1', html: '<input type="checkbox">', desktop: { slot: '
|
|
181
|
+
controlConfig: { c1: { id: 'c1', html: '<input type="checkbox">', desktop: { slot: 'left-top' } } }
|
|
173
182
|
})
|
|
174
|
-
const control = container.querySelector('[data-slot="
|
|
183
|
+
const control = container.querySelector('[data-slot="left-top"] .im-c-control')
|
|
175
184
|
expect(control).toBeTruthy()
|
|
176
185
|
expect(control.innerHTML).toBe('<input type="checkbox">')
|
|
177
186
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/core/renderers/mapButtons.js
|
|
2
2
|
import { MapButton } from '../components/MapButton/MapButton.jsx'
|
|
3
3
|
import { allowedSlots } from './slots.js'
|
|
4
|
+
import { logger } from '../../utils/logger.js'
|
|
4
5
|
|
|
5
6
|
function getMatchingButtons ({ appState, buttonConfig, slot, evaluateProp }) {
|
|
6
7
|
const { breakpoint, mode } = appState
|
|
@@ -163,7 +164,7 @@ function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
|
163
164
|
|
|
164
165
|
/* istanbul ignore next */
|
|
165
166
|
if (process.env.NODE_ENV !== 'production' && typeof group === 'string') {
|
|
166
|
-
|
|
167
|
+
logger.warn(`Button "${buttonId}": group should be an object { name, label?, order? } — string groups are deprecated.`)
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
const name = resolveGroupName(group)
|
|
@@ -174,7 +175,7 @@ function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
|
174
175
|
const existing = groupMap.get(name)
|
|
175
176
|
/* istanbul ignore next */
|
|
176
177
|
if (process.env.NODE_ENV !== 'production' && existing.order !== order) {
|
|
177
|
-
|
|
178
|
+
logger.warn(`Group "${name}" has inconsistent order values (${existing.order} vs ${order}). Using the lower value.`)
|
|
178
179
|
existing.order = Math.min(existing.order, order)
|
|
179
180
|
}
|
|
180
181
|
} else {
|
|
@@ -7,7 +7,7 @@ jest.mock('../registry/panelRegistry.js')
|
|
|
7
7
|
jest.mock('../registry/pluginRegistry.js', () => ({ registeredPlugins: [] }))
|
|
8
8
|
jest.mock('./pluginWrapper.js', () => ({ withPluginContexts: jest.fn((c) => c) }))
|
|
9
9
|
jest.mock('../components/Panel/Panel.jsx', () => ({ Panel: (props) => <div data-testid='panel' {...props} /> }))
|
|
10
|
-
jest.mock('./slots.js', () => ({ allowedSlots: { panel: ['header', 'modal', '
|
|
10
|
+
jest.mock('./slots.js', () => ({ allowedSlots: { panel: ['header', 'modal', 'left-top'] } }))
|
|
11
11
|
|
|
12
12
|
describe('mapPanels', () => {
|
|
13
13
|
const baseConfig = {
|
|
@@ -145,7 +145,7 @@ describe('mapPanels', () => {
|
|
|
145
145
|
expect(map()).toHaveLength(1)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
it('replaces bottom slot with
|
|
148
|
+
it('replaces bottom slot with left-top on non-mobile breakpoints', () => {
|
|
149
149
|
defaultAppState.panelConfig = ({
|
|
150
150
|
p1: {
|
|
151
151
|
desktop: { slot: 'bottom' },
|
|
@@ -153,7 +153,7 @@ describe('mapPanels', () => {
|
|
|
153
153
|
}
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
const result = map(defaultAppState, '
|
|
156
|
+
const result = map(defaultAppState, 'left-top')
|
|
157
157
|
expect(result).toHaveLength(1)
|
|
158
158
|
expect(result[0].id).toBe('p1')
|
|
159
159
|
})
|
|
@@ -4,14 +4,14 @@ import { allowedSlots } from './slots.js'
|
|
|
4
4
|
/**
|
|
5
5
|
* Resolves the target slot for a panel based on its breakpoint config.
|
|
6
6
|
* Modal panels always render in the 'modal' slot, and the bottom slot
|
|
7
|
-
* is only available on mobile — tablet and desktop fall back to '
|
|
7
|
+
* is only available on mobile — tablet and desktop fall back to 'left-top'.
|
|
8
8
|
*/
|
|
9
9
|
export const resolveTargetSlot = (bpConfig, breakpoint) => {
|
|
10
10
|
if (bpConfig.modal) {
|
|
11
11
|
return 'modal'
|
|
12
12
|
}
|
|
13
13
|
if (bpConfig.slot === 'bottom' && ['tablet', 'desktop'].includes(breakpoint)) {
|
|
14
|
-
return '
|
|
14
|
+
return 'left-top'
|
|
15
15
|
}
|
|
16
16
|
return bpConfig.slot
|
|
17
17
|
}
|
|
@@ -7,9 +7,9 @@ describe('resolveTargetSlot', () => {
|
|
|
7
7
|
expect(resolveTargetSlot({ modal: true, slot: 'side' }, 'desktop')).toBe('modal')
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
it('replaces bottom with
|
|
11
|
-
expect(resolveTargetSlot({ slot: 'bottom' }, 'tablet')).toBe('
|
|
12
|
-
expect(resolveTargetSlot({ slot: 'bottom' }, 'desktop')).toBe('
|
|
10
|
+
it('replaces bottom with left-top on tablet and desktop', () => {
|
|
11
|
+
expect(resolveTargetSlot({ slot: 'bottom' }, 'tablet')).toBe('left-top')
|
|
12
|
+
expect(resolveTargetSlot({ slot: 'bottom' }, 'desktop')).toBe('left-top')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
it('keeps bottom on mobile', () => {
|
|
@@ -5,7 +5,6 @@ export const layoutSlots = Object.freeze({
|
|
|
5
5
|
TOP_LEFT: 'top-left',
|
|
6
6
|
TOP_MIDDLE: 'top-middle',
|
|
7
7
|
TOP_RIGHT: 'top-right',
|
|
8
|
-
INSET: 'inset',
|
|
9
8
|
LEFT_TOP: 'left-top',
|
|
10
9
|
LEFT_BOTTOM: 'left-bottom',
|
|
11
10
|
MIDDLE: 'middle',
|
|
@@ -22,7 +21,6 @@ export const allowedSlots = Object.freeze({
|
|
|
22
21
|
layoutSlots.BANNER,
|
|
23
22
|
layoutSlots.TOP_LEFT,
|
|
24
23
|
layoutSlots.TOP_RIGHT,
|
|
25
|
-
layoutSlots.INSET,
|
|
26
24
|
layoutSlots.MIDDLE,
|
|
27
25
|
layoutSlots.FOOTER_RIGHT,
|
|
28
26
|
layoutSlots.BOTTOM,
|
|
@@ -31,7 +29,6 @@ export const allowedSlots = Object.freeze({
|
|
|
31
29
|
panel: [
|
|
32
30
|
layoutSlots.SIDE,
|
|
33
31
|
layoutSlots.BANNER,
|
|
34
|
-
layoutSlots.INSET, // Deprecate
|
|
35
32
|
layoutSlots.LEFT_TOP,
|
|
36
33
|
layoutSlots.LEFT_BOTTOM,
|
|
37
34
|
layoutSlots.MIDDLE,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// src/App/store/dispatchMiddleware.js
|
|
2
2
|
import { EVENTS as events } from '../../config/events.js'
|
|
3
|
-
import { defaultPanelConfig } from '../../config/appConfig.js'
|
|
3
|
+
import { defaultPanelConfig, defaultButtonConfig, defaultControlConfig } from '../../config/appConfig.js'
|
|
4
4
|
import { deepMerge } from '../../utils/deepMerge.js'
|
|
5
|
+
import { allowedSlots } from '../renderer/slots.js'
|
|
6
|
+
import { logger } from '../../utils/logger.js'
|
|
7
|
+
|
|
8
|
+
const BREAKPOINTS = ['mobile', 'tablet', 'desktop']
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Determines which panels were implicitly closed when opening a new panel
|
|
@@ -81,9 +85,37 @@ export function handleActionSideEffects (action, previousState, panelConfig, eve
|
|
|
81
85
|
})
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
if (type === 'ADD_BUTTON') {
|
|
89
|
+
const { id, config } = payload
|
|
90
|
+
const mergedConfig = deepMerge(defaultButtonConfig, config)
|
|
91
|
+
BREAKPOINTS.forEach(bp => {
|
|
92
|
+
const slot = mergedConfig[bp]?.slot
|
|
93
|
+
if (slot && !allowedSlots.button.includes(slot)) {
|
|
94
|
+
logger.warn(`button "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.button.join(', ')}.`)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (type === 'ADD_CONTROL') {
|
|
100
|
+
const { id, config } = payload
|
|
101
|
+
const mergedConfig = deepMerge(defaultControlConfig, config)
|
|
102
|
+
BREAKPOINTS.forEach(bp => {
|
|
103
|
+
const slot = mergedConfig[bp]?.slot
|
|
104
|
+
if (slot && !allowedSlots.control.includes(slot)) {
|
|
105
|
+
logger.warn(`control "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.control.join(', ')}.`)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
if (type === 'ADD_PANEL') {
|
|
85
111
|
const { id, config } = payload
|
|
86
112
|
const mergedConfig = deepMerge(defaultPanelConfig, config)
|
|
113
|
+
BREAKPOINTS.forEach(bp => {
|
|
114
|
+
const slot = mergedConfig[bp]?.slot
|
|
115
|
+
if (slot && !allowedSlots.panel.includes(slot) && !slot.endsWith('-button')) {
|
|
116
|
+
logger.warn(`panel "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.panel.join(', ')}.`)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
87
119
|
const bpConfig = mergedConfig[previousState.breakpoint]
|
|
88
120
|
if (bpConfig?.open) {
|
|
89
121
|
queueMicrotask(() => {
|