@defra/interactive-map 0.0.39-alpha → 0.0.41-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/control-definition.md +9 -1
- package/docs/api/slots.md +26 -5
- package/docs/api.md +4 -4
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +80 -17
- package/docs/plugins/search.md +1 -1
- package/govuk-prototype-kit.config.json +2 -0
- package/jest.setup.js +5 -0
- package/package.json +9 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/index.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +2 -3
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.js +1 -1
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.js +1 -1
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +1 -3
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +17 -3
- package/plugins/datasets/src/api/setOpacity.test.js +9 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
- package/plugins/datasets/src/datasets.scss +0 -1
- package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
- package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
- package/plugins/datasets/src/index.test.js +36 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -5
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
- package/plugins/datasets/src/manifest.js +0 -38
- package/plugins/datasets/src/manifest.test.js +49 -0
- package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
- package/plugins/datasets/src/reducers/pluginState.js +3 -16
- package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
- package/plugins/datasets/src/registry/dataset.js +15 -4
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -9
- package/plugins/datasets/src/registry/datasetRegistry.test.js +17 -5
- package/plugins/datasets/src/registry/globalDataset.js +0 -3
- package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
- package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
- package/plugins/datasets/src/utils/bbox.test.js +125 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +12 -4
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +92 -12
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +5 -28
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -54
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +11 -4
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
- package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +24 -4
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +47 -3
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +32 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +12 -5
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +26 -3
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
- package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
- package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
- package/plugins/draw/src/api/addFeature.js +16 -1
- package/plugins/draw/src/api/addFeature.test.js +62 -5
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/setStyle.js +43 -0
- package/plugins/draw/src/api/setStyle.test.js +75 -0
- package/plugins/draw/src/events.js +21 -14
- package/plugins/draw/src/events.test.js +15 -1
- package/plugins/draw/src/manifest.js +18 -6
- package/plugins/draw/src/manifest.test.js +32 -1
- package/plugins/draw/src/reducer.test.js +7 -0
- package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
- package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
- package/plugins/draw/src/utils/symbolKeys.js +5 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
- package/plugins/draw/src/validation/liveStroke.test.js +64 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +12 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +88 -1
- package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/utils/featureQueries.js +18 -0
- package/plugins/interact/src/utils/featureQueries.test.js +15 -0
- package/plugins/interact/src/utils/spatial.js +9 -3
- package/plugins/interact/src/utils/spatial.test.js +12 -1
- package/plugins/map-key/dist/css/index.css +62 -0
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
- package/plugins/map-key/dist/esm/index.js +1 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
- package/plugins/map-key/dist/umd/index.js +2 -0
- package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/map-key/package.json +28 -0
- package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
- package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/Key.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
- package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
- package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
- package/plugins/map-key/src/index.js +13 -0
- package/plugins/map-key/src/index.test.js +31 -0
- package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
- package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
- package/plugins/map-key/src/manifest.js +30 -0
- package/plugins/map-key/src/manifest.test.js +63 -0
- package/plugins/map-key/src/mapKey.scss +4 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/map-key/src/registry/index.js +3 -0
- package/plugins/map-key/src/registry/index.test.js +15 -0
- package/plugins/menu/dist/css/index.css +80 -0
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/esm/index.js +1 -0
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/umd/index.js +2 -0
- package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/menu/package.json +28 -0
- package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
- package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
- package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
- package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
- package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
- package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
- package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
- package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
- package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
- package/plugins/menu/src/index.js +13 -0
- package/plugins/menu/src/index.test.js +31 -0
- package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
- package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
- package/plugins/menu/src/initialise/createMenu.js +10 -0
- package/plugins/menu/src/initialise/createMenu.test.js +37 -0
- package/plugins/menu/src/manifest.js +35 -0
- package/plugins/menu/src/manifest.test.js +63 -0
- package/plugins/menu/src/menu.scss +4 -0
- package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
- package/plugins/menu/src/reducers/pluginState.js +35 -0
- package/plugins/menu/src/reducers/pluginState.test.js +57 -0
- package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
- package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/menu/src/registry/index.js +3 -0
- package/plugins/menu/src/registry/index.test.js +15 -0
- package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
- package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +7 -5
- package/plugins/search/src/components/Form/Form.module.scss +9 -20
- package/plugins/search/src/index.js +0 -1
- package/plugins/search/src/index.test.js +2 -4
- package/plugins/search/src/manifest.js +1 -1
- package/plugins/search/src/search.scss +17 -16
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
- package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
- package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
- package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
- package/providers/maplibre/src/utils/hoverCursor.js +30 -14
- package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
- package/providers/maplibre/src/utils/patternImages.js +1 -1
- package/providers/maplibre/src/utils/symbolImages.js +41 -3
- package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
- package/rollup.esm.mjs +11 -1
- package/sonar-project.properties +7 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Attributions/Attributions.module.scss +10 -0
- package/src/App/components/Hints/Hints.jsx +7 -2
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +2 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +3 -6
- package/src/App/components/{MoveControl/MoveControl.jsx → MoveControls/MoveControls.jsx} +10 -10
- package/src/App/components/{MoveControl/MoveControl.module.scss → MoveControls/MoveControls.module.scss} +60 -29
- package/src/App/components/{MoveControl/MoveControl.test.jsx → MoveControls/MoveControls.test.jsx} +48 -48
- package/src/App/components/Panel/Panel.jsx +18 -3
- package/src/App/components/Panel/Panel.module.scss +18 -8
- package/src/App/components/Panel/Panel.test.jsx +34 -0
- package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
- package/src/App/components/Viewport/Viewport.jsx +9 -1
- package/src/App/components/Viewport/Viewport.test.jsx +11 -0
- package/src/App/hooks/useFeatureItems.js +16 -7
- package/src/App/hooks/useFeatureItems.test.js +57 -0
- package/src/App/hooks/useLayoutMeasurements.js +95 -84
- package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
- package/src/App/initialiseApp.js +11 -8
- package/src/App/initialiseApp.test.js +15 -3
- package/src/App/layout/Layout.jsx +12 -11
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/keyboardShortcutRegistry.js +25 -13
- package/src/App/registry/keyboardShortcutRegistry.test.js +49 -32
- package/src/App/registry/pluginRegistry.js +2 -3
- package/src/App/registry/pluginRegistry.test.js +16 -3
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/mapButtons.js +28 -12
- package/src/App/renderer/mapButtons.test.js +17 -4
- package/src/App/renderer/mapControls.js +3 -1
- package/src/App/renderer/mapControls.test.js +16 -0
- package/src/App/renderer/mapPanels.js +44 -9
- package/src/App/renderer/mapPanels.test.js +51 -2
- package/src/App/renderer/orderItems.js +28 -0
- package/src/App/renderer/orderItems.test.js +50 -0
- package/src/App/renderer/slotAggregator.js +2 -18
- package/src/App/renderer/slotHelpers.js +3 -1
- package/src/App/renderer/slotHelpers.test.js +5 -0
- package/src/App/renderer/slots.js +3 -0
- package/src/App/store/AppProvider.jsx +4 -2
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/InteractiveMap/InteractiveMap.js +8 -2
- package/src/InteractiveMap/InteractiveMap.test.js +20 -3
- package/src/config/appConfig.js +17 -15
- package/src/config/appConfig.test.js +22 -22
- package/src/config/defaults.js +3 -1
- package/src/config/events.js +2 -0
- package/src/config/mergeConfig.js +7 -0
- package/src/config/mergeConfig.test.js +20 -0
- package/src/scss/main.scss +1 -1
- package/src/scss/settings/_colors.scss +1 -0
- package/src/scss/settings/_dimensions.scss +2 -3
- package/src/scss/settings/_transition.scss +1 -1
- package/src/services/eventBus.js +44 -0
- package/src/services/eventBus.test.js +83 -0
- package/src/services/symbolRegistry.js +1 -1
- package/src/test-utils.js +5 -0
- package/src/types.js +25 -7
- package/webpack.umd.mjs +2 -0
- package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
- package/plugins/datasets/src/components/Key/Key.jsx +0 -50
- package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
- package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
- package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
- package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
- package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
- package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
- /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
|
@@ -2,9 +2,13 @@ import Style from 'ol/style/Style.js'
|
|
|
2
2
|
import Fill from 'ol/style/Fill.js'
|
|
3
3
|
import Stroke from 'ol/style/Stroke.js'
|
|
4
4
|
import CircleStyle from 'ol/style/Circle.js'
|
|
5
|
+
import Icon from 'ol/style/Icon.js'
|
|
5
6
|
import MultiPoint from 'ol/geom/MultiPoint.js'
|
|
6
7
|
import { SIZES } from '../defaults.js'
|
|
7
8
|
import { getPlacedSketchCoords } from '../utils/sketchHelpers.js'
|
|
9
|
+
import { getCachedSymbolImage } from '../../../../../../providers/beta/openlayers/src/utils/symbolImages.js'
|
|
10
|
+
import { symbolRegistry } from '../../../../../../src/services/symbolRegistry.js'
|
|
11
|
+
import { getSymbolAnchor } from '../../../../../../src/utils/symbolUtils.js'
|
|
8
12
|
|
|
9
13
|
const HALO_RADIUS_OFFSET = 3
|
|
10
14
|
|
|
@@ -74,12 +78,74 @@ const createSketchLineStyles = (colors) => ({
|
|
|
74
78
|
})
|
|
75
79
|
})
|
|
76
80
|
|
|
81
|
+
// Placeholder for a committed point feature that has no symbol config, or whose symbol
|
|
82
|
+
// image hasn't rasterised/cached yet (point/pointSymbolImages.js resolves it asynchronously
|
|
83
|
+
// after the feature is added to the store — this is what renders in the gap, and what
|
|
84
|
+
// permanently renders for a point with no symbol properties at all). Stroke/Fill styles
|
|
85
|
+
// have no effect on a Point geometry, so without this branch a committed point renders
|
|
86
|
+
// invisibly.
|
|
87
|
+
const createPointPlaceholderStyle = (colors) => new Style({
|
|
88
|
+
image: new CircleStyle({
|
|
89
|
+
radius: SIZES.vertexRadius + HALO_RADIUS_OFFSET,
|
|
90
|
+
fill: new Fill({ color: colors.shapeFill }),
|
|
91
|
+
stroke: new Stroke({ color: colors.shapeStroke, width: 2 })
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ol/style/Icon defaults (anchorOrigin: 'top-left', anchorXUnits/anchorYUnits: 'fraction')
|
|
96
|
+
// already match symbolAnchor's own [x,y]-fraction-from-top-left convention, so unlike the
|
|
97
|
+
// MapLibre adapter (whose icon-anchor only has 9 discrete positions) no offset compensation
|
|
98
|
+
// is needed here — the raw anchor is usable as-is. Icon style instances are cached per
|
|
99
|
+
// imageId+anchor+pixelRatio so a style function running every render frame doesn't rebuild
|
|
100
|
+
// one each time.
|
|
101
|
+
const createPointStyles = (colors) => {
|
|
102
|
+
const pointStyle = createPointPlaceholderStyle(colors)
|
|
103
|
+
const iconStyleCache = new Map()
|
|
104
|
+
|
|
105
|
+
// imageIdProp lets edit_point's own per-feature style override (point/editPointMode.js)
|
|
106
|
+
// ask for the precomputed "selected" (black ring) variant instead of the normal one — the
|
|
107
|
+
// same variant the interact plugin's own highlight already uses, via the same
|
|
108
|
+
// symbolSelectedImageId point/pointSymbolImages.js resolves for every point.
|
|
109
|
+
const getPointIconStyle = (properties, imageIdProp = 'symbolImageId') => {
|
|
110
|
+
const imageId = properties[imageIdProp]
|
|
111
|
+
const canvas = imageId && getCachedSymbolImage(imageId)
|
|
112
|
+
if (!canvas) {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
const symbolDef = symbolRegistry.getSymbolDef(properties)
|
|
116
|
+
const [anchorX, anchorY] = getSymbolAnchor(properties, symbolDef)
|
|
117
|
+
// The cached canvas was rasterised at viewBox × pixelRatio device pixels for crispness
|
|
118
|
+
// (point/pointSymbolImages.js) — unlike MapLibre's map.addImage(id, data, {pixelRatio}),
|
|
119
|
+
// which uses the registered pixelRatio to auto-derive the displayed CSS size, ol/style/
|
|
120
|
+
// Icon draws its source at native pixel size by default, so `scale` has to cancel that
|
|
121
|
+
// back out here or the icon renders pixelRatio× too big.
|
|
122
|
+
const pixelRatio = properties.symbolPixelRatio || 1
|
|
123
|
+
const cacheKey = `${imageId}|${anchorX}|${anchorY}|${pixelRatio}`
|
|
124
|
+
let iconStyle = iconStyleCache.get(cacheKey)
|
|
125
|
+
if (!iconStyle) {
|
|
126
|
+
iconStyle = new Style({ image: new Icon({ img: canvas, anchor: [anchorX, anchorY], scale: 1 / pixelRatio }) })
|
|
127
|
+
iconStyleCache.set(cacheKey, iconStyle)
|
|
128
|
+
}
|
|
129
|
+
return iconStyle
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
pointStyleFor: (feature) => getPointIconStyle(feature.getProperties()) ?? pointStyle,
|
|
134
|
+
// Falls back through selected → normal → placeholder, so edit_point still shows
|
|
135
|
+
// something sensible before the selected variant has resolved.
|
|
136
|
+
selectedPointStyleFor: (feature) => {
|
|
137
|
+
const properties = feature.getProperties()
|
|
138
|
+
return getPointIconStyle(properties, 'symbolSelectedImageId') ?? getPointIconStyle(properties) ?? pointStyle
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
77
143
|
/**
|
|
78
144
|
* Create all draw-ol style instances for the given resolved color set.
|
|
79
145
|
*
|
|
80
146
|
* @param {object} colors - Output of resolveColors()
|
|
81
147
|
* @returns {{ vertexStyle, selectedVertexStyle, midpointStyle, selectedMidpointStyle,
|
|
82
|
-
* editFeatureStyle, createSketchStyle, createFeatureStyle }}
|
|
148
|
+
* editFeatureStyle, selectedPointStyleFor, createSketchStyle, createFeatureStyle }}
|
|
83
149
|
*/
|
|
84
150
|
export const createStyles = (colors) => {
|
|
85
151
|
const { vertexImage, vertexStyle, selectedVertexStyle } = createVertexStyles(colors)
|
|
@@ -130,7 +196,12 @@ export const createStyles = (colors) => {
|
|
|
130
196
|
return type === geometryType ? [lineStyle, sketchVertexStyle] : [lineStyle]
|
|
131
197
|
}
|
|
132
198
|
|
|
199
|
+
const { pointStyleFor, selectedPointStyleFor } = createPointStyles(colors)
|
|
200
|
+
|
|
133
201
|
const createFeatureStyle = () => (feature) => {
|
|
202
|
+
if (feature.getGeometry().getType() === 'Point') {
|
|
203
|
+
return [pointStyleFor(feature)]
|
|
204
|
+
}
|
|
134
205
|
const p = feature.getProperties()
|
|
135
206
|
const id = colors.mapStyleId
|
|
136
207
|
const stroke = (id && p[`stroke${capitalize(id)}`]) || p.stroke || colors.shapeStroke
|
|
@@ -149,6 +220,7 @@ export const createStyles = (colors) => {
|
|
|
149
220
|
selectedMidpointStyle,
|
|
150
221
|
editFeatureStyle,
|
|
151
222
|
editFeatureStyleInvalid,
|
|
223
|
+
selectedPointStyleFor,
|
|
152
224
|
createSketchStyle,
|
|
153
225
|
createFeatureStyle
|
|
154
226
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Point from 'ol/geom/Point.js'
|
|
2
2
|
import LineString from 'ol/geom/LineString.js'
|
|
3
3
|
import Feature from 'ol/Feature.js'
|
|
4
|
+
import Icon from 'ol/style/Icon.js'
|
|
4
5
|
import { createStyles } from './styles.js'
|
|
5
6
|
import { SIZES } from '../defaults.js'
|
|
6
|
-
import { polygonFeature, lineFeature } from '../__helpers__/harness.js'
|
|
7
|
+
import { polygonFeature, lineFeature, pointFeature } from '../__helpers__/harness.js'
|
|
8
|
+
import { getOrCreateSymbolImage, clearSymbolImageCache } from '../../../../../../providers/beta/openlayers/src/utils/symbolImages.js'
|
|
7
9
|
|
|
8
10
|
const colors = {
|
|
9
11
|
editStroke: '#e5',
|
|
@@ -117,7 +119,11 @@ describe('sketch styles while drawing', () => {
|
|
|
117
119
|
})
|
|
118
120
|
|
|
119
121
|
describe('createFeatureStyle (inactive shapes)', () => {
|
|
120
|
-
const styleFor = (properties) =>
|
|
122
|
+
const styleFor = (properties) => {
|
|
123
|
+
const feature = polygonFeature([[0, 0], [1, 0], [1, 1], [0, 0]])
|
|
124
|
+
feature.setProperties(properties)
|
|
125
|
+
return styles.createFeatureStyle()(feature)[0]
|
|
126
|
+
}
|
|
121
127
|
|
|
122
128
|
test('falls back to the configured shape colours', () => {
|
|
123
129
|
const style = styleFor({})
|
|
@@ -137,7 +143,120 @@ describe('createFeatureStyle (inactive shapes)', () => {
|
|
|
137
143
|
|
|
138
144
|
test('without a map style id only the generic properties apply', () => {
|
|
139
145
|
const plain = createStyles({ ...colors, mapStyleId: null })
|
|
140
|
-
const
|
|
146
|
+
const feature = polygonFeature([[0, 0], [1, 0], [1, 1], [0, 0]])
|
|
147
|
+
feature.setProperties({ strokeRoad: '#r1' })
|
|
148
|
+
const style = plain.createFeatureStyle()(feature)[0]
|
|
141
149
|
expect(style.getStroke().getColor()).toBe(colors.shapeStroke)
|
|
142
150
|
})
|
|
151
|
+
|
|
152
|
+
test('a committed Point feature gets the placeholder circle style, not Stroke/Fill', () => {
|
|
153
|
+
const style = styles.createFeatureStyle()(pointFeature([5, 5]))[0]
|
|
154
|
+
expect(style.getImage()).toBeTruthy()
|
|
155
|
+
expect(style.getStroke()).toBeFalsy()
|
|
156
|
+
expect(style.getFill()).toBeFalsy()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('a Point feature with a resolved+cached symbolImageId renders a real Icon, not the placeholder', () => {
|
|
160
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
161
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
162
|
+
return this._ctx
|
|
163
|
+
})
|
|
164
|
+
clearSymbolImageCache()
|
|
165
|
+
const canvas = getOrCreateSymbolImage('symbol-pin-1x', { width: 10, height: 10 })
|
|
166
|
+
|
|
167
|
+
const feature = pointFeature([5, 5])
|
|
168
|
+
feature.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-1x' })
|
|
169
|
+
|
|
170
|
+
const style = styles.createFeatureStyle()(feature)[0]
|
|
171
|
+
expect(style.getImage()).toBeInstanceOf(Icon)
|
|
172
|
+
expect(style.getImage().getImage(1)).toBe(canvas)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('reuses the same Icon Style instance across renders for the same imageId/anchor/pixelRatio', () => {
|
|
176
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
177
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
178
|
+
return this._ctx
|
|
179
|
+
})
|
|
180
|
+
clearSymbolImageCache()
|
|
181
|
+
getOrCreateSymbolImage('symbol-pin-cache', { width: 10, height: 10 })
|
|
182
|
+
|
|
183
|
+
const feature = pointFeature([5, 5])
|
|
184
|
+
feature.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-cache' })
|
|
185
|
+
|
|
186
|
+
const styleFn = styles.createFeatureStyle()
|
|
187
|
+
const first = styleFn(feature)[0]
|
|
188
|
+
const second = styleFn(feature)[0] // a style function runs every render frame
|
|
189
|
+
expect(second).toBe(first)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('scales the Icon down by the rasterisation pixelRatio, so a high-DPI raster still displays at its intended CSS size', () => {
|
|
193
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
194
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
195
|
+
return this._ctx
|
|
196
|
+
})
|
|
197
|
+
clearSymbolImageCache()
|
|
198
|
+
getOrCreateSymbolImage('symbol-pin-4x', { width: 176, height: 176 }) // 44 viewBox × pixelRatio 4
|
|
199
|
+
|
|
200
|
+
const feature = pointFeature([5, 5])
|
|
201
|
+
feature.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-4x', symbolPixelRatio: 4 })
|
|
202
|
+
|
|
203
|
+
const style = styles.createFeatureStyle()(feature)[0]
|
|
204
|
+
expect(style.getImage().getScale()).toBe(0.25)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('defaults to scale 1 when symbolPixelRatio is missing', () => {
|
|
208
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
209
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
210
|
+
return this._ctx
|
|
211
|
+
})
|
|
212
|
+
clearSymbolImageCache()
|
|
213
|
+
getOrCreateSymbolImage('symbol-pin-no-ratio', { width: 44, height: 44 })
|
|
214
|
+
|
|
215
|
+
const feature = pointFeature([5, 5])
|
|
216
|
+
feature.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-no-ratio' })
|
|
217
|
+
|
|
218
|
+
const style = styles.createFeatureStyle()(feature)[0]
|
|
219
|
+
expect(style.getImage().getScale()).toBe(1)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// edit_point's per-feature style override (point/editPointMode.js) uses this to keep
|
|
223
|
+
// showing the same "selected" (black ring) look the interact plugin's own highlight
|
|
224
|
+
// already uses for the whole edit session — see selectedPointStyleFor's own comment.
|
|
225
|
+
test('selectedPointStyleFor renders the selected variant, falling back to normal then the placeholder', () => {
|
|
226
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
227
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
228
|
+
return this._ctx
|
|
229
|
+
})
|
|
230
|
+
clearSymbolImageCache()
|
|
231
|
+
const selectedCanvas = getOrCreateSymbolImage('symbol-pin-selected', { width: 10, height: 10 })
|
|
232
|
+
|
|
233
|
+
const selected = pointFeature([5, 5])
|
|
234
|
+
selected.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-normal', symbolSelectedImageId: 'symbol-pin-selected' })
|
|
235
|
+
const selectedStyle = styles.selectedPointStyleFor(selected)
|
|
236
|
+
expect(selectedStyle.getImage()).toBeInstanceOf(Icon)
|
|
237
|
+
expect(selectedStyle.getImage().getImage(1)).toBe(selectedCanvas)
|
|
238
|
+
|
|
239
|
+
// No selected variant resolved yet → falls back to the normal icon
|
|
240
|
+
const normalCanvas = getOrCreateSymbolImage('symbol-pin-normal-only', { width: 10, height: 10 })
|
|
241
|
+
const normalOnly = pointFeature([5, 5])
|
|
242
|
+
normalOnly.setProperties({ symbol: 'pin', symbolImageId: 'symbol-pin-normal-only' })
|
|
243
|
+
const fallbackStyle = styles.selectedPointStyleFor(normalOnly)
|
|
244
|
+
expect(fallbackStyle.getImage().getImage(1)).toBe(normalCanvas)
|
|
245
|
+
|
|
246
|
+
// Neither resolved → the placeholder circle
|
|
247
|
+
const placeholderStyle = styles.selectedPointStyleFor(pointFeature([5, 5]))
|
|
248
|
+
expect(placeholderStyle.getStroke()).toBeFalsy()
|
|
249
|
+
expect(placeholderStyle.getImage()).not.toBeInstanceOf(Icon)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('a Point feature whose symbolImageId has not been rasterised/cached yet falls back to the placeholder', () => {
|
|
253
|
+
clearSymbolImageCache()
|
|
254
|
+
const feature = pointFeature([5, 5])
|
|
255
|
+
feature.setProperties({ symbol: 'pin', symbolImageId: 'symbol-not-cached' })
|
|
256
|
+
|
|
257
|
+
const style = styles.createFeatureStyle()(feature)[0]
|
|
258
|
+
expect(style.getImage()).not.toBeInstanceOf(Icon)
|
|
259
|
+
expect(style.getStroke()).toBeFalsy()
|
|
260
|
+
expect(style.getFill()).toBeFalsy()
|
|
261
|
+
})
|
|
143
262
|
})
|
|
@@ -172,6 +172,7 @@ const buildDrawInput = ({ drawInteraction, options, geometryType, getSketch, upd
|
|
|
172
172
|
addVertexButtonId: options.addVertexButtonId,
|
|
173
173
|
mapProvider: options.mapProvider,
|
|
174
174
|
snap: options.snap,
|
|
175
|
+
crossHair: options.crossHair,
|
|
175
176
|
onUndo: () => { drawInteraction.removeLastPoint(); updateVertexCount(); emitUndoValidation() },
|
|
176
177
|
canFinish: () => canFinish(geometryType, getSketch()),
|
|
177
178
|
canPlace: canPlaceVertex
|
|
@@ -4,6 +4,8 @@ import { createFeatureStore } from '../core/featureStore.js'
|
|
|
4
4
|
import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
|
|
5
5
|
import { STYLES_CHANGED_EVENT } from '../core/internalEvents.js'
|
|
6
6
|
import { createFakeMap, createFakeManager, polygonFeature, lineFeature } from '../__helpers__/harness.js'
|
|
7
|
+
import Feature from 'ol/Feature.js'
|
|
8
|
+
import Polygon from 'ol/geom/Polygon.js'
|
|
7
9
|
|
|
8
10
|
jest.mock('./drawInput.js', () => ({
|
|
9
11
|
createDrawInput: jest.fn(() => ({
|
|
@@ -174,6 +176,16 @@ describe('drawing lifecycle', () => {
|
|
|
174
176
|
expect(manager.styles.createSketchStyle).not.toHaveBeenCalledWith('Polygon', true)
|
|
175
177
|
})
|
|
176
178
|
|
|
179
|
+
test('a Polygon sketch with no ring yet (freshly started, before the first coordinate) never throws or goes dashed', () => {
|
|
180
|
+
const { manager, interaction } = setup('Polygon')
|
|
181
|
+
const sketch = new Feature(new Polygon([]))
|
|
182
|
+
sketch.setId('shape-1')
|
|
183
|
+
interaction.dispatchEvent({ type: 'drawstart', feature: sketch })
|
|
184
|
+
// coords[0] is undefined with zero rings — displayedSketch's `?? []` fallback covers it.
|
|
185
|
+
expect(() => sketch.getGeometry().setCoordinates([])).not.toThrow()
|
|
186
|
+
expect(manager.styles.createSketchStyle).not.toHaveBeenCalledWith('Polygon', true)
|
|
187
|
+
})
|
|
188
|
+
|
|
177
189
|
test('2 placed vertices + cursor never go dashed (below the 4-point threshold)', () => {
|
|
178
190
|
const { manager, interaction } = setup('Polygon')
|
|
179
191
|
const sketch = polygonFeature([[0, 0], [5, 5], [0, 0]])
|
|
@@ -187,6 +199,20 @@ describe('drawing lifecycle', () => {
|
|
|
187
199
|
expect(typeof inputOptions.canPlace).toBe('function')
|
|
188
200
|
})
|
|
189
201
|
|
|
202
|
+
test('drawInput receives the crossHair passed to the mode, so it can sync visibility itself', () => {
|
|
203
|
+
const map = createFakeMap()
|
|
204
|
+
const manager = createFakeManager()
|
|
205
|
+
manager.store = createFeatureStore()
|
|
206
|
+
const crossHair = { fixAtCenter: jest.fn(), hide: jest.fn() }
|
|
207
|
+
createDrawMode({
|
|
208
|
+
map,
|
|
209
|
+
manager,
|
|
210
|
+
options: { geometryType: 'Polygon', featureId: 'shape-1', properties: {}, container: null, snap: null, crossHair }
|
|
211
|
+
})
|
|
212
|
+
const inputOptions = createDrawInput.mock.calls.at(-1)[0].options
|
|
213
|
+
expect(inputOptions.crossHair).toBe(crossHair)
|
|
214
|
+
})
|
|
215
|
+
|
|
190
216
|
test('a placement-vetoing path disables Add point; a legal one re-enables it', () => {
|
|
191
217
|
const { emitted, interaction } = setup('Polygon')
|
|
192
218
|
const sketch = polygonFeature([[0, 0], [5, 5], [0, 0]])
|
|
@@ -249,7 +275,7 @@ describe('drawing lifecycle', () => {
|
|
|
249
275
|
expect(changed).toHaveBeenCalled()
|
|
250
276
|
})
|
|
251
277
|
|
|
252
|
-
test('setInterfaceType forwards to the draw input (e.g. DrawInit syncing
|
|
278
|
+
test('setInterfaceType forwards to the draw input (e.g. DrawInit syncing MoveControls-driven interface changes mid-session)', () => {
|
|
253
279
|
const { mode, input } = setup('Polygon')
|
|
254
280
|
mode.setInterfaceType('touch')
|
|
255
281
|
expect(input.setInterfaceType).toHaveBeenCalledWith('touch')
|
|
@@ -2,7 +2,23 @@ import { createVertexPlacement } from './vertexPlacement.js'
|
|
|
2
2
|
|
|
3
3
|
const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'])
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Mirrors DrawInit.jsx's fixAtCenter()/hide() gate, but reasserted on every interface-type transition this module detects — matching MapLibre's own onSetup/onInterfaceTypeChange redundancy.
|
|
6
|
+
// Exported alongside wireInputEvents for the same reason — point/drawPointMode.js reuses it directly.
|
|
7
|
+
export const applyCrossHairVisibility = (crossHair, type) => {
|
|
8
|
+
if (!crossHair) { return }
|
|
9
|
+
if (['touch', 'keyboard'].includes(type)) {
|
|
10
|
+
crossHair.fixAtCenter()
|
|
11
|
+
} else {
|
|
12
|
+
crossHair.hide()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Exported so point/drawPointMode.js can reuse this DOM-wiring layer directly — it's
|
|
17
|
+
// already generic (crosshair/interface-type tracking, add-vertex-button/Enter/undo
|
|
18
|
+
// listeners), parametrised entirely by the callbacks passed in, with no ring-shaped
|
|
19
|
+
// assumptions baked in. draw_point supplies point-shaped versions of those callbacks
|
|
20
|
+
// (no rubber band, no undo) rather than this file growing a second copy of the wiring.
|
|
21
|
+
export const wireInputEvents = ({
|
|
6
22
|
container, addVertexButtonId, olView, onUndo,
|
|
7
23
|
getInterfaceType, setInterfaceType, clearLastCoord,
|
|
8
24
|
updateRubberbanding, placeVertex
|
|
@@ -83,7 +99,7 @@ const wireInputEvents = ({
|
|
|
83
99
|
* @returns {{ getInterfaceType: () => string, destroy: () => void }}
|
|
84
100
|
*/
|
|
85
101
|
export const createDrawInput = ({ drawInteraction, options }) => {
|
|
86
|
-
const { container, addVertexButtonId, mapProvider, snap, onUndo, canFinish, canPlace } = options
|
|
102
|
+
const { container, addVertexButtonId, mapProvider, snap, onUndo, canFinish, canPlace, crossHair } = options
|
|
87
103
|
let interfaceType = options.interfaceType ?? 'mouse'
|
|
88
104
|
const getInterfaceType = () => interfaceType
|
|
89
105
|
|
|
@@ -105,12 +121,15 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
105
121
|
olView,
|
|
106
122
|
onUndo,
|
|
107
123
|
getInterfaceType,
|
|
108
|
-
setInterfaceType: (t) => { interfaceType = t },
|
|
124
|
+
setInterfaceType: (t) => { interfaceType = t; applyCrossHairVisibility(crossHair, t) },
|
|
109
125
|
clearLastCoord: placement.clearLastCoord,
|
|
110
126
|
updateRubberbanding: placement.updateRubberbanding,
|
|
111
127
|
placeVertex: placement.placeVertex
|
|
112
128
|
})
|
|
113
129
|
|
|
130
|
+
// Sync on creation too (mirrors MapLibre's own onSetup), so the crosshair reflects the mode's starting interface type immediately.
|
|
131
|
+
applyCrossHairVisibility(crossHair, interfaceType)
|
|
132
|
+
|
|
114
133
|
// change:center fires once when a keyboard pan animation starts; postrender tracks each frame.
|
|
115
134
|
const onMapRender = () => {
|
|
116
135
|
if (interfaceType !== 'mouse' && olView?.getAnimating()) {
|
|
@@ -122,11 +141,12 @@ export const createDrawInput = ({ drawInteraction, options }) => {
|
|
|
122
141
|
return {
|
|
123
142
|
getInterfaceType,
|
|
124
143
|
// Called when the global interface type changes without any pointer/touch/key
|
|
125
|
-
// event landing on the map container (e.g. panning via
|
|
144
|
+
// event landing on the map container (e.g. panning via MoveControls after
|
|
126
145
|
// switching to touch) — refresh the rubber band immediately rather than
|
|
127
146
|
// waiting for the next incidental change:center/postrender event.
|
|
128
147
|
setInterfaceType (type) {
|
|
129
148
|
interfaceType = type
|
|
149
|
+
applyCrossHairVisibility(crossHair, type)
|
|
130
150
|
if (type !== 'mouse') {
|
|
131
151
|
placement.updateRubberbanding()
|
|
132
152
|
}
|
|
@@ -10,7 +10,7 @@ jest.mock('./vertexPlacement.js', () => ({
|
|
|
10
10
|
}))
|
|
11
11
|
}))
|
|
12
12
|
|
|
13
|
-
const setup = (interfaceType = 'mouse') => {
|
|
13
|
+
const setup = (interfaceType = 'mouse', crossHair) => {
|
|
14
14
|
const map = createFakeMap()
|
|
15
15
|
const container = createContainer()
|
|
16
16
|
const button = document.createElement('button')
|
|
@@ -19,13 +19,15 @@ const setup = (interfaceType = 'mouse') => {
|
|
|
19
19
|
const onUndo = jest.fn()
|
|
20
20
|
const input = createDrawInput({
|
|
21
21
|
drawInteraction: { getMap: () => map },
|
|
22
|
-
options: { container, addVertexButtonId: 'add-pt', interfaceType, mapProvider: {}, snap: null, onUndo }
|
|
22
|
+
options: { container, addVertexButtonId: 'add-pt', interfaceType, mapProvider: {}, snap: null, onUndo, crossHair }
|
|
23
23
|
})
|
|
24
24
|
const placement = createVertexPlacement.mock.results.at(-1).value
|
|
25
25
|
liveInputs.push(input)
|
|
26
26
|
return { map, view: map.getView(), container, button, input, placement, onUndo }
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const fakeCrossHair = () => ({ fixAtCenter: jest.fn(), hide: jest.fn() })
|
|
30
|
+
|
|
29
31
|
const liveInputs = []
|
|
30
32
|
afterEach(() => {
|
|
31
33
|
liveInputs.splice(0).forEach((i) => i.destroy())
|
|
@@ -109,7 +111,7 @@ test('pointer moves and map pans update the rubber band except for the mouse int
|
|
|
109
111
|
expect(mouse.placement.updateRubberbanding).not.toHaveBeenCalled()
|
|
110
112
|
})
|
|
111
113
|
|
|
112
|
-
test('setInterfaceType updates the interface and refreshes the rubber band immediately for non-mouse types, e.g. switching to touch and panning via
|
|
114
|
+
test('setInterfaceType updates the interface and refreshes the rubber band immediately for non-mouse types, e.g. switching to touch and panning via MoveControls mid-session', () => {
|
|
113
115
|
const { input, placement } = setup('mouse')
|
|
114
116
|
input.setInterfaceType('touch')
|
|
115
117
|
expect(input.getInterfaceType()).toBe('touch')
|
|
@@ -130,6 +132,48 @@ test('keyboard pan animations keep the rubber band anchored via postrender', ()
|
|
|
130
132
|
expect(placement.updateRubberbanding).toHaveBeenCalledTimes(1)
|
|
131
133
|
})
|
|
132
134
|
|
|
135
|
+
test('syncs the crosshair on creation to match the starting interface type', () => {
|
|
136
|
+
const touchCrossHair = fakeCrossHair()
|
|
137
|
+
setup('touch', touchCrossHair)
|
|
138
|
+
expect(touchCrossHair.fixAtCenter).toHaveBeenCalled()
|
|
139
|
+
expect(touchCrossHair.hide).not.toHaveBeenCalled()
|
|
140
|
+
|
|
141
|
+
const mouseCrossHair = fakeCrossHair()
|
|
142
|
+
setup('mouse', mouseCrossHair)
|
|
143
|
+
expect(mouseCrossHair.hide).toHaveBeenCalled()
|
|
144
|
+
expect(mouseCrossHair.fixAtCenter).not.toHaveBeenCalled()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('shows the crosshair when a key press switches to keyboard, hides it when a mouse pointerdown switches back', () => {
|
|
148
|
+
const crossHair = fakeCrossHair()
|
|
149
|
+
const { container } = setup('mouse', crossHair)
|
|
150
|
+
expect(crossHair.hide).toHaveBeenCalledTimes(1) // initial sync at creation, starting interface is 'mouse'
|
|
151
|
+
|
|
152
|
+
container.focus()
|
|
153
|
+
key({ key: 'ArrowUp' })
|
|
154
|
+
expect(crossHair.fixAtCenter).toHaveBeenCalledTimes(1)
|
|
155
|
+
|
|
156
|
+
container.dispatchEvent(Object.assign(new Event('pointerdown'), { pointerType: 'mouse' }))
|
|
157
|
+
expect(crossHair.hide).toHaveBeenCalledTimes(2)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('setInterfaceType syncs the crosshair the same way as a detected input change', () => {
|
|
161
|
+
const crossHair = fakeCrossHair()
|
|
162
|
+
const { input } = setup('mouse', crossHair)
|
|
163
|
+
expect(crossHair.hide).toHaveBeenCalledTimes(1) // initial sync at creation, starting interface is 'mouse'
|
|
164
|
+
|
|
165
|
+
input.setInterfaceType('touch')
|
|
166
|
+
expect(crossHair.fixAtCenter).toHaveBeenCalledTimes(1)
|
|
167
|
+
|
|
168
|
+
input.setInterfaceType('mouse')
|
|
169
|
+
expect(crossHair.hide).toHaveBeenCalledTimes(2)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('tolerates a missing crosshair (e.g. edit_vertex mode, which does not supply one)', () => {
|
|
173
|
+
const { input } = setup('mouse')
|
|
174
|
+
expect(() => input.setInterfaceType('keyboard')).not.toThrow()
|
|
175
|
+
})
|
|
176
|
+
|
|
133
177
|
test('destroy removes all listeners', () => {
|
|
134
178
|
const { container, button, input, placement } = setup()
|
|
135
179
|
input.destroy()
|
|
@@ -291,7 +291,7 @@ const buildModeApi = ({ manager, store, olFeature, originalFeatureStyle, selecti
|
|
|
291
291
|
|
|
292
292
|
undo: actions.doUndo,
|
|
293
293
|
deleteVertex: actions.doDeleteVertex,
|
|
294
|
-
//
|
|
294
|
+
// MoveControls' D-pad, routed here via mapProvider.activeMoveTarget (see
|
|
295
295
|
// events.js) once a vertex is selected.
|
|
296
296
|
nudgeSelectedVertex: parts.keyboardHandler.nudgeByDelta,
|
|
297
297
|
|
|
@@ -71,6 +71,13 @@ test('a mid-drag crossing turns the stroke dashed live, and back when it clears'
|
|
|
71
71
|
expect(currentStyle(olFeature)).toBe(manager.styles.editFeatureStyle)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
+
test('a Polygon with no ring yet (transiently empty mid-drag) never throws computing numVertices', () => {
|
|
75
|
+
const { olFeature } = setup()
|
|
76
|
+
// OL's own Polygon geometry supports zero rings natively — coordinates[0] is undefined,
|
|
77
|
+
// exercising updateLiveValidity's `?? 1` fallback rather than a real drag scenario.
|
|
78
|
+
expect(() => olFeature.getGeometry().setCoordinates([])).not.toThrow()
|
|
79
|
+
})
|
|
80
|
+
|
|
74
81
|
test('a validity flip repaints the map without touching the feature — Modify tracks its own drag via feature #change, which setStyle() would trip', () => {
|
|
75
82
|
const { map, olFeature } = setup()
|
|
76
83
|
const renderCallsBefore = map.render.mock.calls.length
|
|
@@ -88,6 +95,30 @@ test('validity flips while editing also gate the Done button', () => {
|
|
|
88
95
|
expect.objectContaining({ valid: true }))
|
|
89
96
|
})
|
|
90
97
|
|
|
98
|
+
test('a LineString edit computes numVertices directly from coordinates.length (no ring -1 adjustment)', () => {
|
|
99
|
+
const map = createFakeMap()
|
|
100
|
+
const manager = createFakeManager()
|
|
101
|
+
manager.store = createFeatureStore()
|
|
102
|
+
manager.store.add({ type: 'Feature', id: 'l1', properties: {}, geometry: { type: 'LineString', coordinates: [[0, 0], [100, 0], [100, 100]] } })
|
|
103
|
+
const container = createContainer()
|
|
104
|
+
map.getViewport().appendChild(container)
|
|
105
|
+
const mode = createEditMode({
|
|
106
|
+
map,
|
|
107
|
+
manager,
|
|
108
|
+
options: { featureId: 'l1', container, interfaceType: 'mouse', deleteVertexButtonId: 'del-v', snap: null }
|
|
109
|
+
})
|
|
110
|
+
liveModes.push(mode)
|
|
111
|
+
const olFeature = manager.store.getOL('l1')
|
|
112
|
+
|
|
113
|
+
expect(manager.emit).toHaveBeenCalledWith(ADAPTER_EVENTS.VERTEX_CHANGE, { numVertices: 3 })
|
|
114
|
+
|
|
115
|
+
// LIVE_RULES (self-intersect/area) only ever apply to Polygon (see rules.js's getPolygon
|
|
116
|
+
// guard) — a self-crossing LineString still drives updateLiveValidity, but never flips
|
|
117
|
+
// the stroke invalid, unlike the Polygon case above.
|
|
118
|
+
olFeature.getGeometry().setCoordinates([[0, 0], [100, 100], [100, 0], [0, 100]])
|
|
119
|
+
expect(currentStyle(olFeature)).toBe(manager.styles.editFeatureStyle)
|
|
120
|
+
})
|
|
121
|
+
|
|
91
122
|
test('the user callback runs throttled during an edit drag', () => {
|
|
92
123
|
jest.useFakeTimers()
|
|
93
124
|
const { manager, olFeature } = setup()
|
|
@@ -123,7 +154,7 @@ test('select via pointer, delete the vertex, then undo restores it', () => {
|
|
|
123
154
|
mode.undo() // empty stack — no-op
|
|
124
155
|
})
|
|
125
156
|
|
|
126
|
-
test('nudgeSelectedVertex (
|
|
157
|
+
test('nudgeSelectedVertex (MoveControls D-pad) moves the selected vertex and is undoable', () => {
|
|
127
158
|
const { container, manager, mode, ring } = setup()
|
|
128
159
|
container.dispatchEvent(domEvent('pointerdown', { pointerType: 'mouse', clientX: 100, clientY: 0 }))
|
|
129
160
|
mode.nudgeSelectedVertex(1, 0, true)
|
|
@@ -104,12 +104,13 @@ const buildKeydownHandler = ({ map, getState, setState, nudge, keyMove, onUndo,
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
const buildKeyupHandler = ({
|
|
107
|
+
const buildKeyupHandler = ({ keyMove, onVertexMoved, onDeleted, isFocused }) => (e) => {
|
|
108
108
|
if (isFocused()) {
|
|
109
109
|
return
|
|
110
110
|
}
|
|
111
111
|
if (ARROW_KEYS.has(e.key) && keyMove.start && keyMove.index != null) {
|
|
112
|
-
snap
|
|
112
|
+
// Not hiding the snap indicator here — nudge.js's own snap.apply() already left it showing
|
|
113
|
+
// correctly, and it should stay that way after the key is released.
|
|
113
114
|
onVertexMoved({ vertexIndex: keyMove.index, previousCoord: keyMove.start })
|
|
114
115
|
keyMove.start = null
|
|
115
116
|
keyMove.index = null
|
|
@@ -133,16 +134,19 @@ const buildKeyupHandler = ({ snap, keyMove, onVertexMoved, onDeleted, isFocused
|
|
|
133
134
|
* convert it. Only pressing a plain/Shift arrow converts it.
|
|
134
135
|
*
|
|
135
136
|
* @param {{ map, getState, setState, snap, onVertexMoved, onInserted, onDeleted, onUndo, onKeyboardActive }} options
|
|
137
|
+
* @param {(olFeature, index: number, coord: number[]) => void} [options.moveCoord] - coordinate
|
|
138
|
+
* writer forwarded to wireNudge, defaulting there to moveVertex; point/editPointMode.js
|
|
139
|
+
* injects point/pointOps.js's movePoint instead.
|
|
136
140
|
* @returns {{ nudgeByDelta: (dx: number, dy: number, isLargeStep: boolean) => void, destroy: () => void }}
|
|
137
141
|
*/
|
|
138
142
|
export const createKeyboardHandler = (options) => {
|
|
139
|
-
const { map, snap, getState, setState, onVertexMoved, onInserted, onDeleted, onUndo, onKeyboardActive } = options
|
|
140
|
-
const { nudge, keyMove, nudgeByDelta } = wireNudge({ map, snap, getState, setState, onInserted, onVertexMoved })
|
|
143
|
+
const { map, snap, getState, setState, onVertexMoved, onInserted, onDeleted, onUndo, onKeyboardActive, moveCoord } = options
|
|
144
|
+
const { nudge, keyMove, nudgeByDelta } = wireNudge({ map, snap, getState, setState, onInserted, onVertexMoved, moveCoord })
|
|
141
145
|
const appViewport = map.getViewport().closest('[role="application"]') ?? map.getViewport()
|
|
142
146
|
const isFocused = () => isInteractiveElementFocused(appViewport)
|
|
143
147
|
|
|
144
148
|
const onKeydown = buildKeydownHandler({ map, getState, setState, nudge, keyMove, onUndo, onKeyboardActive, isFocused })
|
|
145
|
-
const onKeyup = buildKeyupHandler({
|
|
149
|
+
const onKeyup = buildKeyupHandler({ keyMove, onVertexMoved, onDeleted, isFocused })
|
|
146
150
|
|
|
147
151
|
globalThis.addEventListener('keydown', onKeydown, { capture: true })
|
|
148
152
|
globalThis.addEventListener('keyup', onKeyup, { capture: true })
|
|
@@ -33,7 +33,7 @@ afterEach(() => {
|
|
|
33
33
|
|
|
34
34
|
const key = (type, props) => window.dispatchEvent(new KeyboardEvent(type, { cancelable: true, ...props }))
|
|
35
35
|
|
|
36
|
-
test('nudgeByDelta (exposed for
|
|
36
|
+
test('nudgeByDelta (exposed for MoveControls) moves the selected vertex and reports it via onVertexMoved', () => {
|
|
37
37
|
const { state, handler, onVertexMoved } = setup()
|
|
38
38
|
Object.assign(state, { selectedVertexIndex: 1, selectedVertexType: 'vertex' })
|
|
39
39
|
handler.nudgeByDelta(1, 0, true)
|
|
@@ -115,6 +115,39 @@ test('a plain arrow nudges the selected vertex, and keyup commits a single move
|
|
|
115
115
|
expect(onVertexMoved).toHaveBeenCalledTimes(1)
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
// Regression: keyup used to force-hide the snap indicator unconditionally, so it vanished the
|
|
119
|
+
// instant the key was released even when the vertex landed exactly on a snap target — making it
|
|
120
|
+
// impossible to tell whether the nudge had actually snapped. nudge.js's own snap.apply() already
|
|
121
|
+
// leaves the indicator correctly reflecting reality; keyup must leave it alone (mirrors the ML
|
|
122
|
+
// adapter, which never hides its own indicator on keyup either).
|
|
123
|
+
test('keyup does not hide the snap indicator', () => {
|
|
124
|
+
const snap = { apply: jest.fn((c) => c), hideIndicator: jest.fn(), snapRadius: 12 }
|
|
125
|
+
const map = createFakeMap({ center: [98, 98] })
|
|
126
|
+
const state = {
|
|
127
|
+
olFeature: polygonFeature(RING),
|
|
128
|
+
selectedVertexIndex: 1,
|
|
129
|
+
selectedVertexType: 'vertex',
|
|
130
|
+
vertices: [[0, 0], [100, 0], [100, 100]],
|
|
131
|
+
midpoints: [[50, 0], [100, 50], [50, 50]]
|
|
132
|
+
}
|
|
133
|
+
const setState = jest.fn((updates) => Object.assign(state, updates))
|
|
134
|
+
const handler = createKeyboardHandler({
|
|
135
|
+
map,
|
|
136
|
+
getState: () => state,
|
|
137
|
+
setState,
|
|
138
|
+
snap,
|
|
139
|
+
onVertexMoved: jest.fn(),
|
|
140
|
+
onInserted: jest.fn(),
|
|
141
|
+
onDeleted: jest.fn(),
|
|
142
|
+
onUndo: jest.fn(),
|
|
143
|
+
onKeyboardActive: jest.fn()
|
|
144
|
+
})
|
|
145
|
+
liveHandlers.push(handler)
|
|
146
|
+
key('keydown', { key: 'ArrowRight' })
|
|
147
|
+
key('keyup', { key: 'ArrowRight' })
|
|
148
|
+
expect(snap.hideIndicator).not.toHaveBeenCalled()
|
|
149
|
+
})
|
|
150
|
+
|
|
118
151
|
test('arrows without a selection do nothing', () => {
|
|
119
152
|
const { setState } = setup()
|
|
120
153
|
key('keydown', { key: 'ArrowRight' })
|
|
@@ -44,16 +44,18 @@ export const buildModifyCondition = ({ map, getState }) => (mapBrowserEvent) =>
|
|
|
44
44
|
* each completed drag via `onModifyEnd(prevCoords)` with the vertices
|
|
45
45
|
* snapshotted at drag start.
|
|
46
46
|
*
|
|
47
|
+
* @param {(mapBrowserEvent) => boolean} [condition] - overrides buildModifyCondition's
|
|
48
|
+
* fixed-radius vertex hit-test.
|
|
47
49
|
* @returns {{ destroy: () => void }}
|
|
48
50
|
*/
|
|
49
|
-
export const createModifyInteraction = ({ map, olFeature, getState, onModifyEnd }) => {
|
|
50
|
-
const
|
|
51
|
+
export const createModifyInteraction = ({ map, olFeature, getState, onModifyEnd, condition }) => {
|
|
52
|
+
const modifyCondition = condition ?? buildModifyCondition({ map, getState })
|
|
51
53
|
|
|
52
54
|
const modifyInteraction = new Modify({
|
|
53
55
|
features: new Collection([olFeature]),
|
|
54
56
|
style: () => [], // vertex circles rendered by vertexLayer instead
|
|
55
57
|
pixelTolerance: PIXEL_TOLERANCE,
|
|
56
|
-
condition
|
|
58
|
+
condition: modifyCondition
|
|
57
59
|
})
|
|
58
60
|
map.addInteraction(modifyInteraction)
|
|
59
61
|
|