@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.
- package/assets/css/docusaurus.css +104 -0
- package/assets/images/favicon.svg +1 -0
- package/assets/images/hero.png +0 -0
- package/assets/images/slot-map.svg +264 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/slots.md +90 -6
- package/docs/api.md +4 -4
- package/docs/architecture.md +3 -1
- package/docs/{demo.mdx → examples.mdx} +1 -1
- package/docs/getting-started.md +5 -4
- package/docs/index.mdx +42 -0
- package/docs/plugins/datasets.md +561 -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 +8 -16
- package/docusaurus.config.cjs +34 -34
- package/jest.setup.js +1 -1
- package/package.json +6 -5
- package/plugins/beta/datasets/dist/css/index.css +85 -15
- 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/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/DatasetsInit.jsx +24 -9
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
- package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
- package/plugins/beta/datasets/src/api/addDataset.js +3 -9
- package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
- package/plugins/beta/datasets/src/api/getStyle.js +13 -0
- package/plugins/beta/datasets/src/api/removeDataset.js +3 -45
- package/plugins/beta/datasets/src/api/setData.js +8 -0
- package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
- package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
- package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
- package/plugins/beta/datasets/src/api/setStyle.js +22 -0
- package/plugins/beta/datasets/src/datasets.js +33 -59
- package/plugins/beta/datasets/src/defaults.js +43 -9
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +39 -30
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
- package/plugins/beta/datasets/src/manifest.js +27 -19
- package/plugins/beta/datasets/src/panels/Key.jsx +129 -49
- package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
- package/plugins/beta/datasets/src/panels/Layers.jsx +131 -29
- package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
- package/plugins/beta/datasets/src/reducer.js +128 -9
- package/plugins/beta/datasets/src/styles/patterns.js +157 -0
- package/plugins/beta/datasets/src/utils/bbox.js +8 -6
- package/plugins/beta/datasets/src/utils/filters.js +5 -2
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
- 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/css/index.css +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/draw.scss +0 -7
- package/plugins/beta/draw-ml/src/events.js +8 -6
- package/plugins/beta/draw-ml/src/manifest.js +29 -29
- 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/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +9 -9
- 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 +1 -1
- package/plugins/beta/scale-bar/dist/css/index.css +1 -1
- package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
- package/plugins/beta/scale-bar/src/index.test.js +3 -3
- package/plugins/beta/scale-bar/src/manifest.js +3 -3
- package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
- 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/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- 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/interact.scss +0 -7
- package/plugins/interact/src/manifest.js +15 -19
- package/plugins/interact/src/manifest.test.js +6 -5
- 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.module.scss +2 -1
- 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 +3 -3
- package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
- 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 +10 -3
- 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/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.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 +3 -2
- package/providers/maplibre/src/utils/highlightFeatures.test.js +13 -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/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +0 -7
- package/src/App/components/Actions/Actions.test.jsx +1 -1
- package/src/App/components/Icon/Icon.jsx +3 -2
- package/src/App/components/Icon/Icon.module.scss +4 -0
- package/src/App/components/Icon/Icon.test.jsx +43 -4
- package/src/App/components/MapButton/MapButton.jsx +42 -17
- package/src/App/components/MapButton/MapButton.module.scss +4 -13
- package/src/App/components/MapButton/MapButton.test.jsx +27 -3
- package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
- package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
- package/src/App/hooks/useButtonStateEvaluator.js +12 -2
- package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
- package/src/App/hooks/useInterfaceAPI.js +6 -0
- package/src/App/hooks/useLayoutMeasurements.js +84 -18
- package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
- package/src/App/layout/Layout.jsx +12 -7
- package/src/App/layout/Layout.test.jsx +2 -2
- package/src/App/layout/layout.module.scss +67 -29
- package/src/App/registry/pluginRegistry.js +17 -0
- package/src/App/registry/pluginRegistry.test.js +33 -0
- package/src/App/renderer/HtmlElementHost.jsx +2 -1
- package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
- package/src/App/renderer/mapButtons.js +3 -2
- package/src/App/renderer/mapPanels.test.js +2 -2
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +5 -5
- package/src/App/renderer/slots.js +9 -5
- package/src/App/store/AppProvider.jsx +3 -1
- package/src/App/store/AppProvider.test.jsx +1 -1
- package/src/App/store/ServiceProvider.jsx +3 -1
- package/src/App/store/appActionsMap.js +16 -0
- package/src/App/store/appActionsMap.test.js +27 -0
- package/src/App/store/appDispatchMiddleware.js +33 -1
- package/src/App/store/appDispatchMiddleware.test.js +250 -222
- package/src/App/store/appReducer.js +2 -0
- package/src/InteractiveMap/InteractiveMap.js +4 -0
- package/src/config/appConfig.js +7 -4
- package/src/config/events.js +28 -0
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +0 -1
- package/src/services/logger.js +6 -0
- package/src/services/logger.test.js +32 -0
- package/src/utils/getSafeZoneInset.js +9 -7
- package/src/utils/getSafeZoneInset.test.js +10 -10
- package/webpack.dev.mjs +23 -19
- package/docs/govuk-prototype.md +0 -23
- package/docs/index.md +0 -19
- package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
- package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
- package/plugins/beta/datasets/src/api/showDataset.js +0 -14
- package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
- package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
- package/plugins/beta/datasets/src/mapLayers.js +0 -165
|
@@ -24,18 +24,21 @@ const refs = (o = {}) => ({
|
|
|
24
24
|
topRef: { current: o.top === null ? null : el({ offsetTop: 10, ...o.top }) },
|
|
25
25
|
topLeftColRef: { current: el({ offsetHeight: 50, offsetWidth: 200, ...o.topLeftCol }) },
|
|
26
26
|
topRightColRef: { current: el({ offsetHeight: 40, offsetWidth: 180, ...o.topRightCol }) },
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
bottomRef: { current: o.bottom === null ? null : el({ offsetTop: 400, ...o.bottom }) },
|
|
28
|
+
bottomRightRef: { current: el({ offsetTop: 400, ...o.bottomRight }) },
|
|
29
29
|
leftTopRef: { current: el({ offsetHeight: 0, ...o.leftTop }) },
|
|
30
30
|
leftBottomRef: { current: el({ offsetHeight: 0, ...o.leftBottom }) },
|
|
31
31
|
rightTopRef: { current: el({ offsetHeight: 0, ...o.rightTop }) },
|
|
32
|
-
rightBottomRef: { current: el({ offsetHeight: 0, ...o.rightBottom }) }
|
|
32
|
+
rightBottomRef: { current: el({ offsetHeight: 0, ...o.rightBottom }) },
|
|
33
|
+
attributionsRef: { current: el({ offsetHeight: 16, ...o.attributions }) },
|
|
34
|
+
drawerRef: { current: el(o.drawer) },
|
|
35
|
+
actionsRef: { current: el({ offsetTop: 450, ...o.actions }) }
|
|
33
36
|
})
|
|
34
37
|
|
|
35
38
|
const setup = (o = {}) => {
|
|
36
39
|
const dispatch = jest.fn()
|
|
37
40
|
const layoutRefs = refs(o.refs)
|
|
38
|
-
useApp.mockReturnValue({ dispatch, breakpoint: 'desktop', layoutRefs, ...o.app })
|
|
41
|
+
useApp.mockReturnValue({ dispatch, breakpoint: 'desktop', layoutRefs, arePluginsEvaluated: true, ...o.app })
|
|
39
42
|
useMap.mockReturnValue({ mapSize: { width: 800, height: 600 }, isMapReady: true, ...o.map })
|
|
40
43
|
getSafeZoneInset.mockReturnValue({ top: 0, right: 0, bottom: 0, left: 0 })
|
|
41
44
|
return { dispatch, layoutRefs }
|
|
@@ -56,7 +59,7 @@ describe('useLayoutMeasurements', () => {
|
|
|
56
59
|
})
|
|
57
60
|
|
|
58
61
|
test('early return when required refs are null', () => {
|
|
59
|
-
const { layoutRefs } = setup({ refs: { main: null, top: null,
|
|
62
|
+
const { layoutRefs } = setup({ refs: { main: null, top: null, bottom: null } })
|
|
60
63
|
renderHook(() => useLayoutMeasurements())
|
|
61
64
|
expect(layoutRefs.appContainerRef.current.style.setProperty).not.toHaveBeenCalled()
|
|
62
65
|
})
|
|
@@ -71,7 +74,7 @@ describe('useLayoutMeasurements', () => {
|
|
|
71
74
|
|
|
72
75
|
test.each([
|
|
73
76
|
['right-offset-top', { topRightCol: { offsetHeight: 80 }, top: { offsetTop: 15 } }, '95px'],
|
|
74
|
-
['right-offset-bottom', { main: { offsetHeight: 600 },
|
|
77
|
+
['right-offset-bottom', { main: { offsetHeight: 600 }, bottom: { offsetTop: 500 } }, '116px'],
|
|
75
78
|
// leftColumnHeight = 400 - (50+10) - 8 = 332; rightColumnHeight = 400 - (40+10) - 8 = 342
|
|
76
79
|
['left-top-max-height', {}, '332px'],
|
|
77
80
|
['right-top-max-height', {}, '342px']
|
|
@@ -107,6 +110,26 @@ describe('useLayoutMeasurements', () => {
|
|
|
107
110
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--top-col-width', expected)
|
|
108
111
|
})
|
|
109
112
|
|
|
113
|
+
test('uses 0 when bottomRightRef current is null', () => {
|
|
114
|
+
const { layoutRefs } = setup()
|
|
115
|
+
layoutRefs.bottomRightRef.current = null
|
|
116
|
+
renderHook(() => useLayoutMeasurements())
|
|
117
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--right-offset-bottom', '116px')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('uses bottomRight height when bottomRightHeight > 0', () => {
|
|
121
|
+
const { layoutRefs } = setup({
|
|
122
|
+
refs: {
|
|
123
|
+
bottomRight: { offsetHeight: 20 } // 👈 triggers TRUE branch
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
renderHook(() => useLayoutMeasurements())
|
|
127
|
+
// bottomContainerPad = 500 - 400 - 0 = 100
|
|
128
|
+
// expected = 100 + (20 + 8) = 128
|
|
129
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty)
|
|
130
|
+
.toHaveBeenCalledWith('--right-offset-bottom', '128px')
|
|
131
|
+
})
|
|
132
|
+
|
|
110
133
|
test('uses 0 when sub-slot refs have null current', () => {
|
|
111
134
|
const { layoutRefs } = setup()
|
|
112
135
|
layoutRefs.leftTopRef.current = null
|
|
@@ -119,31 +142,100 @@ describe('useLayoutMeasurements', () => {
|
|
|
119
142
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--right-bottom-panel-max-height', '342px')
|
|
120
143
|
})
|
|
121
144
|
|
|
122
|
-
test('dispatches safe zone inset', () => {
|
|
123
|
-
const { dispatch, layoutRefs } = setup()
|
|
145
|
+
test('dispatches safe zone inset on desktop (post-batch RAF read only)', () => {
|
|
146
|
+
const { dispatch, layoutRefs } = setup({ app: { breakpoint: 'desktop' } })
|
|
124
147
|
getSafeZoneInset.mockReturnValue({ top: 10, right: 5, bottom: 15, left: 5 })
|
|
125
148
|
renderHook(() => useLayoutMeasurements())
|
|
126
149
|
expect(getSafeZoneInset).toHaveBeenCalledWith(layoutRefs)
|
|
127
150
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SAFE_ZONE_INSET', payload: { safeZoneInset: { top: 10, right: 5, bottom: 15, left: 5 } } })
|
|
128
151
|
})
|
|
129
152
|
|
|
130
|
-
test('
|
|
153
|
+
test('dispatches safe zone inset on mobile', () => {
|
|
154
|
+
const { dispatch } = setup({ app: { breakpoint: 'mobile' } })
|
|
155
|
+
getSafeZoneInset.mockReturnValue({ top: 10, right: 5, bottom: 40, left: 5 })
|
|
156
|
+
renderHook(() => useLayoutMeasurements())
|
|
157
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
158
|
+
type: 'SET_SAFE_ZONE_INSET',
|
|
159
|
+
payload: { safeZoneInset: { top: 10, right: 5, bottom: 40, left: 5 } }
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('does not dispatch SET_SAFE_ZONE_INSET when getSafeZoneInset returns undefined', () => {
|
|
164
|
+
const { dispatch } = setup()
|
|
165
|
+
getSafeZoneInset.mockReturnValue(undefined)
|
|
166
|
+
renderHook(() => useLayoutMeasurements())
|
|
167
|
+
expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'SET_SAFE_ZONE_INSET' }))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('does not dispatch safe zone when arePluginsEvaluated is false', () => {
|
|
171
|
+
const { dispatch } = setup({ app: { arePluginsEvaluated: false } })
|
|
172
|
+
renderHook(() => useLayoutMeasurements())
|
|
173
|
+
expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'SET_SAFE_ZONE_INSET' }))
|
|
174
|
+
expect(layoutRefs => layoutRefs).toBeDefined() // no layout calculation
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('re-dispatches safe zone when arePluginsEvaluated becomes true', () => {
|
|
178
|
+
setup({ app: { arePluginsEvaluated: false } })
|
|
179
|
+
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
180
|
+
const { dispatch } = setup({ app: { arePluginsEvaluated: true } })
|
|
181
|
+
getSafeZoneInset.mockReturnValue({ top: 5, right: 5, bottom: 60, left: 5 })
|
|
182
|
+
rerender()
|
|
183
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
184
|
+
type: 'SET_SAFE_ZONE_INSET',
|
|
185
|
+
payload: { safeZoneInset: { top: 5, right: 5, bottom: 60, left: 5 } }
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('dispatches CLEAR_PLUGINS_EVALUATED when breakpoint changes', () => {
|
|
190
|
+
setup()
|
|
191
|
+
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
192
|
+
const { dispatch } = setup({ app: { breakpoint: 'mobile' } })
|
|
193
|
+
dispatch.mockClear()
|
|
194
|
+
rerender()
|
|
195
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'CLEAR_PLUGINS_EVALUATED' })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('dispatches CLEAR_PLUGINS_EVALUATED when isMapReady changes', () => {
|
|
131
199
|
setup()
|
|
132
200
|
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
201
|
+
const { dispatch } = setup({ map: { isMapReady: false } })
|
|
202
|
+
dispatch.mockClear()
|
|
203
|
+
rerender()
|
|
204
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'CLEAR_PLUGINS_EVALUATED' })
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('dispatches CLEAR_PLUGINS_EVALUATED when isFullscreen changes', () => {
|
|
208
|
+
setup({ app: { isFullscreen: false } })
|
|
209
|
+
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
210
|
+
const { dispatch } = setup({ app: { isFullscreen: true } })
|
|
211
|
+
dispatch.mockClear()
|
|
212
|
+
rerender()
|
|
213
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'CLEAR_PLUGINS_EVALUATED' })
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('dispatches CLEAR_PLUGINS_EVALUATED when appVisible changes', () => {
|
|
217
|
+
setup({ app: { appVisible: false } })
|
|
218
|
+
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
219
|
+
const { dispatch } = setup({ app: { appVisible: true } })
|
|
220
|
+
dispatch.mockClear()
|
|
221
|
+
rerender()
|
|
222
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'CLEAR_PLUGINS_EVALUATED' })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('recalculates layout when arePluginsEvaluated becomes true', () => {
|
|
226
|
+
setup({ app: { arePluginsEvaluated: false } })
|
|
227
|
+
const { rerender } = renderHook(() => useLayoutMeasurements())
|
|
228
|
+
const { layoutRefs } = setup({ app: { arePluginsEvaluated: true } })
|
|
229
|
+
layoutRefs.appContainerRef.current.style.setProperty.mockClear()
|
|
230
|
+
rerender()
|
|
231
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalled()
|
|
140
232
|
})
|
|
141
233
|
|
|
142
234
|
test('sets up resize observer', () => {
|
|
143
235
|
const { layoutRefs } = setup()
|
|
144
236
|
renderHook(() => useLayoutMeasurements())
|
|
145
237
|
expect(useResizeObserver).toHaveBeenCalledWith(
|
|
146
|
-
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.
|
|
238
|
+
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.bottomRef, layoutRefs.bottomRightRef, layoutRefs.leftTopRef, layoutRefs.leftBottomRef, layoutRefs.rightTopRef, layoutRefs.rightBottomRef, layoutRefs.drawerRef],
|
|
147
239
|
expect.any(Function)
|
|
148
240
|
)
|
|
149
241
|
layoutRefs.appContainerRef.current.style.setProperty.mockClear()
|
|
@@ -151,4 +243,19 @@ describe('useLayoutMeasurements', () => {
|
|
|
151
243
|
expect(rafSpy).toHaveBeenCalled()
|
|
152
244
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalled()
|
|
153
245
|
})
|
|
246
|
+
|
|
247
|
+
test('resize observer does not dispatch safe zone (safe zone is Effect 3 only)', () => {
|
|
248
|
+
const { dispatch } = setup()
|
|
249
|
+
renderHook(() => useLayoutMeasurements())
|
|
250
|
+
dispatch.mockClear()
|
|
251
|
+
useResizeObserver.mock.calls[0][1]()
|
|
252
|
+
expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'SET_SAFE_ZONE_INSET' }))
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('resize observer handles null mainRef without throwing', () => {
|
|
256
|
+
const { layoutRefs } = setup()
|
|
257
|
+
renderHook(() => useLayoutMeasurements())
|
|
258
|
+
layoutRefs.mainRef.current = null
|
|
259
|
+
expect(() => useResizeObserver.mock.calls[0][1]()).not.toThrow()
|
|
260
|
+
})
|
|
154
261
|
})
|
|
@@ -82,19 +82,24 @@ export const Layout = () => {
|
|
|
82
82
|
<SlotRenderer slot={layoutSlots.RIGHT_BOTTOM} />
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
|
-
<div className='im-o-
|
|
86
|
-
<div className='im-o-
|
|
85
|
+
<div className='im-o-app__bottom' ref={layoutRefs.bottomRef}>
|
|
86
|
+
<div className='im-o-app__bottom-col'>
|
|
87
87
|
<Logo />
|
|
88
|
+
<div className='im-o-app__bottom-left'>
|
|
89
|
+
<SlotRenderer slot={layoutSlots.BOTTOM_LEFT} />
|
|
90
|
+
</div>
|
|
88
91
|
</div>
|
|
89
|
-
<div className='im-o-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
+
<div className='im-o-app__bottom-col'>
|
|
93
|
+
<div className='im-o-app__bottom-right' ref={layoutRefs.bottomRightRef}>
|
|
94
|
+
<SlotRenderer slot={layoutSlots.BOTTOM_RIGHT} />
|
|
95
|
+
</div>
|
|
96
|
+
<div className='im-o-app__attributions' ref={layoutRefs.attributionsRef}>
|
|
92
97
|
<Attributions />
|
|
93
98
|
</div>
|
|
94
99
|
</div>
|
|
95
100
|
</div>
|
|
96
|
-
<div className='im-o-
|
|
97
|
-
<SlotRenderer slot={layoutSlots.
|
|
101
|
+
<div className='im-o-app__drawer' ref={layoutRefs.drawerRef}>
|
|
102
|
+
<SlotRenderer slot={layoutSlots.DRAWER} />
|
|
98
103
|
</div>
|
|
99
104
|
<div className='im-o-app__actions' ref={layoutRefs.actionsRef}>
|
|
100
105
|
<SlotRenderer slot={layoutSlots.ACTIONS} />
|
|
@@ -38,7 +38,7 @@ describe('Layout', () => {
|
|
|
38
38
|
topLeftColRef: React.createRef(),
|
|
39
39
|
topRightColRef: React.createRef(),
|
|
40
40
|
rightRef: React.createRef(),
|
|
41
|
-
|
|
41
|
+
bottomRef: React.createRef(),
|
|
42
42
|
actionsRef: React.createRef()
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -86,7 +86,7 @@ describe('Layout', () => {
|
|
|
86
86
|
expect(screen.getByTestId('slot-side')).toBeInTheDocument()
|
|
87
87
|
expect(screen.getByTestId('slot-banner')).toBeInTheDocument()
|
|
88
88
|
expect(screen.getByTestId('slot-top-left')).toBeInTheDocument()
|
|
89
|
-
expect(screen.getByTestId('slot-
|
|
89
|
+
expect(screen.getByTestId('slot-bottom-right')).toBeInTheDocument()
|
|
90
90
|
expect(screen.getByTestId('slot-modal')).toBeInTheDocument()
|
|
91
91
|
})
|
|
92
92
|
|
|
@@ -158,10 +158,6 @@
|
|
|
158
158
|
& > *:empty {
|
|
159
159
|
display: none;
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
163
|
-
transition: bottom 0.15s ease;
|
|
164
|
-
}
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
.im-o-app__left-top {
|
|
@@ -212,10 +208,6 @@
|
|
|
212
208
|
top: var(--right-offset-top);
|
|
213
209
|
bottom: var(--right-offset-bottom);
|
|
214
210
|
gap: var(--divider-gap);
|
|
215
|
-
|
|
216
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
217
|
-
transition: bottom 0.15s ease;
|
|
218
|
-
}
|
|
219
211
|
}
|
|
220
212
|
|
|
221
213
|
.im-o-app__right-top {
|
|
@@ -238,55 +230,74 @@
|
|
|
238
230
|
}
|
|
239
231
|
|
|
240
232
|
// ---------------------------------------------------
|
|
241
|
-
//
|
|
233
|
+
// Bottom: Logo, scalebar, copyright etc
|
|
242
234
|
// ---------------------------------------------------
|
|
243
235
|
|
|
244
|
-
.im-o-
|
|
236
|
+
.im-o-app__bottom {
|
|
245
237
|
display: flex;
|
|
246
238
|
justify-content: space-between;
|
|
247
239
|
align-items: flex-end;
|
|
248
240
|
z-index: -2; // Support masking the viewport
|
|
249
241
|
}
|
|
250
242
|
|
|
251
|
-
.im-o-
|
|
243
|
+
.im-o-app__bottom-col {
|
|
252
244
|
display: flex;
|
|
253
|
-
flex-direction: column;
|
|
254
245
|
min-width: auto;
|
|
255
246
|
}
|
|
256
247
|
|
|
257
|
-
.im-o-
|
|
258
|
-
|
|
248
|
+
.im-o-app__bottom-col:first-child {
|
|
249
|
+
flex-direction: row;
|
|
250
|
+
align-items: flex-end;
|
|
259
251
|
flex-shrink: 0;
|
|
252
|
+
gap: var(--divider-gap);
|
|
260
253
|
}
|
|
261
254
|
|
|
262
|
-
.im-o-
|
|
255
|
+
.im-o-app__bottom-col:last-child {
|
|
256
|
+
position: relative; // anchor for absolutely-positioned attributions
|
|
263
257
|
flex: 1 1 auto;
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
align-items: flex-end;
|
|
266
260
|
min-width: 0;
|
|
267
261
|
}
|
|
268
262
|
|
|
263
|
+
// Horizontal button rows within the bottom area
|
|
264
|
+
.im-o-app__bottom-left,
|
|
265
|
+
.im-o-app__bottom-right {
|
|
266
|
+
display: flex;
|
|
267
|
+
flex-direction: row;
|
|
268
|
+
gap: var(--divider-gap);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.im-o-app__bottom-right {
|
|
272
|
+
justify-content: flex-end;
|
|
273
|
+
}
|
|
274
|
+
|
|
269
275
|
// ---------------------------------------------------
|
|
270
276
|
// Attributions:
|
|
271
277
|
// ---------------------------------------------------
|
|
272
278
|
|
|
273
279
|
.im-o-app__attributions:not(:empty) {
|
|
280
|
+
position: absolute;
|
|
281
|
+
bottom: calc(var(--primary-gap) * -1);
|
|
282
|
+
right: calc(var(--primary-gap) * -1);
|
|
274
283
|
display: flex;
|
|
275
284
|
justify-content: flex-end;
|
|
276
|
-
position: relative;
|
|
277
|
-
padding-top: var(--divider-gap);
|
|
278
|
-
margin-bottom: calc(var(--primary-gap) * -1);
|
|
279
|
-
margin-right: calc(var(--primary-gap) * -1);
|
|
280
285
|
}
|
|
281
286
|
|
|
282
287
|
// ---------------------------------------------------
|
|
283
|
-
//
|
|
288
|
+
// Drawer:
|
|
284
289
|
// ---------------------------------------------------
|
|
285
290
|
|
|
286
|
-
.im-o-
|
|
291
|
+
.im-o-app__drawer {
|
|
287
292
|
z-index: 1;
|
|
288
293
|
}
|
|
289
294
|
|
|
295
|
+
.im-o-app__drawer .im-c-panel {
|
|
296
|
+
@include tools.border-focus-corner-override(
|
|
297
|
+
$corners: 'top'
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
290
301
|
// ---------------------------------------------------
|
|
291
302
|
// Actions:
|
|
292
303
|
// ---------------------------------------------------
|
|
@@ -387,10 +398,14 @@
|
|
|
387
398
|
max-height: calc(100% - (var(--primary-gap) * 2));
|
|
388
399
|
}
|
|
389
400
|
|
|
390
|
-
.im-c-panel--
|
|
401
|
+
.im-c-panel--drawer {
|
|
391
402
|
top: auto;
|
|
392
403
|
bottom: 0;
|
|
393
404
|
max-height: 85%;
|
|
405
|
+
|
|
406
|
+
@include tools.border-focus-corner-override(
|
|
407
|
+
$corners: 'top'
|
|
408
|
+
);
|
|
394
409
|
}
|
|
395
410
|
|
|
396
411
|
[class*="im-c-panel--"][class*="-button"] { // Adjacent to button
|
|
@@ -452,15 +467,27 @@
|
|
|
452
467
|
width: 100%;
|
|
453
468
|
left: 0;
|
|
454
469
|
bottom: calc(var(--primary-gap) * 2);
|
|
455
|
-
|
|
470
|
+
|
|
456
471
|
.im-c-panel {
|
|
457
472
|
max-width: var(--action-bar-max-width);
|
|
458
473
|
}
|
|
474
|
+
|
|
475
|
+
// max-height animation on a bottom-anchored element grows upward, looking like
|
|
476
|
+
// a slide-up. Override to opacity-only so the bar fades in instead.
|
|
477
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
478
|
+
.im-c-actions {
|
|
479
|
+
transition: opacity var(--duration) ease;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.im-c-actions--hidden {
|
|
484
|
+
opacity: 0;
|
|
485
|
+
}
|
|
459
486
|
}
|
|
460
487
|
}
|
|
461
488
|
|
|
462
|
-
//
|
|
463
|
-
.im-o-app--mobile .im-o-
|
|
489
|
+
// Drawer panels corners removed on mobile
|
|
490
|
+
.im-o-app--mobile .im-o-app__drawer,
|
|
464
491
|
.im-o-app--mobile .im-o-app__actions {
|
|
465
492
|
margin: var(--primary-gap) calc(var(--primary-gap) * -1) calc(var(--primary-gap) * -1) calc(var(--primary-gap) * -1);
|
|
466
493
|
|
|
@@ -473,10 +500,21 @@
|
|
|
473
500
|
}
|
|
474
501
|
}
|
|
475
502
|
|
|
476
|
-
.im-o-app--mobile .im-o-
|
|
503
|
+
.im-o-app--mobile .im-o-app__drawer .im-c-panel {
|
|
477
504
|
clip-path: inset(-20px 0 0 0);
|
|
478
505
|
}
|
|
479
506
|
|
|
507
|
+
// Inset focus ring on panels that are flush with the container edge,
|
|
508
|
+
// so the ::after ring isn't clipped by the parent overflow:hidden
|
|
509
|
+
.im-o-app--mobile .im-o-app__drawer .im-c-panel::after,
|
|
510
|
+
.im-o-app--mobile .im-o-app__actions .im-c-panel::after,
|
|
511
|
+
.im-o-app__modal .im-c-panel--drawer::after {
|
|
512
|
+
top: var(--focus-border-width);
|
|
513
|
+
right: var(--focus-border-width);
|
|
514
|
+
bottom: var(--focus-border-width);
|
|
515
|
+
left: var(--focus-border-width);
|
|
516
|
+
}
|
|
517
|
+
|
|
480
518
|
// 4. State styles
|
|
481
519
|
|
|
482
520
|
// 5. Responsive tweaks
|
|
@@ -488,4 +526,4 @@
|
|
|
488
526
|
width: 100%;
|
|
489
527
|
max-width: 80%;
|
|
490
528
|
pointer-events: none;
|
|
491
|
-
}
|
|
529
|
+
}
|
|
@@ -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 '../../services/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: {} }
|
|
@@ -19,7 +19,8 @@ export const getSlotRef = (slot, layoutRefs) => {
|
|
|
19
19
|
middle: layoutRefs.middleRef,
|
|
20
20
|
'right-top': layoutRefs.rightTopRef,
|
|
21
21
|
'right-bottom': layoutRefs.rightBottomRef,
|
|
22
|
-
bottom: layoutRefs.
|
|
22
|
+
'bottom-right': layoutRefs.bottomRightRef,
|
|
23
|
+
drawer: layoutRefs.drawerRef,
|
|
23
24
|
actions: layoutRefs.actionsRef,
|
|
24
25
|
modal: layoutRefs.modalRef
|
|
25
26
|
}
|
|
@@ -14,8 +14,8 @@ jest.mock('../components/Panel/Panel.jsx', () => ({
|
|
|
14
14
|
}))
|
|
15
15
|
jest.mock('./slots.js', () => ({
|
|
16
16
|
allowedSlots: {
|
|
17
|
-
panel: ['left-top', 'side', 'modal', '
|
|
18
|
-
control: ['left-top', 'banner', '
|
|
17
|
+
panel: ['left-top', 'side', 'modal', 'drawer'],
|
|
18
|
+
control: ['left-top', 'banner', 'drawer', 'actions']
|
|
19
19
|
}
|
|
20
20
|
}))
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ const SlotHarness = ({ layoutRefs, children }) => (
|
|
|
28
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
|
-
<div ref={layoutRefs.
|
|
31
|
+
<div ref={layoutRefs.drawerRef} data-slot='drawer' />
|
|
32
32
|
<div ref={layoutRefs.bannerRef} data-slot='banner' />
|
|
33
33
|
<div ref={layoutRefs.actionsRef} data-slot='actions' />
|
|
34
34
|
{children}
|
|
@@ -47,7 +47,7 @@ describe('HtmlElementHost', () => {
|
|
|
47
47
|
topRightColRef: { current: null },
|
|
48
48
|
leftTopRef: { current: null },
|
|
49
49
|
middleRef: { current: null },
|
|
50
|
-
|
|
50
|
+
drawerRef: { current: null },
|
|
51
51
|
actionsRef: { current: null },
|
|
52
52
|
modalRef: { current: null },
|
|
53
53
|
viewportRef: { current: null },
|
|
@@ -146,13 +146,13 @@ describe('HtmlElementHost', () => {
|
|
|
146
146
|
expect(getByTestId('panel-p1').dataset.open).toBe('true')
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it('resolves
|
|
149
|
+
it('resolves drawer slot to left-top on desktop', () => {
|
|
150
150
|
const { container } = renderWithSlots({
|
|
151
|
-
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: '
|
|
151
|
+
panelConfig: { p1: { html: '<p>Hi</p>', label: 'Test', desktop: { slot: 'drawer' } } },
|
|
152
152
|
openPanels: { p1: { props: {} } }
|
|
153
153
|
})
|
|
154
154
|
expect(container.querySelector('[data-slot="left-top"] [data-testid="panel-p1"]')).toBeTruthy()
|
|
155
|
-
expect(container.querySelector('[data-slot="
|
|
155
|
+
expect(container.querySelector('[data-slot="drawer"] [data-testid="panel-p1"]')).toBeNull()
|
|
156
156
|
})
|
|
157
157
|
|
|
158
158
|
it('only shows topmost modal panel', () => {
|
|
@@ -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 '../../services/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 {
|
|
@@ -145,10 +145,10 @@ describe('mapPanels', () => {
|
|
|
145
145
|
expect(map()).toHaveLength(1)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
it('replaces
|
|
148
|
+
it('replaces drawer slot with left-top on non-mobile breakpoints', () => {
|
|
149
149
|
defaultAppState.panelConfig = ({
|
|
150
150
|
p1: {
|
|
151
|
-
desktop: { slot: '
|
|
151
|
+
desktop: { slot: 'drawer' },
|
|
152
152
|
includeModes: ['view']
|
|
153
153
|
}
|
|
154
154
|
})
|
|
@@ -3,14 +3,14 @@ import { allowedSlots } from './slots.js'
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Resolves the target slot for a panel based on its breakpoint config.
|
|
6
|
-
* Modal panels always render in the 'modal' slot, and the
|
|
6
|
+
* Modal panels always render in the 'modal' slot, and the drawer slot
|
|
7
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
|
-
if (bpConfig.slot === '
|
|
13
|
+
if (bpConfig.slot === 'drawer' && ['tablet', 'desktop'].includes(breakpoint)) {
|
|
14
14
|
return 'left-top'
|
|
15
15
|
}
|
|
16
16
|
return bpConfig.slot
|
|
@@ -7,13 +7,13 @@ describe('resolveTargetSlot', () => {
|
|
|
7
7
|
expect(resolveTargetSlot({ modal: true, slot: 'side' }, 'desktop')).toBe('modal')
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
it('replaces
|
|
11
|
-
expect(resolveTargetSlot({ slot: '
|
|
12
|
-
expect(resolveTargetSlot({ slot: '
|
|
10
|
+
it('replaces drawer with left-top on tablet and desktop', () => {
|
|
11
|
+
expect(resolveTargetSlot({ slot: 'drawer' }, 'tablet')).toBe('left-top')
|
|
12
|
+
expect(resolveTargetSlot({ slot: 'drawer' }, 'desktop')).toBe('left-top')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
it('keeps
|
|
16
|
-
expect(resolveTargetSlot({ slot: '
|
|
15
|
+
it('keeps drawer on mobile', () => {
|
|
16
|
+
expect(resolveTargetSlot({ slot: 'drawer' }, 'mobile')).toBe('drawer')
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
it('returns slot as-is otherwise', () => {
|