@defra/interactive-map 0.0.40-alpha → 0.0.42-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/button-definition.md +7 -8
- package/docs/api/control-definition.md +17 -1
- package/docs/api/panel-definition.md +5 -0
- package/docs/api/slots.md +50 -6
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/add-polygons.mdx +8 -2
- package/docs/examples/add-symbols.mdx +8 -2
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/datasets.md +23 -15
- package/docs/plugins/draw.md +79 -16
- package/docs/plugins/map-key.md +147 -0
- package/docs/plugins/search.md +1 -1
- package/docs/plugins.md +5 -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/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.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/createNewShape.js +41 -0
- package/plugins/draw/src/api/createNewShape.test.js +63 -0
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newLine.js +2 -40
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/newPolygon.js +2 -40
- 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/manifest.js +1 -1
- 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 +68 -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 +43 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +21 -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 +26 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +22 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.jsx +7 -2
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +8 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +26 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.jsx +8 -3
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +17 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +53 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +147 -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/KeyboardHelp/KeyboardHelp.jsx +25 -23
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +22 -5
- package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
- package/src/App/components/Panel/Panel.jsx +65 -8
- package/src/App/components/Panel/Panel.module.scss +49 -15
- package/src/App/components/Panel/Panel.test.jsx +114 -1
- package/src/App/components/Tabs/Tabs.jsx +43 -17
- package/src/App/components/Tabs/Tabs.module.scss +32 -5
- package/src/App/components/Tabs/Tabs.test.jsx +40 -10
- 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/groupByKey.js +28 -0
- package/src/App/renderer/groupByKey.test.js +39 -0
- package/src/App/renderer/groupIntoTabs.js +42 -0
- package/src/App/renderer/groupIntoTabs.test.js +95 -0
- package/src/App/renderer/mapButtons.js +97 -129
- package/src/App/renderer/mapButtons.test.js +67 -83
- package/src/App/renderer/mapControls.js +4 -1
- package/src/App/renderer/mapControls.test.js +32 -0
- package/src/App/renderer/mapPanels.js +53 -11
- package/src/App/renderer/mapPanels.test.js +95 -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/appConfig.js +2 -2
- 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 +31 -3
- 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/KeySvgRect.jsx +0 -22
- 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
|
@@ -9,10 +9,17 @@ import { ADAPTER_EVENTS } from './adapterEvents.js'
|
|
|
9
9
|
import { validateGeometry } from './validation/validateGeometry.js'
|
|
10
10
|
import { MIN_VERTICES_REASONS } from './validation/rules.js'
|
|
11
11
|
import { MAP_SIZE_SCALES } from './defaults.js'
|
|
12
|
+
import { stripInternalProperties } from './utils/stripInternalProperties.js'
|
|
12
13
|
|
|
13
14
|
const EDIT_VERTEX_MODE = 'edit_vertex'
|
|
14
15
|
const GEOMETRY_INVALID_EVENT = 'draw:geometryinvalid'
|
|
15
16
|
|
|
17
|
+
// edit_point behaves like edit_vertex for the two checks below (restore-on-cancel, live
|
|
18
|
+
// invalid-stroke gating) — a relocate session, just with one coordinate instead of a ring.
|
|
19
|
+
// enterEditVertexMode (below) stays edit_vertex-only — it's only ever reached for a shape
|
|
20
|
+
// that finished invalid, and Point geometry has no such failure mode (see rules.js).
|
|
21
|
+
const EDIT_MODES = new Set([EDIT_VERTEX_MODE, 'edit_point'])
|
|
22
|
+
|
|
16
23
|
// A shape that simply hasn't reached its minimum vertex count yet isn't a mistake —
|
|
17
24
|
// it's the normal, expected state of "still being drawn" — so it's excluded below
|
|
18
25
|
// even though it's a genuine SOFT_RULES failure like any other.
|
|
@@ -86,10 +93,10 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
86
93
|
handleDone: () => { draw.done() },
|
|
87
94
|
handleCancel: () => {
|
|
88
95
|
const mode = draw.getMode()
|
|
89
|
-
if (mode
|
|
96
|
+
if (EDIT_MODES.has(mode) && tempFeature?.id) { draw.add(feature) }
|
|
90
97
|
pendingCreateId = null
|
|
91
98
|
draw.cancel(); resetState()
|
|
92
|
-
eventBus.emit('draw:cancelled', feature)
|
|
99
|
+
eventBus.emit('draw:cancelled', stripInternalProperties(feature))
|
|
93
100
|
},
|
|
94
101
|
handleUndo: () => draw.undo(),
|
|
95
102
|
handleDeleteVertex: () => draw.deleteVertex(),
|
|
@@ -104,11 +111,11 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
104
111
|
const { valid, reason } = validateGeometry(f, { phase: 'create', mode: draw.getMode() }, { onGeometryChange: draw._geometryValidator })
|
|
105
112
|
if (!valid) {
|
|
106
113
|
pendingCreateId = f.id
|
|
107
|
-
emitGeometryInvalid({ feature: f, reason, phase: 'create', mode: EDIT_VERTEX_MODE })
|
|
114
|
+
emitGeometryInvalid({ feature: stripInternalProperties(f), reason, phase: 'create', mode: EDIT_VERTEX_MODE })
|
|
108
115
|
setTimeout(() => enterEditVertexMode({ draw, appState, appConfig, mapState, dispatch }, f.id), 0)
|
|
109
116
|
return
|
|
110
117
|
}
|
|
111
|
-
resetState(); setTimeout(() => draw.changeMode('disabled'), 0); eventBus.emit('draw:created', f)
|
|
118
|
+
resetState(); setTimeout(() => draw.changeMode('disabled'), 0); eventBus.emit('draw:created', stripInternalProperties(f))
|
|
112
119
|
},
|
|
113
120
|
onEditFinish: (f) => {
|
|
114
121
|
resetState()
|
|
@@ -116,15 +123,15 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
116
123
|
// A shape that was drawn-then-fixed reports as a creation, not an edit.
|
|
117
124
|
if (pendingCreateId && f.id === pendingCreateId) {
|
|
118
125
|
pendingCreateId = null
|
|
119
|
-
eventBus.emit('draw:created', f)
|
|
126
|
+
eventBus.emit('draw:created', stripInternalProperties(f))
|
|
120
127
|
} else {
|
|
121
|
-
eventBus.emit('draw:edited', f)
|
|
128
|
+
eventBus.emit('draw:edited', stripInternalProperties(f))
|
|
122
129
|
}
|
|
123
130
|
},
|
|
124
131
|
onCancel: () => {},
|
|
125
132
|
...createVertexSelectionHandlers({ draw, pluginState, mapProvider, eventBus }),
|
|
126
133
|
onUndoChange: (l) => { pluginState.dispatch({ type: 'SET_UNDO_STACK_LENGTH', payload: l }) },
|
|
127
|
-
onUpdate: (f) => { eventBus.emit('draw:updated', f) },
|
|
134
|
+
onUpdate: (f) => { eventBus.emit('draw:updated', stripInternalProperties(f)) },
|
|
128
135
|
onGeometryChange: (e) => {
|
|
129
136
|
// Only commit-level changes (commit-add/move/insert/delete) carry a `phase`.
|
|
130
137
|
// Preview events (e.g. split's live preview) have none and are ignored.
|
|
@@ -141,17 +148,17 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
141
148
|
// The invalid stroke is committed-validity-driven in edit mode only; in draw
|
|
142
149
|
// mode the adapters drive it live from the displayed geometry (placed
|
|
143
150
|
// vertices + cursor) on every rubber-band move.
|
|
144
|
-
if (mode
|
|
151
|
+
if (EDIT_MODES.has(mode)) {
|
|
145
152
|
draw.setInvalid?.(!valid)
|
|
146
153
|
}
|
|
147
154
|
if (!valid) {
|
|
148
|
-
emitGeometryInvalid({ reason, ...context, feature: e.feature })
|
|
155
|
+
emitGeometryInvalid({ reason, ...context, feature: stripInternalProperties(e.feature) })
|
|
149
156
|
}
|
|
150
157
|
},
|
|
151
158
|
// A vertex placement was rejected (hard rule or user callback veto). Surface it
|
|
152
159
|
// on the public bus with phase 'place', and as a hint toast, so the reason is visible.
|
|
153
160
|
onPlacementBlocked: (e) => {
|
|
154
|
-
emitGeometryInvalid(e)
|
|
161
|
+
emitGeometryInvalid({ ...e, feature: stripInternalProperties(e.feature) })
|
|
155
162
|
},
|
|
156
163
|
// Live (mid-drag) validity flip while editing — the displayed shape is exactly
|
|
157
164
|
// what Done finishes there, so it gates the Done button in real time. Emitted
|
|
@@ -88,6 +88,13 @@ describe('button handlers', () => {
|
|
|
88
88
|
expect(mapProvider.activeMoveTarget).toBeNull()
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
+
test('cancel re-adds the feature when cancelling a point edit too', () => {
|
|
92
|
+
const { buttonConfig, draw } = setup()
|
|
93
|
+
draw.getMode.mockReturnValue('edit_point')
|
|
94
|
+
buttonConfig.drawCancel.onClick()
|
|
95
|
+
expect(draw.add).toHaveBeenCalledWith({ id: 'F' })
|
|
96
|
+
})
|
|
97
|
+
|
|
91
98
|
test('cancel does not re-add outside a vertex edit', () => {
|
|
92
99
|
const { buttonConfig, draw } = setup()
|
|
93
100
|
draw.getMode.mockReturnValue('draw_polygon')
|
|
@@ -308,6 +315,13 @@ describe('geometrychange validation', () => {
|
|
|
308
315
|
expect(draw.setInvalid).toHaveBeenCalledWith(true)
|
|
309
316
|
})
|
|
310
317
|
|
|
318
|
+
test('drives the invalid stroke from committed validity in a point edit too', () => {
|
|
319
|
+
const { draw } = setup()
|
|
320
|
+
draw.getMode.mockReturnValue('edit_point')
|
|
321
|
+
drawHandler(draw, 'geometrychange')({ feature: squareFeature, phase: 'commit-move', vertexIndex: 0 })
|
|
322
|
+
expect(draw.setInvalid).toHaveBeenCalledWith(false)
|
|
323
|
+
})
|
|
324
|
+
|
|
311
325
|
test('a live validity flip (edit drag) drives the Done gate', () => {
|
|
312
326
|
const { draw, dispatch } = setup()
|
|
313
327
|
drawHandler(draw, 'validitychange')({ valid: false, reason: 'Shape must not intersect itself' })
|
|
@@ -2,8 +2,10 @@ import { initialState, actions } from './reducer.js'
|
|
|
2
2
|
import { DrawInit } from './DrawInit.jsx'
|
|
3
3
|
import { newPolygon } from './api/newPolygon.js'
|
|
4
4
|
import { newLine } from './api/newLine.js'
|
|
5
|
+
import { newPoint } from './api/newPoint.js'
|
|
5
6
|
import { editFeature } from './api/editFeature.js'
|
|
6
7
|
import { addFeature } from './api/addFeature.js'
|
|
8
|
+
import { setStyle } from './api/setStyle.js'
|
|
7
9
|
import { deleteFeature } from './api/deleteFeature.js'
|
|
8
10
|
import { split } from './api/split.js'
|
|
9
11
|
import { merge } from './api/merge.js'
|
|
@@ -11,6 +13,11 @@ import { isMac } from '../../../src/utils/isMac.js'
|
|
|
11
13
|
|
|
12
14
|
const DRAW_ACTIONS_SLOT = 'top-middle'
|
|
13
15
|
|
|
16
|
+
// edit_point behaves like edit_vertex for Done/Menu/Undo, but stays out of drawDeletePoint and mergeShapes (neither applies to a single coordinate).
|
|
17
|
+
const EDIT_MODES = new Set(['edit_vertex', 'edit_point'])
|
|
18
|
+
// .has(), not spread — [...aSet] can silently misbehave under a loose-mode Babel build (see OLDrawManager.js's emit()).
|
|
19
|
+
const isEditMode = (mode) => EDIT_MODES.has(mode)
|
|
20
|
+
|
|
14
21
|
// Show the platform-appropriate undo modifier (⌘ on macOS, Ctrl elsewhere).
|
|
15
22
|
const undoCommand = isMac() ? '<kbd>Command</kbd> + <kbd>Z</kbd>' : '<kbd>Ctrl</kbd> + <kbd>Z</kbd>'
|
|
16
23
|
|
|
@@ -43,7 +50,7 @@ export const manifest = {
|
|
|
43
50
|
variant: 'primary',
|
|
44
51
|
exclusiveSlot: true,
|
|
45
52
|
hiddenWhen: ({ appState, pluginState }) =>
|
|
46
|
-
!['draw_polygon', 'draw_line'].includes(pluginState.mode) || appState.interfaceType !== 'touch',
|
|
53
|
+
!['draw_polygon', 'draw_line', 'draw_point'].includes(pluginState.mode) || appState.interfaceType !== 'touch',
|
|
47
54
|
// Disabled while placing at the crosshair would be vetoed (validatePlacement) —
|
|
48
55
|
// driven live so the button never looks active when a tap would do nothing.
|
|
49
56
|
enableWhen: ({ pluginState }) => pluginState.canAddPoint,
|
|
@@ -54,27 +61,30 @@ export const manifest = {
|
|
|
54
61
|
label: 'Done',
|
|
55
62
|
variant: 'primary',
|
|
56
63
|
exclusiveSlot: true,
|
|
57
|
-
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'
|
|
64
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
58
65
|
enableWhen: ({ pluginState }) => {
|
|
59
66
|
const { mode, geometryValid } = pluginState
|
|
60
67
|
// Min-vertices, area and self-intersection are all enforced by the validation
|
|
61
68
|
// rules via geometryValid, so the gate is simply "is the geometry valid now".
|
|
62
|
-
return ['draw_polygon', 'draw_line'
|
|
69
|
+
return (['draw_polygon', 'draw_line'].includes(mode) || isEditMode(mode)) && geometryValid
|
|
63
70
|
},
|
|
64
71
|
...createButtonSlots(true)
|
|
65
72
|
},
|
|
66
73
|
{
|
|
67
74
|
id: 'drawMenu',
|
|
68
|
-
label: ({ pluginState }) => pluginState.mode
|
|
75
|
+
label: ({ pluginState }) => isEditMode(pluginState.mode) ? 'Edit actions' : 'Draw actions',
|
|
69
76
|
iconId: 'menu',
|
|
70
77
|
exclusiveSlot: true,
|
|
71
|
-
|
|
78
|
+
// draw_point belongs here too — it has no Undo (nothing to undo before a single-click
|
|
79
|
+
// commit, gated separately below) and no delete-vertex, but it DOES support snapping,
|
|
80
|
+
// and the Snap toggle is a menuItem living inside this same button.
|
|
81
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line', 'draw_point'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
72
82
|
menuItems: [
|
|
73
83
|
{
|
|
74
84
|
id: 'drawUndo',
|
|
75
85
|
label: 'Undo',
|
|
76
86
|
iconId: 'undo',
|
|
77
|
-
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'
|
|
87
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
78
88
|
enableWhen: ({ pluginState }) => {
|
|
79
89
|
if (['draw_polygon', 'draw_line'].includes(pluginState.mode)) {
|
|
80
90
|
return pluginState.numVertices > 0
|
|
@@ -161,8 +171,10 @@ export const manifest = {
|
|
|
161
171
|
api: {
|
|
162
172
|
newPolygon,
|
|
163
173
|
newLine,
|
|
174
|
+
newPoint,
|
|
164
175
|
editFeature,
|
|
165
176
|
addFeature,
|
|
177
|
+
setStyle,
|
|
166
178
|
deleteFeature,
|
|
167
179
|
split,
|
|
168
180
|
merge
|
|
@@ -9,7 +9,7 @@ describe('manifest structure', () => {
|
|
|
9
9
|
expect(manifest.reducer).toHaveProperty('actions')
|
|
10
10
|
expect(manifest.InitComponent).toBeDefined()
|
|
11
11
|
expect(Object.keys(manifest.api)).toEqual(expect.arrayContaining([
|
|
12
|
-
'newPolygon', 'newLine', 'editFeature', 'addFeature', 'deleteFeature', 'split', 'merge'
|
|
12
|
+
'newPolygon', 'newLine', 'newPoint', 'editFeature', 'addFeature', 'setStyle', 'deleteFeature', 'split', 'merge'
|
|
13
13
|
]))
|
|
14
14
|
})
|
|
15
15
|
})
|
|
@@ -44,12 +44,14 @@ describe('drawDone', () => {
|
|
|
44
44
|
test('is hidden outside draw/edit modes', () => {
|
|
45
45
|
expect(btn().hiddenWhen({ pluginState: { mode: null } })).toBe(true)
|
|
46
46
|
expect(btn().hiddenWhen({ pluginState: { mode: 'draw_polygon' } })).toBe(false)
|
|
47
|
+
expect(btn().hiddenWhen({ pluginState: { mode: 'edit_point' } })).toBe(false)
|
|
47
48
|
})
|
|
48
49
|
|
|
49
50
|
test('enables in a draw/edit mode only when the geometry is valid', () => {
|
|
50
51
|
expect(btn().enableWhen({ pluginState: { mode: 'draw_polygon', geometryValid: true } })).toBe(true)
|
|
51
52
|
expect(btn().enableWhen({ pluginState: { mode: 'draw_line', geometryValid: true } })).toBe(true)
|
|
52
53
|
expect(btn().enableWhen({ pluginState: { mode: 'edit_vertex', geometryValid: true } })).toBe(true)
|
|
54
|
+
expect(btn().enableWhen({ pluginState: { mode: 'edit_point', geometryValid: true } })).toBe(true)
|
|
53
55
|
expect(btn().enableWhen({ pluginState: { mode: 'disabled', geometryValid: true } })).toBe(false)
|
|
54
56
|
})
|
|
55
57
|
|
|
@@ -57,6 +59,7 @@ describe('drawDone', () => {
|
|
|
57
59
|
expect(btn().enableWhen({ pluginState: { mode: 'draw_polygon', geometryValid: false } })).toBe(false)
|
|
58
60
|
expect(btn().enableWhen({ pluginState: { mode: 'draw_line', geometryValid: false } })).toBe(false)
|
|
59
61
|
expect(btn().enableWhen({ pluginState: { mode: 'edit_vertex', geometryValid: false } })).toBe(false)
|
|
62
|
+
expect(btn().enableWhen({ pluginState: { mode: 'edit_point', geometryValid: false } })).toBe(false)
|
|
60
63
|
})
|
|
61
64
|
})
|
|
62
65
|
|
|
@@ -66,12 +69,24 @@ describe('drawMenu', () => {
|
|
|
66
69
|
expect(findButton('drawMenu').hiddenWhen({ pluginState: { mode: 'edit_vertex' } })).toBe(false)
|
|
67
70
|
})
|
|
68
71
|
|
|
72
|
+
// draw_point has no Undo and no delete-vertex, but it does support snapping, and Snap is
|
|
73
|
+
// a menuItem inside this same button — so the button itself must stay visible for it even
|
|
74
|
+
// though drawUndo/drawDeletePoint (below) correctly keep excluding it.
|
|
75
|
+
test('is visible during draw_point, for the Snap toggle, even though it has no undo/delete', () => {
|
|
76
|
+
expect(findButton('drawMenu').hiddenWhen({ pluginState: { mode: 'draw_point' } })).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
69
79
|
test('labels "Edit actions" in edit mode, "Draw actions" otherwise', () => {
|
|
70
80
|
expect(findButton('drawMenu').label({ pluginState: { mode: 'edit_vertex' } })).toBe('Edit actions')
|
|
81
|
+
expect(findButton('drawMenu').label({ pluginState: { mode: 'edit_point' } })).toBe('Edit actions')
|
|
71
82
|
expect(findButton('drawMenu').label({ pluginState: { mode: 'draw_polygon' } })).toBe('Draw actions')
|
|
72
83
|
expect(findButton('drawMenu').label({ pluginState: { mode: 'draw_line' } })).toBe('Draw actions')
|
|
73
84
|
})
|
|
74
85
|
|
|
86
|
+
test('is visible during edit_point', () => {
|
|
87
|
+
expect(findButton('drawMenu').hiddenWhen({ pluginState: { mode: 'edit_point' } })).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
|
|
75
90
|
describe('drawUndo', () => {
|
|
76
91
|
const item = () => findMenuItem('drawMenu', 'drawUndo')
|
|
77
92
|
|
|
@@ -80,11 +95,21 @@ describe('drawMenu', () => {
|
|
|
80
95
|
expect(item().hiddenWhen({ pluginState: { mode: 'draw_line' } })).toBe(false)
|
|
81
96
|
})
|
|
82
97
|
|
|
98
|
+
test('is visible in edit_point', () => {
|
|
99
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'edit_point' } })).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('stays hidden for draw_point — nothing to undo before a single-click commit', () => {
|
|
103
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'draw_point' } })).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
|
|
83
106
|
test('enables from vertex count while drawing and from the undo stack while editing', () => {
|
|
84
107
|
expect(item().enableWhen({ pluginState: { mode: 'draw_polygon', numVertices: 1 } })).toBe(true)
|
|
85
108
|
expect(item().enableWhen({ pluginState: { mode: 'draw_polygon', numVertices: 0 } })).toBe(false)
|
|
86
109
|
expect(item().enableWhen({ pluginState: { mode: 'edit_vertex', undoStackLength: 2 } })).toBe(true)
|
|
87
110
|
expect(item().enableWhen({ pluginState: { mode: 'edit_vertex', undoStackLength: 0 } })).toBe(false)
|
|
111
|
+
expect(item().enableWhen({ pluginState: { mode: 'edit_point', undoStackLength: 1 } })).toBe(true)
|
|
112
|
+
expect(item().enableWhen({ pluginState: { mode: 'edit_point', undoStackLength: 0 } })).toBe(false)
|
|
88
113
|
})
|
|
89
114
|
})
|
|
90
115
|
|
|
@@ -111,6 +136,12 @@ describe('drawMenu', () => {
|
|
|
111
136
|
expect(item().hiddenWhen({ pluginState: { mode: 'edit_vertex' } })).toBe(false)
|
|
112
137
|
})
|
|
113
138
|
|
|
139
|
+
// Deliberately excluded from edit_point — deleting a point's one coordinate is deleting
|
|
140
|
+
// the whole feature (deleteFeature's job), not a per-vertex action.
|
|
141
|
+
test('stays hidden for edit_point', () => {
|
|
142
|
+
expect(item().hiddenWhen({ pluginState: { mode: 'edit_point' } })).toBe(true)
|
|
143
|
+
})
|
|
144
|
+
|
|
114
145
|
test('enables only with a selection and enough vertices (polygon vs line)', () => {
|
|
115
146
|
expect(item().enableWhen({ pluginState: { selectedVertexIndex: -1 } })).toBe(false)
|
|
116
147
|
expect(item().enableWhen({ pluginState: { selectedVertexIndex: 0, feature: { geometry: { type: 'Polygon' } }, numVertices: 4 } })).toBe(true)
|
|
@@ -77,6 +77,13 @@ describe('SET_UNDO_STACK_LENGTH', () => {
|
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
+
describe('SET_GEOMETRY_VALID', () => {
|
|
81
|
+
test('coerces the payload to a boolean', () => {
|
|
82
|
+
expect(actions.SET_GEOMETRY_VALID(initialState, false).geometryValid).toBe(false)
|
|
83
|
+
expect(actions.SET_GEOMETRY_VALID(initialState, 1).geometryValid).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
80
87
|
describe('SET_CAN_ADD_POINT', () => {
|
|
81
88
|
test('coerces the payload to a boolean', () => {
|
|
82
89
|
expect(actions.SET_CAN_ADD_POINT(initialState, false).canAddPoint).toBe(false)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SYMBOL_KEYS } from './symbolKeys.js'
|
|
2
|
+
|
|
3
|
+
// Drops adapter-derived bookkeeping from a feature before it reaches a public event:
|
|
4
|
+
// symbol* keys not in SYMBOL_KEYS (e.g. symbolImageId, symbolPixelRatio), and `sortKey`
|
|
5
|
+
// (drives fill/line/symbol-sort-key render order — see MaplibreDrawAdapter.js — never
|
|
6
|
+
// caller-supplied or meaningful application data).
|
|
7
|
+
export const stripInternalProperties = (feature) => {
|
|
8
|
+
if (!feature?.properties) {
|
|
9
|
+
return feature
|
|
10
|
+
}
|
|
11
|
+
const properties = {}
|
|
12
|
+
for (const [key, value] of Object.entries(feature.properties)) {
|
|
13
|
+
const isInternal = key === 'sortKey' || (key.startsWith('symbol') && !SYMBOL_KEYS.has(key))
|
|
14
|
+
if (!isInternal) {
|
|
15
|
+
properties[key] = value
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { ...feature, properties }
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { stripInternalProperties } from './stripInternalProperties.js'
|
|
2
|
+
|
|
3
|
+
describe('stripInternalProperties', () => {
|
|
4
|
+
test('drops adapter-derived symbol* keys not in SYMBOL_KEYS', () => {
|
|
5
|
+
const feature = {
|
|
6
|
+
id: 'a',
|
|
7
|
+
properties: {
|
|
8
|
+
symbol: 'pin',
|
|
9
|
+
symbolBackgroundColor: '#ca3535',
|
|
10
|
+
symbolImageId: 'symbol-abc-2x',
|
|
11
|
+
symbolIconAnchor: 'bottom',
|
|
12
|
+
symbolIconOffset: [0, -10],
|
|
13
|
+
symbolPixelRatio: 2,
|
|
14
|
+
symbolActiveImageId: 'symbol-act-abc-2x',
|
|
15
|
+
symbolSelectedImageId: 'symbol-sel-abc-2x'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect(stripInternalProperties(feature)).toEqual({
|
|
20
|
+
id: 'a',
|
|
21
|
+
properties: { symbol: 'pin', symbolBackgroundColor: '#ca3535' }
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('drops the internal sortKey property', () => {
|
|
26
|
+
const feature = { id: 'a', properties: { label: 'My point', sortKey: 3 } }
|
|
27
|
+
expect(stripInternalProperties(feature)).toEqual({ id: 'a', properties: { label: 'My point' } })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('leaves non-symbol-prefixed properties untouched', () => {
|
|
31
|
+
const feature = { id: 'a', properties: { label: 'My point', stroke: '#1d70b8' } }
|
|
32
|
+
expect(stripInternalProperties(feature)).toEqual(feature)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('does not mutate the original feature', () => {
|
|
36
|
+
const feature = { id: 'a', properties: { symbol: 'pin', symbolImageId: 'x' } }
|
|
37
|
+
stripInternalProperties(feature)
|
|
38
|
+
expect(feature.properties.symbolImageId).toBe('x')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('returns the feature unchanged when it has no properties', () => {
|
|
42
|
+
const feature = { id: 'a', geometry: { type: 'Point', coordinates: [0, 0] } }
|
|
43
|
+
expect(stripInternalProperties(feature)).toBe(feature)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('returns null/undefined as-is', () => {
|
|
47
|
+
expect(stripInternalProperties(null)).toBeNull()
|
|
48
|
+
expect(stripInternalProperties(undefined)).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Public symbol-config keys a caller can set on a Point (see docs/api/symbol-config.md).
|
|
2
|
+
export const SYMBOL_KEYS = new Set([
|
|
3
|
+
'symbol', 'symbolSvgContent', 'symbolBackgroundColor', 'symbolForegroundColor',
|
|
4
|
+
'symbolHaloWidth', 'symbolGraphic', 'symbolViewBox', 'symbolAnchor'
|
|
5
|
+
])
|
|
@@ -49,6 +49,14 @@ describe('built-in rules (synchronous, independently floored, flip-guarded)', ()
|
|
|
49
49
|
expect(onStrokeChange).not.toHaveBeenCalled()
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
test('a non-Polygon/LineString geometry type (e.g. Point) floors at 0 via the MIN_VERTICES fallback, and never flips', () => {
|
|
53
|
+
const { onStrokeChange, onPlaceChange, checks } = setup()
|
|
54
|
+
const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] } }
|
|
55
|
+
expect(() => checks.update({ feature: point, numVertices: 0 })).not.toThrow()
|
|
56
|
+
expect(onStrokeChange).not.toHaveBeenCalled()
|
|
57
|
+
expect(onPlaceChange).not.toHaveBeenCalled()
|
|
58
|
+
})
|
|
59
|
+
|
|
52
60
|
test('numVertices defaults to 0 when omitted from context', () => {
|
|
53
61
|
const { onStrokeChange, checks } = setup()
|
|
54
62
|
checks.update({ feature: closingEdgeCrosses })
|
|
@@ -156,4 +164,50 @@ describe('reset / destroy', () => {
|
|
|
156
164
|
jest.runAllTimers()
|
|
157
165
|
expect(onGeometryChange).not.toHaveBeenCalled()
|
|
158
166
|
})
|
|
167
|
+
|
|
168
|
+
// Belt-and-braces: recompute()/runUserRule() re-check `pending` themselves rather than
|
|
169
|
+
// trusting that cancelFrame() actually stopped the callback — simulated here by making
|
|
170
|
+
// cancelAnimationFrame a no-op, so an already-queued frame still fires after reset()/
|
|
171
|
+
// destroy() have cleared `pending`. The guards must make that a safe no-op.
|
|
172
|
+
test('a frame that still fires despite cancellation is a no-op, guarded by pending being cleared', () => {
|
|
173
|
+
const noopCancel = jest.fn()
|
|
174
|
+
const originalCaf = globalThis.cancelAnimationFrame
|
|
175
|
+
globalThis.cancelAnimationFrame = noopCancel
|
|
176
|
+
try {
|
|
177
|
+
const { onStrokeChange, onPlaceChange, checks } = setup()
|
|
178
|
+
const onGeometryChange = jest.fn(() => true)
|
|
179
|
+
checks.update({ feature: square, numVertices: 3, onGeometryChange })
|
|
180
|
+
checks.reset() // calls our no-op cancel — the queued frame is NOT actually removed
|
|
181
|
+
expect(noopCancel).toHaveBeenCalled()
|
|
182
|
+
onStrokeChange.mockClear()
|
|
183
|
+
onPlaceChange.mockClear()
|
|
184
|
+
expect(() => jest.runAllTimers()).not.toThrow()
|
|
185
|
+
expect(onGeometryChange).not.toHaveBeenCalled() // guard returns before reading pending's fields
|
|
186
|
+
expect(onStrokeChange).not.toHaveBeenCalled()
|
|
187
|
+
expect(onPlaceChange).not.toHaveBeenCalled()
|
|
188
|
+
} finally {
|
|
189
|
+
globalThis.cancelAnimationFrame = originalCaf
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// recompute() carries its own `if (!pending) return` guard too, distinct from
|
|
194
|
+
// runUserRule()'s — reachable when the user's own onGeometryChange callback
|
|
195
|
+
// reentrantly clears `pending` (e.g. via reset()) partway through the very frame that's
|
|
196
|
+
// evaluating it, before runUserRule() reaches its trailing recompute() call.
|
|
197
|
+
test('recompute() guards against pending being cleared reentrantly by the user callback', () => {
|
|
198
|
+
const { onStrokeChange, onPlaceChange, checks } = setup()
|
|
199
|
+
const onGeometryChange = jest.fn(() => {
|
|
200
|
+
checks.reset() // reentrant — clears `pending` mid-evaluation, frame is already null by now
|
|
201
|
+
return true
|
|
202
|
+
})
|
|
203
|
+
checks.update({ feature: square, numVertices: 3, onGeometryChange })
|
|
204
|
+
onStrokeChange.mockClear()
|
|
205
|
+
onPlaceChange.mockClear()
|
|
206
|
+
expect(() => jest.runAllTimers()).not.toThrow()
|
|
207
|
+
expect(onGeometryChange).toHaveBeenCalledTimes(1)
|
|
208
|
+
// recompute()'s trailing call (after the reentrant reset) is a safe no-op — no further
|
|
209
|
+
// gate changes are reported off a pending the reentrant reset() already cleared.
|
|
210
|
+
expect(onStrokeChange).not.toHaveBeenCalled()
|
|
211
|
+
expect(onPlaceChange).not.toHaveBeenCalled()
|
|
212
|
+
})
|
|
159
213
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLiveStroke } from './liveStroke.js'
|
|
1
|
+
import { createLiveStroke, requestFrame, cancelFrame } from './liveStroke.js'
|
|
2
2
|
|
|
3
3
|
const poly = (ring) => ({ type: 'Feature', geometry: { type: 'Polygon', coordinates: [ring] } })
|
|
4
4
|
|
|
@@ -105,6 +105,45 @@ describe('custom validate function', () => {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
describe('requestFrame / cancelFrame (engine-agnostic frame scheduling)', () => {
|
|
109
|
+
test('uses requestAnimationFrame/cancelAnimationFrame when available', () => {
|
|
110
|
+
const raf = jest.spyOn(globalThis, 'requestAnimationFrame')
|
|
111
|
+
const caf = jest.spyOn(globalThis, 'cancelAnimationFrame')
|
|
112
|
+
const cb = jest.fn()
|
|
113
|
+
const id = requestFrame(cb)
|
|
114
|
+
expect(raf).toHaveBeenCalledWith(cb)
|
|
115
|
+
cancelFrame(id)
|
|
116
|
+
expect(caf).toHaveBeenCalledWith(id)
|
|
117
|
+
raf.mockRestore()
|
|
118
|
+
caf.mockRestore()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('falls back to setTimeout/clearTimeout when rAF is unavailable (e.g. SSR)', () => {
|
|
122
|
+
const rafDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'requestAnimationFrame')
|
|
123
|
+
const cafDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'cancelAnimationFrame')
|
|
124
|
+
// jsdom defines these as configurable globals — delete rather than assign undefined,
|
|
125
|
+
// so `typeof requestAnimationFrame` genuinely reads 'undefined' inside the module.
|
|
126
|
+
delete globalThis.requestAnimationFrame
|
|
127
|
+
delete globalThis.cancelAnimationFrame
|
|
128
|
+
try {
|
|
129
|
+
const cb = jest.fn()
|
|
130
|
+
requestFrame(cb)
|
|
131
|
+
expect(cb).not.toHaveBeenCalled()
|
|
132
|
+
jest.advanceTimersByTime(16)
|
|
133
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
134
|
+
|
|
135
|
+
const cb2 = jest.fn()
|
|
136
|
+
const id2 = requestFrame(cb2)
|
|
137
|
+
cancelFrame(id2)
|
|
138
|
+
jest.advanceTimersByTime(16)
|
|
139
|
+
expect(cb2).not.toHaveBeenCalled()
|
|
140
|
+
} finally {
|
|
141
|
+
Object.defineProperty(globalThis, 'requestAnimationFrame', rafDescriptor)
|
|
142
|
+
Object.defineProperty(globalThis, 'cancelAnimationFrame', cafDescriptor)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
108
147
|
describe('set / reset / destroy', () => {
|
|
109
148
|
test('set() applies through the flip guard and drops any pending frame', () => {
|
|
110
149
|
const { onChange, stroke } = setup()
|
|
@@ -147,4 +186,28 @@ describe('set / reset / destroy', () => {
|
|
|
147
186
|
jest.runAllTimers()
|
|
148
187
|
expect(onGeometryChange).not.toHaveBeenCalled()
|
|
149
188
|
})
|
|
189
|
+
|
|
190
|
+
// Belt-and-braces: runUserRule() re-checks `pending` itself rather than trusting that
|
|
191
|
+
// cancelFrame() actually stopped the callback — simulated here by making
|
|
192
|
+
// cancelAnimationFrame a no-op, so the already-queued frame still fires after
|
|
193
|
+
// cancelPending() has cleared `pending`. The guard must make that a safe no-op rather
|
|
194
|
+
// than reading a null pending's properties.
|
|
195
|
+
test('a frame that still fires despite cancellation is a no-op, guarded by pending being cleared', () => {
|
|
196
|
+
const noopCancel = jest.fn()
|
|
197
|
+
const originalCaf = globalThis.cancelAnimationFrame
|
|
198
|
+
globalThis.cancelAnimationFrame = noopCancel
|
|
199
|
+
try {
|
|
200
|
+
const { onChange, stroke } = setup()
|
|
201
|
+
const onGeometryChange = jest.fn(() => true)
|
|
202
|
+
stroke.update({ feature: square, numVertices: 3, onGeometryChange })
|
|
203
|
+
stroke.destroy() // calls our no-op cancel — the queued frame is NOT actually removed
|
|
204
|
+
expect(noopCancel).toHaveBeenCalled()
|
|
205
|
+
onChange.mockClear()
|
|
206
|
+
expect(() => jest.runAllTimers()).not.toThrow()
|
|
207
|
+
expect(onGeometryChange).not.toHaveBeenCalled() // guard returns before reading pending's fields
|
|
208
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
209
|
+
} finally {
|
|
210
|
+
globalThis.cancelAnimationFrame = originalCaf
|
|
211
|
+
}
|
|
212
|
+
})
|
|
150
213
|
})
|