@defra/interactive-map 0.0.40-alpha → 0.0.42-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/button-definition.md +7 -8
- package/docs/api/control-definition.md +17 -1
- package/docs/api/panel-definition.md +5 -0
- package/docs/api/slots.md +50 -6
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/add-polygons.mdx +8 -2
- package/docs/examples/add-symbols.mdx +8 -2
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/datasets.md +23 -15
- package/docs/plugins/draw.md +79 -16
- package/docs/plugins/map-key.md +147 -0
- package/docs/plugins/search.md +1 -1
- package/docs/plugins.md +5 -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/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.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.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
- 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 -4
- 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 +13 -1
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
- package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
- 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 +10 -2
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
- 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/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 +4 -27
- 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 +3 -53
- 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 +10 -3
- 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.test.js +12 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
- 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 +10 -3
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
- 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/createNewShape.js +41 -0
- package/plugins/draw/src/api/createNewShape.test.js +63 -0
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newLine.js +2 -40
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/newPolygon.js +2 -40
- 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 +17 -10
- package/plugins/draw/src/events.test.js +14 -0
- 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/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/manifest.js +1 -1
- 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 +68 -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 +43 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +21 -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 +26 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +22 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.jsx +7 -2
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +8 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +26 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.jsx +8 -3
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +17 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +53 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +147 -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 +2 -3
- 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/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +25 -23
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +22 -5
- package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
- package/src/App/components/Panel/Panel.jsx +65 -8
- package/src/App/components/Panel/Panel.module.scss +49 -15
- package/src/App/components/Panel/Panel.test.jsx +114 -1
- package/src/App/components/Tabs/Tabs.jsx +43 -17
- package/src/App/components/Tabs/Tabs.module.scss +32 -5
- package/src/App/components/Tabs/Tabs.test.jsx +40 -10
- package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
- 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/layout/Layout.jsx +9 -10
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/pluginRegistry.js +1 -1
- package/src/App/registry/pluginRegistry.test.js +12 -0
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/groupByKey.js +28 -0
- package/src/App/renderer/groupByKey.test.js +39 -0
- package/src/App/renderer/groupIntoTabs.js +42 -0
- package/src/App/renderer/groupIntoTabs.test.js +95 -0
- package/src/App/renderer/mapButtons.js +97 -129
- package/src/App/renderer/mapButtons.test.js +67 -83
- package/src/App/renderer/mapControls.js +4 -1
- package/src/App/renderer/mapControls.test.js +32 -0
- package/src/App/renderer/mapPanels.js +53 -11
- package/src/App/renderer/mapPanels.test.js +95 -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 +1 -0
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/config/appConfig.js +2 -2
- package/src/config/events.js +2 -0
- 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/types.js +31 -3
- 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/KeySvgRect.jsx +0 -22
- 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
|
@@ -69,7 +69,7 @@ describe('Panel', () => {
|
|
|
69
69
|
const { container } = renderPanel()
|
|
70
70
|
|
|
71
71
|
// 2. Target by class to avoid role collision with the parent panel
|
|
72
|
-
const body = container.querySelector('
|
|
72
|
+
const body = container.querySelector('[data-panel-slot]')
|
|
73
73
|
|
|
74
74
|
expect(body).toHaveAttribute('tabIndex', '0')
|
|
75
75
|
expect(body).toHaveAttribute('role', 'region')
|
|
@@ -194,5 +194,118 @@ describe('Panel', () => {
|
|
|
194
194
|
renderPanel({}, { html: '<p>HTML content</p>' })
|
|
195
195
|
expect(screen.getByText('HTML content')).toBeInTheDocument()
|
|
196
196
|
})
|
|
197
|
+
|
|
198
|
+
it('renders each items entry in list order', () => {
|
|
199
|
+
const items = [
|
|
200
|
+
{ id: 'a', element: <p>First</p> },
|
|
201
|
+
{ id: 'b', element: <p>Second</p> }
|
|
202
|
+
]
|
|
203
|
+
renderPanel({}, { items })
|
|
204
|
+
expect(screen.getByText('First')).toBeInTheDocument()
|
|
205
|
+
expect(screen.getByText('Second')).toBeInTheDocument()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('prefers items over WrappedChild/children when provided', () => {
|
|
209
|
+
const Wrapped = () => <p>Wrapped</p>
|
|
210
|
+
renderPanel({}, {
|
|
211
|
+
items: [{ id: 'a', element: <p>Item</p> }],
|
|
212
|
+
WrappedChild: Wrapped,
|
|
213
|
+
children: <p>Child content</p>
|
|
214
|
+
})
|
|
215
|
+
expect(screen.getByText('Item')).toBeInTheDocument()
|
|
216
|
+
expect(screen.queryByText('Wrapped')).not.toBeInTheDocument()
|
|
217
|
+
expect(screen.queryByText('Child content')).not.toBeInTheDocument()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('stamps data-panel-slot on the items-capable body, for controls DOM-projected via the JS API', () => {
|
|
221
|
+
const { container } = renderPanel({}, { items: [{ id: 'a', element: <p>Item</p> }] })
|
|
222
|
+
const body = container.querySelector('[data-panel-slot]')
|
|
223
|
+
expect(body).toHaveAttribute('data-panel-slot', 'settings-panel')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('does not stamp data-panel-slot on a static-html body', () => {
|
|
227
|
+
const { container } = renderPanel({}, { html: '<p>HTML content</p>' })
|
|
228
|
+
const body = container.querySelector('.im-c-panel__body')
|
|
229
|
+
expect(body).not.toHaveAttribute('data-panel-slot')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('tabs', () => {
|
|
233
|
+
const tabs = [
|
|
234
|
+
{ name: 'First', items: [{ id: 'a', element: <p>First content</p> }] },
|
|
235
|
+
{ name: 'Second', items: [{ id: 'b', element: <p>Second content</p> }] }
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
it('renders items grouped into tabs when the tabs prop is provided', () => {
|
|
239
|
+
renderPanel({}, { tabs })
|
|
240
|
+
expect(screen.getByRole('tablist')).toBeInTheDocument()
|
|
241
|
+
expect(screen.getByRole('tab', { name: 'First' })).toBeInTheDocument()
|
|
242
|
+
expect(screen.getByRole('tab', { name: 'Second' })).toBeInTheDocument()
|
|
243
|
+
expect(screen.getByText('First content')).toBeInTheDocument()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('switches tab content when a different tab is activated', () => {
|
|
247
|
+
renderPanel({}, { tabs })
|
|
248
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Second' }))
|
|
249
|
+
expect(screen.getByText('Second content')).toBeVisible()
|
|
250
|
+
expect(screen.getByText('First content')).not.toBeVisible()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('prefers tabs over items/WrappedChild/children when provided', () => {
|
|
254
|
+
renderPanel({}, {
|
|
255
|
+
tabs,
|
|
256
|
+
items: [{ id: 'x', element: <p>Flat item</p> }],
|
|
257
|
+
WrappedChild: () => <p>Wrapped</p>,
|
|
258
|
+
children: <p>Child content</p>
|
|
259
|
+
})
|
|
260
|
+
expect(screen.getByText('First content')).toBeInTheDocument()
|
|
261
|
+
expect(screen.queryByText('Flat item')).not.toBeInTheDocument()
|
|
262
|
+
expect(screen.queryByText('Wrapped')).not.toBeInTheDocument()
|
|
263
|
+
expect(screen.queryByText('Child content')).not.toBeInTheDocument()
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('keeps the panel body wrapper (padding, data-panel-slot) around the whole tabs block', () => {
|
|
267
|
+
const { container } = renderPanel({}, { tabs })
|
|
268
|
+
const body = container.querySelector('[data-panel-slot]')
|
|
269
|
+
expect(body).toHaveAttribute('data-panel-slot', 'settings-panel')
|
|
270
|
+
expect(body.querySelector('[role="tablist"]')).not.toBeNull()
|
|
271
|
+
expect(body.querySelector('[role="tabpanel"]')).not.toBeNull()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('keeps the outer wrapper as the normal, unconditional im-c-panel__body — the tablist and tabpanel pick up their inset by being nested inside it, same as any other panel content', () => {
|
|
275
|
+
const { container } = renderPanel({}, { tabs })
|
|
276
|
+
const body = container.querySelector('[data-panel-slot]')
|
|
277
|
+
expect(body).toHaveClass('im-c-panel__body')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('keeps the tablist outside the scrollable tabpanel, so it does not scroll away with long tab content', () => {
|
|
281
|
+
const { container } = renderPanel({}, { tabs })
|
|
282
|
+
const tabpanel = container.querySelector('[role="tabpanel"]')
|
|
283
|
+
expect(tabpanel.querySelector('[role="tablist"]')).toBeNull()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('places the tablist before the tabpanel in DOM order, so keyboard focus reaches it first', () => {
|
|
287
|
+
const { container } = renderPanel({}, { tabs })
|
|
288
|
+
const tablist = container.querySelector('[role="tablist"]')
|
|
289
|
+
const tabpanel = container.querySelector('[role="tabpanel"]')
|
|
290
|
+
// DOCUMENT_POSITION_FOLLOWING (4) means `tabpanel` comes after `tablist`
|
|
291
|
+
// eslint-disable-next-line no-bitwise
|
|
292
|
+
expect(tablist.compareDocumentPosition(tabpanel) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('moves the scrollable-region treatment onto the tabpanel itself, not the outer wrapper', () => {
|
|
296
|
+
useIsScrollable.mockReturnValue(true)
|
|
297
|
+
|
|
298
|
+
const { container } = renderPanel({}, { tabs })
|
|
299
|
+
const body = container.querySelector('[data-panel-slot]')
|
|
300
|
+
const tabpanel = screen.getByRole('tabpanel')
|
|
301
|
+
|
|
302
|
+
expect(tabpanel).toHaveAttribute('tabIndex', '0')
|
|
303
|
+
expect(body).not.toHaveAttribute('tabIndex')
|
|
304
|
+
expect(body).not.toHaveAttribute('role', 'region')
|
|
305
|
+
expect(tabpanel).not.toHaveAttribute('data-panel-slot')
|
|
306
|
+
|
|
307
|
+
useIsScrollable.mockReturnValue(false)
|
|
308
|
+
})
|
|
309
|
+
})
|
|
197
310
|
})
|
|
198
311
|
})
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react'
|
|
2
|
+
import { useConfig } from '../../store/configContext'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
// Prefixed with the app's own id (not the im-c- BEM prefix, which is for CSS classes, not DOM
|
|
5
|
+
// ids — see Panel.jsx's/MapButton.jsx's own ${id}-... ids for the same convention) so these
|
|
6
|
+
// stay unique when more than one map instance is on the same page.
|
|
7
|
+
const toTabId = (idPrefix, name) => `${idPrefix}-tabs-tab-${name.toLowerCase().replaceAll(/\s+/g, '-')}`
|
|
8
|
+
const toPanelId = (idPrefix, name) => `${idPrefix}-tabs-panel-${name.toLowerCase().replaceAll(/\s+/g, '-')}`
|
|
5
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} [panelProps] Extra props merged onto the tabpanel div (not the tablist) —
|
|
12
|
+
* e.g. a scrollable container's ref/className/tabIndex, for a caller (Panel.jsx) that needs
|
|
13
|
+
* that behaviour scoped to just the active tab's content, not the tablist above it. `id`,
|
|
14
|
+
* `role` and `aria-labelledby` are structural to the tabs pattern and always win over any
|
|
15
|
+
* same-named key in `panelProps`; `tabIndex` defers to `panelProps.tabIndex` when provided,
|
|
16
|
+
* since a scrollable tabpanel legitimately wants tabIndex 0 rather than the -1 default.
|
|
17
|
+
*/
|
|
6
18
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
7
19
|
// sonarjs/disable-next-line function-name
|
|
8
|
-
export const Tabs = ({ tabs, defaultTab }) => { // NOSONAR: project does not use PropTypes
|
|
20
|
+
export const Tabs = ({ tabs, defaultTab, panelProps }) => { // NOSONAR: project does not use PropTypes
|
|
21
|
+
const { id: idPrefix } = useConfig()
|
|
9
22
|
const names = tabs.map(t => t.name)
|
|
10
23
|
const [activeTab, setActiveTab] = useState(defaultTab ?? names[0])
|
|
11
24
|
|
|
12
25
|
const activateTab = useCallback((name) => {
|
|
13
26
|
setActiveTab(name)
|
|
14
|
-
document.getElementById(toTabId(name))?.focus()
|
|
15
|
-
}, [])
|
|
27
|
+
document.getElementById(toTabId(idPrefix, name))?.focus()
|
|
28
|
+
}, [idPrefix])
|
|
16
29
|
|
|
17
30
|
const handleKeyDown = useCallback((e) => {
|
|
18
31
|
const idx = names.indexOf(activeTab)
|
|
@@ -33,7 +46,7 @@ export const Tabs = ({ tabs, defaultTab }) => { // NOSONAR: project does not use
|
|
|
33
46
|
}
|
|
34
47
|
}, [names, activeTab, activateTab])
|
|
35
48
|
|
|
36
|
-
const
|
|
49
|
+
const panelClassName = ['im-c-tabs__panel', panelProps?.className].filter(Boolean).join(' ')
|
|
37
50
|
|
|
38
51
|
return (
|
|
39
52
|
<div className='im-c-tabs'>
|
|
@@ -42,10 +55,10 @@ export const Tabs = ({ tabs, defaultTab }) => { // NOSONAR: project does not use
|
|
|
42
55
|
<button
|
|
43
56
|
type='button'
|
|
44
57
|
key={name}
|
|
45
|
-
id={toTabId(name)}
|
|
58
|
+
id={toTabId(idPrefix, name)}
|
|
46
59
|
role='tab'
|
|
47
60
|
aria-selected={activeTab === name}
|
|
48
|
-
aria-controls={toPanelId(name)}
|
|
61
|
+
aria-controls={toPanelId(idPrefix, name)}
|
|
49
62
|
tabIndex={activeTab === name ? 0 : -1}
|
|
50
63
|
className='im-c-tabs__tab'
|
|
51
64
|
onClick={() => activateTab(name)}
|
|
@@ -55,15 +68,28 @@ export const Tabs = ({ tabs, defaultTab }) => { // NOSONAR: project does not use
|
|
|
55
68
|
</button>
|
|
56
69
|
))}
|
|
57
70
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
{/* All tabpanels render, hidden unless active — not swapped in/out — so every tab's
|
|
72
|
+
aria-controls always resolves to a real element, rather than only the active one's.
|
|
73
|
+
panelProps (ref/tabIndex — a scrollable container's, from a caller like Panel.jsx)
|
|
74
|
+
can only meaningfully target one DOM node, so it's applied to the active panel only;
|
|
75
|
+
className is shared by all of them so there's nothing to restyle on switch. */}
|
|
76
|
+
{tabs.map(({ name, content }) => {
|
|
77
|
+
const isActive = activeTab === name
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
key={name}
|
|
81
|
+
ref={isActive ? panelProps?.ref : undefined}
|
|
82
|
+
id={toPanelId(idPrefix, name)}
|
|
83
|
+
role='tabpanel'
|
|
84
|
+
aria-labelledby={toTabId(idPrefix, name)}
|
|
85
|
+
tabIndex={isActive ? (panelProps?.tabIndex ?? -1) : -1} // nosonar — panel has no focusable children by default (-1); a scrollable active one wants 0, via panelProps
|
|
86
|
+
hidden={!isActive}
|
|
87
|
+
className={panelClassName}
|
|
88
|
+
>
|
|
89
|
+
{content}
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
})}
|
|
67
93
|
</div>
|
|
68
94
|
)
|
|
69
95
|
}
|
|
@@ -4,9 +4,18 @@
|
|
|
4
4
|
// Component: Tabs
|
|
5
5
|
// ===================================================
|
|
6
6
|
|
|
7
|
+
// Lets a caller (e.g. Panel.jsx) give Tabs a bounded height — only .im-c-tabs__panel scrolls.
|
|
8
|
+
.im-c-tabs {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
min-height: 0;
|
|
12
|
+
flex: 1 1 auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
.im-c-tabs__list {
|
|
8
16
|
position: relative;
|
|
9
17
|
display: flex;
|
|
18
|
+
flex: 0 0 auto;
|
|
10
19
|
flex-wrap: wrap;
|
|
11
20
|
padding: 0;
|
|
12
21
|
margin: 0;
|
|
@@ -14,8 +23,14 @@
|
|
|
14
23
|
z-index: 1;
|
|
15
24
|
}
|
|
16
25
|
|
|
26
|
+
.im-c-tabs__panel {
|
|
27
|
+
flex: 1 1 auto;
|
|
28
|
+
overflow: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
.im-c-tabs__tab {
|
|
18
32
|
position: relative;
|
|
33
|
+
z-index: 0; // scopes the active tab's ::before (below) to this button's own stacking context
|
|
19
34
|
appearance: none;
|
|
20
35
|
background: var(--button-hover-color);
|
|
21
36
|
border: 1px solid transparent;
|
|
@@ -42,12 +57,24 @@
|
|
|
42
57
|
}
|
|
43
58
|
}
|
|
44
59
|
|
|
60
|
+
// The selected tab looks taller via an absolutely-positioned ::before rather than changing
|
|
61
|
+
// the button's own box, so the label never moves and the tablist's height never shifts.
|
|
45
62
|
&[aria-selected='true'] {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
z-index: 1; // sit above unselected siblings
|
|
64
|
+
background: transparent;
|
|
65
|
+
|
|
66
|
+
&::before {
|
|
67
|
+
content: '';
|
|
68
|
+
position: absolute;
|
|
69
|
+
z-index: -1;
|
|
70
|
+
top: -5px;
|
|
71
|
+
right: -1px;
|
|
72
|
+
bottom: -6px;
|
|
73
|
+
left: -1px;
|
|
74
|
+
background: var(--background-color);
|
|
75
|
+
border: 1px solid var(--app-border-color);
|
|
76
|
+
border-bottom-color: var(--background-color);
|
|
77
|
+
}
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
&:focus .im-c-tabs__label {
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
3
|
import { Tabs } from './Tabs'
|
|
4
|
+
import { useConfig } from '../../store/configContext'
|
|
5
|
+
|
|
6
|
+
jest.mock('../../store/configContext', () => ({ useConfig: jest.fn() }))
|
|
4
7
|
|
|
5
8
|
const TAB_A = { name: 'Alpha', content: <p>Alpha content</p> }
|
|
6
9
|
const TAB_B = { name: 'Beta', content: <p>Beta content</p> }
|
|
7
10
|
const TAB_C = { name: 'Gamma', content: <p>Gamma content</p> }
|
|
8
11
|
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
useConfig.mockReturnValue({ id: 'test-app' })
|
|
14
|
+
})
|
|
15
|
+
|
|
9
16
|
// ─── rendering ───────────────────────────────────────────────────────────────
|
|
10
17
|
|
|
11
18
|
describe('Tabs — rendering', () => {
|
|
@@ -15,17 +22,17 @@ describe('Tabs — rendering', () => {
|
|
|
15
22
|
expect(screen.getByRole('tab', { name: 'Beta' })).toBeInTheDocument()
|
|
16
23
|
})
|
|
17
24
|
|
|
18
|
-
it('shows content for the active tab only', () => {
|
|
25
|
+
it('shows content for the active tab only — the inactive one stays in the DOM (so its aria-controls target exists) but hidden', () => {
|
|
19
26
|
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
20
|
-
expect(screen.getByText('Alpha content')).
|
|
21
|
-
expect(screen.
|
|
27
|
+
expect(screen.getByText('Alpha content')).toBeVisible()
|
|
28
|
+
expect(screen.getByText('Beta content')).not.toBeVisible()
|
|
22
29
|
})
|
|
23
30
|
|
|
24
31
|
it('uses defaultTab to set the initial active tab', () => {
|
|
25
32
|
render(<Tabs tabs={[TAB_A, TAB_B]} defaultTab='Beta' />)
|
|
26
33
|
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('aria-selected', 'true')
|
|
27
|
-
expect(screen.getByText('Beta content')).
|
|
28
|
-
expect(screen.
|
|
34
|
+
expect(screen.getByText('Beta content')).toBeVisible()
|
|
35
|
+
expect(screen.getByText('Alpha content')).not.toBeVisible()
|
|
29
36
|
})
|
|
30
37
|
|
|
31
38
|
it('falls back to first tab when defaultTab is not provided', () => {
|
|
@@ -41,8 +48,8 @@ describe('Tabs — click behaviour', () => {
|
|
|
41
48
|
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
42
49
|
fireEvent.click(screen.getByRole('tab', { name: 'Beta' }))
|
|
43
50
|
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('aria-selected', 'true')
|
|
44
|
-
expect(screen.getByText('Beta content')).
|
|
45
|
-
expect(screen.
|
|
51
|
+
expect(screen.getByText('Beta content')).toBeVisible()
|
|
52
|
+
expect(screen.getByText('Alpha content')).not.toBeVisible()
|
|
46
53
|
})
|
|
47
54
|
})
|
|
48
55
|
|
|
@@ -55,10 +62,33 @@ describe('Tabs — WCAG attributes', () => {
|
|
|
55
62
|
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('tabindex', '-1')
|
|
56
63
|
})
|
|
57
64
|
|
|
58
|
-
it('
|
|
65
|
+
it('every tab has aria-controls pointing to a real element — including inactive tabs, since their panel stays in the DOM (hidden) rather than being unmounted', () => {
|
|
66
|
+
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
67
|
+
const activeTab = screen.getByRole('tab', { name: 'Alpha' })
|
|
68
|
+
const inactiveTab = screen.getByRole('tab', { name: 'Beta' })
|
|
69
|
+
expect(document.getElementById(activeTab.getAttribute('aria-controls'))).toBeInTheDocument()
|
|
70
|
+
expect(document.getElementById(inactiveTab.getAttribute('aria-controls'))).toBeInTheDocument()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('gives every tabpanel its own stable id, distinct per tab, not just the active one', () => {
|
|
74
|
+
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
75
|
+
const activeTab = screen.getByRole('tab', { name: 'Alpha' })
|
|
76
|
+
const inactiveTab = screen.getByRole('tab', { name: 'Beta' })
|
|
77
|
+
expect(activeTab.getAttribute('aria-controls')).not.toBe(inactiveTab.getAttribute('aria-controls'))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('hides the inactive tabpanel via the hidden attribute rather than unmounting it', () => {
|
|
81
|
+
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
82
|
+
const inactiveTab = screen.getByRole('tab', { name: 'Beta' })
|
|
83
|
+
const inactivePanel = document.getElementById(inactiveTab.getAttribute('aria-controls'))
|
|
84
|
+
expect(inactivePanel).toHaveAttribute('hidden')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("labels every tabpanel with its own tab, not just the active one's", () => {
|
|
59
88
|
render(<Tabs tabs={[TAB_A, TAB_B]} />)
|
|
60
|
-
const
|
|
61
|
-
|
|
89
|
+
const inactiveTab = screen.getByRole('tab', { name: 'Beta' })
|
|
90
|
+
const inactivePanel = document.getElementById(inactiveTab.getAttribute('aria-controls'))
|
|
91
|
+
expect(inactivePanel.getAttribute('aria-labelledby')).toBe(inactiveTab.id)
|
|
62
92
|
})
|
|
63
93
|
|
|
64
94
|
it('panel has aria-labelledby pointing to the active tab', () => {
|
|
@@ -45,34 +45,32 @@ describe('Tooltip', () => {
|
|
|
45
45
|
|
|
46
46
|
it('shows and hides tooltip on mouse interactions', () => {
|
|
47
47
|
const { button, tooltip } = renderTooltip()
|
|
48
|
-
|
|
48
|
+
fireEvent.mouseEnter(button)
|
|
49
49
|
advanceTimers()
|
|
50
50
|
expect(tooltip).toHaveClass('im-c-tooltip--is-visible')
|
|
51
51
|
expect(getTooltipPosition).toHaveBeenCalled()
|
|
52
|
-
|
|
52
|
+
fireEvent.mouseLeave(button)
|
|
53
53
|
advanceTimers(0)
|
|
54
54
|
expect(tooltip).not.toHaveClass('im-c-tooltip--is-visible')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it('cancels tooltip on mouse down or key down', () => {
|
|
58
58
|
const { button, tooltip } = renderTooltip()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
fireEvent.keyDown(button)
|
|
64
|
-
})
|
|
59
|
+
fireEvent.mouseEnter(button)
|
|
60
|
+
fireEvent.mouseDown(button)
|
|
61
|
+
fireEvent.mouseEnter(button)
|
|
62
|
+
fireEvent.keyDown(button)
|
|
65
63
|
advanceTimers()
|
|
66
64
|
expect(tooltip).not.toHaveClass('im-c-tooltip--is-visible')
|
|
67
65
|
})
|
|
68
66
|
|
|
69
67
|
it('handles focus interactions for keyboard interface', () => {
|
|
70
68
|
const { button, tooltip, wrapper } = renderTooltip({}, 'keyboard')
|
|
71
|
-
|
|
69
|
+
fireEvent.focus(button)
|
|
72
70
|
advanceTimers()
|
|
73
71
|
expect(wrapper).toHaveClass('im-c-tooltip-wrapper--has-focus')
|
|
74
72
|
expect(tooltip).toHaveClass('im-c-tooltip--is-visible')
|
|
75
|
-
|
|
73
|
+
fireEvent.blur(button)
|
|
76
74
|
advanceTimers(0)
|
|
77
75
|
expect(wrapper).not.toHaveClass('im-c-tooltip-wrapper--has-focus')
|
|
78
76
|
expect(tooltip).not.toHaveClass('im-c-tooltip--is-visible')
|
|
@@ -80,22 +78,22 @@ describe('Tooltip', () => {
|
|
|
80
78
|
|
|
81
79
|
it('does not show tooltip on focus if interfaceType is mouse', () => {
|
|
82
80
|
const { button, tooltip } = renderTooltip()
|
|
83
|
-
|
|
81
|
+
fireEvent.focus(button)
|
|
84
82
|
advanceTimers()
|
|
85
83
|
expect(tooltip).not.toHaveClass('im-c-tooltip--is-visible')
|
|
86
84
|
})
|
|
87
85
|
|
|
88
86
|
it('hides tooltip on Escape key', () => {
|
|
89
87
|
const { button, tooltip } = renderTooltip()
|
|
90
|
-
|
|
88
|
+
fireEvent.mouseEnter(button)
|
|
91
89
|
advanceTimers()
|
|
92
|
-
|
|
90
|
+
fireEvent.keyDown(window, { key: 'Escape' })
|
|
93
91
|
expect(tooltip).not.toHaveClass('im-c-tooltip--is-visible')
|
|
94
92
|
})
|
|
95
93
|
|
|
96
94
|
it('removes keydown listener on unmount', () => {
|
|
97
95
|
const { button, unmount } = renderTooltip({}, 'keyboard')
|
|
98
|
-
|
|
96
|
+
fireEvent.mouseEnter(button)
|
|
99
97
|
advanceTimers()
|
|
100
98
|
const removeSpy = jest.spyOn(window, 'removeEventListener')
|
|
101
99
|
unmount()
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
|
|
3
|
+
const SET_FEATURES = 'map:setfeatures'
|
|
4
|
+
const SET_FEATURES_SUPPRESSED = 'map:setfeaturessuppressed'
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
|
-
* Subscribes to map:setfeatures
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Subscribes to map:setfeatures for the listbox item list, and map:setfeaturessuppressed
|
|
8
|
+
* (any plugin can emit this) to report items as empty while suppressed regardless of what's
|
|
9
|
+
* actually visible — which is what drives <Features>'s own tabIndex/aria-hidden.
|
|
7
10
|
*
|
|
8
11
|
* @param {object} eventBus
|
|
9
12
|
* @returns {{ items: Array<{ id: string, label: string }>, multiselectable: boolean }}
|
|
@@ -11,20 +14,26 @@ import { useState, useEffect } from 'react'
|
|
|
11
14
|
export function useFeatureItems (eventBus) {
|
|
12
15
|
const [items, setItems] = useState([])
|
|
13
16
|
const [multiselectable, setMultiselectable] = useState(false)
|
|
17
|
+
const [suppressed, setSuppressed] = useState(false)
|
|
14
18
|
|
|
15
19
|
useEffect(() => {
|
|
16
20
|
if (!eventBus) {
|
|
17
21
|
return undefined
|
|
18
22
|
}
|
|
19
|
-
const
|
|
23
|
+
const handleSetFeatures = ({ items: next = [], multiselectable: nextMultiselectable = false }) => {
|
|
20
24
|
setItems(next)
|
|
21
25
|
setMultiselectable(nextMultiselectable)
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
const handleSuppressed = ({ suppressed: next = false } = {}) => {
|
|
28
|
+
setSuppressed(next)
|
|
29
|
+
}
|
|
30
|
+
eventBus.on(SET_FEATURES, handleSetFeatures)
|
|
31
|
+
eventBus.on(SET_FEATURES_SUPPRESSED, handleSuppressed)
|
|
24
32
|
return () => {
|
|
25
|
-
eventBus.off(
|
|
33
|
+
eventBus.off(SET_FEATURES, handleSetFeatures)
|
|
34
|
+
eventBus.off(SET_FEATURES_SUPPRESSED, handleSuppressed)
|
|
26
35
|
}
|
|
27
36
|
}, [eventBus])
|
|
28
37
|
|
|
29
|
-
return { items, multiselectable }
|
|
38
|
+
return { items: suppressed ? [] : items, multiselectable }
|
|
30
39
|
}
|
|
@@ -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
|
+
})
|