@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,10 +2,7 @@
|
|
|
2
2
|
import { SIZES } from './defaults.js'
|
|
3
3
|
import { resolveColors } from '../../utils/resolveColors.js'
|
|
4
4
|
|
|
5
|
-
// `defaultValue` is the already-resolved fallback (
|
|
6
|
-
// plugin-config override if one was set, else the built-in default), not the
|
|
7
|
-
// raw COLORS constant, so a shapeStroke/shapeFill override applies to every
|
|
8
|
-
// feature that doesn't set its own stroke/fill property.
|
|
5
|
+
// `defaultValue` is the already-resolved fallback (plugin-config override or built-in default), so a shapeStroke/shapeFill override applies to every feature without its own.
|
|
9
6
|
const getUserProp = (mapStyle, prop, defaultValue) => [
|
|
10
7
|
'coalesce',
|
|
11
8
|
['get', `user_${prop}${mapStyle.id.charAt(0).toUpperCase() + mapStyle.id.slice(1)}`],
|
|
@@ -13,11 +10,18 @@ const getUserProp = (mapStyle, prop, defaultValue) => [
|
|
|
13
10
|
defaultValue
|
|
14
11
|
]
|
|
15
12
|
|
|
13
|
+
// Every feature gets an incrementing user_sortKey property when it's created (see
|
|
14
|
+
// MaplibreDrawAdapter.js's _nextSortKey) — later-drawn features paint on top of earlier ones
|
|
15
|
+
// within the same shared layer, instead of the order being incidental to which of mapbox-gl-
|
|
16
|
+
// draw's cold/hot buckets a feature happens to be in.
|
|
17
|
+
const SORT_KEY_PROP = ['get', 'user_sortKey']
|
|
18
|
+
|
|
16
19
|
// Inactive lines and fills
|
|
17
20
|
const fillInactive = (mapStyle, colors) => ({
|
|
18
21
|
id: 'fill-inactive',
|
|
19
22
|
type: 'fill',
|
|
20
23
|
filter: ['all', ['==', '$type', 'Polygon'], ['==', 'active', 'false']],
|
|
24
|
+
layout: { 'fill-sort-key': SORT_KEY_PROP },
|
|
21
25
|
paint: { 'fill-color': getUserProp(mapStyle, 'fill', colors.shapeFill) }
|
|
22
26
|
})
|
|
23
27
|
|
|
@@ -25,7 +29,7 @@ const strokeInactive = (mapStyle, colors) => ({
|
|
|
25
29
|
id: 'stroke-inactive',
|
|
26
30
|
type: 'line',
|
|
27
31
|
filter: ['all', ['any', ['==', '$type', 'Polygon'], ['==', '$type', 'LineString']], ['==', 'active', 'false'], ['!has', 'user_splitter']],
|
|
28
|
-
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
32
|
+
layout: { 'line-cap': 'round', 'line-join': 'round', 'line-sort-key': SORT_KEY_PROP },
|
|
29
33
|
paint: {
|
|
30
34
|
'line-color': getUserProp(mapStyle, 'stroke', colors.shapeStroke),
|
|
31
35
|
'line-width': colors.strokeWidth
|
|
@@ -48,9 +52,7 @@ const strokeActive = (editStrokeColor) => ({
|
|
|
48
52
|
paint: { 'line-color': editStrokeColor, 'line-width': 2, 'line-opacity': 1 }
|
|
49
53
|
})
|
|
50
54
|
|
|
51
|
-
// Dashed stroke shown in place of stroke-active while the
|
|
52
|
-
// filter as stroke-active; the adapter's setInvalid() toggles the two layers'
|
|
53
|
-
// visibility (line-dasharray can't be data-driven per feature in MapLibre).
|
|
55
|
+
// Dashed stroke shown in place of stroke-active while invalid — the adapter's setInvalid() toggles the two layers' visibility (line-dasharray can't be data-driven per feature).
|
|
54
56
|
const strokeActiveInvalid = (invalidStrokeColor) => ({
|
|
55
57
|
id: 'stroke-active-invalid',
|
|
56
58
|
type: 'line',
|
|
@@ -150,6 +152,32 @@ const circle = (editStrokeColor) => ({
|
|
|
150
152
|
paint: { 'line-color': editStrokeColor, 'line-width': 2, 'line-opacity': 0.8 }
|
|
151
153
|
})
|
|
152
154
|
|
|
155
|
+
// A committed point renders its resolved symbol-config icon (pointSymbolImages.js writes user_symbolImageId/user_symbolIconAnchor onto each feature). While a point is being edited (active:'true'), icon-image swaps to its precomputed "selected" variant — the same one the interact plugin's own highlight uses — so the look persists for the whole edit session instead of disappearing when that highlight is cleared on entering edit mode.
|
|
156
|
+
// icon-offset is deliberately NOT a per-feature `get` here — MapLibre's GeoJSON sources
|
|
157
|
+
// silently JSON.stringify array properties, so it would read back a string, not an array.
|
|
158
|
+
// pointSymbolImages.js's registerSymbolIconOffset overwrites this default via
|
|
159
|
+
// setLayoutProperty with a `match` expression once a point needs a non-zero offset.
|
|
160
|
+
const pointSymbol = () => ({
|
|
161
|
+
id: 'point-symbol',
|
|
162
|
+
type: 'symbol',
|
|
163
|
+
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'feature']],
|
|
164
|
+
layout: {
|
|
165
|
+
'icon-image': [
|
|
166
|
+
'case',
|
|
167
|
+
['==', ['get', 'active'], 'true'],
|
|
168
|
+
['coalesce', ['get', 'user_symbolSelectedImageId'], ['get', 'user_symbolImageId']],
|
|
169
|
+
['get', 'user_symbolImageId']
|
|
170
|
+
],
|
|
171
|
+
'icon-anchor': ['get', 'user_symbolIconAnchor'],
|
|
172
|
+
'icon-offset': ['literal', [0, 0]],
|
|
173
|
+
'icon-allow-overlap': true,
|
|
174
|
+
'icon-ignore-placement': true,
|
|
175
|
+
'symbol-sort-key': SORT_KEY_PROP
|
|
176
|
+
},
|
|
177
|
+
// Empty but required — updateDrawStyles() below iterates every layer's paint object unconditionally.
|
|
178
|
+
paint: {}
|
|
179
|
+
})
|
|
180
|
+
|
|
153
181
|
const touchVertexIndicator = () => ({
|
|
154
182
|
id: 'touch-vertex-indicator',
|
|
155
183
|
type: 'circle',
|
|
@@ -157,9 +185,7 @@ const touchVertexIndicator = () => ({
|
|
|
157
185
|
paint: { 'circle-radius': 30, 'circle-color': '#3bb2d0', 'circle-stroke-width': 3, 'circle-stroke-color': '#ffffff', 'circle-opacity': 0.9 }
|
|
158
186
|
})
|
|
159
187
|
|
|
160
|
-
// `pluginConfig` lets callers override any of these colours/strokeWidth
|
|
161
|
-
// resolveColors() and the draw plugin's own options docs. Unaffected keys
|
|
162
|
-
// still resolve to the built-in COLORS/SIZES defaults.
|
|
188
|
+
// `pluginConfig` lets callers override any of these colours/strokeWidth (see resolveColors()); unaffected keys fall back to the built-in defaults.
|
|
163
189
|
const createDrawStyles = (mapStyle, pluginConfig = {}) => {
|
|
164
190
|
const colors = resolveColors(mapStyle, pluginConfig)
|
|
165
191
|
const { vertexRadius, midpointRadius, vertexHaloRadius, midpointHaloRadius } = SIZES
|
|
@@ -170,6 +196,7 @@ const createDrawStyles = (mapStyle, pluginConfig = {}) => {
|
|
|
170
196
|
strokeActive(colors.editStroke),
|
|
171
197
|
strokeActiveInvalid(colors.invalidStroke),
|
|
172
198
|
strokeInactive(mapStyle, colors),
|
|
199
|
+
pointSymbol(),
|
|
173
200
|
drawInvalidSplitter(colors.splitInvalid),
|
|
174
201
|
drawValidSplitter(colors.splitValid),
|
|
175
202
|
drawPreviewLine(colors.editStroke),
|
|
@@ -11,12 +11,51 @@ describe('createDrawStyles', () => {
|
|
|
11
11
|
const layers = createDrawStyles(mapStyle)
|
|
12
12
|
expect(layers.map((l) => l.id)).toEqual([
|
|
13
13
|
'fill-inactive', 'fill-active', 'stroke-active', 'stroke-active-invalid', 'stroke-inactive',
|
|
14
|
+
'point-symbol',
|
|
14
15
|
'stroke-invalid-splitter', 'stroke-valid-splitter', 'stroke-preview-line',
|
|
15
16
|
'midpoint', 'midpoint-halo', 'midpoint-active',
|
|
16
17
|
'vertex', 'vertex-halo', 'vertex-active', 'circle', 'touch-vertex-indicator'
|
|
17
18
|
])
|
|
18
19
|
})
|
|
19
20
|
|
|
21
|
+
test('the point-symbol layer is data-driven per feature and only matches real committed point features', () => {
|
|
22
|
+
const layers = createDrawStyles(mapStyle)
|
|
23
|
+
const point = findLayer(layers, 'point-symbol')
|
|
24
|
+
expect(point.filter).toEqual(['all', ['==', '$type', 'Point'], ['==', 'meta', 'feature']])
|
|
25
|
+
expect(point.layout['icon-anchor']).toEqual(['get', 'user_symbolIconAnchor'])
|
|
26
|
+
// Static default — pointSymbolImages.js's registerSymbolIconOffset overwrites this via
|
|
27
|
+
// setLayoutProperty with a match expression once a point needs a non-zero offset (a raw
|
|
28
|
+
// per-feature `get` here would read back a JSON.stringify'd string, not the real array —
|
|
29
|
+
// see that function's comment).
|
|
30
|
+
expect(point.layout['icon-offset']).toEqual(['literal', [0, 0]])
|
|
31
|
+
expect(point.layout['icon-allow-overlap']).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Drives predictable draw-order stacking within each shared layer (a later-drawn feature
|
|
35
|
+
// paints above an earlier one) — see MaplibreDrawAdapter.js's _nextSortKey/sortKey property.
|
|
36
|
+
test('fill/line/symbol layers sort by the per-feature user_sortKey property', () => {
|
|
37
|
+
const layers = createDrawStyles(mapStyle)
|
|
38
|
+
expect(findLayer(layers, 'fill-inactive').layout['fill-sort-key']).toEqual(['get', 'user_sortKey'])
|
|
39
|
+
expect(findLayer(layers, 'stroke-inactive').layout['line-sort-key']).toEqual(['get', 'user_sortKey'])
|
|
40
|
+
expect(findLayer(layers, 'point-symbol').layout['symbol-sort-key']).toEqual(['get', 'user_sortKey'])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// While a point is the one being edited (edit_point mode's toDisplayFeatures sets
|
|
44
|
+
// active:'true' just for it), icon-image swaps to the same precomputed "selected" (black
|
|
45
|
+
// ring) variant the interact plugin's own selection highlight already uses — see
|
|
46
|
+
// pointSymbol()'s own comment — so the look persists for the whole edit session instead of
|
|
47
|
+
// disappearing when the interact plugin's selection gets cleared on entering edit mode.
|
|
48
|
+
test('the point-symbol layer swaps to the selected-variant icon while active, falling back to the normal one if no variant was resolved', () => {
|
|
49
|
+
const layers = createDrawStyles(mapStyle)
|
|
50
|
+
const point = findLayer(layers, 'point-symbol')
|
|
51
|
+
expect(point.layout['icon-image']).toEqual([
|
|
52
|
+
'case',
|
|
53
|
+
['==', ['get', 'active'], 'true'],
|
|
54
|
+
['coalesce', ['get', 'user_symbolSelectedImageId'], ['get', 'user_symbolImageId']],
|
|
55
|
+
['get', 'user_symbolImageId']
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
20
59
|
test('the invalid stroke layer is a hidden dashed line matching the active shape', () => {
|
|
21
60
|
const layers = createDrawStyles(mapStyle)
|
|
22
61
|
const invalid = findLayer(layers, 'stroke-active-invalid')
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CUSTOM_DRAW_EVENTS } from '../drawEvents.js'
|
|
2
|
+
import { getSnapInstance, clearSnapIndicator } from './snapHelpers.js'
|
|
3
|
+
|
|
4
|
+
// Builds the `handlers` map both modes' setupEventListeners bind to bindEditModeListeners —
|
|
5
|
+
// the base set of window/container/scale/move/interface-type-change handlers is identical
|
|
6
|
+
// in both, bound to whichever mode instance is passed in; `extra` supplies the handlers that
|
|
7
|
+
// differ per mode (nudge, plus edit_vertex's own selectionchange/update).
|
|
8
|
+
export const buildEditModeHandlers = (mode, state, extra = {}) => {
|
|
9
|
+
const bind = (handler) => (event) => handler.call(mode, state, event)
|
|
10
|
+
const handlers = {
|
|
11
|
+
keydown: bind(mode.onKeydown),
|
|
12
|
+
keyup: bind(mode.onKeyup),
|
|
13
|
+
pointerdown: bind(mode.onPointerevent),
|
|
14
|
+
pointermove: bind(mode.onPointerevent),
|
|
15
|
+
pointerup: bind(mode.onPointerevent),
|
|
16
|
+
click: bind(mode.onButtonClick),
|
|
17
|
+
touchstart: bind(mode.onTouchstart),
|
|
18
|
+
touchmove: bind(mode.onTouchmove),
|
|
19
|
+
touchend: bind(mode.onTouchend),
|
|
20
|
+
scalechange: bind(mode.onScaleChange),
|
|
21
|
+
move: bind(mode.onMove),
|
|
22
|
+
interfacetypechange: bind(mode.onInterfaceTypeChange)
|
|
23
|
+
}
|
|
24
|
+
for (const [name, handler] of Object.entries(extra)) {
|
|
25
|
+
handlers[name] = bind(handler)
|
|
26
|
+
}
|
|
27
|
+
return handlers
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Shared DOM/map event wiring for edit_point and edit_vertex — both modes' setupEventListeners
|
|
31
|
+
// bind the exact same base set of window/container listeners plus scale/move/interface-type-
|
|
32
|
+
// change/nudge map events; edit_vertex additionally wires its own selectionchange/update map
|
|
33
|
+
// events on top of this. Each mode still builds its own `handlers` map (bound to its own onXxx
|
|
34
|
+
// methods) — this just wires/unwires it, keyed on the handler names both modes share (the
|
|
35
|
+
// nudge entry is named `nudge` in both, even though what it does — nudgePointByDelta vs
|
|
36
|
+
// nudgeVertexByDelta — differs per mode).
|
|
37
|
+
export const bindEditModeListeners = (state, map, handlers) => {
|
|
38
|
+
window.addEventListener('keydown', handlers.keydown, { capture: true })
|
|
39
|
+
window.addEventListener('keyup', handlers.keyup, { capture: true })
|
|
40
|
+
window.addEventListener('click', handlers.click)
|
|
41
|
+
state.container.addEventListener('pointerdown', handlers.pointerdown)
|
|
42
|
+
state.container.addEventListener('pointermove', handlers.pointermove)
|
|
43
|
+
state.container.addEventListener('pointerup', handlers.pointerup)
|
|
44
|
+
state.container.addEventListener('touchstart', handlers.touchstart, { passive: false })
|
|
45
|
+
state.container.addEventListener('touchmove', handlers.touchmove, { passive: false })
|
|
46
|
+
state.container.addEventListener('touchend', handlers.touchend, { passive: false })
|
|
47
|
+
map.on('draw.scalechange', handlers.scalechange)
|
|
48
|
+
map.on('move', handlers.move)
|
|
49
|
+
map.on(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, handlers.interfacetypechange)
|
|
50
|
+
map.on(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, handlers.nudge)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Mirrors bindEditModeListeners, plus the dragPan re-enable both modes' onStop does right
|
|
54
|
+
// alongside it (dragPan is disabled for the duration of the edit session elsewhere).
|
|
55
|
+
export const unbindEditModeListeners = (state, map, handlers) => {
|
|
56
|
+
state.container.removeEventListener('pointerdown', handlers.pointerdown)
|
|
57
|
+
state.container.removeEventListener('pointermove', handlers.pointermove)
|
|
58
|
+
state.container.removeEventListener('pointerup', handlers.pointerup)
|
|
59
|
+
state.container.removeEventListener('touchstart', handlers.touchstart)
|
|
60
|
+
state.container.removeEventListener('touchmove', handlers.touchmove)
|
|
61
|
+
state.container.removeEventListener('touchend', handlers.touchend)
|
|
62
|
+
map.off('draw.scalechange', handlers.scalechange)
|
|
63
|
+
map.off('move', handlers.move)
|
|
64
|
+
map.off(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, handlers.interfacetypechange)
|
|
65
|
+
map.off(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, handlers.nudge)
|
|
66
|
+
map.dragPan.enable()
|
|
67
|
+
window.removeEventListener('click', handlers.click)
|
|
68
|
+
window.removeEventListener('keydown', handlers.keydown, { capture: true })
|
|
69
|
+
window.removeEventListener('keyup', handlers.keyup, { capture: true })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clear any snap indicator left over from a previous interaction — both edit modes do this
|
|
73
|
+
// once, at onSetup.
|
|
74
|
+
export const clearActiveSnapIndicator = (map) => {
|
|
75
|
+
const snap = getSnapInstance(map)
|
|
76
|
+
if (snap) {
|
|
77
|
+
clearSnapIndicator(snap, map)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { bindEditModeListeners, unbindEditModeListeners, clearActiveSnapIndicator, buildEditModeHandlers } from './editModeEvents.js'
|
|
2
|
+
import { CUSTOM_DRAW_EVENTS } from '../drawEvents.js'
|
|
3
|
+
|
|
4
|
+
const makeHandlers = () => ({
|
|
5
|
+
keydown: jest.fn(),
|
|
6
|
+
keyup: jest.fn(),
|
|
7
|
+
click: jest.fn(),
|
|
8
|
+
pointerdown: jest.fn(),
|
|
9
|
+
pointermove: jest.fn(),
|
|
10
|
+
pointerup: jest.fn(),
|
|
11
|
+
touchstart: jest.fn(),
|
|
12
|
+
touchmove: jest.fn(),
|
|
13
|
+
touchend: jest.fn(),
|
|
14
|
+
scalechange: jest.fn(),
|
|
15
|
+
move: jest.fn(),
|
|
16
|
+
interfacetypechange: jest.fn(),
|
|
17
|
+
nudge: jest.fn()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const makeMap = () => ({ on: jest.fn(), off: jest.fn(), dragPan: { enable: jest.fn() } })
|
|
21
|
+
|
|
22
|
+
describe('bindEditModeListeners', () => {
|
|
23
|
+
test('wires the shared window/container listeners and map events', () => {
|
|
24
|
+
const state = { container: document.createElement('div') }
|
|
25
|
+
jest.spyOn(window, 'addEventListener')
|
|
26
|
+
jest.spyOn(state.container, 'addEventListener')
|
|
27
|
+
const map = makeMap()
|
|
28
|
+
const handlers = makeHandlers()
|
|
29
|
+
|
|
30
|
+
bindEditModeListeners(state, map, handlers)
|
|
31
|
+
|
|
32
|
+
expect(window.addEventListener).toHaveBeenCalledWith('keydown', handlers.keydown, { capture: true })
|
|
33
|
+
expect(window.addEventListener).toHaveBeenCalledWith('keyup', handlers.keyup, { capture: true })
|
|
34
|
+
expect(window.addEventListener).toHaveBeenCalledWith('click', handlers.click)
|
|
35
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('pointerdown', handlers.pointerdown)
|
|
36
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('pointermove', handlers.pointermove)
|
|
37
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('pointerup', handlers.pointerup)
|
|
38
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('touchstart', handlers.touchstart, { passive: false })
|
|
39
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('touchmove', handlers.touchmove, { passive: false })
|
|
40
|
+
expect(state.container.addEventListener).toHaveBeenCalledWith('touchend', handlers.touchend, { passive: false })
|
|
41
|
+
expect(map.on).toHaveBeenCalledWith('draw.scalechange', handlers.scalechange)
|
|
42
|
+
expect(map.on).toHaveBeenCalledWith('move', handlers.move)
|
|
43
|
+
expect(map.on).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, handlers.interfacetypechange)
|
|
44
|
+
expect(map.on).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, handlers.nudge)
|
|
45
|
+
|
|
46
|
+
window.addEventListener.mockRestore()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('unbindEditModeListeners', () => {
|
|
51
|
+
test('tears down the shared window/container listeners and map events, and re-enables dragPan', () => {
|
|
52
|
+
const state = { container: document.createElement('div') }
|
|
53
|
+
jest.spyOn(window, 'removeEventListener')
|
|
54
|
+
jest.spyOn(state.container, 'removeEventListener')
|
|
55
|
+
const map = makeMap()
|
|
56
|
+
const handlers = makeHandlers()
|
|
57
|
+
|
|
58
|
+
unbindEditModeListeners(state, map, handlers)
|
|
59
|
+
|
|
60
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('pointerdown', handlers.pointerdown)
|
|
61
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('pointermove', handlers.pointermove)
|
|
62
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('pointerup', handlers.pointerup)
|
|
63
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('touchstart', handlers.touchstart)
|
|
64
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('touchmove', handlers.touchmove)
|
|
65
|
+
expect(state.container.removeEventListener).toHaveBeenCalledWith('touchend', handlers.touchend)
|
|
66
|
+
expect(map.off).toHaveBeenCalledWith('draw.scalechange', handlers.scalechange)
|
|
67
|
+
expect(map.off).toHaveBeenCalledWith('move', handlers.move)
|
|
68
|
+
expect(map.off).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.INTERFACE_TYPE_CHANGE, handlers.interfacetypechange)
|
|
69
|
+
expect(map.off).toHaveBeenCalledWith(CUSTOM_DRAW_EVENTS.NUDGE_VERTEX, handlers.nudge)
|
|
70
|
+
expect(map.dragPan.enable).toHaveBeenCalled()
|
|
71
|
+
expect(window.removeEventListener).toHaveBeenCalledWith('click', handlers.click)
|
|
72
|
+
expect(window.removeEventListener).toHaveBeenCalledWith('keydown', handlers.keydown, { capture: true })
|
|
73
|
+
expect(window.removeEventListener).toHaveBeenCalledWith('keyup', handlers.keyup, { capture: true })
|
|
74
|
+
|
|
75
|
+
window.removeEventListener.mockRestore()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('clearActiveSnapIndicator', () => {
|
|
80
|
+
test('clears the indicator when a snap instance is present', () => {
|
|
81
|
+
const snap = { snapStatus: true, snapCoords: [1, 2] }
|
|
82
|
+
const map = { _snapInstance: snap, getLayer: jest.fn(() => null), setLayoutProperty: jest.fn() }
|
|
83
|
+
|
|
84
|
+
clearActiveSnapIndicator(map)
|
|
85
|
+
|
|
86
|
+
expect(snap.snapStatus).toBe(false)
|
|
87
|
+
expect(snap.snapCoords).toBeNull()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('is a no-op without a snap instance', () => {
|
|
91
|
+
expect(() => clearActiveSnapIndicator({})).not.toThrow()
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('buildEditModeHandlers', () => {
|
|
96
|
+
test('builds the shared base handlers bound to the given mode and state', () => {
|
|
97
|
+
const mode = {
|
|
98
|
+
onKeydown: jest.fn(),
|
|
99
|
+
onKeyup: jest.fn(),
|
|
100
|
+
onPointerevent: jest.fn(),
|
|
101
|
+
onButtonClick: jest.fn(),
|
|
102
|
+
onTouchstart: jest.fn(),
|
|
103
|
+
onTouchmove: jest.fn(),
|
|
104
|
+
onTouchend: jest.fn(),
|
|
105
|
+
onScaleChange: jest.fn(),
|
|
106
|
+
onMove: jest.fn(),
|
|
107
|
+
onInterfaceTypeChange: jest.fn()
|
|
108
|
+
}
|
|
109
|
+
const state = {}
|
|
110
|
+
const handlers = buildEditModeHandlers(mode, state)
|
|
111
|
+
|
|
112
|
+
expect(Object.keys(handlers).sort()).toEqual([
|
|
113
|
+
'click', 'interfacetypechange', 'keydown', 'keyup', 'move', 'pointerdown',
|
|
114
|
+
'pointermove', 'pointerup', 'scalechange', 'touchend', 'touchmove', 'touchstart'
|
|
115
|
+
])
|
|
116
|
+
handlers.keydown('event')
|
|
117
|
+
handlers.pointerdown('down-event')
|
|
118
|
+
handlers.pointermove('move-event')
|
|
119
|
+
expect(mode.onKeydown).toHaveBeenCalledWith(state, 'event')
|
|
120
|
+
// pointerdown/pointermove/pointerup all route through the single onPointerevent handler
|
|
121
|
+
expect(mode.onPointerevent).toHaveBeenCalledWith(state, 'down-event')
|
|
122
|
+
expect(mode.onPointerevent).toHaveBeenCalledWith(state, 'move-event')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('binds extra per-mode handlers (e.g. nudge, selectionchange, update) alongside the base set', () => {
|
|
126
|
+
const mode = { onNudgeVertex: jest.fn(), onSelectionChange: jest.fn() }
|
|
127
|
+
const state = {}
|
|
128
|
+
const handlers = buildEditModeHandlers(mode, state, { nudge: mode.onNudgeVertex, selectionchange: mode.onSelectionChange })
|
|
129
|
+
|
|
130
|
+
handlers.nudge('e1')
|
|
131
|
+
handlers.selectionchange('e2')
|
|
132
|
+
expect(mode.onNudgeVertex).toHaveBeenCalledWith(state, 'e1')
|
|
133
|
+
expect(mode.onSelectionChange).toHaveBeenCalledWith(state, 'e2')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Shared by editPointMode/undoHandlers.js and editVertexMode/undoHandlers.js — recording an
|
|
2
|
+
// undo op and feeding it to commit-level validation is identical plumbing for both modes; only
|
|
3
|
+
// the op types themselves differ (a Point only ever moves, a ring vertex can also be inserted
|
|
4
|
+
// or deleted), which each mode still declares locally via its own UNDO_OP_PHASE/
|
|
5
|
+
// UNDO_INVERSE_PHASE maps (read here off `this`, since pushUndo is mixed into both modes).
|
|
6
|
+
export const sharedUndoHandlers = {
|
|
7
|
+
// Fire geometry change event (for external listeners)
|
|
8
|
+
fireGeometryChange (state) {
|
|
9
|
+
const feature = this.getFeature(state.featureId)
|
|
10
|
+
if (feature) {
|
|
11
|
+
this.map.fire('draw.update', {
|
|
12
|
+
features: [feature.toGeoJSON()],
|
|
13
|
+
action: 'change_coordinates'
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// Emit a commit-level geometrychange (feature + change phase + vertex index) so the
|
|
19
|
+
// validation layer can accept or reject the change. Deferred a tick to avoid
|
|
20
|
+
// re-entrancy: rejection calls draw.undo(), which must run after the current
|
|
21
|
+
// mutation (and its undo bookkeeping) has fully settled.
|
|
22
|
+
emitGeometryValidation (phase, vertexIndex, featureId) {
|
|
23
|
+
if (!phase) { return }
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
const feature = this.getFeature(featureId)
|
|
26
|
+
if (!feature) { return }
|
|
27
|
+
this.map.fire('draw.geometrychange', { feature: feature.toGeoJSON(), phase, vertexIndex })
|
|
28
|
+
}, 0)
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Undo support
|
|
32
|
+
pushUndo (operation) {
|
|
33
|
+
const undoStack = this.map._undoStack
|
|
34
|
+
if (!undoStack) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
undoStack.push(operation)
|
|
38
|
+
// Every edit commit (move/insert/delete, via mouse/touch/keyboard/D-pad) records an undo
|
|
39
|
+
// op here, so this is the single point that feeds commit-level validation.
|
|
40
|
+
this.emitGeometryValidation(this.UNDO_OP_PHASE[operation.type], operation.vertexIndex, operation.featureId)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { sharedUndoHandlers } from './geometryValidation.js'
|
|
2
|
+
import { createUndoStack } from '../../../utils/undoStack.js'
|
|
3
|
+
|
|
4
|
+
const makeCtx = (overrides = {}) => ({
|
|
5
|
+
...sharedUndoHandlers,
|
|
6
|
+
UNDO_OP_PHASE: { move: 'commit-move' },
|
|
7
|
+
map: { fire: jest.fn(), _undoStack: createUndoStack(() => {}) },
|
|
8
|
+
getFeature: jest.fn((id) => (id === 'missing' ? undefined : { toGeoJSON: () => ({ type: 'Feature', id }) })),
|
|
9
|
+
...overrides
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('fireGeometryChange', () => {
|
|
13
|
+
test('fires draw.update only when the feature exists', () => {
|
|
14
|
+
const ctx = makeCtx()
|
|
15
|
+
ctx.fireGeometryChange({ featureId: 'feat-1' })
|
|
16
|
+
expect(ctx.map.fire).toHaveBeenCalledWith('draw.update', expect.objectContaining({ action: 'change_coordinates' }))
|
|
17
|
+
|
|
18
|
+
ctx.map.fire.mockClear()
|
|
19
|
+
ctx.fireGeometryChange({ featureId: 'missing' })
|
|
20
|
+
expect(ctx.map.fire).not.toHaveBeenCalled()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('emitGeometryValidation', () => {
|
|
25
|
+
test('does nothing without a phase', () => {
|
|
26
|
+
jest.useFakeTimers()
|
|
27
|
+
const ctx = makeCtx()
|
|
28
|
+
ctx.emitGeometryValidation(undefined, 0, 'feat-1')
|
|
29
|
+
jest.runAllTimers()
|
|
30
|
+
expect(ctx.map.fire).not.toHaveBeenCalled()
|
|
31
|
+
jest.useRealTimers()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('fires a deferred draw.geometrychange with the phase, vertexIndex and feature', () => {
|
|
35
|
+
jest.useFakeTimers()
|
|
36
|
+
const ctx = makeCtx()
|
|
37
|
+
ctx.emitGeometryValidation('commit-move', 2, 'feat-1')
|
|
38
|
+
// Deferred a tick to avoid re-entrancy — nothing fired synchronously.
|
|
39
|
+
expect(ctx.map.fire).not.toHaveBeenCalled()
|
|
40
|
+
|
|
41
|
+
jest.runAllTimers()
|
|
42
|
+
expect(ctx.map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({
|
|
43
|
+
phase: 'commit-move',
|
|
44
|
+
vertexIndex: 2,
|
|
45
|
+
feature: expect.any(Object)
|
|
46
|
+
}))
|
|
47
|
+
jest.useRealTimers()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('does not fire once the feature is gone', () => {
|
|
51
|
+
jest.useFakeTimers()
|
|
52
|
+
const ctx = makeCtx()
|
|
53
|
+
ctx.emitGeometryValidation('commit-move', 0, 'missing')
|
|
54
|
+
jest.runAllTimers()
|
|
55
|
+
expect(ctx.map.fire).not.toHaveBeenCalled()
|
|
56
|
+
jest.useRealTimers()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('pushUndo', () => {
|
|
61
|
+
test('is a no-op without a stack', () => {
|
|
62
|
+
const ctx = makeCtx({ map: { fire: jest.fn(), _undoStack: null } })
|
|
63
|
+
expect(() => ctx.pushUndo({ type: 'move' })).not.toThrow()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('pushes the operation and emits validation for a mapped op type', () => {
|
|
67
|
+
jest.useFakeTimers()
|
|
68
|
+
const ctx = makeCtx()
|
|
69
|
+
ctx.pushUndo({ type: 'move', featureId: 'feat-1', vertexIndex: 0 })
|
|
70
|
+
expect(ctx.map._undoStack.length).toBe(1)
|
|
71
|
+
|
|
72
|
+
jest.runAllTimers()
|
|
73
|
+
expect(ctx.map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({ phase: 'commit-move' }))
|
|
74
|
+
jest.useRealTimers()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('does not emit validation for an unmapped op type', () => {
|
|
78
|
+
jest.useFakeTimers()
|
|
79
|
+
const ctx = makeCtx()
|
|
80
|
+
ctx.pushUndo({ type: 'unmapped', featureId: 'feat-1' })
|
|
81
|
+
jest.runAllTimers()
|
|
82
|
+
expect(ctx.map.fire).not.toHaveBeenCalled()
|
|
83
|
+
jest.useRealTimers()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Shared by editPointMode/keyboardHandlers.js and editVertexMode/keyboardHandlers.js — both
|
|
2
|
+
// modes gate their shortcuts on the same "is focus somewhere that should eat the keystroke"
|
|
3
|
+
// and "is this the undo chord" questions, independent of the ring-vs-single-coordinate
|
|
4
|
+
// bookkeeping the rest of each mode's keyboard handling differs on.
|
|
5
|
+
export const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'])
|
|
6
|
+
export const ARROW_OFFSETS = { ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0] }
|
|
7
|
+
|
|
8
|
+
const INTERACTIVE_TAGS = new Set(['INPUT', 'TEXTAREA', 'BUTTON', 'SELECT', 'A'])
|
|
9
|
+
|
|
10
|
+
// Keyboard shortcuts are ignored while a form control outside the map viewport has
|
|
11
|
+
// focus, but still work for elements inside the viewport (e.g. draw toolbar buttons).
|
|
12
|
+
export const isInteractiveElementFocused = (state) => {
|
|
13
|
+
const el = document.activeElement
|
|
14
|
+
if (!el || el === document.body) { return false }
|
|
15
|
+
if (state.container?.contains(el)) { return false }
|
|
16
|
+
return INTERACTIVE_TAGS.has(el.tagName) || el.isContentEditable || el.hasAttribute('tabindex')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const isUndoShortcut = (e) => e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey
|
|
20
|
+
|
|
21
|
+
// Mixin method — spread into both modes' keyboardHandlers so `this.handleUndo` resolves to
|
|
22
|
+
// whichever mode it's mixed into.
|
|
23
|
+
export const sharedKeyboardHandlers = {
|
|
24
|
+
// Cmd/Ctrl+Z: undo the last edit, unless the user is typing in a text field.
|
|
25
|
+
handleUndoShortcut (state, e) {
|
|
26
|
+
const tag = document.activeElement?.tagName
|
|
27
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
e.preventDefault()
|
|
31
|
+
e.stopPropagation()
|
|
32
|
+
this.handleUndo(state)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isInteractiveElementFocused, isUndoShortcut, sharedKeyboardHandlers } from './keyboardShortcuts.js'
|
|
2
|
+
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
document.body.innerHTML = ''
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
describe('isInteractiveElementFocused', () => {
|
|
8
|
+
test('returns false when nothing has been focused (activeElement defaults to body)', () => {
|
|
9
|
+
expect(document.activeElement).toBe(document.body)
|
|
10
|
+
expect(isInteractiveElementFocused({ container: document.createElement('div') })).toBe(false)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('returns false for focus inside the container', () => {
|
|
14
|
+
const container = document.createElement('div')
|
|
15
|
+
const button = document.createElement('button')
|
|
16
|
+
container.appendChild(button)
|
|
17
|
+
document.body.appendChild(container)
|
|
18
|
+
button.focus()
|
|
19
|
+
expect(isInteractiveElementFocused({ container })).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('returns true for an interactive tag focused outside the container', () => {
|
|
23
|
+
const input = document.createElement('input')
|
|
24
|
+
document.body.appendChild(input)
|
|
25
|
+
input.focus()
|
|
26
|
+
expect(isInteractiveElementFocused({ container: document.createElement('div') })).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('returns true for a tabindex/contentEditable element focused outside the container', () => {
|
|
30
|
+
const div = document.createElement('div')
|
|
31
|
+
div.tabIndex = 0
|
|
32
|
+
document.body.appendChild(div)
|
|
33
|
+
div.focus()
|
|
34
|
+
expect(isInteractiveElementFocused({ container: document.createElement('div') })).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('a missing container is treated as outside — falls through to the tag/attribute check', () => {
|
|
38
|
+
const input = document.createElement('input')
|
|
39
|
+
document.body.appendChild(input)
|
|
40
|
+
input.focus()
|
|
41
|
+
expect(isInteractiveElementFocused({ container: undefined })).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('isUndoShortcut', () => {
|
|
46
|
+
test('true for Cmd+Z or Ctrl+Z without shift', () => {
|
|
47
|
+
expect(isUndoShortcut({ key: 'z', metaKey: true })).toBe(true)
|
|
48
|
+
expect(isUndoShortcut({ key: 'z', ctrlKey: true })).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('falsy when shift is also held, the key is wrong, or no modifier is held', () => {
|
|
52
|
+
expect(isUndoShortcut({ key: 'z', metaKey: true, shiftKey: true })).toBe(false)
|
|
53
|
+
expect(isUndoShortcut({ key: 'y', metaKey: true })).toBe(false)
|
|
54
|
+
expect(isUndoShortcut({ key: 'z' })).toBeFalsy()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('sharedKeyboardHandlers.handleUndoShortcut', () => {
|
|
59
|
+
test('swallows the default/propagation and delegates to handleUndo', () => {
|
|
60
|
+
const handleUndo = jest.fn()
|
|
61
|
+
const e = { preventDefault: jest.fn(), stopPropagation: jest.fn() }
|
|
62
|
+
sharedKeyboardHandlers.handleUndoShortcut.call({ handleUndo }, {}, e)
|
|
63
|
+
expect(e.preventDefault).toHaveBeenCalled()
|
|
64
|
+
expect(e.stopPropagation).toHaveBeenCalled()
|
|
65
|
+
expect(handleUndo).toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('is ignored while typing in an input/textarea', () => {
|
|
69
|
+
const input = document.createElement('input')
|
|
70
|
+
document.body.appendChild(input)
|
|
71
|
+
input.focus()
|
|
72
|
+
const handleUndo = jest.fn()
|
|
73
|
+
const e = { preventDefault: jest.fn(), stopPropagation: jest.fn() }
|
|
74
|
+
sharedKeyboardHandlers.handleUndoShortcut.call({ handleUndo }, {}, e)
|
|
75
|
+
expect(handleUndo).not.toHaveBeenCalled()
|
|
76
|
+
expect(e.preventDefault).not.toHaveBeenCalled()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -52,7 +52,8 @@ export function getSnapCoords (snap) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Trigger snap detection at a given point
|
|
55
|
+
* Trigger snap detection at a given point. Marked `_explicit` so it still runs even while
|
|
56
|
+
* setAutoSnapSuspended (below) has silenced the library's own auto-query.
|
|
56
57
|
* @param {MapboxSnap} snap - Snap instance
|
|
57
58
|
* @param {maplibregl.Map} map - Map instance
|
|
58
59
|
* @param {{ x: number, y: number }} point - Screen point
|
|
@@ -64,10 +65,22 @@ export function triggerSnapAtPoint (snap, map, point) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const lngLat = map.unproject(point)
|
|
67
|
-
snap.snapToClosestPoint({ point, lngLat })
|
|
68
|
+
snap.snapToClosestPoint({ point, lngLat, _explicit: true })
|
|
68
69
|
return true
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Silences the snap library's own mousemove-driven auto-query (see prototypePatches.js), which
|
|
74
|
+
* otherwise re-snaps at the raw cursor and fights an edit mode's own offset-aware drag query.
|
|
75
|
+
* @param {MapboxSnap} snap - Snap instance
|
|
76
|
+
* @param {boolean} suspended
|
|
77
|
+
*/
|
|
78
|
+
export function setAutoSnapSuspended (snap, suspended) {
|
|
79
|
+
if (snap) {
|
|
80
|
+
snap._autoSnapSuspended = suspended
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
/**
|
|
72
85
|
* Trigger snap detection at map center (for touch/keyboard modes)
|
|
73
86
|
* @param {MapboxSnap} snap - Snap instance
|
|
@@ -82,7 +95,7 @@ export function triggerSnapAtCenter (snap, map) {
|
|
|
82
95
|
|
|
83
96
|
const center = map.getCenter()
|
|
84
97
|
const point = map.project(center)
|
|
85
|
-
snap.snapToClosestPoint({ point, lngLat: center })
|
|
98
|
+
snap.snapToClosestPoint({ point, lngLat: center, _explicit: true })
|
|
86
99
|
return true
|
|
87
100
|
}
|
|
88
101
|
|