@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
|
@@ -36,10 +36,48 @@ export const anchorToMaplibre = ([ax, ay]) => {
|
|
|
36
36
|
return (y + (x && y ? '-' : '') + x) || 'center'
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// The discrete fraction (0, 0.5 or 1) icon-anchor actually renders a given axis at — the
|
|
40
|
+
// same left/right/top/bottom/center snapping xAnchor/yAnchor above already do, expressed as
|
|
41
|
+
// a number instead of a string so it can be compared against the true, unsnapped fraction.
|
|
42
|
+
const discreteFraction = (a) => {
|
|
43
|
+
if (a <= ANCHOR_LOW) { return 0 }
|
|
44
|
+
if (a >= ANCHOR_HIGH) { return 1 }
|
|
45
|
+
return 0.5
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* MapLibre's icon-anchor only has 9 discrete positions — anchorToMaplibre above snaps a
|
|
50
|
+
* fractional anchor to the nearest one, which loses precision for anything off-grid (e.g.
|
|
51
|
+
* pin's [0.5, 0.9] renders as if it were 1.0/'bottom', shifting the whole icon ~10% of its
|
|
52
|
+
* height off from where a DOM marker using the same anchor value would place it —
|
|
53
|
+
* SymbolMarker.jsx positions markers with the exact fraction via CSS margins, with no such
|
|
54
|
+
* snapping). icon-offset corrects this: the pixel distance, in the icon's own display-space
|
|
55
|
+
* dimensions (derived from its viewBox), between the true anchor and the discrete one it
|
|
56
|
+
* snapped to — applied on top of icon-anchor, on both provider and dataset symbol layers.
|
|
57
|
+
*
|
|
58
|
+
* @param {number[]} anchor - [x, y] in 0–1 space (the true, unsnapped anchor)
|
|
59
|
+
* @param {string} viewBox - SVG viewBox string, e.g. '0 0 44 44'
|
|
60
|
+
* @returns {number[]} [offsetX, offsetY] in pixels, for the icon-offset layout property
|
|
61
|
+
*/
|
|
62
|
+
// Rounded to 2dp: sub-hundredth-of-a-pixel precision is meaningless for rendering, and
|
|
63
|
+
// without it fractions like 0.8 produce float noise (8.799999999999997, not 8.8).
|
|
64
|
+
const round2dp = (n) => Math.round(n * 100) / 100
|
|
65
|
+
|
|
66
|
+
export const anchorToMaplibreOffset = ([ax, ay], viewBox) => {
|
|
67
|
+
const [,, width, height] = viewBox.split(' ').map(Number)
|
|
68
|
+
return [
|
|
69
|
+
round2dp((discreteFraction(ax) - ax) * width),
|
|
70
|
+
round2dp((discreteFraction(ay) - ay) * height)
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
|
|
39
74
|
/**
|
|
40
75
|
* Register normal, active (both rings) and selected (black ring) symbol images.
|
|
41
76
|
* Skips images that are already registered (safe to call on style change).
|
|
42
|
-
*
|
|
77
|
+
* Merges into `map._activeSymbolImageMap` (normal→active) and `map._selectedSymbolImageMap`
|
|
78
|
+
* (normal→selected) rather than replacing them — more than one caller (e.g. the datasets
|
|
79
|
+
* and draw plugins) can register symbols on the same map, and each call must add to that
|
|
80
|
+
* shared registry, not wipe out whatever another caller already registered there.
|
|
43
81
|
*
|
|
44
82
|
* @param {Object} map - MapLibre map instance
|
|
45
83
|
* @param {Object[]} styleArray - an array of symbol configs
|
|
@@ -53,8 +91,8 @@ export const addSymbolsToMap = async (map, styleArray, mapStyle, symbolRegistry,
|
|
|
53
91
|
return
|
|
54
92
|
}
|
|
55
93
|
|
|
56
|
-
map._activeSymbolImageMap
|
|
57
|
-
map._selectedSymbolImageMap
|
|
94
|
+
map._activeSymbolImageMap ??= {}
|
|
95
|
+
map._selectedSymbolImageMap ??= {}
|
|
58
96
|
|
|
59
97
|
await Promise.all(styleArray.flatMap(config => {
|
|
60
98
|
const normalId = symbolRegistry.getSymbolImageId(config, mapStyle, false, pixelRatio)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anchorToMaplibre, addSymbolsToMap } from './symbolImages.js'
|
|
1
|
+
import { anchorToMaplibre, anchorToMaplibreOffset, addSymbolsToMap } from './symbolImages.js'
|
|
2
2
|
import { symbolRegistry } from '../../../../src/services/symbolRegistry.js'
|
|
3
3
|
|
|
4
4
|
beforeAll(() => {
|
|
@@ -84,6 +84,36 @@ describe('anchorToMaplibre', () => {
|
|
|
84
84
|
})
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
+
// ─── anchorToMaplibreOffset ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
describe('anchorToMaplibreOffset', () => {
|
|
90
|
+
const VIEW_BOX = '0 0 44 44'
|
|
91
|
+
|
|
92
|
+
it('is zero for an anchor that already sits exactly on a discrete position', () => {
|
|
93
|
+
expect(anchorToMaplibreOffset([0.5, 0.5], VIEW_BOX)).toEqual([0, 0]) // center
|
|
94
|
+
expect(anchorToMaplibreOffset([0, 0], VIEW_BOX)).toEqual([0, 0]) // top-left
|
|
95
|
+
expect(anchorToMaplibreOffset([1, 1], VIEW_BOX)).toEqual([0, 0]) // bottom-right
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('corrects for pin-style [0.5, 0.9] snapping to bottom (1.0)', () => {
|
|
99
|
+
expect(anchorToMaplibreOffset([0.5, 0.9], VIEW_BOX)).toEqual([0, 4.4])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('corrects both axes when both are off-grid', () => {
|
|
103
|
+
// 0.1 snaps to 'left'/'top' (0), 0.9 snaps to 'right'/'bottom' (1)
|
|
104
|
+
expect(anchorToMaplibreOffset([0.1, 0.1], VIEW_BOX)).toEqual([-4.4, -4.4])
|
|
105
|
+
expect(anchorToMaplibreOffset([0.9, 0.9], VIEW_BOX)).toEqual([4.4, 4.4])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('still corrects a value inside the centre band that is not exactly 0.5 — the whole band snaps to 0.5, not just its edges', () => {
|
|
109
|
+
expect(anchorToMaplibreOffset([0.4, 0.6], VIEW_BOX)).toEqual([4.4, -4.4])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('scales with the viewBox, not just the anchor fraction', () => {
|
|
113
|
+
expect(anchorToMaplibreOffset([0.5, 0.9], '0 0 100 100')).toEqual([0, 10])
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
87
117
|
// ─── addSymbolsToMap ──────────────────────────────────────────────────────────
|
|
88
118
|
|
|
89
119
|
const makeMap = (existingIds = []) => ({
|
|
@@ -103,13 +133,17 @@ describe('addSymbolsToMap — registration', () => {
|
|
|
103
133
|
expect(map.addImage).not.toHaveBeenCalled()
|
|
104
134
|
})
|
|
105
135
|
|
|
106
|
-
it('
|
|
136
|
+
it('merges into existing _activeSymbolImageMap/_selectedSymbolImageMap rather than replacing them', async () => {
|
|
137
|
+
// More than one caller (datasets, draw, ...) can register symbols on the same map —
|
|
138
|
+
// a later call must add to the shared registry, not wipe an earlier caller's entries.
|
|
107
139
|
const map = makeMap()
|
|
108
|
-
map._activeSymbolImageMap = {
|
|
109
|
-
map._selectedSymbolImageMap = {
|
|
140
|
+
map._activeSymbolImageMap = { 'other-normal-id': 'other-active-id' }
|
|
141
|
+
map._selectedSymbolImageMap = { 'other-normal-id': 'other-selected-id' }
|
|
110
142
|
await addSymbolsToMap(map, [{ symbol: 'pin' }], mapStyle, symbolRegistry)
|
|
111
|
-
expect(map._activeSymbolImageMap).
|
|
112
|
-
expect(map._selectedSymbolImageMap).
|
|
143
|
+
expect(map._activeSymbolImageMap['other-normal-id']).toBe('other-active-id')
|
|
144
|
+
expect(map._selectedSymbolImageMap['other-normal-id']).toBe('other-selected-id')
|
|
145
|
+
const normalId = symbolRegistry.getSymbolImageId({ symbol: 'pin' }, mapStyle, false)
|
|
146
|
+
expect(map._activeSymbolImageMap).toHaveProperty(normalId)
|
|
113
147
|
})
|
|
114
148
|
|
|
115
149
|
it('calls addImage for normal, active and selected variants', async () => {
|
package/rollup.esm.mjs
CHANGED
|
@@ -182,7 +182,7 @@ const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null,
|
|
|
182
182
|
open: false,
|
|
183
183
|
gzipSize: true
|
|
184
184
|
})]
|
|
185
|
-
|
|
185
|
+
: [])
|
|
186
186
|
],
|
|
187
187
|
|
|
188
188
|
output: {
|
|
@@ -303,6 +303,16 @@ const ALL_BUILDS = [
|
|
|
303
303
|
entryPath: './plugins/beta/frame/src/index.js',
|
|
304
304
|
outDir: 'plugins/beta/frame/dist/esm',
|
|
305
305
|
manualChunks: (id) => id.includes('/manifest') ? 'im-frame-plugin' : undefined
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
entryPath: './plugins/map-key/src/index.js',
|
|
309
|
+
outDir: 'plugins/map-key/dist/esm',
|
|
310
|
+
manualChunks: (id) => id.includes('/manifest') ? 'im-map-key-plugin' : undefined
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
entryPath: './plugins/menu/src/index.js',
|
|
314
|
+
outDir: 'plugins/menu/dist/esm',
|
|
315
|
+
manualChunks: (id) => id.includes('/manifest') ? 'im-menu-plugin' : undefined
|
|
306
316
|
}
|
|
307
317
|
]
|
|
308
318
|
|
package/sonar-project.properties
CHANGED
|
@@ -17,7 +17,7 @@ sonar.test.inclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**
|
|
|
17
17
|
sonar.cpd.exclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**,**/__helpers__/**
|
|
18
18
|
|
|
19
19
|
# Ignored rules
|
|
20
|
-
sonar.issue.ignore.multicriteria=reactPropsJs,reactPropsJsx,preferGlobalThisJs,preferGlobalThisJsx,preferAtJs,preferAtJsx,replaceAllJs,replaceAllJsx,stringReplaceAllJs,stringReplaceAllJsx,pascalCaseFunctionsJs,pascalCaseFunctionsJsx,pascalCaseFunctionsTs,pascalCaseFunctionsTsx,cssScopingRootMixins
|
|
20
|
+
sonar.issue.ignore.multicriteria=reactPropsJs,reactPropsJsx,preferGlobalThisJs,preferGlobalThisJsx,preferAtJs,preferAtJsx,replaceAllJs,replaceAllJsx,stringReplaceAllJs,stringReplaceAllJsx,pascalCaseFunctionsJs,pascalCaseFunctionsJsx,pascalCaseFunctionsTs,pascalCaseFunctionsTsx,cssScopingRootMixins,parameterizedTestsJs,parameterizedTestsJsx
|
|
21
21
|
|
|
22
22
|
# S6774: React props validation - using TypeScript/JSDoc for prop types instead
|
|
23
23
|
sonar.issue.ignore.multicriteria.reactPropsJs.ruleKey=javascript:S6774
|
|
@@ -59,6 +59,12 @@ sonar.issue.ignore.multicriteria.pascalCaseFunctionsTs.resourceKey=**/*.ts
|
|
|
59
59
|
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.ruleKey=javascript:S100
|
|
60
60
|
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.resourceKey=**/*.tsx
|
|
61
61
|
|
|
62
|
+
# S5976: Replace these N tests with a single Parameterized one - jest.each is preferred over parameterized tests
|
|
63
|
+
sonar.issue.ignore.multicriteria.parameterizedTestsJs.ruleKey=javascript:S5976
|
|
64
|
+
sonar.issue.ignore.multicriteria.parameterizedTestsJs.resourceKey=**/*.test.js
|
|
65
|
+
sonar.issue.ignore.multicriteria.parameterizedTestsJsx.ruleKey=javascript:S5976
|
|
66
|
+
sonar.issue.ignore.multicriteria.parameterizedTestsJsx.resourceKey=**/*.test.jsx
|
|
67
|
+
|
|
62
68
|
# S8776: Missing scoping root for & - false positive on Sass mixins, which are always
|
|
63
69
|
# @included inside a real selector so & resolves correctly once compiled
|
|
64
70
|
sonar.issue.ignore.multicriteria.cssScopingRootMixins.ruleKey=css:S8776
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render, screen } from '@testing-library/react'
|
|
3
3
|
import { Actions } from './Actions.jsx'
|
|
4
|
+
import { mapButtons } from '../../renderer/mapButtons.js'
|
|
4
5
|
import * as AppContext from '../../store/appContext' // import the module to mock
|
|
5
6
|
|
|
6
7
|
// Simple child component to handle isHidden
|
|
7
8
|
const TestChild = ({ isHidden, children, ...props }) => <div {...props}>{children}</div>
|
|
8
9
|
|
|
10
|
+
jest.mock('../MapButton/MapButton.jsx', () => ({
|
|
11
|
+
MapButton: ({ isHidden }) => <button data-testid='map-button' hidden={isHidden} />
|
|
12
|
+
}))
|
|
13
|
+
|
|
9
14
|
const ACTIONS_SELECTOR = '.im-c-actions'
|
|
10
15
|
const HIDDEN_CLASS = 'im-c-actions--hidden'
|
|
11
16
|
|
|
@@ -80,4 +85,46 @@ describe('Actions component', () => {
|
|
|
80
85
|
const container = screen.getByTestId('child1').closest(ACTIONS_SELECTOR)
|
|
81
86
|
expect(container).toHaveClass(HIDDEN_CLASS)
|
|
82
87
|
})
|
|
88
|
+
|
|
89
|
+
// Integration with the real mapButtons() output, not just the TestChild stand-in above —
|
|
90
|
+
// guards the isHidden/variant contract at the actual seam between the two modules, since
|
|
91
|
+
// mapButtons.js's own unit tests can't catch a break here on their own.
|
|
92
|
+
describe('with real mapButtons() output', () => {
|
|
93
|
+
const evaluateProp = (prop) => (typeof prop === 'function' ? prop() : prop)
|
|
94
|
+
const appConfig = { id: 'test' }
|
|
95
|
+
const baseAppState = {
|
|
96
|
+
breakpoint: 'desktop',
|
|
97
|
+
mode: 'view',
|
|
98
|
+
isFullscreen: true,
|
|
99
|
+
openPanels: {},
|
|
100
|
+
dispatch: jest.fn(),
|
|
101
|
+
disabledButtons: new Set(),
|
|
102
|
+
hiddenButtons: new Set(),
|
|
103
|
+
pressedButtons: new Set(),
|
|
104
|
+
expandedButtons: new Set(),
|
|
105
|
+
panelConfig: {},
|
|
106
|
+
layoutRefs: { viewportRef: { current: { focus: jest.fn() } } }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const renderActionsWith = (appState) => {
|
|
110
|
+
const items = mapButtons({ slot: 'actions', appState, appConfig, evaluateProp })
|
|
111
|
+
return render(<Actions slot='actions'>{items.map(i => i.element)}</Actions>)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it('shows the actions bar when a real mapButtons() button is visible', () => {
|
|
115
|
+
const appState = { ...baseAppState, buttonConfig: { cancel: { label: 'Cancel', desktop: { slot: 'actions' } } } }
|
|
116
|
+
renderActionsWith(appState)
|
|
117
|
+
expect(screen.getByTestId('map-button').closest(ACTIONS_SELECTOR)).not.toHaveClass(HIDDEN_CLASS)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('hides the actions bar when the only real mapButtons() button is hidden', () => {
|
|
121
|
+
const appState = {
|
|
122
|
+
...baseAppState,
|
|
123
|
+
hiddenButtons: new Set(['cancel']),
|
|
124
|
+
buttonConfig: { cancel: { label: 'Cancel', desktop: { slot: 'actions' } } }
|
|
125
|
+
}
|
|
126
|
+
renderActionsWith(appState)
|
|
127
|
+
expect(screen.getByTestId('map-button').closest(ACTIONS_SELECTOR)).toHaveClass(HIDDEN_CLASS)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
83
130
|
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use '../../../scss/tools/index' as tools;
|
|
2
|
+
|
|
1
3
|
.im-c-attributions {
|
|
2
4
|
color: var(--attributions-foreground-color);
|
|
3
5
|
background-color: var(--attributions-background-color);
|
|
@@ -7,4 +9,12 @@
|
|
|
7
9
|
overflow: hidden;
|
|
8
10
|
text-overflow: ellipsis;
|
|
9
11
|
pointer-events: auto;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.im-c-attributions a {
|
|
15
|
+
color: inherit;
|
|
16
|
+
|
|
17
|
+
&[data-focus-visible="true"] {
|
|
18
|
+
@include tools.link-focus;
|
|
19
|
+
}
|
|
10
20
|
}
|
|
@@ -17,7 +17,7 @@ import { htmlToPlainText } from '../../../utils/htmlToPlainText.js'
|
|
|
17
17
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
18
18
|
// sonarjs/disable-next-line function-name
|
|
19
19
|
export const Hints = () => {
|
|
20
|
-
const { id, keyboardHintText } = useConfig()
|
|
20
|
+
const { id, keyboardHintText, enableMoveControls, moveControlsHintText } = useConfig()
|
|
21
21
|
const { hints } = useService()
|
|
22
22
|
const { layoutRefs } = useApp()
|
|
23
23
|
const [activeHint, setActiveHint] = useState(null)
|
|
@@ -32,6 +32,11 @@ export const Hints = () => {
|
|
|
32
32
|
return null
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Composed at render time so overriding one text doesn't drop the other.
|
|
36
|
+
const keyboardDescriptionHtml = enableMoveControls
|
|
37
|
+
? `${keyboardHintText} ${moveControlsHintText}`
|
|
38
|
+
: keyboardHintText
|
|
39
|
+
|
|
35
40
|
return createPortal(
|
|
36
41
|
<div className='im-o-hints'>
|
|
37
42
|
<div id={`${id}-hints`} className='im-c-hints'>
|
|
@@ -45,7 +50,7 @@ export const Hints = () => {
|
|
|
45
50
|
<div
|
|
46
51
|
id={`${id}-keyboard-desc`}
|
|
47
52
|
className='im-u-visually-hidden'
|
|
48
|
-
dangerouslySetInnerHTML={{ __html: htmlToPlainText(
|
|
53
|
+
dangerouslySetInnerHTML={{ __html: htmlToPlainText(keyboardDescriptionHtml) }}
|
|
49
54
|
/>
|
|
50
55
|
</div>,
|
|
51
56
|
layoutRefs.mainRef.current
|
|
@@ -52,6 +52,19 @@ describe('Hints — rendering', () => {
|
|
|
52
52
|
expect(desc.innerHTML).toBe('Shift + ?')
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
+
it('appends the move-controls hint text when enableMoveControls is true', () => {
|
|
56
|
+
const mainEl = setup()
|
|
57
|
+
useConfig.mockReturnValue({
|
|
58
|
+
id: 'test-map',
|
|
59
|
+
keyboardHintText: '<kbd>Shift</kbd> + <kbd>?</kbd>',
|
|
60
|
+
enableMoveControls: true,
|
|
61
|
+
moveControlsHintText: '<kbd>Arrow keys</kbd> to move'
|
|
62
|
+
})
|
|
63
|
+
render(<Hints />)
|
|
64
|
+
const desc = mainEl.querySelector('#test-map-keyboard-desc')
|
|
65
|
+
expect(desc.innerHTML).toBe('Shift + ? Arrow keys to move')
|
|
66
|
+
})
|
|
67
|
+
|
|
55
68
|
it('renders no hint content when there is no active hint', () => {
|
|
56
69
|
const mainEl = setup()
|
|
57
70
|
render(<Hints />)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { useConfig } from '../../store/configContext'
|
|
4
4
|
import { useApp } from '../../store/appContext.js'
|
|
5
|
-
import { getKeyboardShortcuts } from '../../registry/keyboardShortcutRegistry.js'
|
|
6
5
|
import { Tabs } from '../Tabs/Tabs.jsx'
|
|
7
6
|
|
|
8
7
|
const ShortcutList = ({ items }) => (
|
|
@@ -42,8 +41,8 @@ const getDefaultTab = (groupEntries, context) => {
|
|
|
42
41
|
// sonarjs/disable-next-line function-name
|
|
43
42
|
export const KeyboardHelp = ({ context = 'viewport' }) => { // NOSONAR: project does not use PropTypes
|
|
44
43
|
const appConfig = useConfig()
|
|
45
|
-
const { listboxIsActive } = useApp()
|
|
46
|
-
const allShortcuts = getKeyboardShortcuts(appConfig)
|
|
44
|
+
const { listboxIsActive, keyboardShortcutRegistry } = useApp()
|
|
45
|
+
const allShortcuts = keyboardShortcutRegistry.getKeyboardShortcuts(appConfig)
|
|
47
46
|
const shortcuts = listboxIsActive
|
|
48
47
|
? allShortcuts
|
|
49
48
|
: allShortcuts.filter(s => s.context !== 'listbox')
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
4
4
|
import { KeyboardHelp } from './KeyboardHelp'
|
|
5
|
-
import { getKeyboardShortcuts } from '../../registry/keyboardShortcutRegistry.js'
|
|
6
5
|
import { useConfig } from '../../store/configContext'
|
|
7
6
|
import { useApp } from '../../store/appContext.js'
|
|
8
7
|
|
|
9
|
-
jest.
|
|
10
|
-
getKeyboardShortcuts: jest.fn()
|
|
11
|
-
}))
|
|
8
|
+
const getKeyboardShortcuts = jest.fn()
|
|
12
9
|
|
|
13
10
|
jest.mock('../../store/configContext', () => ({
|
|
14
11
|
useConfig: jest.fn()
|
|
@@ -40,7 +37,7 @@ const GLOBAL_SHORTCUTS = [
|
|
|
40
37
|
|
|
41
38
|
beforeEach(() => {
|
|
42
39
|
useConfig.mockReturnValue({})
|
|
43
|
-
useApp.mockReturnValue({ listboxIsActive: false })
|
|
40
|
+
useApp.mockReturnValue({ listboxIsActive: false, keyboardShortcutRegistry: { getKeyboardShortcuts } })
|
|
44
41
|
})
|
|
45
42
|
|
|
46
43
|
afterEach(() => {
|
|
@@ -79,7 +76,7 @@ describe('KeyboardHelp — tabs', () => {
|
|
|
79
76
|
const allShortcuts = [...GLOBAL_SHORTCUTS, ...VIEWPORT_SHORTCUTS, ...LISTBOX_SHORTCUTS]
|
|
80
77
|
|
|
81
78
|
beforeEach(() => {
|
|
82
|
-
useApp.mockReturnValue({ listboxIsActive: true })
|
|
79
|
+
useApp.mockReturnValue({ listboxIsActive: true, keyboardShortcutRegistry: { getKeyboardShortcuts } })
|
|
83
80
|
})
|
|
84
81
|
|
|
85
82
|
it('renders a tab for each group when listboxIsActive is true', () => {
|
|
@@ -18,13 +18,13 @@ const ZOOM_ACTIONS = [
|
|
|
18
18
|
{ id: 'nudgeZoomOut', label: 'Zoom out', announceLabel: 'Zoomed out', method: 'zoomOut' }
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
export const
|
|
21
|
+
export const MoveControls = () => {
|
|
22
22
|
const { id: appId, mapProvider, panDelta, nudgePanDelta, zoomDelta, nudgeZoomDelta } = useConfig()
|
|
23
23
|
const { dispatch, expandedButtons, nudgeStepSize, interfaceType, layoutRefs } = useApp()
|
|
24
24
|
const { isAtMaxZoom, isAtMinZoom } = useMap()
|
|
25
25
|
const { announce } = useService()
|
|
26
26
|
|
|
27
|
-
const isOpen = expandedButtons.has('
|
|
27
|
+
const isOpen = expandedButtons.has('moveControls')
|
|
28
28
|
const firstDirectionButtonId = `${appId}-pan-up`
|
|
29
29
|
|
|
30
30
|
useEffect(() => {
|
|
@@ -41,7 +41,7 @@ export const MoveControl = () => {
|
|
|
41
41
|
// Registry buttons (declared via a plugin's manifest) auto-return focus to the map
|
|
42
42
|
// viewport after a click (see createButtonClickHandler in mapButtons.js) unless
|
|
43
43
|
// marked keepFocus — which is how e.g. draw's own toolbar buttons never leave
|
|
44
|
-
// keyboard shortcuts (like arrow-key vertex nudging) stranded afterward.
|
|
44
|
+
// keyboard shortcuts (like arrow-key vertex nudging) stranded afterward. MoveControls
|
|
45
45
|
// renders its own buttons directly, bypassing that mechanism entirely, so it has to
|
|
46
46
|
// replicate the same return-to-viewport behaviour itself. But unlike a one-off
|
|
47
47
|
// action button, the D-pad is meant to be pressed repeatedly — a keyboard/switch
|
|
@@ -70,7 +70,7 @@ export const MoveControl = () => {
|
|
|
70
70
|
|
|
71
71
|
// Any plugin can claim the D-pad for something other than panning the map (e.g.
|
|
72
72
|
// moving a selected draw vertex) by setting mapProvider.activeMoveTarget — a
|
|
73
|
-
// generic { move(dx, dy, isLargeStep), label? } contract.
|
|
73
|
+
// generic { move(dx, dy, isLargeStep), label? } contract. MoveControls doesn't
|
|
74
74
|
// know or care what's claiming it; it just checks for a claimant before
|
|
75
75
|
// defaulting to its own panBy behaviour. Read fresh on every click rather than
|
|
76
76
|
// subscribed to reactively, since it's a plain mapProvider property, not state.
|
|
@@ -107,7 +107,7 @@ export const MoveControl = () => {
|
|
|
107
107
|
// first tabbing all the way back to the map. Handling arrow keys here — while
|
|
108
108
|
// focus is anywhere within this control, not just on a direction button — covers
|
|
109
109
|
// that directly, without needing draw's (or the app's own) keyboard-shortcut
|
|
110
|
-
// guards to special-case
|
|
110
|
+
// guards to special-case MoveControls' buttons. Only arrow keys are handled;
|
|
111
111
|
// other shortcuts (delete/escape/undo) still require focus on the viewport,
|
|
112
112
|
// which mouse/touch users already get back automatically after any click here.
|
|
113
113
|
const handleContainerKeyDown = (e) => {
|
|
@@ -120,12 +120,12 @@ export const MoveControl = () => {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const containerClassName = [
|
|
123
|
-
'im-c-move-
|
|
124
|
-
!isOpen && 'im-c-move-
|
|
123
|
+
'im-c-move-controls',
|
|
124
|
+
!isOpen && 'im-c-move-controls--collapsed'
|
|
125
125
|
].filter(Boolean).join(' ')
|
|
126
126
|
|
|
127
127
|
const directionsGroup = (
|
|
128
|
-
<div key='directions' role='group' aria-label='Direction controls' className='im-c-move-
|
|
128
|
+
<div key='directions' role='group' aria-label='Direction controls' className='im-c-move-controls__directions'>{/* NOSONAR - div with role="group" is correct for a button group */}
|
|
129
129
|
{DIRECTIONS.map(({ id, verb, dx, dy }) => (
|
|
130
130
|
<MapButton
|
|
131
131
|
key={id}
|
|
@@ -154,7 +154,7 @@ export const MoveControl = () => {
|
|
|
154
154
|
)
|
|
155
155
|
|
|
156
156
|
const zoomGroup = (
|
|
157
|
-
<div key='zoom' role='group' aria-label='Zoom controls' className='im-c-move-
|
|
157
|
+
<div key='zoom' role='group' aria-label='Zoom controls' className='im-c-move-controls__zoom'>{/* NOSONAR - div with role="group" is correct for a button group */}
|
|
158
158
|
{ZOOM_ACTIONS.map(({ id, label, announceLabel, method }) => (
|
|
159
159
|
<MapButton
|
|
160
160
|
key={id}
|
|
@@ -169,7 +169,7 @@ export const MoveControl = () => {
|
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
return (
|
|
172
|
-
<div id={`${appId}-move-
|
|
172
|
+
<div id={`${appId}-move-controls-content`} className={containerClassName} onKeyDown={handleContainerKeyDown} data-map-keyboard-scope>{/* NOSONAR - only catches arrow-key bubbling from the real, already-focusable button descendants below; the div itself needs no role/tabIndex */}
|
|
173
173
|
{directionsGroup}
|
|
174
174
|
{zoomGroup}
|
|
175
175
|
</div>
|
|
@@ -1,62 +1,63 @@
|
|
|
1
1
|
@use '../../../scss/tools/index' as tools;
|
|
2
2
|
|
|
3
3
|
// ===================================================
|
|
4
|
-
// Component:
|
|
4
|
+
// Component: MoveControls
|
|
5
5
|
// ===================================================
|
|
6
6
|
|
|
7
7
|
// 1. Base styles
|
|
8
|
-
.im-c-move-
|
|
8
|
+
.im-c-move-controls {
|
|
9
9
|
display: flex;
|
|
10
10
|
flex-direction: column;
|
|
11
11
|
align-items: center;
|
|
12
12
|
gap: var(--divider-gap);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
.im-c-move-
|
|
15
|
+
.im-c-move-controls.im-c-move-controls--collapsed {
|
|
16
16
|
display: none;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// 2. Elements
|
|
20
|
-
.im-c-move-
|
|
20
|
+
.im-c-move-controls__directions {
|
|
21
21
|
display: grid;
|
|
22
22
|
grid-template-columns: repeat(3, var(--button-size));
|
|
23
23
|
grid-template-rows: repeat(3, var(--button-size));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
.im-c-move-
|
|
26
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-up {
|
|
27
27
|
grid-column: 2;
|
|
28
28
|
grid-row: 1;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
.im-c-move-
|
|
31
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-left {
|
|
32
32
|
grid-column: 1;
|
|
33
33
|
grid-row: 2;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
.im-c-move-
|
|
36
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-right {
|
|
37
37
|
grid-column: 3;
|
|
38
38
|
grid-row: 2;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
.im-c-move-
|
|
41
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-down {
|
|
42
42
|
grid-column: 2;
|
|
43
43
|
grid-row: 3;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.im-c-move-
|
|
46
|
+
.im-c-move-controls__directions .im-c-button-wrapper--nudge-step-toggle {
|
|
47
47
|
grid-column: 2;
|
|
48
48
|
grid-row: 2;
|
|
49
|
+
z-index: 1;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
.im-c-move-
|
|
52
|
+
.im-c-move-controls__directions .im-c-map-button--pan-up svg {
|
|
52
53
|
transform: rotate(180deg);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
.im-c-move-
|
|
56
|
+
.im-c-move-controls__directions .im-c-map-button--pan-left svg {
|
|
56
57
|
transform: rotate(90deg);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
.im-c-move-
|
|
60
|
+
.im-c-move-controls__directions .im-c-map-button--pan-right svg {
|
|
60
61
|
transform: rotate(-90deg);
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -66,35 +67,60 @@
|
|
|
66
67
|
// express a plus-shaped grid where the centre button touches all four neighbours —
|
|
67
68
|
// clip-path is set directly per button instead, clipping only the side(s) where it
|
|
68
69
|
// touches another button and leaving the exposed side(s) showing the shadow as normal.
|
|
69
|
-
.im-c-move-
|
|
70
|
-
|
|
70
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-up .im-c-map-button {
|
|
71
|
+
border-bottom-left-radius: 0;
|
|
72
|
+
border-bottom-right-radius: 0;
|
|
73
|
+
|
|
74
|
+
&::before {
|
|
75
|
+
clip-path: inset(-20px -20px 0 -20px); // touches nudge-step-toggle below
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
.im-c-move-
|
|
74
|
-
|
|
79
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-left .im-c-map-button {
|
|
80
|
+
border-top-right-radius: 0;
|
|
81
|
+
border-bottom-right-radius: 0;
|
|
82
|
+
|
|
83
|
+
&::before {
|
|
84
|
+
clip-path: inset(-20px 0 -20px -20px); // touches nudge-step-toggle to the right
|
|
85
|
+
}
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
.im-c-move-
|
|
78
|
-
|
|
88
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-right .im-c-map-button {
|
|
89
|
+
border-top-left-radius: 0;
|
|
90
|
+
border-bottom-left-radius: 0;
|
|
91
|
+
|
|
92
|
+
&::before {
|
|
93
|
+
clip-path: inset(-20px -20px -20px 0); // touches nudge-step-toggle to the left
|
|
94
|
+
}
|
|
79
95
|
}
|
|
80
96
|
|
|
81
|
-
.im-c-move-
|
|
82
|
-
|
|
97
|
+
.im-c-move-controls__directions .im-c-button-wrapper--pan-down .im-c-map-button {
|
|
98
|
+
border-top-left-radius: 0;
|
|
99
|
+
border-top-right-radius: 0;
|
|
100
|
+
|
|
101
|
+
&::before {
|
|
102
|
+
clip-path: inset(0 -20px -20px -20px); // touches nudge-step-toggle above
|
|
103
|
+
}
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
.im-c-move-
|
|
86
|
-
|
|
106
|
+
.im-c-move-controls__directions .im-c-button-wrapper--nudge-step-toggle .im-c-map-button {
|
|
107
|
+
border-radius: 0;
|
|
108
|
+
outline: 1px solid var(--button-hover-color);
|
|
109
|
+
|
|
110
|
+
&::before {
|
|
111
|
+
clip-path: inset(0); // touches all four neighbours
|
|
112
|
+
}
|
|
87
113
|
}
|
|
88
114
|
|
|
89
115
|
// Same inverted "pressed" treatment already used for actions-slot toggle buttons
|
|
90
116
|
// (e.g. draw's Snap toggle) — reused here so Nudge mode reads consistently active.
|
|
91
|
-
.im-c-move-
|
|
117
|
+
.im-c-move-controls .im-c-map-button[aria-pressed="true"] {
|
|
92
118
|
color: var(--pressed-button-foreground-color);
|
|
93
119
|
border-color: var(--pressed-button-border-color);
|
|
94
120
|
background-color: var(--pressed-button-background-color);
|
|
95
121
|
}
|
|
96
122
|
|
|
97
|
-
// The trigger lives in the right-top slot, outside .im-c-move-
|
|
123
|
+
// The trigger lives in the right-top slot, outside .im-c-move-controls, so this is
|
|
98
124
|
// its own top-level rule. Same colour treatment as above, but keyed off
|
|
99
125
|
// aria-expanded rather than aria-pressed — the trigger is correctly a disclosure
|
|
100
126
|
// button (it reveals the control below), not a toggle button, so it doesn't carry
|
|
@@ -106,26 +132,31 @@
|
|
|
106
132
|
// hovering the expanded trigger button was a coin-flip decided by cascade order —
|
|
107
133
|
// one cssnano mergeRules reshuffle in production and hover started winning,
|
|
108
134
|
// showing the hover tint instead of the pressed colours. The sibling
|
|
109
|
-
// .im-c-move-
|
|
135
|
+
// .im-c-move-controls .im-c-map-button[aria-pressed="true"] rule above never had
|
|
110
136
|
// this problem (it's already (0,3,0), safely ahead of hover); this one needed the
|
|
111
137
|
// same headroom.
|
|
112
|
-
.im-c-map-button.im-c-map-button--move-
|
|
138
|
+
.im-c-map-button.im-c-map-button--move-controls[aria-expanded="true"] {
|
|
113
139
|
color: var(--pressed-button-foreground-color);
|
|
114
140
|
border-color: var(--pressed-button-border-color);
|
|
115
141
|
background-color: var(--pressed-button-background-color);
|
|
116
142
|
}
|
|
117
143
|
|
|
118
|
-
.im-c-move-
|
|
144
|
+
.im-c-move-controls__zoom {
|
|
119
145
|
display: flex;
|
|
120
146
|
flex-direction: row;
|
|
121
147
|
}
|
|
122
148
|
|
|
123
149
|
// A simple two-item linear row, so the standard button-group corner/shadow treatment
|
|
124
150
|
// applies directly (matches .im-o-app__top's first/last-child pattern).
|
|
125
|
-
.im-c-move-
|
|
151
|
+
.im-c-move-controls__zoom .im-c-button-wrapper--nudge-zoom-in .im-c-map-button {
|
|
126
152
|
@include tools.border-focus-corner-override($corners: 'left');
|
|
127
153
|
}
|
|
128
154
|
|
|
129
|
-
.im-c-move-
|
|
155
|
+
.im-c-move-controls__zoom .im-c-button-wrapper--nudge-zoom-out .im-c-map-button {
|
|
130
156
|
@include tools.border-focus-corner-override($corners: 'right');
|
|
131
157
|
}
|
|
158
|
+
|
|
159
|
+
// App dark modifiers
|
|
160
|
+
.im-o-app--dark-app .im-c-move-controls__directions .im-c-button-wrapper--nudge-step-toggle .im-c-map-button {
|
|
161
|
+
outline-offset: -1px;
|
|
162
|
+
}
|