@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
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { DrawPointMode } from './drawPointMode.js'
|
|
2
|
+
import { createUndoStack } from '../../../utils/undoStack.js'
|
|
3
|
+
import PointFeature from '../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/point.js'
|
|
4
|
+
|
|
5
|
+
const CENTER = { lng: 5, lat: 5 }
|
|
6
|
+
|
|
7
|
+
const createMap = () => {
|
|
8
|
+
const canvas = document.createElement('canvas')
|
|
9
|
+
const listeners = {}
|
|
10
|
+
return {
|
|
11
|
+
_undoInProgress: false,
|
|
12
|
+
_undoStack: null,
|
|
13
|
+
_snapInstance: null,
|
|
14
|
+
_drawGeometryValidator: undefined,
|
|
15
|
+
getCanvas: () => canvas,
|
|
16
|
+
getCenter: () => ({ ...CENTER }),
|
|
17
|
+
project: jest.fn(() => ({ x: 50, y: 50 })),
|
|
18
|
+
unproject: jest.fn(() => ({ ...CENTER })),
|
|
19
|
+
fire: jest.fn(function (type, e) { (listeners[type] ?? []).forEach((h) => h(e)) }),
|
|
20
|
+
on: jest.fn((type, h) => { (listeners[type] ??= []).push(h) }),
|
|
21
|
+
off: jest.fn((type, h) => { listeners[type] = (listeners[type] ?? []).filter((x) => x !== h) })
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const contexts = []
|
|
26
|
+
const removeListeners = (ctx) => ctx._listeners?.forEach(([t, e, h]) =>
|
|
27
|
+
t.removeEventListener ? t.removeEventListener(e, h) : t.off(e, h))
|
|
28
|
+
|
|
29
|
+
const createModeContext = () => {
|
|
30
|
+
const map = createMap()
|
|
31
|
+
const features = new Map()
|
|
32
|
+
const store = {
|
|
33
|
+
add: jest.fn((f) => features.set(f.id, f)),
|
|
34
|
+
delete: jest.fn((ids) => ids.forEach((id) => features.delete(id))),
|
|
35
|
+
render: jest.fn(),
|
|
36
|
+
featureChanged: jest.fn(),
|
|
37
|
+
getInitialConfigValue: jest.fn(() => true)
|
|
38
|
+
}
|
|
39
|
+
const api = { changeMode: jest.fn(), add: jest.fn(), delete: jest.fn() }
|
|
40
|
+
const ctx = {
|
|
41
|
+
map,
|
|
42
|
+
_ctx: { store, api, options: { userProperties: true } },
|
|
43
|
+
addFeature: (f) => store.add(f),
|
|
44
|
+
getFeature: (id) => features.get(id),
|
|
45
|
+
deleteFeature: jest.fn((ids) => ids.forEach((id) => features.delete(id))),
|
|
46
|
+
clearSelectedFeatures: jest.fn(),
|
|
47
|
+
updateUIClasses: jest.fn(),
|
|
48
|
+
activateUIButton: jest.fn(),
|
|
49
|
+
setActionableState: jest.fn(),
|
|
50
|
+
changeMode: jest.fn()
|
|
51
|
+
}
|
|
52
|
+
ctx.newFeature = (geojson) => new PointFeature(ctx._ctx, geojson)
|
|
53
|
+
Object.assign(ctx, DrawPointMode)
|
|
54
|
+
contexts.push(ctx)
|
|
55
|
+
return ctx
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const createContainer = () => {
|
|
59
|
+
const container = document.createElement('div')
|
|
60
|
+
container.tabIndex = 0
|
|
61
|
+
const button = document.createElement('button')
|
|
62
|
+
button.id = 'add-point'
|
|
63
|
+
container.appendChild(button)
|
|
64
|
+
document.body.appendChild(container)
|
|
65
|
+
return { container, button }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const createCrossHair = () => ({ show: jest.fn(), hide: jest.fn() })
|
|
69
|
+
|
|
70
|
+
const setup = (options = {}) => {
|
|
71
|
+
const ctx = createModeContext()
|
|
72
|
+
const dom = createContainer()
|
|
73
|
+
ctx.map._undoStack = createUndoStack(() => {})
|
|
74
|
+
const crossHair = createCrossHair()
|
|
75
|
+
const state = ctx.onSetup({
|
|
76
|
+
container: dom.container,
|
|
77
|
+
addVertexButtonId: 'add-point',
|
|
78
|
+
interfaceType: 'mouse',
|
|
79
|
+
crossHair,
|
|
80
|
+
featureId: 'point-1',
|
|
81
|
+
properties: { label: 'a point' },
|
|
82
|
+
...options
|
|
83
|
+
})
|
|
84
|
+
return { ctx, state, crossHair, ...dom }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const clickEvent = (ctx, lng, lat, overrides = {}) => ({
|
|
88
|
+
lngLat: { lng, lat },
|
|
89
|
+
point: { x: lng, y: lat },
|
|
90
|
+
originalEvent: { button: 0, target: ctx.map.getCanvas(), ...overrides }
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const firedWith = (map, type) => map.fire.mock.calls.filter(([t]) => t === type).map(([, e]) => e)
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
contexts.splice(0).forEach(removeListeners)
|
|
97
|
+
document.body.innerHTML = ''
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('DrawPointMode', () => {
|
|
101
|
+
describe('onSetup', () => {
|
|
102
|
+
it('creates an empty in-progress point feature and applies caller properties', () => {
|
|
103
|
+
const { state } = setup({ properties: { label: 'field' } })
|
|
104
|
+
expect(state.point.type).toBe('Point')
|
|
105
|
+
expect(state.point.coordinates).toEqual([])
|
|
106
|
+
expect(state.point.properties).toEqual({ label: 'field' })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('shows the crosshair for touch/keyboard interface types', () => {
|
|
110
|
+
const { crossHair } = setup({ interfaceType: 'touch' })
|
|
111
|
+
expect(crossHair.show).toHaveBeenCalled()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('hides the crosshair for mouse', () => {
|
|
115
|
+
const { crossHair } = setup({ interfaceType: 'mouse' })
|
|
116
|
+
expect(crossHair.hide).toHaveBeenCalled()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('falls back to a DOM vertexMarker element when no crossHair is supplied', () => {
|
|
120
|
+
const ctx = createModeContext()
|
|
121
|
+
const dom = createContainer()
|
|
122
|
+
const marker = document.createElement('div')
|
|
123
|
+
marker.id = 'vertex-marker'
|
|
124
|
+
dom.container.appendChild(marker)
|
|
125
|
+
ctx.map._undoStack = createUndoStack(() => {})
|
|
126
|
+
|
|
127
|
+
const state = ctx.onSetup({
|
|
128
|
+
container: dom.container,
|
|
129
|
+
vertexMarkerId: 'vertex-marker',
|
|
130
|
+
interfaceType: 'touch', // shown on setup
|
|
131
|
+
featureId: 'point-1',
|
|
132
|
+
properties: {}
|
|
133
|
+
})
|
|
134
|
+
expect(marker.style.display).toBe('block')
|
|
135
|
+
|
|
136
|
+
ctx.onPointermove(state, { pointerType: 'mouse' }) // hidden
|
|
137
|
+
expect(marker.style.display).toBe('none')
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('onClick (mouse)', () => {
|
|
142
|
+
it('commits the coordinate, re-ids the feature to the caller featureId, and fires draw.create', () => {
|
|
143
|
+
const { ctx, state } = setup()
|
|
144
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
145
|
+
expect(state.point.coordinates).toEqual([1, 2])
|
|
146
|
+
const created = firedWith(ctx.map, 'draw.create')
|
|
147
|
+
expect(created).toHaveLength(1)
|
|
148
|
+
expect(created[0].features[0].geometry.coordinates).toEqual([1, 2])
|
|
149
|
+
// Re-id happens via the internal draw.create listener registered in onSetup
|
|
150
|
+
expect(ctx._ctx.api.delete).toHaveBeenCalled()
|
|
151
|
+
expect(ctx._ctx.api.add).toHaveBeenCalledWith(
|
|
152
|
+
expect.objectContaining({ id: 'point-1' }),
|
|
153
|
+
{ userProperties: true }
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('calls the adapter-injected resolvePointSymbol hook with the final featureId, after re-id', () => {
|
|
158
|
+
const resolvePointSymbol = jest.fn()
|
|
159
|
+
const { ctx, state } = setup({ resolvePointSymbol, properties: { symbol: 'pin' } })
|
|
160
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
161
|
+
expect(resolvePointSymbol).toHaveBeenCalledWith('point-1', { symbol: 'pin' })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('does not throw when no resolvePointSymbol hook was supplied', () => {
|
|
165
|
+
const { ctx, state } = setup()
|
|
166
|
+
expect(() => ctx.onClick(state, clickEvent(ctx, 1, 2))).not.toThrow()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('ignores non-primary-button clicks', () => {
|
|
170
|
+
const { ctx, state } = setup()
|
|
171
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2, { button: 1 }))
|
|
172
|
+
expect(state.point.coordinates).toEqual([])
|
|
173
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('ignores clicks whose target is not the map canvas', () => {
|
|
177
|
+
const { ctx, state } = setup()
|
|
178
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2, { target: document.createElement('div') }))
|
|
179
|
+
expect(state.point.coordinates).toEqual([])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('vetoes placement via onGeometryChange and never commits', () => {
|
|
183
|
+
const { ctx, state } = setup()
|
|
184
|
+
ctx.map._drawGeometryValidator = () => ({ valid: false, reason: 'not allowed here' })
|
|
185
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
186
|
+
expect(state.point.coordinates).toEqual([])
|
|
187
|
+
const blocked = firedWith(ctx.map, 'draw.placementblocked')
|
|
188
|
+
expect(blocked).toHaveLength(1)
|
|
189
|
+
expect(blocked[0]).toMatchObject({ reason: 'not allowed here', phase: 'place', mode: 'draw_point' })
|
|
190
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('defaults the blocked reason to null when the veto gives none', () => {
|
|
194
|
+
const { ctx, state } = setup()
|
|
195
|
+
ctx.map._drawGeometryValidator = () => false // no {reason} shape
|
|
196
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
197
|
+
const blocked = firedWith(ctx.map, 'draw.placementblocked')
|
|
198
|
+
expect(blocked[0].reason).toBeNull()
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe('onTap', () => {
|
|
203
|
+
it('behaves the same as onClick', () => {
|
|
204
|
+
const { ctx, state } = setup()
|
|
205
|
+
ctx.onTap(state, clickEvent(ctx, 3, 4))
|
|
206
|
+
expect(state.point.coordinates).toEqual([3, 4])
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('crosshair / keyboard placement', () => {
|
|
211
|
+
it('Enter commits at the map centre once keyboard interaction has started', () => {
|
|
212
|
+
const { ctx, state, container } = setup({ interfaceType: 'keyboard' })
|
|
213
|
+
container.focus()
|
|
214
|
+
ctx.onKeydown(state, { key: 'Enter' })
|
|
215
|
+
ctx.onKeyup(state, { key: 'Enter' })
|
|
216
|
+
expect(state.point.coordinates).toEqual([CENTER.lng, CENTER.lat])
|
|
217
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(1)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('does not commit on Enter before onKeydown has marked the session active', () => {
|
|
221
|
+
const { ctx, state, container } = setup({ interfaceType: 'keyboard' })
|
|
222
|
+
container.focus()
|
|
223
|
+
ctx.onKeyup(state, { key: 'Enter' })
|
|
224
|
+
expect(state.point.coordinates).toEqual([])
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('onKeydown ignores keys while the container is not focused', () => {
|
|
228
|
+
const { ctx, state } = setup({ interfaceType: 'mouse' })
|
|
229
|
+
expect(() => ctx.onKeydown(state, { key: 'Enter' })).not.toThrow()
|
|
230
|
+
expect(state.isActive).toBeFalsy()
|
|
231
|
+
expect(state.interfaceType).toBe('mouse') // never switched to keyboard
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('onKeydown Escape is a no-op there (handled window-level in onKeyup instead)', () => {
|
|
235
|
+
const { ctx, state, container } = setup({ interfaceType: 'mouse' })
|
|
236
|
+
container.focus()
|
|
237
|
+
const preventDefault = jest.fn()
|
|
238
|
+
ctx.onKeydown(state, { key: 'Escape', preventDefault })
|
|
239
|
+
expect(preventDefault).toHaveBeenCalled()
|
|
240
|
+
expect(state.interfaceType).toBe('mouse')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('onKeydown ignores non-interface keys', () => {
|
|
244
|
+
const { ctx, state, container } = setup({ interfaceType: 'mouse' })
|
|
245
|
+
container.focus()
|
|
246
|
+
ctx.onKeydown(state, { key: 'a' })
|
|
247
|
+
expect(state.interfaceType).toBe('mouse')
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('onKeyup ignores non-Escape keys while the container is not focused', () => {
|
|
251
|
+
const { ctx, state } = setup({ interfaceType: 'mouse' })
|
|
252
|
+
expect(() => ctx.onKeyup(state, { key: 'Enter' })).not.toThrow()
|
|
253
|
+
expect(state.interfaceType).toBe('mouse')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('onKeyup ignores non-interface keys', () => {
|
|
257
|
+
const { ctx, state, container } = setup({ interfaceType: 'mouse' })
|
|
258
|
+
container.focus()
|
|
259
|
+
ctx.onKeyup(state, { key: 'a' })
|
|
260
|
+
expect(state.interfaceType).toBe('mouse')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('the "Add point" button commits at the map centre', () => {
|
|
264
|
+
const { ctx, state, button } = setup()
|
|
265
|
+
ctx.onVertexButtonClick(state, { target: button })
|
|
266
|
+
expect(state.point.coordinates).toEqual([CENTER.lng, CENTER.lat])
|
|
267
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(1)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('a click on an unrelated element does not commit', () => {
|
|
271
|
+
const { ctx, state } = setup()
|
|
272
|
+
ctx.onVertexButtonClick(state, { target: document.createElement('div') })
|
|
273
|
+
expect(state.point.coordinates).toEqual([])
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
describe('Escape', () => {
|
|
278
|
+
it('fires draw.cancel regardless of interface type', () => {
|
|
279
|
+
const { ctx, state } = setup({ interfaceType: 'mouse' })
|
|
280
|
+
ctx.onKeyup(state, { key: 'Escape' })
|
|
281
|
+
expect(firedWith(ctx.map, 'draw.cancel')).toHaveLength(1)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('does not throw via the neutralised built-in capital onKeyUp', () => {
|
|
285
|
+
const { ctx, state } = setup()
|
|
286
|
+
expect(() => ctx.onKeyUp(state, { key: 'Enter' })).not.toThrow()
|
|
287
|
+
expect(state.point.coordinates).toEqual([])
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
describe('no-op handlers required by createLifecycle\'s bind contract', () => {
|
|
292
|
+
it('onUndo and onPointerup are harmless no-ops — nothing to undo/report before a single-click commit', () => {
|
|
293
|
+
const { ctx, state } = setup()
|
|
294
|
+
expect(() => ctx.onUndo(state)).not.toThrow()
|
|
295
|
+
expect(() => ctx.onPointerup(state)).not.toThrow()
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe('onStop', () => {
|
|
300
|
+
it('deletes an uncommitted point', () => {
|
|
301
|
+
const { ctx, state } = setup()
|
|
302
|
+
ctx.onStop(state)
|
|
303
|
+
expect(ctx.deleteFeature).toHaveBeenCalledWith([state.point.id], { silent: true })
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('keeps a committed point', () => {
|
|
307
|
+
const { ctx, state } = setup()
|
|
308
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
309
|
+
ctx.deleteFeature.mockClear()
|
|
310
|
+
ctx.onStop(state)
|
|
311
|
+
expect(ctx.deleteFeature).not.toHaveBeenCalled()
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
describe('interface type tracking', () => {
|
|
316
|
+
it('touch start shows the crosshair', () => {
|
|
317
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'mouse' })
|
|
318
|
+
crossHair.show.mockClear()
|
|
319
|
+
ctx.onTouchStart(state)
|
|
320
|
+
expect(state.interfaceType).toBe('touch')
|
|
321
|
+
expect(crossHair.show).toHaveBeenCalled()
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('a non-touch pointerdown switches interface type to mouse without forcing the crosshair visible', () => {
|
|
325
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'touch' })
|
|
326
|
+
crossHair.show.mockClear()
|
|
327
|
+
ctx.onPointerdown(state, { pointerType: 'mouse' })
|
|
328
|
+
expect(state.interfaceType).toBe('mouse')
|
|
329
|
+
expect(crossHair.show).not.toHaveBeenCalled()
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('a touch pointerdown leaves the interface type alone', () => {
|
|
333
|
+
const { ctx, state } = setup({ interfaceType: 'touch' })
|
|
334
|
+
ctx.onPointerdown(state, { pointerType: 'touch' })
|
|
335
|
+
expect(state.interfaceType).toBe('touch')
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('a non-touch pointermove hides the crosshair', () => {
|
|
339
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'touch' })
|
|
340
|
+
ctx.onPointermove(state, { pointerType: 'mouse' })
|
|
341
|
+
expect(crossHair.hide).toHaveBeenCalled()
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('a touch pointermove does not hide the crosshair', () => {
|
|
345
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'touch' })
|
|
346
|
+
ctx.onPointermove(state, { pointerType: 'touch' })
|
|
347
|
+
expect(crossHair.hide).not.toHaveBeenCalled()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('touch end shows the crosshair', () => {
|
|
351
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'mouse' })
|
|
352
|
+
crossHair.show.mockClear()
|
|
353
|
+
ctx.onTouchEnd(state)
|
|
354
|
+
expect(state.interfaceType).toBe('touch')
|
|
355
|
+
expect(crossHair.show).toHaveBeenCalled()
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('onInterfaceTypeChange only shows the crosshair for touch/keyboard', () => {
|
|
359
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'touch' })
|
|
360
|
+
crossHair.show.mockClear()
|
|
361
|
+
ctx.onInterfaceTypeChange(state, { interfaceType: 'mouse' })
|
|
362
|
+
expect(state.interfaceType).toBe('mouse')
|
|
363
|
+
expect(crossHair.show).not.toHaveBeenCalled()
|
|
364
|
+
|
|
365
|
+
ctx.onInterfaceTypeChange(state, { interfaceType: 'keyboard' })
|
|
366
|
+
expect(state.interfaceType).toBe('keyboard')
|
|
367
|
+
expect(crossHair.show).toHaveBeenCalled()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('blur outside the container hides the crosshair', () => {
|
|
371
|
+
const { ctx, state, crossHair } = setup({ interfaceType: 'touch' })
|
|
372
|
+
ctx.onBlur(state, { target: document.createElement('div') })
|
|
373
|
+
expect(crossHair.hide).toHaveBeenCalled()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('blur landing on the container itself does not hide the crosshair', () => {
|
|
377
|
+
const { ctx, state, crossHair, container } = setup({ interfaceType: 'touch' })
|
|
378
|
+
crossHair.hide.mockClear()
|
|
379
|
+
ctx.onBlur(state, { target: container })
|
|
380
|
+
expect(crossHair.hide).not.toHaveBeenCalled()
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('snap', () => {
|
|
385
|
+
it('onMouseMove triggers a snap check at the pointer when snapping is enabled', () => {
|
|
386
|
+
const { ctx, state } = setup()
|
|
387
|
+
const snap = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
|
|
388
|
+
ctx.map._snapInstance = snap
|
|
389
|
+
state.getSnapEnabled = () => true
|
|
390
|
+
ctx.onMouseMove(state, { point: { x: 7, y: 8 } })
|
|
391
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point: { x: 7, y: 8 }, lngLat: CENTER, _explicit: true })
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('onMouseMove does nothing while snapping is disabled', () => {
|
|
395
|
+
const { ctx, state } = setup()
|
|
396
|
+
const snap = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
|
|
397
|
+
ctx.map._snapInstance = snap
|
|
398
|
+
state.getSnapEnabled = () => false
|
|
399
|
+
ctx.onMouseMove(state, { point: { x: 7, y: 8 } })
|
|
400
|
+
expect(snap.snapToClosestPoint).not.toHaveBeenCalled()
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
it('a real click commits the snapped coordinate when a snap candidate is active', () => {
|
|
404
|
+
const { ctx, state } = setup()
|
|
405
|
+
ctx.map._snapInstance = { status: true, snapStatus: true, snapCoords: [9, 9], snapToClosestPoint: jest.fn() }
|
|
406
|
+
state.getSnapEnabled = () => true
|
|
407
|
+
ctx.onClick(state, clickEvent(ctx, 1, 2))
|
|
408
|
+
expect(state.point.coordinates).toEqual([9, 9])
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('crosshair placement snaps to a candidate found at the map centre', () => {
|
|
412
|
+
const { ctx, state, button } = setup()
|
|
413
|
+
const snap = { status: true, snapStatus: true, snapCoords: [9, 9], snapToClosestPoint: jest.fn() }
|
|
414
|
+
ctx.map._snapInstance = snap
|
|
415
|
+
state.getSnapEnabled = () => true
|
|
416
|
+
ctx.onVertexButtonClick(state, { target: button })
|
|
417
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalled()
|
|
418
|
+
expect(state.point.coordinates).toEqual([9, 9])
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
// Regression coverage: onMove used to be a no-op, so touch/keyboard users never saw a
|
|
422
|
+
// live snap-target indicator while panning — it only ever checked at the moment of
|
|
423
|
+
// commit, with nothing shown beforehand.
|
|
424
|
+
it('onMove keeps the snap indicator live at the crosshair while panning, touch/keyboard only', () => {
|
|
425
|
+
const { ctx, state } = setup({ interfaceType: 'touch' })
|
|
426
|
+
const snap = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
|
|
427
|
+
ctx.map._snapInstance = snap
|
|
428
|
+
state.getSnapEnabled = () => true
|
|
429
|
+
|
|
430
|
+
ctx.onMove(state)
|
|
431
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalledWith({ point: expect.any(Object), lngLat: CENTER, _explicit: true })
|
|
432
|
+
|
|
433
|
+
snap.snapToClosestPoint.mockClear()
|
|
434
|
+
state.interfaceType = 'mouse'
|
|
435
|
+
ctx.onMove(state)
|
|
436
|
+
expect(snap.snapToClosestPoint).not.toHaveBeenCalled()
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('switching to touch or keyboard shows the snap indicator immediately, not just on the first pan', () => {
|
|
440
|
+
const snap = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
|
|
441
|
+
|
|
442
|
+
const touch = setup({ interfaceType: 'mouse' })
|
|
443
|
+
touch.ctx.map._snapInstance = snap
|
|
444
|
+
touch.state.getSnapEnabled = () => true
|
|
445
|
+
touch.ctx.onTouchStart(touch.state)
|
|
446
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalled()
|
|
447
|
+
|
|
448
|
+
snap.snapToClosestPoint.mockClear()
|
|
449
|
+
const keyboard = setup({ interfaceType: 'mouse' })
|
|
450
|
+
keyboard.ctx.map._snapInstance = snap
|
|
451
|
+
keyboard.state.getSnapEnabled = () => true
|
|
452
|
+
keyboard.container.focus()
|
|
453
|
+
keyboard.ctx.onKeydown(keyboard.state, { key: 'ArrowUp' })
|
|
454
|
+
expect(snap.snapToClosestPoint).toHaveBeenCalled()
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { EditPointMode } from '../../editPointMode.js'
|
|
2
|
+
import { createUndoStack } from '../../../../../utils/undoStack.js'
|
|
3
|
+
import PointFeature from '../../../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/point.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared test harness for the point-edit mode and its handler modules. Builds the real
|
|
7
|
+
* EditPointMode (with the real DirectSelect parent methods it spreads in and a real
|
|
8
|
+
* mapbox-gl-draw Point feature class) over test doubles for the map, store, api and DOM.
|
|
9
|
+
* Mirrors editVertexMode/__helpers__/harness.js. Excluded from coverage.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Deterministic projection: lngLat <-> pixel is a x10 scale.
|
|
13
|
+
const project = (p) => {
|
|
14
|
+
const [lng, lat] = Array.isArray(p) ? p : [p.lng, p.lat]
|
|
15
|
+
return { x: lng * 10, y: lat * 10 }
|
|
16
|
+
}
|
|
17
|
+
const unproject = ({ x, y }) => ({ lng: x / 10, lat: y / 10 })
|
|
18
|
+
|
|
19
|
+
export const POINT = () => ({ type: 'Point', coordinates: [5, 5] })
|
|
20
|
+
export const dragEvt = (lng, lat) => ({ originalEvent: { stopPropagation: jest.fn() }, lngLat: { lng, lat }, point: { x: lng * 10, y: lat * 10 } })
|
|
21
|
+
export const svgTarget = (state) => ({ parentNode: state.touchPointTarget })
|
|
22
|
+
|
|
23
|
+
const contexts = []
|
|
24
|
+
|
|
25
|
+
export const createHarness = (geometry = POINT(), setupOptions = {}) => {
|
|
26
|
+
const features = new Map()
|
|
27
|
+
const store = {
|
|
28
|
+
featureChanged: jest.fn(),
|
|
29
|
+
render: jest.fn(),
|
|
30
|
+
get: (id) => features.get(id)
|
|
31
|
+
}
|
|
32
|
+
const api = {
|
|
33
|
+
add: jest.fn((geojson) => {
|
|
34
|
+
features.set(geojson.id, new PointFeature(_ctx, { ...geojson, id: geojson.id }))
|
|
35
|
+
return [geojson.id]
|
|
36
|
+
}),
|
|
37
|
+
changeMode: jest.fn(),
|
|
38
|
+
delete: jest.fn(),
|
|
39
|
+
trash: jest.fn()
|
|
40
|
+
}
|
|
41
|
+
const _ctx = { store, api, options: {} }
|
|
42
|
+
|
|
43
|
+
const feature = new PointFeature(_ctx, { type: 'Feature', id: 'feat-1', properties: {}, geometry })
|
|
44
|
+
features.set('feat-1', feature)
|
|
45
|
+
|
|
46
|
+
const listeners = {}
|
|
47
|
+
const map = {
|
|
48
|
+
_undoStack: createUndoStack(() => {}),
|
|
49
|
+
_snapInstance: null,
|
|
50
|
+
_lastEditFeatureId: null,
|
|
51
|
+
_drawEditContainer: null,
|
|
52
|
+
_editingFeatureId: null,
|
|
53
|
+
_drawCurrentMapStyle: { mapColorScheme: 'light' },
|
|
54
|
+
dragPan: { enable: jest.fn(), disable: jest.fn(), isEnabled: () => true },
|
|
55
|
+
doubleClickZoom: { enable: jest.fn(), disable: jest.fn() },
|
|
56
|
+
getSource: jest.fn(() => ({ setData: jest.fn() })),
|
|
57
|
+
getLayer: jest.fn(() => null),
|
|
58
|
+
setLayoutProperty: jest.fn(),
|
|
59
|
+
getCenter: () => ({ lng: 5, lat: 5 }),
|
|
60
|
+
project: jest.fn(project),
|
|
61
|
+
unproject: jest.fn(unproject),
|
|
62
|
+
fire: jest.fn(function (type, e) { (listeners[type] ?? []).forEach((h) => h(e)) }),
|
|
63
|
+
on: jest.fn((type, h) => { (listeners[type] ??= []).push(h) }),
|
|
64
|
+
off: jest.fn((type, h) => { listeners[type] = (listeners[type] ?? []).filter((x) => x !== h) })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const container = document.createElement('div')
|
|
68
|
+
container.tabIndex = 0
|
|
69
|
+
document.body.appendChild(container)
|
|
70
|
+
|
|
71
|
+
const ctx = {
|
|
72
|
+
map,
|
|
73
|
+
_ctx,
|
|
74
|
+
getFeature: (id) => features.get(id),
|
|
75
|
+
getSelected: jest.fn(() => [feature]),
|
|
76
|
+
setSelected: jest.fn(),
|
|
77
|
+
setSelectedCoordinates: jest.fn(),
|
|
78
|
+
clearSelectedCoordinates: jest.fn(),
|
|
79
|
+
setActionableState: jest.fn(),
|
|
80
|
+
updateUIClasses: jest.fn(),
|
|
81
|
+
fire: jest.fn()
|
|
82
|
+
}
|
|
83
|
+
Object.assign(ctx, EditPointMode)
|
|
84
|
+
|
|
85
|
+
const options = {
|
|
86
|
+
container,
|
|
87
|
+
featureId: 'feat-1',
|
|
88
|
+
interfaceType: 'mouse',
|
|
89
|
+
undoButtonId: 'undo-vertex',
|
|
90
|
+
isPanEnabled: true,
|
|
91
|
+
...setupOptions
|
|
92
|
+
}
|
|
93
|
+
const state = ctx.onSetup(options)
|
|
94
|
+
contexts.push({ ctx, state })
|
|
95
|
+
return { ctx, state, feature, features, map, store, api, container, options }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
contexts.splice(0).forEach(({ ctx, state }) => { try { ctx.onStop(state) } catch { /* already stopped */ } })
|
|
100
|
+
document.body.innerHTML = ''
|
|
101
|
+
jest.useRealTimers()
|
|
102
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ARROW_KEYS, ARROW_OFFSETS, isInteractiveElementFocused, isUndoShortcut, sharedKeyboardHandlers } from '../../utils/keyboardShortcuts.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keyboard interaction for the point-edit mode: arrow-key/Shift+arrow nudge and Cmd/Ctrl+Z
|
|
5
|
+
* undo. Mixed into EditPointMode. Unlike editVertexMode: the point is always selected (no
|
|
6
|
+
* Space-to-select step, no Alt+arrow navigation — nothing to navigate to with one
|
|
7
|
+
* coordinate), and Escape is deliberately left unbound here — deselecting would strand the
|
|
8
|
+
* user with no way back except the Cancel button.
|
|
9
|
+
*/
|
|
10
|
+
export const keyboardHandlers = {
|
|
11
|
+
...sharedKeyboardHandlers,
|
|
12
|
+
|
|
13
|
+
onKeydown (state, e) {
|
|
14
|
+
if (isInteractiveElementFocused(state)) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
state.interfaceType = 'keyboard'
|
|
19
|
+
this.hideTouchPointIndicator(state)
|
|
20
|
+
|
|
21
|
+
if (e.key === ' ') {
|
|
22
|
+
// Prevent the page from scrolling; there's nothing to select, the point already is.
|
|
23
|
+
e.preventDefault()
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
if (ARROW_KEYS.has(e.key)) {
|
|
27
|
+
this.handleArrowKey(state, e)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (isUndoShortcut(e)) {
|
|
31
|
+
this.handleUndoShortcut(state, e)
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// A plain or Shift+arrow nudges the point (Shift's finer-step-vs-coarse-step distinction
|
|
36
|
+
// lives in pointOperations.js's getOffset). Alt+arrow is left unhandled — nothing to
|
|
37
|
+
// navigate to with a single coordinate.
|
|
38
|
+
handleArrowKey (state, e) {
|
|
39
|
+
e.preventDefault()
|
|
40
|
+
e.stopPropagation()
|
|
41
|
+
this.movePointByKey(state, e)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
movePointByKey (state, e) {
|
|
45
|
+
const currentCoord = this.getPointCoord(state)
|
|
46
|
+
if (!currentCoord) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Save starting position for undo (only on first move of a held-key sequence)
|
|
51
|
+
if (!state._keyboardMoveStartPosition) {
|
|
52
|
+
state._keyboardMoveStartPosition = [...currentCoord]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.movePoint(state, this._keyboardMoveTarget(state, e, currentCoord))
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Resolve the destination coordinate for a keyboard nudge, applying or breaking snap —
|
|
59
|
+
// delegates to the shared resolver (utils/snapMovement.js) also used by MoveControls'
|
|
60
|
+
// nudgePointByDelta, so both snap identically.
|
|
61
|
+
_keyboardMoveTarget (state, e, currentCoord) {
|
|
62
|
+
const [dx, dy] = ARROW_OFFSETS[e.key]
|
|
63
|
+
return this.resolveSnapTarget(state, dx, dy, currentCoord, () => this.getNewCoord(state, e))
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
onKeyup (state, e) {
|
|
67
|
+
if (isInteractiveElementFocused(state)) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
state.interfaceType = 'keyboard'
|
|
72
|
+
if (ARROW_KEYS.has(e.key)) {
|
|
73
|
+
e.stopPropagation()
|
|
74
|
+
|
|
75
|
+
// Push undo for the whole held-key move sequence as one step
|
|
76
|
+
if (state._keyboardMoveStartPosition) {
|
|
77
|
+
this.pushUndo({ type: 'move_point', featureId: state.featureId, vertexIndex: 0, previousPosition: state._keyboardMoveStartPosition })
|
|
78
|
+
state._keyboardMoveStartPosition = null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|