@defra/interactive-map 0.0.39-alpha → 0.0.41-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/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/control-definition.md +9 -1
- package/docs/api/slots.md +26 -5
- package/docs/api.md +4 -4
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +80 -17
- package/docs/plugins/search.md +1 -1
- package/govuk-prototype-kit.config.json +2 -0
- package/jest.setup.js +5 -0
- package/package.json +9 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.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-ol/dist/esm/im-draw-ol-plugin.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/dist/umd/index.js +1 -1
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/index.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +2 -3
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.js +1 -1
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.js +1 -1
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +1 -3
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +17 -3
- package/plugins/datasets/src/api/setOpacity.test.js +9 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
- package/plugins/datasets/src/datasets.scss +0 -1
- package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
- package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
- package/plugins/datasets/src/index.test.js +36 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -5
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
- package/plugins/datasets/src/manifest.js +0 -38
- package/plugins/datasets/src/manifest.test.js +49 -0
- package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
- package/plugins/datasets/src/reducers/pluginState.js +3 -16
- package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
- package/plugins/datasets/src/registry/dataset.js +15 -4
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -9
- package/plugins/datasets/src/registry/datasetRegistry.test.js +17 -5
- package/plugins/datasets/src/registry/globalDataset.js +0 -3
- package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
- package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
- package/plugins/datasets/src/utils/bbox.test.js +125 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +12 -4
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +92 -12
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +5 -28
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -54
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +11 -4
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
- package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +24 -4
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +47 -3
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +32 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +12 -5
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +26 -3
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
- package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
- package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
- package/plugins/draw/src/api/addFeature.js +16 -1
- package/plugins/draw/src/api/addFeature.test.js +62 -5
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/setStyle.js +43 -0
- package/plugins/draw/src/api/setStyle.test.js +75 -0
- package/plugins/draw/src/events.js +21 -14
- package/plugins/draw/src/events.test.js +15 -1
- package/plugins/draw/src/manifest.js +18 -6
- package/plugins/draw/src/manifest.test.js +32 -1
- package/plugins/draw/src/reducer.test.js +7 -0
- package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
- package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
- package/plugins/draw/src/utils/symbolKeys.js +5 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
- package/plugins/draw/src/validation/liveStroke.test.js +64 -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/dist/umd/index.js +1 -1
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +12 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +88 -1
- package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/utils/featureQueries.js +18 -0
- package/plugins/interact/src/utils/featureQueries.test.js +15 -0
- package/plugins/interact/src/utils/spatial.js +9 -3
- package/plugins/interact/src/utils/spatial.test.js +12 -1
- package/plugins/map-key/dist/css/index.css +62 -0
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
- package/plugins/map-key/dist/esm/index.js +1 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
- package/plugins/map-key/dist/umd/index.js +2 -0
- package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/map-key/package.json +28 -0
- package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
- package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/Key.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
- package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
- package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
- package/plugins/map-key/src/index.js +13 -0
- package/plugins/map-key/src/index.test.js +31 -0
- package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
- package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
- package/plugins/map-key/src/manifest.js +30 -0
- package/plugins/map-key/src/manifest.test.js +63 -0
- package/plugins/map-key/src/mapKey.scss +4 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/map-key/src/registry/index.js +3 -0
- package/plugins/map-key/src/registry/index.test.js +15 -0
- package/plugins/menu/dist/css/index.css +80 -0
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/esm/index.js +1 -0
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/umd/index.js +2 -0
- package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/menu/package.json +28 -0
- package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
- package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
- package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
- package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
- package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
- package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
- package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
- package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
- package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
- package/plugins/menu/src/index.js +13 -0
- package/plugins/menu/src/index.test.js +31 -0
- package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
- package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
- package/plugins/menu/src/initialise/createMenu.js +10 -0
- package/plugins/menu/src/initialise/createMenu.test.js +37 -0
- package/plugins/menu/src/manifest.js +35 -0
- package/plugins/menu/src/manifest.test.js +63 -0
- package/plugins/menu/src/menu.scss +4 -0
- package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
- package/plugins/menu/src/reducers/pluginState.js +35 -0
- package/plugins/menu/src/reducers/pluginState.test.js +57 -0
- package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
- package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/menu/src/registry/index.js +3 -0
- package/plugins/menu/src/registry/index.test.js +15 -0
- package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
- package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
- 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 -5
- package/plugins/search/src/components/Form/Form.module.scss +9 -20
- package/plugins/search/src/index.js +0 -1
- package/plugins/search/src/index.test.js +2 -4
- package/plugins/search/src/manifest.js +1 -1
- package/plugins/search/src/search.scss +17 -16
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
- package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
- package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
- package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
- package/providers/maplibre/src/utils/hoverCursor.js +30 -14
- package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
- package/providers/maplibre/src/utils/patternImages.js +1 -1
- package/providers/maplibre/src/utils/symbolImages.js +41 -3
- package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
- package/rollup.esm.mjs +11 -1
- package/sonar-project.properties +7 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Attributions/Attributions.module.scss +10 -0
- package/src/App/components/Hints/Hints.jsx +7 -2
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +2 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +3 -6
- package/src/App/components/{MoveControl/MoveControl.jsx → MoveControls/MoveControls.jsx} +10 -10
- package/src/App/components/{MoveControl/MoveControl.module.scss → MoveControls/MoveControls.module.scss} +60 -29
- package/src/App/components/{MoveControl/MoveControl.test.jsx → MoveControls/MoveControls.test.jsx} +48 -48
- package/src/App/components/Panel/Panel.jsx +18 -3
- package/src/App/components/Panel/Panel.module.scss +18 -8
- package/src/App/components/Panel/Panel.test.jsx +34 -0
- package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
- package/src/App/components/Viewport/Viewport.jsx +9 -1
- package/src/App/components/Viewport/Viewport.test.jsx +11 -0
- package/src/App/hooks/useFeatureItems.js +16 -7
- package/src/App/hooks/useFeatureItems.test.js +57 -0
- package/src/App/hooks/useLayoutMeasurements.js +95 -84
- package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
- package/src/App/initialiseApp.js +11 -8
- package/src/App/initialiseApp.test.js +15 -3
- package/src/App/layout/Layout.jsx +12 -11
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/keyboardShortcutRegistry.js +25 -13
- package/src/App/registry/keyboardShortcutRegistry.test.js +49 -32
- package/src/App/registry/pluginRegistry.js +2 -3
- package/src/App/registry/pluginRegistry.test.js +16 -3
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/mapButtons.js +28 -12
- package/src/App/renderer/mapButtons.test.js +17 -4
- package/src/App/renderer/mapControls.js +3 -1
- package/src/App/renderer/mapControls.test.js +16 -0
- package/src/App/renderer/mapPanels.js +44 -9
- package/src/App/renderer/mapPanels.test.js +51 -2
- package/src/App/renderer/orderItems.js +28 -0
- package/src/App/renderer/orderItems.test.js +50 -0
- package/src/App/renderer/slotAggregator.js +2 -18
- package/src/App/renderer/slotHelpers.js +3 -1
- package/src/App/renderer/slotHelpers.test.js +5 -0
- package/src/App/renderer/slots.js +3 -0
- package/src/App/store/AppProvider.jsx +4 -2
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/InteractiveMap/InteractiveMap.js +8 -2
- package/src/InteractiveMap/InteractiveMap.test.js +20 -3
- package/src/config/appConfig.js +17 -15
- package/src/config/appConfig.test.js +22 -22
- package/src/config/defaults.js +3 -1
- package/src/config/events.js +2 -0
- package/src/config/mergeConfig.js +7 -0
- package/src/config/mergeConfig.test.js +20 -0
- package/src/scss/main.scss +1 -1
- package/src/scss/settings/_colors.scss +1 -0
- package/src/scss/settings/_dimensions.scss +2 -3
- package/src/scss/settings/_transition.scss +1 -1
- package/src/services/eventBus.js +44 -0
- package/src/services/eventBus.test.js +83 -0
- package/src/services/symbolRegistry.js +1 -1
- package/src/test-utils.js +5 -0
- package/src/types.js +25 -7
- package/webpack.umd.mjs +2 -0
- package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
- package/plugins/datasets/src/components/Key/Key.jsx +0 -50
- package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
- package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
- package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
- package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
- package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
- package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
- /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
|
@@ -2,6 +2,7 @@ import { renderHook, act } from '@testing-library/react'
|
|
|
2
2
|
import { useFeatureItems } from './useFeatureItems.js'
|
|
3
3
|
|
|
4
4
|
const SET_FEATURES = 'map:setfeatures' // NOSONAR
|
|
5
|
+
const SET_FEATURES_SUPPRESSED = 'map:setfeaturessuppressed' // NOSONAR
|
|
5
6
|
|
|
6
7
|
const makeEventBus = () => {
|
|
7
8
|
const listeners = {}
|
|
@@ -37,11 +38,18 @@ describe('useFeatureItems — event subscription', () => {
|
|
|
37
38
|
expect(eb.on).toHaveBeenCalledWith(SET_FEATURES, expect.any(Function))
|
|
38
39
|
})
|
|
39
40
|
|
|
41
|
+
it('subscribes to map:setfeaturessuppressed on mount', () => {
|
|
42
|
+
const eb = makeEventBus()
|
|
43
|
+
renderHook(() => useFeatureItems(eb))
|
|
44
|
+
expect(eb.on).toHaveBeenCalledWith(SET_FEATURES_SUPPRESSED, expect.any(Function))
|
|
45
|
+
})
|
|
46
|
+
|
|
40
47
|
it('unsubscribes on unmount', () => {
|
|
41
48
|
const eb = makeEventBus()
|
|
42
49
|
const { unmount } = renderHook(() => useFeatureItems(eb))
|
|
43
50
|
unmount()
|
|
44
51
|
expect(eb.off).toHaveBeenCalledWith(SET_FEATURES, expect.any(Function))
|
|
52
|
+
expect(eb.off).toHaveBeenCalledWith(SET_FEATURES_SUPPRESSED, expect.any(Function))
|
|
45
53
|
})
|
|
46
54
|
|
|
47
55
|
it('does not subscribe when eventBus is undefined', () => {
|
|
@@ -96,3 +104,52 @@ describe('useFeatureItems — multiselectable updates', () => {
|
|
|
96
104
|
expect(result.current.multiselectable).toBe(false)
|
|
97
105
|
})
|
|
98
106
|
})
|
|
107
|
+
|
|
108
|
+
// ─── useFeatureItems — suppression ───────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
describe('useFeatureItems — suppression', () => {
|
|
111
|
+
const items = [{ id: 'a', label: 'Feature A' }]
|
|
112
|
+
|
|
113
|
+
it('reports empty items while suppressed, regardless of what was last set', () => {
|
|
114
|
+
const eb = makeEventBus()
|
|
115
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
116
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
117
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, { suppressed: true }))
|
|
118
|
+
expect(result.current.items).toEqual([])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('restores the last known items once suppression is lifted', () => {
|
|
122
|
+
const eb = makeEventBus()
|
|
123
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
124
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
125
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, { suppressed: true }))
|
|
126
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, { suppressed: false }))
|
|
127
|
+
expect(result.current.items).toEqual(items)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('defaults suppressed to false when the payload omits it', () => {
|
|
131
|
+
const eb = makeEventBus()
|
|
132
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
133
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
134
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, {}))
|
|
135
|
+
expect(result.current.items).toEqual(items)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('defaults suppressed to false when the event carries no payload at all', () => {
|
|
139
|
+
const eb = makeEventBus()
|
|
140
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
141
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
142
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED))
|
|
143
|
+
expect(result.current.items).toEqual(items)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('a features update received while suppressed is still applied once restored', () => {
|
|
147
|
+
const eb = makeEventBus()
|
|
148
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
149
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, { suppressed: true }))
|
|
150
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
151
|
+
expect(result.current.items).toEqual([]) // still suppressed
|
|
152
|
+
act(() => eb.emit(SET_FEATURES_SUPPRESSED, { suppressed: false }))
|
|
153
|
+
expect(result.current.items).toEqual(items)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -4,58 +4,61 @@ import { useApp } from '../store/appContext.js'
|
|
|
4
4
|
import { useMap } from '../store/mapContext.js'
|
|
5
5
|
import { getSafeZoneInset } from '../../utils/getSafeZoneInset.js'
|
|
6
6
|
|
|
7
|
+
const BANNER_DOCKED_CLASS = 'im-o-app__banner--docked'
|
|
8
|
+
const BANNER_PANEL_SELECTOR = '.im-c-panel--banner'
|
|
9
|
+
|
|
7
10
|
const buttonHeight = (ref) => ref?.current?.offsetHeight ?? 0
|
|
11
|
+
const buttonWidth = (ref) => ref?.current?.offsetWidth ?? 0
|
|
12
|
+
|
|
13
|
+
// Max of both sides, so centred content doesn't lean toward the emptier one.
|
|
14
|
+
const symmetricWidth = (left, right) => left || right ? Math.max(left, right) : 0
|
|
15
|
+
|
|
16
|
+
const subSlotMaxHeight = (columnHeight, siblingButtons, gap) => columnHeight - (siblingButtons ? siblingButtons + gap : 0)
|
|
17
|
+
|
|
18
|
+
// bottomRightHeight is 0 when empty, falling back to the attributions' own height for spacing.
|
|
19
|
+
const rightOffsetBottom = (containerPad, bottomRightHeight, attributionsHeight, gap) =>
|
|
20
|
+
containerPad + (bottomRightHeight > 0 ? bottomRightHeight + gap : attributionsHeight)
|
|
21
|
+
|
|
22
|
+
// Mobile's actions bar sits in flow (already reflected in baseBottom); tablet/desktop's floats, so this clears it too.
|
|
23
|
+
const hintBottom = (main, bottom, actionsEl, gap) => {
|
|
24
|
+
const baseBottom = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
|
|
25
|
+
const actionsHeight = actionsEl?.offsetHeight ?? 0
|
|
26
|
+
const actionsOffset = actionsHeight > 0 ? main.offsetHeight - actionsEl.offsetTop : 0
|
|
27
|
+
return Math.max(baseBottom, actionsOffset + gap)
|
|
28
|
+
}
|
|
8
29
|
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
// Space between .im-o-app__left/.im-o-app__right for the banner to dock in.
|
|
31
|
+
const bannerGutterWidth = (mainWidth, sideColWidth, gap) => mainWidth - (sideColWidth * 2) - (gap * 2)
|
|
11
32
|
|
|
12
|
-
const
|
|
13
|
-
|
|
33
|
+
const isBannerDocked = (gutterWidth, preferredWidth) => gutterWidth >= preferredWidth
|
|
34
|
+
|
|
35
|
+
// Widest explicit width configured on a banner panel, if any, overriding the default.
|
|
36
|
+
const bannerConfiguredWidth = (bannerEl) => {
|
|
37
|
+
const widths = Array.from(bannerEl?.querySelectorAll(BANNER_PANEL_SELECTOR) ?? [])
|
|
38
|
+
.map(el => Number.parseInt(el.style.width, 10))
|
|
39
|
+
.filter(w => !Number.isNaN(w))
|
|
40
|
+
return widths.length ? Math.max(...widths) : null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Mobile always stacks full-width, so any inline width Panel.jsx applied is ignored.
|
|
44
|
+
const clearBannerPanelWidths = (bannerEl) => {
|
|
45
|
+
bannerEl?.querySelectorAll(BANNER_PANEL_SELECTOR).forEach(el => { el.style.width = '' })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Docked insets to the side-column width; stacked is full-bleed.
|
|
49
|
+
const bannerInset = (isDocked, primaryGap, sideColWidth, gap) =>
|
|
50
|
+
isDocked ? primaryGap + sideColWidth + gap : primaryGap
|
|
14
51
|
|
|
15
52
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* ## Lifecycle
|
|
20
|
-
*
|
|
21
|
-
* The safe zone must only be dispatched once every plugin button's reactive
|
|
22
|
-
* props (`hiddenWhen`, `enableWhen`, `pressedWhen`, `expandedWhen`) have been
|
|
23
|
-
* evaluated for the current app/map state. Dispatching too early — before
|
|
24
|
-
* buttons that affect layout (e.g. the actions bar) have their correct
|
|
25
|
-
* visibility — produces a stale inset that causes the map to jump when the UI
|
|
26
|
-
* then settles into its real state.
|
|
27
|
-
*
|
|
28
|
-
* ### Trigger events
|
|
29
|
-
* The following state changes can alter which buttons are visible and therefore
|
|
30
|
-
* how much space the UI occupies:
|
|
31
|
-
* - `breakpoint` — responsive layout changes (desktop ↔ mobile / tablet)
|
|
32
|
-
* - `mapSize` — map container size variant changes
|
|
33
|
-
* - `isMapReady` — plugins are enabled on `map:ready`, changing button visibility
|
|
34
|
-
* - `isFullscreen` — fullscreen entry/exit changes which buttons are visible
|
|
35
|
-
* - `appVisible` — app shown/hidden by parent HTML outside React (hybrid mode)
|
|
36
|
-
*
|
|
37
|
-
* When any of these change, `CLEAR_PLUGINS_EVALUATED` is dispatched (Effect 2),
|
|
38
|
-
* which prevents the safe zone from being re-dispatched until
|
|
39
|
-
* `useButtonStateEvaluator` has completed a full pass with no button state
|
|
40
|
-
* changes and sets `PLUGINS_EVALUATED` again.
|
|
41
|
-
*
|
|
42
|
-
* ### Safe zone dispatch
|
|
43
|
-
* Effect 3 fires whenever `arePluginsEvaluated` transitions to `true`, at which
|
|
44
|
-
* point DOM dimensions are stable and `getSafeZoneInset` can be read reliably.
|
|
45
|
-
* A `requestAnimationFrame` is used to ensure the browser has committed all
|
|
46
|
-
* layout changes before measuring.
|
|
47
|
-
*
|
|
48
|
-
* ### Resize observer
|
|
49
|
-
* Effect 4 keeps CSS custom properties up to date whenever any observed element
|
|
50
|
-
* resizes (e.g. panels opening, banner appearing, actions buttons toggling).
|
|
51
|
-
* It does not dispatch the safe zone — safe zone dispatch is owned entirely by
|
|
52
|
-
* Effect 3 to prevent jumps on panel open/close and other non-structural resizes.
|
|
53
|
+
* Computes layout CSS vars for the map overlay and dispatches the safe zone inset used
|
|
54
|
+
* for `fitBounds`/`setView`. Waits for `arePluginsEvaluated` so the inset reflects final
|
|
55
|
+
* button visibility rather than a mid-evaluation state, which would make the map jump.
|
|
53
56
|
*/
|
|
54
|
-
function calculateLayout (layoutRefs) {
|
|
57
|
+
function calculateLayout (layoutRefs, breakpoint) {
|
|
55
58
|
const {
|
|
56
59
|
appContainerRef, mainRef, topRef, topLeftColRef, topRightColRef,
|
|
57
60
|
bottomRef, attributionsRef, bottomRightRef, leftTopRef, leftBottomRef,
|
|
58
|
-
rightTopRef, rightBottomRef, actionsRef
|
|
61
|
+
rightTopRef, rightBottomRef, actionsRef, bannerRef, leftRef, rightRef
|
|
59
62
|
} = layoutRefs
|
|
60
63
|
|
|
61
64
|
const appContainer = appContainerRef.current
|
|
@@ -65,6 +68,7 @@ function calculateLayout (layoutRefs) {
|
|
|
65
68
|
const topRightCol = topRightColRef.current
|
|
66
69
|
const bottom = bottomRef.current
|
|
67
70
|
const attributions = attributionsRef.current
|
|
71
|
+
const banner = bannerRef?.current
|
|
68
72
|
|
|
69
73
|
if ([main, top, bottom].some(r => !r)) {
|
|
70
74
|
return
|
|
@@ -72,41 +76,62 @@ function calculateLayout (layoutRefs) {
|
|
|
72
76
|
|
|
73
77
|
const root = document.documentElement
|
|
74
78
|
const dividerGap = Number.parseInt(getComputedStyle(root).getPropertyValue('--divider-gap'), 10)
|
|
79
|
+
const primaryGap = Number.parseInt(getComputedStyle(root).getPropertyValue('--primary-gap'), 10)
|
|
75
80
|
|
|
76
81
|
// === Top column width ===
|
|
77
|
-
|
|
82
|
+
const topColWidthPx = symmetricWidth(topLeftCol.offsetWidth, topRightCol.offsetWidth)
|
|
83
|
+
appContainer.style.setProperty('--top-col-width', `${topColWidthPx}px`)
|
|
84
|
+
|
|
85
|
+
// === Banner: docks centred between the side columns when there's room, otherwise
|
|
86
|
+
// stacks full-width. Mobile always stacks. ===
|
|
87
|
+
const isMobile = breakpoint === 'mobile'
|
|
88
|
+
if (isMobile) {
|
|
89
|
+
clearBannerPanelWidths(banner)
|
|
90
|
+
}
|
|
91
|
+
const bannerHeight = buttonHeight(bannerRef)
|
|
92
|
+
const hasBanner = bannerHeight > 0
|
|
93
|
+
const bannerSideColWidth = symmetricWidth(buttonWidth(leftRef), buttonWidth(rightRef))
|
|
94
|
+
const defaultPreferredWidth = Number.parseInt(getComputedStyle(root).getPropertyValue('--banner-preferred-width'), 10)
|
|
95
|
+
const preferredWidth = bannerConfiguredWidth(banner) ?? defaultPreferredWidth
|
|
96
|
+
appContainer.style.setProperty('--banner-preferred-width', `${preferredWidth}px`)
|
|
97
|
+
const gutterWidth = bannerGutterWidth(top.offsetWidth, bannerSideColWidth, dividerGap)
|
|
98
|
+
const isDocked = !isMobile && isBannerDocked(gutterWidth, preferredWidth)
|
|
99
|
+
banner?.classList.toggle(BANNER_DOCKED_CLASS, isDocked)
|
|
100
|
+
|
|
101
|
+
const bannerSideInset = `${bannerInset(isDocked, primaryGap, bannerSideColWidth, dividerGap)}px`
|
|
102
|
+
appContainer.style.setProperty('--banner-left', bannerSideInset)
|
|
103
|
+
appContainer.style.setProperty('--banner-right', bannerSideInset)
|
|
104
|
+
|
|
105
|
+
// Sits at the top row's bottom edge; top.offsetHeight already includes a trailing gap.
|
|
106
|
+
const isBannerStacked = hasBanner && !isDocked
|
|
107
|
+
const bannerTop = hasBanner ? top.offsetTop + top.offsetHeight : 0
|
|
108
|
+
appContainer.style.setProperty('--banner-top', `${bannerTop}px`)
|
|
109
|
+
|
|
110
|
+
// Stacked pushes the side columns below the banner plus a trailing gap — added here, not
|
|
111
|
+
// via a CSS last-child margin, since closed consumer HTML panels stay in the DOM (display:none).
|
|
112
|
+
const sideOffsetTop = (colHeight) => isBannerStacked
|
|
113
|
+
? bannerTop + bannerHeight + dividerGap
|
|
114
|
+
: colHeight + top.offsetTop
|
|
78
115
|
|
|
79
116
|
// === Left container offsets ===
|
|
80
|
-
const leftOffsetTop = topLeftCol.offsetHeight
|
|
117
|
+
const leftOffsetTop = sideOffsetTop(topLeftCol.offsetHeight)
|
|
81
118
|
const leftColumnHeight = bottom.offsetTop - leftOffsetTop - dividerGap
|
|
82
119
|
appContainer.style.setProperty('--left-offset-top', `${leftOffsetTop}px`)
|
|
83
120
|
appContainer.style.setProperty('--left-offset-bottom', `${main.offsetHeight - bottom.offsetTop + dividerGap}px`)
|
|
84
121
|
appContainer.style.setProperty('--left-top-max-height', `${leftColumnHeight}px`)
|
|
85
122
|
|
|
86
|
-
// === Right container offsets ===
|
|
87
|
-
|
|
88
|
-
// bottomRight.offsetHeight is 0 when no buttons so the offset collapses to just
|
|
89
|
-
// the padding between the bottom of the bottom container and the bottom of main.
|
|
90
|
-
const bottomRightHeight = bottomRightRef?.current?.offsetHeight ?? 0
|
|
123
|
+
// === Right container offsets === (mirrors the left formula)
|
|
124
|
+
const bottomRightHeight = buttonHeight(bottomRightRef)
|
|
91
125
|
const bottomContainerPad = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
|
|
92
|
-
const rightOffsetTop = topRightCol.offsetHeight
|
|
126
|
+
const rightOffsetTop = sideOffsetTop(topRightCol.offsetHeight)
|
|
93
127
|
const rightEffectiveBottom = bottom.offsetTop + bottom.offsetHeight - bottomRightHeight
|
|
94
128
|
const rightColumnHeight = rightEffectiveBottom - rightOffsetTop - dividerGap
|
|
95
|
-
const rightOffsetBottom = bottomContainerPad + (bottomRightHeight > 0 ? (bottomRightHeight + dividerGap) : attributions.offsetHeight)
|
|
96
129
|
appContainer.style.setProperty('--right-offset-top', `${rightOffsetTop}px`)
|
|
97
|
-
appContainer.style.setProperty('--right-offset-bottom', `${rightOffsetBottom}px`)
|
|
130
|
+
appContainer.style.setProperty('--right-offset-bottom', `${rightOffsetBottom(bottomContainerPad, bottomRightHeight, attributions.offsetHeight, dividerGap)}px`)
|
|
98
131
|
appContainer.style.setProperty('--right-top-max-height', `${rightColumnHeight}px`)
|
|
99
132
|
|
|
100
133
|
// === Keyboard hint bottom offset ===
|
|
101
|
-
|
|
102
|
-
// On tablet/desktop the actions bar is position:absolute (not in flow), so baseBottom
|
|
103
|
-
// only sees the bottom padding. actionsOffset measures the gap from the actions bar's
|
|
104
|
-
// top edge to the bottom of main, ensuring the hint always clears the floating bar.
|
|
105
|
-
const actionsEl = actionsRef?.current
|
|
106
|
-
const actionsHeight = actionsEl?.offsetHeight ?? 0
|
|
107
|
-
const baseBottom = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
|
|
108
|
-
const actionsOffset = actionsHeight > 0 ? main.offsetHeight - actionsEl.offsetTop : 0
|
|
109
|
-
appContainer.style.setProperty('--hint-bottom', `${Math.max(baseBottom, actionsOffset + dividerGap)}px`)
|
|
134
|
+
appContainer.style.setProperty('--hint-bottom', `${hintBottom(main, bottom, actionsRef?.current, dividerGap)}px`)
|
|
110
135
|
|
|
111
136
|
// === Sub-slot panel max-heights ===
|
|
112
137
|
appContainer.style.setProperty('--left-top-panel-max-height', `${subSlotMaxHeight(leftColumnHeight, buttonHeight(leftBottomRef), dividerGap)}px`)
|
|
@@ -119,28 +144,20 @@ export function useLayoutMeasurements () {
|
|
|
119
144
|
const { dispatch, breakpoint, layoutRefs, arePluginsEvaluated, appVisible, isFullscreen } = useApp()
|
|
120
145
|
const { mapSize, isMapReady } = useMap()
|
|
121
146
|
|
|
122
|
-
const { bannerRef, mainRef, topRef, topLeftColRef, topRightColRef, bottomRef, bottomRightRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef, drawerRef, actionsRef } = layoutRefs
|
|
147
|
+
const { bannerRef, mainRef, headerRef, topRef, topLeftColRef, topRightColRef, bottomRef, bottomRightRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef, drawerRef, actionsRef, leftRef, rightRef } = layoutRefs
|
|
123
148
|
|
|
124
|
-
//
|
|
125
|
-
// 1. Clear the evaluated flag when structural inputs change so the safe zone
|
|
126
|
-
// is not dispatched until useButtonStateEvaluator has completed a full
|
|
127
|
-
// pass with the new app/map state and set PLUGINS_EVALUATED.
|
|
128
|
-
// --------------------------------
|
|
149
|
+
// 1. Clear the evaluated flag on structural changes, gating the safe zone until re-evaluated.
|
|
129
150
|
useLayoutEffect(() => {
|
|
130
151
|
dispatch({ type: 'CLEAR_PLUGINS_EVALUATED' })
|
|
131
152
|
}, [breakpoint, mapSize, isMapReady, appVisible, isFullscreen])
|
|
132
153
|
|
|
133
|
-
//
|
|
134
|
-
// 2. Once all plugin button props have been evaluated (arePluginsEvaluated),
|
|
135
|
-
// recalculate layout and dispatch the safe zone inset.
|
|
136
|
-
// RAF required to ensure browser layout is committed before measuring.
|
|
137
|
-
// --------------------------------
|
|
154
|
+
// 2. Once evaluated, recalculate layout and dispatch the safe zone (RAF waits for layout to commit).
|
|
138
155
|
useLayoutEffect(() => {
|
|
139
156
|
if (!arePluginsEvaluated) {
|
|
140
157
|
return
|
|
141
158
|
}
|
|
142
159
|
requestAnimationFrame(() => {
|
|
143
|
-
calculateLayout(layoutRefs)
|
|
160
|
+
calculateLayout(layoutRefs, breakpoint)
|
|
144
161
|
const safeZoneInset = getSafeZoneInset(layoutRefs)
|
|
145
162
|
if (safeZoneInset) {
|
|
146
163
|
dispatch({ type: 'SET_SAFE_ZONE_INSET', payload: { safeZoneInset } })
|
|
@@ -148,20 +165,14 @@ export function useLayoutMeasurements () {
|
|
|
148
165
|
})
|
|
149
166
|
}, [arePluginsEvaluated])
|
|
150
167
|
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// actions buttons, etc.). Safe zone is intentionally not dispatched here —
|
|
154
|
-
// that is Effect 2's responsibility.
|
|
155
|
-
// --------------------------------
|
|
156
|
-
// Stable reference — all entries are ref objects from useApp that never change.
|
|
157
|
-
// Prevents useResizeObserver's effect from re-running (and cancelling its RAF)
|
|
158
|
-
// on every render due to a new array literal being passed each time.
|
|
168
|
+
// 3. Recalculate CSS vars on resize; safe zone dispatch stays Effect 2's job.
|
|
169
|
+
// Memoized so useResizeObserver doesn't re-run (and cancel its RAF) on every render.
|
|
159
170
|
const observedRefs = useMemo(
|
|
160
|
-
() => [bannerRef, mainRef, topRef, topLeftColRef, topRightColRef, actionsRef, bottomRef, bottomRightRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef, drawerRef],
|
|
171
|
+
() => [bannerRef, mainRef, headerRef, topRef, topLeftColRef, topRightColRef, actionsRef, bottomRef, bottomRightRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef, drawerRef, leftRef, rightRef],
|
|
161
172
|
[]
|
|
162
173
|
)
|
|
163
174
|
|
|
164
175
|
useResizeObserver(observedRefs, () => {
|
|
165
|
-
calculateLayout(layoutRefs)
|
|
176
|
+
calculateLayout(layoutRefs, breakpoint)
|
|
166
177
|
})
|
|
167
178
|
}
|
|
@@ -20,10 +20,13 @@ const el = (props = {}) => {
|
|
|
20
20
|
const refs = (o = {}) => ({
|
|
21
21
|
appContainerRef: { current: o.appContainer || el() },
|
|
22
22
|
mainRef: { current: o.main === null ? null : el({ offsetHeight: 500, ...o.main }) },
|
|
23
|
+
headerRef: { current: el(o.header) },
|
|
23
24
|
bannerRef: { current: el(o.banner) },
|
|
24
25
|
topRef: { current: o.top === null ? null : el({ offsetTop: 10, ...o.top }) },
|
|
25
26
|
topLeftColRef: { current: el({ offsetHeight: 50, offsetWidth: 200, ...o.topLeftCol }) },
|
|
26
27
|
topRightColRef: { current: el({ offsetHeight: 40, offsetWidth: 180, ...o.topRightCol }) },
|
|
28
|
+
leftRef: { current: el(o.left) },
|
|
29
|
+
rightRef: { current: el(o.right) },
|
|
27
30
|
bottomRef: { current: o.bottom === null ? null : el({ offsetTop: 400, ...o.bottom }) },
|
|
28
31
|
bottomRightRef: { current: el({ offsetTop: 400, ...o.bottomRight }) },
|
|
29
32
|
leftTopRef: { current: el({ offsetHeight: 0, ...o.leftTop }) },
|
|
@@ -140,6 +143,63 @@ describe('useLayoutMeasurements', () => {
|
|
|
140
143
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--top-col-width', expected)
|
|
141
144
|
})
|
|
142
145
|
|
|
146
|
+
test('clears inline banner panel widths on mobile', () => {
|
|
147
|
+
const { layoutRefs } = setup({ app: { breakpoint: 'mobile' } })
|
|
148
|
+
const panel = document.createElement('div')
|
|
149
|
+
panel.className = 'im-c-panel--banner'
|
|
150
|
+
panel.style.width = '250px'
|
|
151
|
+
layoutRefs.bannerRef.current.appendChild(panel)
|
|
152
|
+
renderHook(() => useLayoutMeasurements())
|
|
153
|
+
expect(panel.style.width).toBe('')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('uses widest parseable banner panel width, ignoring unparseable ones', () => {
|
|
157
|
+
const { layoutRefs } = setup()
|
|
158
|
+
const banner = layoutRefs.bannerRef.current
|
|
159
|
+
const panel = (width) => {
|
|
160
|
+
const p = document.createElement('div')
|
|
161
|
+
p.className = 'im-c-panel--banner'
|
|
162
|
+
p.style.width = width
|
|
163
|
+
return p
|
|
164
|
+
}
|
|
165
|
+
banner.appendChild(panel('250px'))
|
|
166
|
+
banner.appendChild(panel('auto')) // unparseable, filtered out
|
|
167
|
+
banner.appendChild(panel('400px'))
|
|
168
|
+
renderHook(() => useLayoutMeasurements())
|
|
169
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--banner-preferred-width', '400px')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('falls back to default preferred width when banner element is absent', () => {
|
|
173
|
+
const { layoutRefs } = setup()
|
|
174
|
+
layoutRefs.bannerRef.current = null
|
|
175
|
+
renderHook(() => useLayoutMeasurements())
|
|
176
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--banner-preferred-width', '8px')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('docks the banner when the gutter is wide enough for the preferred width', () => {
|
|
180
|
+
const { layoutRefs } = setup({ refs: { top: { offsetWidth: 1000 } } })
|
|
181
|
+
renderHook(() => useLayoutMeasurements())
|
|
182
|
+
// docked: primaryGap (8) + sideColWidth (0) + dividerGap (8)
|
|
183
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--banner-left', '16px')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('treats a null leftRef current as 0 width in the banner gutter calc', () => {
|
|
187
|
+
const { layoutRefs } = setup({ refs: { top: { offsetWidth: 1000 }, right: { offsetWidth: 20 } } })
|
|
188
|
+
layoutRefs.leftRef.current = null
|
|
189
|
+
renderHook(() => useLayoutMeasurements())
|
|
190
|
+
// symmetricWidth(0, 20) = 20; docked: primaryGap (8) + sideColWidth (20) + dividerGap (8)
|
|
191
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--banner-left', '36px')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('stacks a present banner below the top row when not docked', () => {
|
|
195
|
+
const { layoutRefs } = setup({ refs: { banner: { offsetHeight: 30 } } })
|
|
196
|
+
renderHook(() => useLayoutMeasurements())
|
|
197
|
+
// bannerTop = top.offsetTop (10) + top.offsetHeight (0)
|
|
198
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--banner-top', '10px')
|
|
199
|
+
// stacked: left column pushed below the banner (bannerTop 10 + bannerHeight 30 + dividerGap 8)
|
|
200
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--left-offset-top', '48px')
|
|
201
|
+
})
|
|
202
|
+
|
|
143
203
|
test('uses 0 when bottomRightRef current is null', () => {
|
|
144
204
|
const { layoutRefs } = setup()
|
|
145
205
|
layoutRefs.bottomRightRef.current = null
|
|
@@ -264,7 +324,7 @@ describe('useLayoutMeasurements', () => {
|
|
|
264
324
|
const { layoutRefs } = setup()
|
|
265
325
|
renderHook(() => useLayoutMeasurements())
|
|
266
326
|
expect(useResizeObserver).toHaveBeenCalledWith(
|
|
267
|
-
[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],
|
|
327
|
+
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.headerRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.bottomRef, layoutRefs.bottomRightRef, layoutRefs.leftTopRef, layoutRefs.leftBottomRef, layoutRefs.rightTopRef, layoutRefs.rightBottomRef, layoutRefs.drawerRef, layoutRefs.leftRef, layoutRefs.rightRef],
|
|
268
328
|
expect.any(Function)
|
|
269
329
|
)
|
|
270
330
|
layoutRefs.appContainerRef.current.style.setProperty.mockClear()
|
package/src/App/initialiseApp.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createButtonRegistry } from './registry/buttonRegistry.js'
|
|
|
5
5
|
import { createPanelRegistry } from './registry/panelRegistry.js'
|
|
6
6
|
import { createControlRegistry } from './registry/controlRegistry.js'
|
|
7
7
|
import { createPluginRegistry } from './registry/pluginRegistry.js'
|
|
8
|
-
import {
|
|
8
|
+
import { createKeyboardShortcutRegistry } from './registry/keyboardShortcutRegistry.js'
|
|
9
9
|
import { mergeManifests } from './registry/mergeManifests.js'
|
|
10
10
|
import { App } from './App.jsx'
|
|
11
11
|
|
|
@@ -19,13 +19,15 @@ const getOrCreateRegistries = (rootElement) => {
|
|
|
19
19
|
const buttonRegistry = createButtonRegistry()
|
|
20
20
|
const panelRegistry = createPanelRegistry()
|
|
21
21
|
const controlRegistry = createControlRegistry()
|
|
22
|
+
const keyboardShortcutRegistry = createKeyboardShortcutRegistry()
|
|
22
23
|
const pluginRegistry = createPluginRegistry({
|
|
23
24
|
registerButton: buttonRegistry.registerButton,
|
|
24
25
|
registerPanel: panelRegistry.registerPanel,
|
|
25
|
-
registerControl: controlRegistry.registerControl
|
|
26
|
+
registerControl: controlRegistry.registerControl,
|
|
27
|
+
registerKeyboardShortcut: keyboardShortcutRegistry.registerKeyboardShortcut
|
|
26
28
|
})
|
|
27
29
|
|
|
28
|
-
registries = { buttonRegistry, panelRegistry, controlRegistry, pluginRegistry }
|
|
30
|
+
registries = { buttonRegistry, panelRegistry, controlRegistry, pluginRegistry, keyboardShortcutRegistry }
|
|
29
31
|
registryMap.set(rootElement, registries)
|
|
30
32
|
}
|
|
31
33
|
return registries
|
|
@@ -70,15 +72,15 @@ export async function initialiseApp (rootElement, {
|
|
|
70
72
|
mapProviderMap.set(rootElement, mapProvider)
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
// Reuse or create registries (persist across app open/close cycles)
|
|
76
|
+
const { buttonRegistry, panelRegistry, controlRegistry, pluginRegistry, keyboardShortcutRegistry } = getOrCreateRegistries(rootElement)
|
|
77
|
+
const { registerPlugin } = pluginRegistry
|
|
78
|
+
|
|
73
79
|
// Register provider-supported shortcuts
|
|
74
80
|
if (mapProvider.capabilities?.supportedShortcuts) {
|
|
75
|
-
setProviderSupportedShortcuts(mapProvider.capabilities.supportedShortcuts)
|
|
81
|
+
keyboardShortcutRegistry.setProviderSupportedShortcuts(mapProvider.capabilities.supportedShortcuts)
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
// Reuse or create registries (persist across app open/close cycles)
|
|
79
|
-
const { buttonRegistry, panelRegistry, controlRegistry, pluginRegistry } = getOrCreateRegistries(rootElement)
|
|
80
|
-
const { registerPlugin } = pluginRegistry
|
|
81
|
-
|
|
82
84
|
// Clear previous plugins (but keep runtime additions)
|
|
83
85
|
pluginRegistry.clear()
|
|
84
86
|
|
|
@@ -121,6 +123,7 @@ export async function initialiseApp (rootElement, {
|
|
|
121
123
|
panelRegistry={panelRegistry}
|
|
122
124
|
controlRegistry={controlRegistry}
|
|
123
125
|
pluginRegistry={pluginRegistry}
|
|
126
|
+
keyboardShortcutRegistry={keyboardShortcutRegistry}
|
|
124
127
|
mapProvider={mapProvider}
|
|
125
128
|
/>)
|
|
126
129
|
|
|
@@ -3,7 +3,6 @@ import { initialiseApp } from './initialiseApp.js'
|
|
|
3
3
|
import { createRoot } from 'react-dom/client'
|
|
4
4
|
import { EVENTS as events } from '../config/events.js'
|
|
5
5
|
import { createMockRegistries } from '../test-utils.js'
|
|
6
|
-
import { setProviderSupportedShortcuts } from './registry/keyboardShortcutRegistry.js'
|
|
7
6
|
|
|
8
7
|
// --------------------------------------------------
|
|
9
8
|
// Shared mocks
|
|
@@ -48,7 +47,7 @@ jest.mock('./registry/pluginRegistry.js', () => ({
|
|
|
48
47
|
}))
|
|
49
48
|
|
|
50
49
|
jest.mock('./registry/keyboardShortcutRegistry.js', () => ({
|
|
51
|
-
|
|
50
|
+
createKeyboardShortcutRegistry: jest.fn(() => mockRegistries.keyboardShortcutRegistry)
|
|
52
51
|
}))
|
|
53
52
|
|
|
54
53
|
jest.mock('./registry/mergeManifests.js', () => ({
|
|
@@ -179,6 +178,19 @@ describe('initialiseApp', () => {
|
|
|
179
178
|
|
|
180
179
|
await initApp()
|
|
181
180
|
|
|
182
|
-
expect(setProviderSupportedShortcuts).not.toHaveBeenCalled()
|
|
181
|
+
expect(mockRegistries.keyboardShortcutRegistry.setProviderSupportedShortcuts).not.toHaveBeenCalled()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('registers provider-supported shortcuts on the per-instance registry', async () => {
|
|
185
|
+
await initApp()
|
|
186
|
+
|
|
187
|
+
expect(mockRegistries.keyboardShortcutRegistry.setProviderSupportedShortcuts).toHaveBeenCalledWith(['ctrl+a'])
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('passes keyboardShortcutRegistry through to the rendered App', async () => {
|
|
191
|
+
await initApp()
|
|
192
|
+
|
|
193
|
+
const rendered = createRoot.mock.results[0].value.render.mock.calls[0][0]
|
|
194
|
+
expect(rendered.props.keyboardShortcutRegistry).toBe(mockRegistries.keyboardShortcutRegistry)
|
|
183
195
|
})
|
|
184
196
|
})
|
|
@@ -16,7 +16,7 @@ import { getMapThemeVars } from '../../config/mapTheme.js'
|
|
|
16
16
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
17
17
|
// sonarjs/disable-next-line function-name
|
|
18
18
|
export const Layout = () => {
|
|
19
|
-
const { id } = useConfig()
|
|
19
|
+
const { id, mapLabel, mapHintText } = useConfig()
|
|
20
20
|
const { breakpoint, interfaceType, preferredColorScheme, layoutRefs, isLayoutReady, hasExclusiveControl, isFullscreen } = useApp()
|
|
21
21
|
const { mapStyle } = useMap()
|
|
22
22
|
|
|
@@ -37,33 +37,34 @@ export const Layout = () => {
|
|
|
37
37
|
style={{ backgroundColor: mapStyle?.backgroundColor || undefined, ...getMapThemeVars(mapStyle) }}
|
|
38
38
|
ref={layoutRefs.appContainerRef}
|
|
39
39
|
>
|
|
40
|
+
{/* Leads with mapLabel so screen reader users can tell multiple maps on a page apart. */}
|
|
41
|
+
<div className='im-u-visually-hidden'>{mapLabel}. {mapHintText}</div>
|
|
40
42
|
<Viewport />
|
|
41
43
|
<div className={`im-o-app__overlay${isLayoutReady ? '' : ' im-o-app__overlay--not-ready'}`}>
|
|
42
44
|
<div className='im-o-app__side' ref={layoutRefs.sideRef}>
|
|
43
45
|
<SlotRenderer slot={layoutSlots.SIDE} />
|
|
44
46
|
</div>
|
|
45
47
|
<div className='im-o-app__main' ref={layoutRefs.mainRef}>
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
</div>
|
|
50
|
-
)}
|
|
48
|
+
<div className='im-o-app__header' ref={layoutRefs.headerRef}>
|
|
49
|
+
<SlotRenderer slot={layoutSlots.HEADER} />
|
|
50
|
+
</div>
|
|
51
51
|
<div className='im-o-app__top' ref={layoutRefs.topRef}>
|
|
52
52
|
<div className='im-o-app__top-col' ref={layoutRefs.topLeftColRef}>
|
|
53
53
|
<SlotRenderer slot={layoutSlots.TOP_LEFT} />
|
|
54
54
|
</div>
|
|
55
55
|
<div className='im-o-app__top-col'>
|
|
56
56
|
<SlotRenderer slot={layoutSlots.TOP_MIDDLE} />
|
|
57
|
-
{['desktop'].includes(breakpoint) && (
|
|
58
|
-
<div className='im-o-app__banner' ref={layoutRefs.bannerRef}>
|
|
59
|
-
<SlotRenderer slot={layoutSlots.BANNER} />
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
57
|
</div>
|
|
63
58
|
<div className='im-o-app__top-col' ref={layoutRefs.topRightColRef}>
|
|
64
59
|
<SlotRenderer slot={layoutSlots.TOP_RIGHT} />
|
|
65
60
|
</div>
|
|
66
61
|
</div>
|
|
62
|
+
{/* Docked (centred in the top-column gutter) vs stacked (full-width, below the
|
|
63
|
+
whole top row) is decided in useLayoutMeasurements — same DOM position either
|
|
64
|
+
way, so reading/tab order stays identical across breakpoints and modes. */}
|
|
65
|
+
<div className='im-o-app__banner' ref={layoutRefs.bannerRef}>
|
|
66
|
+
<SlotRenderer slot={layoutSlots.BANNER} />
|
|
67
|
+
</div>
|
|
67
68
|
<div className='im-o-app__left' ref={layoutRefs.leftRef}>
|
|
68
69
|
<div className='im-o-app__left-top' ref={layoutRefs.leftTopRef}>
|
|
69
70
|
<SlotRenderer slot={layoutSlots.LEFT_TOP} />
|
|
@@ -36,6 +36,7 @@ describe('Layout', () => {
|
|
|
36
36
|
const mockRefs = {
|
|
37
37
|
appContainerRef: React.createRef(),
|
|
38
38
|
mainRef: React.createRef(),
|
|
39
|
+
headerRef: React.createRef(),
|
|
39
40
|
bannerRef: React.createRef(),
|
|
40
41
|
topRef: React.createRef(),
|
|
41
42
|
topLeftColRef: React.createRef(),
|
|
@@ -89,6 +90,7 @@ describe('Layout', () => {
|
|
|
89
90
|
expect(screen.getByTestId('attributions')).toBeInTheDocument()
|
|
90
91
|
|
|
91
92
|
expect(screen.getByTestId('slot-side')).toBeInTheDocument()
|
|
93
|
+
expect(screen.getByTestId('slot-header')).toBeInTheDocument()
|
|
92
94
|
expect(screen.getByTestId('slot-banner')).toBeInTheDocument()
|
|
93
95
|
expect(screen.getByTestId('slot-top-left')).toBeInTheDocument()
|
|
94
96
|
expect(screen.getByTestId('slot-bottom-right')).toBeInTheDocument()
|