@defra/interactive-map 0.0.40-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/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +79 -16
- 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/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.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
- package/plugins/datasets/src/api/setOpacity.test.js +9 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
- package/plugins/datasets/src/datasets.scss +0 -1
- package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
- package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
- package/plugins/datasets/src/index.test.js +36 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
- package/plugins/datasets/src/manifest.js +0 -38
- package/plugins/datasets/src/manifest.test.js +49 -0
- package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
- package/plugins/datasets/src/reducers/pluginState.js +3 -16
- package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
- package/plugins/datasets/src/registry/dataset.js +13 -1
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
- package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
- package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
- package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
- package/plugins/datasets/src/utils/bbox.test.js +125 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +10 -2
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
- package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
- package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
- package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
- package/plugins/draw/src/api/addFeature.js +16 -1
- package/plugins/draw/src/api/addFeature.test.js +62 -5
- package/plugins/draw/src/api/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 +17 -10
- package/plugins/draw/src/events.test.js +14 -0
- package/plugins/draw/src/manifest.js +18 -6
- package/plugins/draw/src/manifest.test.js +32 -1
- package/plugins/draw/src/reducer.test.js +7 -0
- package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
- package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
- package/plugins/draw/src/utils/symbolKeys.js +5 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
- package/plugins/draw/src/validation/liveStroke.test.js +64 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/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 +2 -3
- package/plugins/search/src/components/Form/Form.module.scss +9 -20
- package/plugins/search/src/index.js +0 -1
- package/plugins/search/src/index.test.js +2 -4
- package/plugins/search/src/manifest.js +1 -1
- package/plugins/search/src/search.scss +17 -16
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
- package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
- package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
- package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
- package/providers/maplibre/src/utils/hoverCursor.js +30 -14
- package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
- package/providers/maplibre/src/utils/patternImages.js +1 -1
- package/providers/maplibre/src/utils/symbolImages.js +41 -3
- package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
- package/rollup.esm.mjs +11 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
- 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/hooks/useFeatureItems.js +16 -7
- package/src/App/hooks/useFeatureItems.test.js +57 -0
- package/src/App/hooks/useLayoutMeasurements.js +95 -84
- package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
- package/src/App/layout/Layout.jsx +9 -10
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/pluginRegistry.js +1 -1
- package/src/App/registry/pluginRegistry.test.js +12 -0
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/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 +1 -0
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/config/events.js +2 -0
- package/src/scss/settings/_colors.scss +1 -0
- package/src/scss/settings/_dimensions.scss +2 -3
- package/src/scss/settings/_transition.scss +1 -1
- package/src/services/eventBus.js +44 -0
- package/src/services/eventBus.test.js +83 -0
- package/src/services/symbolRegistry.js +1 -1
- package/src/types.js +11 -1
- 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
|
@@ -70,6 +70,42 @@ test('the offset target hides when its vertex cannot be projected', () => {
|
|
|
70
70
|
handler.destroy()
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
+
// The offset target is the drag handle under your finger and must track it exactly, even
|
|
74
|
+
// while snap pulls the vertex itself elsewhere (mirrors the ML adapter) — the vertex is what
|
|
75
|
+
// visibly "jumps" to the snap candidate, not the target. Once the drag actually ends, the
|
|
76
|
+
// target re-syncs to the vertex's own final (possibly snapped) position, collapsing the gap.
|
|
77
|
+
test('the offset target tracks the raw touch point while dragging, then snaps to the vertex on release', () => {
|
|
78
|
+
const snap = { apply: jest.fn((c) => [c[0] + 5, c[1]]), hideIndicator: jest.fn() }
|
|
79
|
+
const { container, handler, touch } = setup({}, { snap })
|
|
80
|
+
handler.updateTargetPosition()
|
|
81
|
+
touch('touchstart', 100, 0)
|
|
82
|
+
touch('touchmove', 120, 10) // raw touch → [120,10]; vertex snaps to [125,10]
|
|
83
|
+
const target = container.querySelector('[data-im-draw-touch-target]')
|
|
84
|
+
expect(target.style.left).toBe('120px') // tracks the finger…
|
|
85
|
+
expect(target.style.left).not.toBe('125px') // …not the snapped vertex, while still dragging
|
|
86
|
+
|
|
87
|
+
touch('touchend', 120, 10)
|
|
88
|
+
expect(target.style.left).toBe('125px') // re-synced to the vertex's actual final position
|
|
89
|
+
handler.destroy()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// touchcancel (the browser interrupting a gesture itself — a scroll takeover, app switch,
|
|
93
|
+
// etc.) must resync the target exactly like touchend does — without this listener a
|
|
94
|
+
// cancelled drag left the target stuck wherever the last touchmove put it.
|
|
95
|
+
test('touchcancel resyncs the target the same way touchend does', () => {
|
|
96
|
+
const snap = { apply: jest.fn((c) => [c[0] + 5, c[1]]), hideIndicator: jest.fn() }
|
|
97
|
+
const { container, handler, touch, grip } = setup({}, { snap })
|
|
98
|
+
handler.updateTargetPosition()
|
|
99
|
+
touch('touchstart', 100, 0)
|
|
100
|
+
touch('touchmove', 120, 10) // raw touch → [120,10]; vertex snaps to [125,10]
|
|
101
|
+
const target = container.querySelector('[data-im-draw-touch-target]')
|
|
102
|
+
expect(target.style.left).toBe('120px') // tracking the finger mid-drag
|
|
103
|
+
|
|
104
|
+
grip.dispatchEvent(domEvent('touchcancel', { touches: [{ clientX: 120, clientY: 10 }], changedTouches: [{ clientX: 120, clientY: 10 }] }))
|
|
105
|
+
expect(target.style.left).toBe('125px') // re-synced to the vertex's actual final position
|
|
106
|
+
handler.destroy()
|
|
107
|
+
})
|
|
108
|
+
|
|
73
109
|
test('a drag applies snapping to the moved coordinate when a snap manager is active', () => {
|
|
74
110
|
const snap = { apply: jest.fn((c) => [c[0] + 5, c[1]]), hideIndicator: jest.fn() }
|
|
75
111
|
const { state, handler, touch } = setup({}, { snap })
|
|
@@ -77,8 +113,17 @@ test('a drag applies snapping to the moved coordinate when a snap manager is act
|
|
|
77
113
|
touch('touchstart', 100, 0)
|
|
78
114
|
touch('touchmove', 120, 10)
|
|
79
115
|
expect(snap.apply).toHaveBeenCalled()
|
|
80
|
-
expect(snap.hideIndicator).toHaveBeenCalled()
|
|
81
116
|
expect(state.vertices[1]).toEqual([125, 10]) // dragged to [120,10] then snapped +5 on x
|
|
117
|
+
|
|
118
|
+
// Regression: a stray hideIndicator() right after snap.apply() on every touchmove undid its
|
|
119
|
+
// own show, so the indicator could never actually be seen mid-drag — it's only meant to
|
|
120
|
+
// clear for real once the drag actually finishes.
|
|
121
|
+
expect(snap.hideIndicator).not.toHaveBeenCalled()
|
|
122
|
+
// And it must stay showing after the drag ends too — snap.apply()'s own last call already
|
|
123
|
+
// left it correctly reflecting the snapped position (mirrors the ML adapter, which never
|
|
124
|
+
// hides its own indicator on release either).
|
|
125
|
+
touch('touchend', 120, 10)
|
|
126
|
+
expect(snap.hideIndicator).not.toHaveBeenCalled()
|
|
82
127
|
handler.destroy()
|
|
83
128
|
})
|
|
84
129
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { OLDrawManager } from './core/OLDrawManager.js'
|
|
2
2
|
import { MAP_SIZE_SCALES } from './defaults.js'
|
|
3
|
+
import { refreshAllPointSymbols } from './point/pointSymbolImages.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Creates the OLDrawManager, attaches it to mapProvider, and wires
|
|
6
|
-
* app-level events (MAP_SET_SIZE for scale-aware touch targets,
|
|
7
|
-
* MAP_SET_STYLE for dynamic color updates).
|
|
8
|
-
*
|
|
6
|
+
* Creates the OLDrawManager, attaches it to mapProvider, and wires app-level size/style events.
|
|
9
7
|
* @returns {{ manager: OLDrawManager, remove: () => void }}
|
|
10
8
|
*/
|
|
11
9
|
export const createOLDraw = ({ mapProvider, events, eventBus, pluginConfig = {}, mapStyle = null }) => {
|
|
@@ -18,13 +16,22 @@ export const createOLDraw = ({ mapProvider, events, eventBus, pluginConfig = {},
|
|
|
18
16
|
|
|
19
17
|
mapProvider.draw = manager
|
|
20
18
|
|
|
19
|
+
// Seed drawScale from the provider's own starting size — MAP_SET_SIZE only fires on a later runtime change, not the initial one.
|
|
20
|
+
mapProvider.drawScale = MAP_SIZE_SCALES[mapProvider.mapSize] ?? 1
|
|
21
|
+
|
|
22
|
+
// Nudges useHighlightSync to re-apply the highlight overlay — OL's own MAP_DATA_CHANGE is driven purely by basemap 'tileloadend', unrelated to the draw source.
|
|
23
|
+
const notifyPointSymbolsRefreshed = () => eventBus.emit(events.MAP_DATA_CHANGE)
|
|
24
|
+
|
|
21
25
|
const handleSetMapSize = (size) => {
|
|
22
26
|
mapProvider.drawScale = MAP_SIZE_SCALES[size] ?? 1
|
|
27
|
+
refreshAllPointSymbols({ manager, mapProvider }).then(notifyPointSymbolsRefreshed)
|
|
23
28
|
}
|
|
24
29
|
eventBus.on(events.MAP_SET_SIZE, handleSetMapSize)
|
|
25
30
|
|
|
26
31
|
const handleSetMapStyle = (newMapStyle) => {
|
|
27
32
|
manager.setMapStyle(newMapStyle)
|
|
33
|
+
// Rasterised point symbol images are style-scoped (colours resolve per map style).
|
|
34
|
+
refreshAllPointSymbols({ manager, mapProvider }).then(notifyPointSymbolsRefreshed)
|
|
28
35
|
}
|
|
29
36
|
eventBus.on(events.MAP_SET_STYLE, handleSetMapStyle)
|
|
30
37
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createOLDraw } from './olDraw.js'
|
|
2
2
|
import { OLDrawManager } from './core/OLDrawManager.js'
|
|
3
|
+
import { refreshAllPointSymbols } from './point/pointSymbolImages.js'
|
|
3
4
|
import { MAP_SIZE_SCALES } from './defaults.js'
|
|
4
5
|
|
|
5
6
|
jest.mock('./core/OLDrawManager.js', () => ({
|
|
@@ -8,15 +9,16 @@ jest.mock('./core/OLDrawManager.js', () => ({
|
|
|
8
9
|
this.remove = jest.fn()
|
|
9
10
|
})
|
|
10
11
|
}))
|
|
12
|
+
jest.mock('./point/pointSymbolImages.js', () => ({ refreshAllPointSymbols: jest.fn(() => Promise.resolve()) }))
|
|
11
13
|
|
|
12
|
-
const events = { MAP_SET_SIZE: 'app:size', MAP_SET_STYLE: 'app:style' }
|
|
14
|
+
const events = { MAP_SET_SIZE: 'app:size', MAP_SET_STYLE: 'app:style', MAP_DATA_CHANGE: 'app:datachange' }
|
|
13
15
|
|
|
14
16
|
const setup = (mapStyle = null) => {
|
|
15
17
|
const listeners = {}
|
|
16
18
|
const eventBus = {
|
|
17
19
|
on: jest.fn((type, handler) => { listeners[type] = handler }),
|
|
18
20
|
off: jest.fn(),
|
|
19
|
-
emit: (type, payload) => listeners[type]?.(payload)
|
|
21
|
+
emit: jest.fn((type, payload) => listeners[type]?.(payload))
|
|
20
22
|
}
|
|
21
23
|
const mapProvider = { map: { id: 'ol-map' } }
|
|
22
24
|
const olDraw = createOLDraw({ mapProvider, events, eventBus, pluginConfig: { snapRadius: 5 }, mapStyle })
|
|
@@ -35,14 +37,46 @@ test('creates the manager for the map, exposes it as mapProvider.draw and applie
|
|
|
35
37
|
expect(setup().manager.setMapStyle).not.toHaveBeenCalled() // no initial style
|
|
36
38
|
})
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
// Regression: MAP_SET_SIZE only ever fires from the map-size UI control being clicked at
|
|
41
|
+
// runtime, so a map that loads directly at medium/large left drawScale unset (defaulting to 1,
|
|
42
|
+
// i.e. small) until the user happened to change size — rasterising the first symbol placed at
|
|
43
|
+
// the wrong resolution. mapProvider.mapSize already holds the size the map actually loaded at.
|
|
44
|
+
test('seeds drawScale from the provider\'s own starting map size, before any size-change event', () => {
|
|
45
|
+
const eventBus = { on: jest.fn(), off: jest.fn() }
|
|
46
|
+
const mapProvider = { map: { id: 'ol-map' }, mapSize: 'large' }
|
|
47
|
+
createOLDraw({ mapProvider, events, eventBus })
|
|
48
|
+
expect(mapProvider.drawScale).toBe(MAP_SIZE_SCALES.large)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('defaults the seeded drawScale to 1 when the provider has no starting map size', () => {
|
|
52
|
+
const eventBus = { on: jest.fn(), off: jest.fn() }
|
|
53
|
+
const mapProvider = { map: { id: 'ol-map' } }
|
|
54
|
+
createOLDraw({ mapProvider, events, eventBus })
|
|
55
|
+
expect(mapProvider.drawScale).toBe(1)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('map size changes update the draw UI scale, defaulting to 1 for unknown sizes, and re-resolve point symbols', async () => {
|
|
59
|
+
const { eventBus, mapProvider, manager } = setup()
|
|
40
60
|
eventBus.emit(events.MAP_SET_SIZE, 'large')
|
|
41
61
|
expect(mapProvider.drawScale).toBe(MAP_SIZE_SCALES.large)
|
|
62
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledWith({ manager, mapProvider })
|
|
42
63
|
eventBus.emit(events.MAP_SET_SIZE, 'enormous')
|
|
43
64
|
expect(mapProvider.drawScale).toBe(1)
|
|
44
65
|
})
|
|
45
66
|
|
|
67
|
+
// A selected point's highlight overlay only re-applies on MAP_DATA_CHANGE, and (unlike
|
|
68
|
+
// MapLibre) OL's own MAP_DATA_CHANGE is driven purely by basemap 'tileloadend' — nothing
|
|
69
|
+
// ties it to the draw VectorSource, so without this explicit nudge a resize/style change
|
|
70
|
+
// with no tiles loading would never re-trigger the highlight refresh at all, leaving an
|
|
71
|
+
// already-selected point's ring stuck on its old (now wrong-resolution) icon.
|
|
72
|
+
test('emits MAP_DATA_CHANGE once point symbols have actually finished re-resolving after a size change', async () => {
|
|
73
|
+
const { eventBus, manager, mapProvider } = setup()
|
|
74
|
+
eventBus.emit(events.MAP_SET_SIZE, 'large')
|
|
75
|
+
await Promise.resolve() // flush the refreshAllPointSymbols().then(...) microtask
|
|
76
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledWith({ manager, mapProvider })
|
|
77
|
+
expect(eventBus.emit).toHaveBeenCalledWith(events.MAP_DATA_CHANGE)
|
|
78
|
+
})
|
|
79
|
+
|
|
46
80
|
test('pluginConfig and mapStyle are optional, defaulting to {} and no initial style', () => {
|
|
47
81
|
const eventBus = { on: jest.fn(), off: jest.fn() }
|
|
48
82
|
const mapProvider = { map: { id: 'ol-map' } }
|
|
@@ -54,10 +88,18 @@ test('pluginConfig and mapStyle are optional, defaulting to {} and no initial st
|
|
|
54
88
|
olDraw.remove()
|
|
55
89
|
})
|
|
56
90
|
|
|
57
|
-
test('map style changes are forwarded to the manager', () => {
|
|
58
|
-
const { eventBus, manager } = setup()
|
|
91
|
+
test('map style changes are forwarded to the manager and re-resolve point symbols', () => {
|
|
92
|
+
const { eventBus, manager, mapProvider } = setup()
|
|
59
93
|
eventBus.emit(events.MAP_SET_STYLE, { id: 'dark' })
|
|
60
94
|
expect(manager.setMapStyle).toHaveBeenCalledWith({ id: 'dark' })
|
|
95
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledWith({ manager, mapProvider })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('emits MAP_DATA_CHANGE once point symbols have actually finished re-resolving after a style change', async () => {
|
|
99
|
+
const { eventBus } = setup()
|
|
100
|
+
eventBus.emit(events.MAP_SET_STYLE, { id: 'dark' })
|
|
101
|
+
await Promise.resolve()
|
|
102
|
+
expect(eventBus.emit).toHaveBeenCalledWith(events.MAP_DATA_CHANGE)
|
|
61
103
|
})
|
|
62
104
|
|
|
63
105
|
test('remove unsubscribes, destroys the manager and clears mapProvider.draw', () => {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import Draw from 'ol/interaction/Draw.js'
|
|
2
|
+
import { noModifierKeys } from 'ol/events/condition.js'
|
|
3
|
+
import { wireInputEvents, applyCrossHairVisibility } from '../draw/drawInput.js'
|
|
4
|
+
import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
|
|
5
|
+
import { validatePlacement } from '../../../validation/validateGeometry.js'
|
|
6
|
+
import { TOLERANCES } from '../defaults.js'
|
|
7
|
+
|
|
8
|
+
const MODE = 'draw_point'
|
|
9
|
+
|
|
10
|
+
// A point has no ring/rubber-band to build a candidate from — just the coordinate about to
|
|
11
|
+
// be placed. Mirrors validateGeometry.js's attemptPlacement, but that helper hardcodes
|
|
12
|
+
// Polygon/LineString via MODE_BY_GEOMETRY, so a Point candidate is built and validated
|
|
13
|
+
// directly (same approach as the MapLibre adapter's drawPointMode.js).
|
|
14
|
+
const canPlaceVertex = (manager) => (coordinate) => {
|
|
15
|
+
const candidate = { type: 'Feature', geometry: { type: 'Point', coordinates: coordinate }, properties: {} }
|
|
16
|
+
const { valid, reason } = validatePlacement(
|
|
17
|
+
candidate,
|
|
18
|
+
{ mode: MODE, vertexIndex: 0 },
|
|
19
|
+
{ onGeometryChange: manager._geometryValidator }
|
|
20
|
+
)
|
|
21
|
+
if (!valid) {
|
|
22
|
+
manager.emit(ADAPTER_EVENTS.PLACEMENT_BLOCKED, { feature: candidate, reason: reason ?? null, phase: 'place', mode: MODE, vertexIndex: 0 })
|
|
23
|
+
}
|
|
24
|
+
return valid
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Commit a finished point to the store under the requested id and emit CREATE — mirrors
|
|
28
|
+
// draw/DrawMode.js's finalizeDrawnFeature (identical shape, kept separate rather than
|
|
29
|
+
// imported since draw/DrawMode.js isn't otherwise a shared dependency of this mode).
|
|
30
|
+
// Symbol resolution runs after the feature is in the store (not before), matching the
|
|
31
|
+
// MapLibre adapter's own ordering. The resolver itself is injected by OLDrawManager via
|
|
32
|
+
// changeMode's options (mirrors the ML adapter's state.resolvePointSymbol convention — see
|
|
33
|
+
// MaplibreDrawAdapter.js's changeMode) so this mode has no direct dependency on
|
|
34
|
+
// symbolRegistry/mapProvider/pointSymbolImages.js.
|
|
35
|
+
const finalizeDrawnFeature = (manager, resolvePointSymbol, olFeature, featureId, properties) => {
|
|
36
|
+
olFeature.setId(String(featureId))
|
|
37
|
+
olFeature.setProperties(properties)
|
|
38
|
+
manager.store.source.addFeature(olFeature)
|
|
39
|
+
manager.emit(ADAPTER_EVENTS.CREATE, manager.store.toGeoJSON(olFeature))
|
|
40
|
+
resolvePointSymbol?.(olFeature)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Touch/keyboard/"Add point" input for draw_point — the same concerns as
|
|
45
|
+
* draw/drawInput.js (crosshair, interface-type tracking, add-vertex-button/Enter), reused
|
|
46
|
+
* directly, but with a much smaller placement step: a point has no rubber band to update
|
|
47
|
+
* while positioning (the crosshair itself already shows where it'll land) and nothing to
|
|
48
|
+
* undo before a single-click commit, so those callbacks are no-ops here.
|
|
49
|
+
*/
|
|
50
|
+
const buildPointInput = ({ drawInteraction, options, canPlace }) => {
|
|
51
|
+
const { container, addVertexButtonId, mapProvider, snap, crossHair } = options
|
|
52
|
+
let interfaceType = options.interfaceType ?? 'mouse'
|
|
53
|
+
const getInterfaceType = () => interfaceType
|
|
54
|
+
|
|
55
|
+
// appendCoordinates() only builds up LineString/Polygon rings — for a Point-type Draw
|
|
56
|
+
// interaction it does nothing beyond starting the sketch (confirmed by reading OL's own
|
|
57
|
+
// source), so finishDrawing() has to be called explicitly right after to actually commit.
|
|
58
|
+
const placePoint = () => {
|
|
59
|
+
const raw = mapProvider.getCenter()
|
|
60
|
+
const coord = (getInterfaceType() !== 'mouse' && snap) ? snap.apply(raw) : raw
|
|
61
|
+
snap?.hideIndicator()
|
|
62
|
+
if (!canPlace(coord)) { return }
|
|
63
|
+
drawInteraction.appendCoordinates([coord])
|
|
64
|
+
drawInteraction.finishDrawing()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Keeps the snap indicator visible at the crosshair while positioning via touch/keyboard
|
|
68
|
+
// (mouse hover is handled by the separate OL snap interaction already attached by
|
|
69
|
+
// OLDrawManager) — the closest point-shaped equivalent of updateRubberbanding.
|
|
70
|
+
const updateSnapIndicator = () => {
|
|
71
|
+
if (getInterfaceType() !== 'mouse' && snap) { snap.apply(mapProvider.getCenter()) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Mirrors draw/drawInput.js's createDrawInput: an interface-type switch to touch/keyboard
|
|
75
|
+
// must refresh the snap indicator immediately at the crosshair, not wait for the next
|
|
76
|
+
// pointermove/change:center — otherwise it stays hidden until the user first pans or drags
|
|
77
|
+
// after entering the mode (the same gap the MapLibre adapter's onInterfaceTypeChange already
|
|
78
|
+
// guards against by calling onMove(state) right after _setInterface).
|
|
79
|
+
const applyInterfaceType = (type) => {
|
|
80
|
+
interfaceType = type
|
|
81
|
+
applyCrossHairVisibility(crossHair, type)
|
|
82
|
+
updateSnapIndicator()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const events = wireInputEvents({
|
|
86
|
+
container,
|
|
87
|
+
addVertexButtonId,
|
|
88
|
+
olView: drawInteraction.getMap()?.getView(),
|
|
89
|
+
onUndo: null,
|
|
90
|
+
getInterfaceType,
|
|
91
|
+
setInterfaceType: applyInterfaceType,
|
|
92
|
+
clearLastCoord: () => {},
|
|
93
|
+
updateRubberbanding: updateSnapIndicator,
|
|
94
|
+
placeVertex: placePoint
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
applyCrossHairVisibility(crossHair, interfaceType)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
getInterfaceType,
|
|
101
|
+
setInterfaceType: applyInterfaceType,
|
|
102
|
+
destroy () { events.destroy() }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Draw mode for placing a single point: mouse click, or crosshair + Enter/"Add point"
|
|
108
|
+
* button for touch/keyboard. Commits immediately on the first placement — unlike
|
|
109
|
+
* draw_line/draw_polygon, there is no rubber band, no multi-vertex undo, and no "Done"
|
|
110
|
+
* step (mirrors the MapLibre adapter's drawPointMode.js decision).
|
|
111
|
+
*
|
|
112
|
+
* OL's own Draw({type:'Point'}) interaction already finishes a real mouse click
|
|
113
|
+
* immediately (confirmed by reading its handleUpEvent — mode 'Point' always attempts
|
|
114
|
+
* finishDrawing() on pointer-up, no second click needed), so the mouse path needs no
|
|
115
|
+
* extra wiring beyond the placement-veto condition every mode already has. Deliberately
|
|
116
|
+
* NOT built via draw/DrawMode.js's createDrawMode()/createVertexPlacement() — those are
|
|
117
|
+
* parametrised around a ring of placed vertices + a trailing rubber-band coordinate,
|
|
118
|
+
* which a single-click commit doesn't have (mirrors the same decision already made for
|
|
119
|
+
* the MapLibre adapter, and the OL EditMode/DrawMode split noted in
|
|
120
|
+
* feedback_adapter_parity.md: don't force point through machinery shaped for lines/polygons
|
|
121
|
+
* just because they happen to share a file).
|
|
122
|
+
*/
|
|
123
|
+
export const createDrawPointMode = ({ map, manager, options }) => {
|
|
124
|
+
const { featureId, properties = {} } = options
|
|
125
|
+
const canPlace = canPlaceVertex(manager)
|
|
126
|
+
|
|
127
|
+
const drawInteraction = new Draw({
|
|
128
|
+
type: 'Point',
|
|
129
|
+
// Without an explicit style, OL renders its own default sketch style (a blue circle)
|
|
130
|
+
// following the cursor before commit — core/styles.js's createSketchStyle() already
|
|
131
|
+
// suppresses this same default for the "ghost" Point sketch OL renders alongside a
|
|
132
|
+
// Polygon/LineString sketch (see its own comment); a real Point sketch needs the same
|
|
133
|
+
// treatment, since it commits on the very first click with nothing meaningful to preview.
|
|
134
|
+
style: () => [],
|
|
135
|
+
snapTolerance: TOLERANCES.snapRadius,
|
|
136
|
+
condition: (e) => noModifierKeys(e) && canPlace(e.coordinate)
|
|
137
|
+
})
|
|
138
|
+
map.addInteraction(drawInteraction)
|
|
139
|
+
|
|
140
|
+
drawInteraction.on('drawend', (e) => finalizeDrawnFeature(manager, options.resolvePointSymbol, e.feature, featureId, properties))
|
|
141
|
+
drawInteraction.on('drawabort', () => { manager.emit(ADAPTER_EVENTS.CANCEL) })
|
|
142
|
+
|
|
143
|
+
const input = buildPointInput({ drawInteraction, options, canPlace })
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
done () {}, // no-op: commit is immediate, there's no pending sketch to finish
|
|
147
|
+
cancel () { drawInteraction.abortDrawing() },
|
|
148
|
+
undo () {}, // no-op: nothing placed yet to undo before commit
|
|
149
|
+
setInterfaceType (type) { input.setInterfaceType(type) },
|
|
150
|
+
destroy () {
|
|
151
|
+
manager.emit(ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE, { interfaceType: input.getInterfaceType() })
|
|
152
|
+
input.destroy()
|
|
153
|
+
map.removeInteraction(drawInteraction)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { createDrawPointMode } from './drawPointMode.js'
|
|
2
|
+
import { wireInputEvents, applyCrossHairVisibility } from '../draw/drawInput.js'
|
|
3
|
+
import { createFeatureStore } from '../core/featureStore.js'
|
|
4
|
+
import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
|
|
5
|
+
import { createFakeMap, createFakeManager, pointFeature } from '../__helpers__/harness.js'
|
|
6
|
+
|
|
7
|
+
jest.mock('../draw/drawInput.js', () => ({
|
|
8
|
+
wireInputEvents: jest.fn(() => ({ destroy: jest.fn() })),
|
|
9
|
+
applyCrossHairVisibility: jest.fn()
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
// Matches DrawMode.test.js's own convention: real OL interactions are used, but drawstart/
|
|
13
|
+
// drawend/drawabort are dispatched as plain event objects directly (rather than exercised
|
|
14
|
+
// through the real appendCoordinates/startDrawing_/finishDrawing internals, which need a
|
|
15
|
+
// fully-featured OL map — getMap().getView().getProjection() — well beyond what the
|
|
16
|
+
// lightweight fake map here provides or needs to).
|
|
17
|
+
const setup = (options = {}) => {
|
|
18
|
+
const map = createFakeMap()
|
|
19
|
+
const manager = createFakeManager()
|
|
20
|
+
manager.store = createFeatureStore()
|
|
21
|
+
const emitted = () => manager.emit.mock.calls.map(([type, payload]) => ({ type, payload }))
|
|
22
|
+
const mode = createDrawPointMode({
|
|
23
|
+
map,
|
|
24
|
+
manager,
|
|
25
|
+
options: {
|
|
26
|
+
featureId: 'point-1',
|
|
27
|
+
properties: { symbol: 'pin' },
|
|
28
|
+
container: null,
|
|
29
|
+
crossHair: null,
|
|
30
|
+
snap: null,
|
|
31
|
+
mapProvider: { getCenter: () => [5, 5] },
|
|
32
|
+
...options
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
const interaction = map.interactions[0]
|
|
36
|
+
const wireArgs = wireInputEvents.mock.calls.at(-1)[0]
|
|
37
|
+
return { map, manager, emitted, mode, interaction, wireArgs }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
afterEach(() => jest.clearAllMocks())
|
|
41
|
+
|
|
42
|
+
describe('createDrawPointMode', () => {
|
|
43
|
+
test('adds a Point-type Draw interaction to the map', () => {
|
|
44
|
+
const { interaction } = setup()
|
|
45
|
+
expect(interaction).toBeDefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('suppresses OL\'s default sketch style — no blue cursor-following circle', () => {
|
|
49
|
+
const { interaction } = setup()
|
|
50
|
+
// OL stores the resolved style on the sketch overlay layer, not on the interaction
|
|
51
|
+
// itself — this is what OL actually calls per frame to render the sketch/hover point.
|
|
52
|
+
expect(interaction.overlay_.getStyle()()).toEqual([])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('drawend commits the sketch under the requested id and fires CREATE', () => {
|
|
56
|
+
const { interaction, manager, emitted } = setup()
|
|
57
|
+
interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })
|
|
58
|
+
|
|
59
|
+
const created = emitted().filter((e) => e.type === ADAPTER_EVENTS.CREATE)
|
|
60
|
+
expect(created).toHaveLength(1)
|
|
61
|
+
expect(created[0].payload.id).toBe('point-1') // re-id'd, not the OL-generated one
|
|
62
|
+
expect(created[0].payload.geometry.coordinates).toEqual([1, 2])
|
|
63
|
+
expect(created[0].payload.properties).toEqual({ symbol: 'pin' })
|
|
64
|
+
expect(manager.store.get('point-1')).toBeTruthy()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('calls the adapter-injected resolvePointSymbol hook with the committed OL feature, after re-id', () => {
|
|
68
|
+
const resolvePointSymbol = jest.fn()
|
|
69
|
+
const { interaction } = setup({ resolvePointSymbol })
|
|
70
|
+
interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })
|
|
71
|
+
|
|
72
|
+
expect(resolvePointSymbol).toHaveBeenCalledTimes(1)
|
|
73
|
+
const [olFeature] = resolvePointSymbol.mock.calls[0]
|
|
74
|
+
expect(olFeature.getId()).toBe('point-1')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('does not throw when no resolvePointSymbol hook was supplied', () => {
|
|
78
|
+
const { interaction } = setup()
|
|
79
|
+
expect(() => interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })).not.toThrow()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('defaults properties to {} when none are supplied', () => {
|
|
83
|
+
const { interaction, manager, emitted } = setup({ properties: undefined })
|
|
84
|
+
interaction.dispatchEvent({ type: 'drawend', feature: pointFeature([1, 2], 'ol-generated-id') })
|
|
85
|
+
const created = emitted().filter((e) => e.type === ADAPTER_EVENTS.CREATE)
|
|
86
|
+
// OL's GeoJSON writer reports no properties as null (not {}) — the meaningful
|
|
87
|
+
// assertion is what setProperties() was actually called with, on the stored feature.
|
|
88
|
+
expect(created[0].payload.properties).toBeNull()
|
|
89
|
+
const { geometry, ...nonGeometryProps } = manager.store.getOL('point-1').getProperties()
|
|
90
|
+
expect(nonGeometryProps).toEqual({})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('condition vetoes placement via onGeometryChange', () => {
|
|
94
|
+
const { interaction, manager, emitted } = setup()
|
|
95
|
+
manager._geometryValidator = () => ({ valid: false, reason: 'not allowed here' })
|
|
96
|
+
const condition = interaction.condition_
|
|
97
|
+
const allowed = condition({ coordinate: [1, 2], originalEvent: {} })
|
|
98
|
+
expect(allowed).toBe(false)
|
|
99
|
+
const blocked = emitted().filter((e) => e.type === ADAPTER_EVENTS.PLACEMENT_BLOCKED)
|
|
100
|
+
expect(blocked).toHaveLength(1)
|
|
101
|
+
expect(blocked[0].payload).toMatchObject({ reason: 'not allowed here', phase: 'place', mode: 'draw_point' })
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('condition allows an unvetoed placement', () => {
|
|
105
|
+
const { interaction } = setup()
|
|
106
|
+
const condition = interaction.condition_
|
|
107
|
+
expect(condition({ coordinate: [1, 2], originalEvent: {} })).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('condition blocks modifier-key clicks', () => {
|
|
111
|
+
const { interaction } = setup()
|
|
112
|
+
const condition = interaction.condition_
|
|
113
|
+
expect(condition({ coordinate: [1, 2], originalEvent: { shiftKey: true } })).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('drawabort emits CANCEL', () => {
|
|
117
|
+
const { interaction, emitted } = setup()
|
|
118
|
+
interaction.dispatchEvent({ type: 'drawabort' })
|
|
119
|
+
expect(emitted().some((e) => e.type === ADAPTER_EVENTS.CANCEL)).toBe(true)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('touch/keyboard placement (via the wired placeVertex callback)', () => {
|
|
123
|
+
// appendCoordinates()/finishDrawing() are OL-internal machinery already exercised by
|
|
124
|
+
// OL's own test suite — what this mode owns is calling them with the right (possibly
|
|
125
|
+
// snapped) coordinate, gated by the same placement veto the mouse path uses.
|
|
126
|
+
const spyOnCommit = (interaction) => ({
|
|
127
|
+
append: jest.spyOn(interaction, 'appendCoordinates').mockImplementation(() => {}),
|
|
128
|
+
finish: jest.spyOn(interaction, 'finishDrawing').mockImplementation(() => {})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('places at the (possibly snapped) map centre and hides the indicator', () => {
|
|
132
|
+
const snap = { apply: jest.fn((c) => [c[0] + 1, c[1] + 1]), hideIndicator: jest.fn() }
|
|
133
|
+
const mapProvider = { getCenter: () => [5, 5] }
|
|
134
|
+
const { wireArgs, interaction } = setup({ snap, mapProvider, interfaceType: 'touch' })
|
|
135
|
+
const { append, finish } = spyOnCommit(interaction)
|
|
136
|
+
|
|
137
|
+
wireArgs.placeVertex()
|
|
138
|
+
|
|
139
|
+
expect(snap.apply).toHaveBeenCalledWith([5, 5])
|
|
140
|
+
expect(snap.hideIndicator).toHaveBeenCalled()
|
|
141
|
+
expect(append).toHaveBeenCalledWith([[6, 6]])
|
|
142
|
+
expect(finish).toHaveBeenCalled()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('does not snap on mouse interface', () => {
|
|
146
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
147
|
+
const { wireArgs, interaction } = setup({ snap, interfaceType: 'mouse' })
|
|
148
|
+
const { append } = spyOnCommit(interaction)
|
|
149
|
+
wireArgs.placeVertex()
|
|
150
|
+
expect(snap.apply).not.toHaveBeenCalled()
|
|
151
|
+
expect(append).toHaveBeenCalledWith([[5, 5]])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('a vetoed crosshair placement never commits', () => {
|
|
155
|
+
const { wireArgs, manager, emitted, interaction } = setup()
|
|
156
|
+
const { append, finish } = spyOnCommit(interaction)
|
|
157
|
+
manager._geometryValidator = () => false
|
|
158
|
+
wireArgs.placeVertex()
|
|
159
|
+
expect(append).not.toHaveBeenCalled()
|
|
160
|
+
expect(finish).not.toHaveBeenCalled()
|
|
161
|
+
expect(emitted().filter((e) => e.type === ADAPTER_EVENTS.PLACEMENT_BLOCKED)).toHaveLength(1)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('updateRubberbanding shows the snap indicator at the crosshair for touch/keyboard, not mouse', () => {
|
|
165
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
166
|
+
const mapProvider = { getCenter: () => [7, 7] }
|
|
167
|
+
const { wireArgs } = setup({ snap, mapProvider, interfaceType: 'keyboard' })
|
|
168
|
+
wireArgs.updateRubberbanding()
|
|
169
|
+
expect(snap.apply).toHaveBeenCalledWith([7, 7])
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('updateRubberbanding does nothing on mouse interface', () => {
|
|
173
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
174
|
+
const { wireArgs } = setup({ snap, interfaceType: 'mouse' })
|
|
175
|
+
wireArgs.updateRubberbanding()
|
|
176
|
+
expect(snap.apply).not.toHaveBeenCalled()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// Regression: without this, switching to touch/keyboard left the indicator hidden until
|
|
180
|
+
// the next pointermove/change:center — mirrors draw/drawInput.js's createDrawInput and
|
|
181
|
+
// the MapLibre adapter's onInterfaceTypeChange (which calls onMove(state) for the same
|
|
182
|
+
// reason).
|
|
183
|
+
test('switching interface type via the wired setInterfaceType immediately refreshes the snap indicator at the crosshair', () => {
|
|
184
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
185
|
+
const mapProvider = { getCenter: () => [9, 9] }
|
|
186
|
+
const { wireArgs } = setup({ snap, mapProvider, interfaceType: 'mouse' })
|
|
187
|
+
wireArgs.setInterfaceType('touch')
|
|
188
|
+
expect(snap.apply).toHaveBeenCalledWith([9, 9])
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('switching interface type via the wired setInterfaceType does not query snap for mouse', () => {
|
|
192
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
193
|
+
const { wireArgs } = setup({ snap, interfaceType: 'touch' })
|
|
194
|
+
wireArgs.setInterfaceType('mouse')
|
|
195
|
+
expect(snap.apply).not.toHaveBeenCalled()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('clearLastCoord is a harmless no-op (points have no lastPlacedCoord bookkeeping)', () => {
|
|
199
|
+
const { wireArgs } = setup()
|
|
200
|
+
expect(() => wireArgs.clearLastCoord()).not.toThrow()
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('applies crosshair visibility for the initial interface type on setup', () => {
|
|
205
|
+
setup({ interfaceType: 'touch' })
|
|
206
|
+
expect(applyCrossHairVisibility).toHaveBeenCalledWith(null, 'touch')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('mode API', () => {
|
|
210
|
+
test('done/undo are no-ops — a single click has nothing pending or to undo', () => {
|
|
211
|
+
const { mode } = setup()
|
|
212
|
+
expect(() => mode.done()).not.toThrow()
|
|
213
|
+
expect(() => mode.undo()).not.toThrow()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('cancel aborts the in-progress sketch', () => {
|
|
217
|
+
const { mode, interaction } = setup()
|
|
218
|
+
const abortSpy = jest.spyOn(interaction, 'abortDrawing').mockImplementation(() => {})
|
|
219
|
+
mode.cancel()
|
|
220
|
+
expect(abortSpy).toHaveBeenCalled()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('setInterfaceType delegates to applyCrossHairVisibility', () => {
|
|
224
|
+
const { mode } = setup()
|
|
225
|
+
applyCrossHairVisibility.mockClear()
|
|
226
|
+
mode.setInterfaceType('touch')
|
|
227
|
+
expect(applyCrossHairVisibility).toHaveBeenCalledWith(null, 'touch')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('setInterfaceType also refreshes the snap indicator at the crosshair for touch/keyboard', () => {
|
|
231
|
+
const snap = { apply: jest.fn(), hideIndicator: jest.fn() }
|
|
232
|
+
const mapProvider = { getCenter: () => [3, 4] }
|
|
233
|
+
const { mode } = setup({ snap, mapProvider })
|
|
234
|
+
mode.setInterfaceType('keyboard')
|
|
235
|
+
expect(snap.apply).toHaveBeenCalledWith([3, 4])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('destroy emits the final interface type, tears down input and removes the interaction', () => {
|
|
239
|
+
const { mode, map, interaction, emitted } = setup()
|
|
240
|
+
const removeSpy = jest.spyOn(map, 'removeInteraction')
|
|
241
|
+
mode.destroy()
|
|
242
|
+
expect(emitted().some((e) => e.type === ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE)).toBe(true)
|
|
243
|
+
expect(removeSpy).toHaveBeenCalledWith(interaction)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
})
|