@defra/interactive-map 0.0.39-alpha → 0.0.41-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/control-definition.md +9 -1
- package/docs/api/slots.md +26 -5
- package/docs/api.md +4 -4
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +80 -17
- package/docs/plugins/search.md +1 -1
- package/govuk-prototype-kit.config.json +2 -0
- package/jest.setup.js +5 -0
- package/package.json +9 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/index.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +2 -3
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.js +1 -1
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.js +1 -1
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +1 -3
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +17 -3
- package/plugins/datasets/src/api/setOpacity.test.js +9 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
- package/plugins/datasets/src/datasets.scss +0 -1
- package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
- package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
- package/plugins/datasets/src/index.test.js +36 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -5
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
- package/plugins/datasets/src/manifest.js +0 -38
- package/plugins/datasets/src/manifest.test.js +49 -0
- package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
- package/plugins/datasets/src/reducers/pluginState.js +3 -16
- package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
- package/plugins/datasets/src/registry/dataset.js +15 -4
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -9
- package/plugins/datasets/src/registry/datasetRegistry.test.js +17 -5
- package/plugins/datasets/src/registry/globalDataset.js +0 -3
- package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
- package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
- package/plugins/datasets/src/utils/bbox.test.js +125 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +12 -4
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +92 -12
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +5 -28
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -54
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +11 -4
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
- package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +24 -4
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +47 -3
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +32 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +12 -5
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +26 -3
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
- package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
- package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
- package/plugins/draw/src/api/addFeature.js +16 -1
- package/plugins/draw/src/api/addFeature.test.js +62 -5
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/setStyle.js +43 -0
- package/plugins/draw/src/api/setStyle.test.js +75 -0
- package/plugins/draw/src/events.js +21 -14
- package/plugins/draw/src/events.test.js +15 -1
- package/plugins/draw/src/manifest.js +18 -6
- package/plugins/draw/src/manifest.test.js +32 -1
- package/plugins/draw/src/reducer.test.js +7 -0
- package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
- package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
- package/plugins/draw/src/utils/symbolKeys.js +5 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
- package/plugins/draw/src/validation/liveStroke.test.js +64 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +12 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +88 -1
- package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/utils/featureQueries.js +18 -0
- package/plugins/interact/src/utils/featureQueries.test.js +15 -0
- package/plugins/interact/src/utils/spatial.js +9 -3
- package/plugins/interact/src/utils/spatial.test.js +12 -1
- package/plugins/map-key/dist/css/index.css +62 -0
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
- package/plugins/map-key/dist/esm/index.js +1 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
- package/plugins/map-key/dist/umd/index.js +2 -0
- package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/map-key/package.json +28 -0
- package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
- package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/Key.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
- package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
- package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
- package/plugins/map-key/src/index.js +13 -0
- package/plugins/map-key/src/index.test.js +31 -0
- package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
- package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
- package/plugins/map-key/src/manifest.js +30 -0
- package/plugins/map-key/src/manifest.test.js +63 -0
- package/plugins/map-key/src/mapKey.scss +4 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/map-key/src/registry/index.js +3 -0
- package/plugins/map-key/src/registry/index.test.js +15 -0
- package/plugins/menu/dist/css/index.css +80 -0
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/esm/index.js +1 -0
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/umd/index.js +2 -0
- package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/menu/package.json +28 -0
- package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
- package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
- package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
- package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
- package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
- package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
- package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
- package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
- package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
- package/plugins/menu/src/index.js +13 -0
- package/plugins/menu/src/index.test.js +31 -0
- package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
- package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
- package/plugins/menu/src/initialise/createMenu.js +10 -0
- package/plugins/menu/src/initialise/createMenu.test.js +37 -0
- package/plugins/menu/src/manifest.js +35 -0
- package/plugins/menu/src/manifest.test.js +63 -0
- package/plugins/menu/src/menu.scss +4 -0
- package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
- package/plugins/menu/src/reducers/pluginState.js +35 -0
- package/plugins/menu/src/reducers/pluginState.test.js +57 -0
- package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
- package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/menu/src/registry/index.js +3 -0
- package/plugins/menu/src/registry/index.test.js +15 -0
- package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
- package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +7 -5
- package/plugins/search/src/components/Form/Form.module.scss +9 -20
- package/plugins/search/src/index.js +0 -1
- package/plugins/search/src/index.test.js +2 -4
- package/plugins/search/src/manifest.js +1 -1
- package/plugins/search/src/search.scss +17 -16
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
- package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
- package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
- package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
- package/providers/maplibre/src/utils/hoverCursor.js +30 -14
- package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
- package/providers/maplibre/src/utils/patternImages.js +1 -1
- package/providers/maplibre/src/utils/symbolImages.js +41 -3
- package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
- package/rollup.esm.mjs +11 -1
- package/sonar-project.properties +7 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Attributions/Attributions.module.scss +10 -0
- package/src/App/components/Hints/Hints.jsx +7 -2
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +2 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +3 -6
- package/src/App/components/{MoveControl/MoveControl.jsx → MoveControls/MoveControls.jsx} +10 -10
- package/src/App/components/{MoveControl/MoveControl.module.scss → MoveControls/MoveControls.module.scss} +60 -29
- package/src/App/components/{MoveControl/MoveControl.test.jsx → MoveControls/MoveControls.test.jsx} +48 -48
- package/src/App/components/Panel/Panel.jsx +18 -3
- package/src/App/components/Panel/Panel.module.scss +18 -8
- package/src/App/components/Panel/Panel.test.jsx +34 -0
- package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
- package/src/App/components/Viewport/Viewport.jsx +9 -1
- package/src/App/components/Viewport/Viewport.test.jsx +11 -0
- package/src/App/hooks/useFeatureItems.js +16 -7
- package/src/App/hooks/useFeatureItems.test.js +57 -0
- package/src/App/hooks/useLayoutMeasurements.js +95 -84
- package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
- package/src/App/initialiseApp.js +11 -8
- package/src/App/initialiseApp.test.js +15 -3
- package/src/App/layout/Layout.jsx +12 -11
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/keyboardShortcutRegistry.js +25 -13
- package/src/App/registry/keyboardShortcutRegistry.test.js +49 -32
- package/src/App/registry/pluginRegistry.js +2 -3
- package/src/App/registry/pluginRegistry.test.js +16 -3
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/mapButtons.js +28 -12
- package/src/App/renderer/mapButtons.test.js +17 -4
- package/src/App/renderer/mapControls.js +3 -1
- package/src/App/renderer/mapControls.test.js +16 -0
- package/src/App/renderer/mapPanels.js +44 -9
- package/src/App/renderer/mapPanels.test.js +51 -2
- package/src/App/renderer/orderItems.js +28 -0
- package/src/App/renderer/orderItems.test.js +50 -0
- package/src/App/renderer/slotAggregator.js +2 -18
- package/src/App/renderer/slotHelpers.js +3 -1
- package/src/App/renderer/slotHelpers.test.js +5 -0
- package/src/App/renderer/slots.js +3 -0
- package/src/App/store/AppProvider.jsx +4 -2
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/InteractiveMap/InteractiveMap.js +8 -2
- package/src/InteractiveMap/InteractiveMap.test.js +20 -3
- package/src/config/appConfig.js +17 -15
- package/src/config/appConfig.test.js +22 -22
- package/src/config/defaults.js +3 -1
- package/src/config/events.js +2 -0
- package/src/config/mergeConfig.js +7 -0
- package/src/config/mergeConfig.test.js +20 -0
- package/src/scss/main.scss +1 -1
- package/src/scss/settings/_colors.scss +1 -0
- package/src/scss/settings/_dimensions.scss +2 -3
- package/src/scss/settings/_transition.scss +1 -1
- package/src/services/eventBus.js +44 -0
- package/src/services/eventBus.test.js +83 -0
- package/src/services/symbolRegistry.js +1 -1
- package/src/test-utils.js +5 -0
- package/src/types.js +25 -7
- package/webpack.umd.mjs +2 -0
- package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
- package/plugins/datasets/src/components/Key/Key.jsx +0 -50
- package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
- package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
- package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
- package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
- package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
- package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
- /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
|
@@ -6,12 +6,19 @@ import { setupTouchClickWorkaround } from './utils/touchClickWorkaround.js'
|
|
|
6
6
|
import { applyTouchVertexColors } from './modes/editVertexMode/touchHandlers.js'
|
|
7
7
|
import { resolveColors } from '../../utils/resolveColors.js'
|
|
8
8
|
import { TOLERANCES, MAP_SIZE_SCALES } from './defaults.js'
|
|
9
|
+
import { refreshAllPointSymbols } from './pointSymbolImages.js'
|
|
9
10
|
import { createMapboxDraw } from './mapboxDraw.js'
|
|
10
11
|
|
|
11
12
|
jest.mock('@mapbox/mapbox-gl-draw', () => {
|
|
12
13
|
const MockDraw = jest.fn(function () {
|
|
13
14
|
this.modes = {}
|
|
14
15
|
this.changeMode = jest.fn()
|
|
16
|
+
// Mirrors mapbox-gl-draw's own index.js: `api.options = options` — the fully-processed
|
|
17
|
+
// (cold+hot-duplicated) styles array survives on the public instance regardless of any
|
|
18
|
+
// later map.setStyle() wiping the actual map layers built from it.
|
|
19
|
+
this.options = { styles: [{ id: 'fill-inactive.cold' }, { id: 'fill-inactive.hot' }] }
|
|
20
|
+
this.set = jest.fn()
|
|
21
|
+
this.getAll = jest.fn(() => ({ type: 'FeatureCollection', features: [{ type: 'Feature', id: 'f1' }] }))
|
|
15
22
|
})
|
|
16
23
|
MockDraw.constants = { classes: {} }
|
|
17
24
|
MockDraw.modes = { existing_mode: { id: 'existing' } }
|
|
@@ -22,6 +29,8 @@ jest.mock('./modes/disabledMode.js', () => ({ DisabledMode: { id: 'disabled' } }
|
|
|
22
29
|
jest.mock('./modes/editVertexMode.js', () => ({ EditVertexMode: { id: 'edit_vertex' } }))
|
|
23
30
|
jest.mock('./modes/drawPolygonMode.js', () => ({ DrawPolygonMode: { id: 'draw_polygon' } }))
|
|
24
31
|
jest.mock('./modes/drawLineMode.js', () => ({ DrawLineMode: { id: 'draw_line' } }))
|
|
32
|
+
jest.mock('./modes/drawPointMode.js', () => ({ DrawPointMode: { id: 'draw_point' } }))
|
|
33
|
+
jest.mock('./pointSymbolImages.js', () => ({ refreshAllPointSymbols: jest.fn(() => Promise.resolve()) }))
|
|
25
34
|
jest.mock('./styles.js', () => ({
|
|
26
35
|
createDrawStyles: jest.fn(() => ['style']),
|
|
27
36
|
updateDrawStyles: jest.fn()
|
|
@@ -38,21 +47,25 @@ jest.mock('./defaults.js', () => ({
|
|
|
38
47
|
MAP_SIZE_SCALES: { medium: 1.5 }
|
|
39
48
|
}))
|
|
40
49
|
|
|
41
|
-
const EVENTS = { MAP_SET_STYLE: 'map:setstyle', MAP_SET_SIZE: 'map:setsize' }
|
|
50
|
+
const EVENTS = { MAP_SET_STYLE: 'map:setstyle', MAP_STYLE_CHANGE: 'map:stylechange', MAP_SET_SIZE: 'map:setsize', MAP_SET_PIXEL_RATIO: 'map:setpixelratio', MAP_DATA_CHANGE: 'map:datachange' }
|
|
42
51
|
|
|
43
52
|
const handlerFor = (mockFn, eventName) =>
|
|
44
53
|
mockFn.mock.calls.find(([name]) => name === eventName)?.[1]
|
|
45
54
|
|
|
46
|
-
const createMap = () => ({
|
|
55
|
+
const createMap = ({ hasDrawSource = true } = {}) => ({
|
|
47
56
|
addControl: jest.fn(),
|
|
48
57
|
once: jest.fn(),
|
|
49
58
|
on: jest.fn(),
|
|
50
59
|
off: jest.fn(),
|
|
51
|
-
fire: jest.fn()
|
|
60
|
+
fire: jest.fn(),
|
|
61
|
+
getSource: jest.fn(() => (hasDrawSource ? {} : undefined)),
|
|
62
|
+
addSource: jest.fn(),
|
|
63
|
+
addLayer: jest.fn(),
|
|
64
|
+
triggerRepaint: jest.fn()
|
|
52
65
|
})
|
|
53
66
|
|
|
54
|
-
const setup = ({ existingDraw, existingUndoStack, pluginConfig } = {}) => {
|
|
55
|
-
const map = createMap()
|
|
67
|
+
const setup = ({ existingDraw, existingUndoStack, pluginConfig, hasDrawSource } = {}) => {
|
|
68
|
+
const map = createMap({ hasDrawSource })
|
|
56
69
|
const mapProvider = {
|
|
57
70
|
map,
|
|
58
71
|
_mapboxDrawInstance: existingDraw,
|
|
@@ -201,36 +214,153 @@ describe('createMapboxDraw – setup side effects', () => {
|
|
|
201
214
|
})
|
|
202
215
|
|
|
203
216
|
describe('createMapboxDraw – event handlers', () => {
|
|
204
|
-
|
|
217
|
+
// MAP_SET_STYLE fires the instant a style change is *requested* — before map.setStyle()
|
|
218
|
+
// has necessarily even been called. Registering the idle/re-add work directly off it risks
|
|
219
|
+
// catching the map still idle from the *previous* style, no-opping, and consuming itself
|
|
220
|
+
// before the real wipe even happens. Only MAP_STYLE_CHANGE (which MapLibre only ever emits
|
|
221
|
+
// off the native 'style.load' event, i.e. strictly after setStyle() was actually called)
|
|
222
|
+
// should trigger the settle/idle work — this is the split under test throughout this block.
|
|
223
|
+
test('MAP_SET_STYLE alone only stashes the style — no idle listener registered yet', () => {
|
|
205
224
|
const { map, eventBus } = setup()
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
map._drawEditContainer = { querySelector: jest.fn(() => 'svg-el') }
|
|
209
|
-
styleHandler('dark')
|
|
226
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
210
227
|
|
|
211
228
|
expect(map._drawCurrentMapStyle).toBe('dark')
|
|
229
|
+
expect(map.once).not.toHaveBeenCalled()
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('MAP_STYLE_CHANGE updates the current style and restyles on idle', () => {
|
|
233
|
+
const { map, eventBus, mapProvider, result } = setup()
|
|
234
|
+
|
|
235
|
+
map._drawEditContainer = { querySelector: jest.fn(() => 'svg-el') }
|
|
236
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
237
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
212
238
|
|
|
213
239
|
const idleCallback = handlerFor(map.once, 'idle')
|
|
214
240
|
idleCallback()
|
|
215
241
|
expect(updateDrawStyles).toHaveBeenCalledWith(map, 'dark', {})
|
|
216
242
|
expect(map._drawEditContainer.querySelector).toHaveBeenCalledWith('[data-im-draw-touch-target]')
|
|
217
243
|
expect(applyTouchVertexColors).toHaveBeenCalledWith('svg-el', 'dark', {})
|
|
244
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledWith({ draw: result.draw, mapProvider, map })
|
|
218
245
|
})
|
|
219
246
|
|
|
220
|
-
|
|
247
|
+
// MAP_STYLE_CHANGE genuinely fires twice per style change (mapEvents.js's permanent
|
|
248
|
+
// 'style.load' listener alongside appEvents.js's one-shot listener, both bound to the same
|
|
249
|
+
// native event) — without a dedupe guard the expensive settle work (idle-wait, re-rasterise
|
|
250
|
+
// every point, one full source rewrite) runs twice back to back, and the second cycle's
|
|
251
|
+
// completion can land after useHighlightSync's settle window has already closed.
|
|
252
|
+
test('MAP_STYLE_CHANGE fired twice in a row (both listeners reacting to the same style.load) only settles once', async () => {
|
|
221
253
|
const { map, eventBus } = setup()
|
|
222
254
|
|
|
223
255
|
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
256
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
257
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
258
|
+
|
|
259
|
+
expect(map.once).toHaveBeenCalledTimes(1)
|
|
260
|
+
|
|
261
|
+
handlerFor(map.once, 'idle')()
|
|
262
|
+
await Promise.resolve()
|
|
263
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledTimes(1)
|
|
264
|
+
expect(eventBus.emit).toHaveBeenCalledTimes(1)
|
|
265
|
+
|
|
266
|
+
// Once the in-flight settle has fully completed (onDone fired), a genuinely new style
|
|
267
|
+
// change is free to trigger another one — the guard isn't stuck permanently latched.
|
|
268
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
269
|
+
expect(map.once).toHaveBeenCalledTimes(2)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// MapLibre's own setStyle() discards the entire previous Style — including every
|
|
273
|
+
// programmatically-added source/layer, draw's included — and mapbox-gl-draw has no
|
|
274
|
+
// listener of its own to restore them. Without this, every drawn feature (not just a
|
|
275
|
+
// selected point) goes invisible and unqueryable after a style reload.
|
|
276
|
+
//
|
|
277
|
+
// draw.set() is asserted only after refreshAllPointSymbols resolves, not right after the
|
|
278
|
+
// idle callback fires: mapbox-gl-draw's own store.render() is requestAnimationFrame-
|
|
279
|
+
// debounced and bails out silently if the source doesn't exist yet, so pushing draw.getAll()
|
|
280
|
+
// back in *before* every point's image ids have been re-resolved for the new style would
|
|
281
|
+
// schedule a deferred render using the previous (already-wiped) style's ids — racing a
|
|
282
|
+
// still-in-flight worker tile parse of that stale data against the correct one moments
|
|
283
|
+
// later. Exactly one write, with final data, after refreshAllPointSymbols settles.
|
|
284
|
+
test('re-creates the sources and layers, then re-pushes every feature (only once symbols have re-resolved), when the draw source is missing after settling', async () => {
|
|
285
|
+
const { map, eventBus, result } = setup({ hasDrawSource: false })
|
|
286
|
+
|
|
287
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
288
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
289
|
+
handlerFor(map.once, 'idle')()
|
|
290
|
+
|
|
291
|
+
expect(map.addSource).toHaveBeenCalledWith('mapbox-gl-draw-cold', { data: { type: 'FeatureCollection', features: [] }, type: 'geojson' })
|
|
292
|
+
expect(map.addSource).toHaveBeenCalledWith('mapbox-gl-draw-hot', { data: { type: 'FeatureCollection', features: [] }, type: 'geojson' })
|
|
293
|
+
// draw.options.styles (mirrors mapbox-gl-draw's own api.options = options) — the fully
|
|
294
|
+
// processed cold+hot layer pairs from construction time, not re-derived from scratch.
|
|
295
|
+
expect(map.addLayer).toHaveBeenCalledWith({ id: 'fill-inactive.cold' })
|
|
296
|
+
expect(map.addLayer).toHaveBeenCalledWith({ id: 'fill-inactive.hot' })
|
|
297
|
+
// Not yet — refreshAllPointSymbols (mocked as a pending promise below) hasn't resolved.
|
|
298
|
+
expect(result.draw.set).not.toHaveBeenCalled()
|
|
299
|
+
|
|
300
|
+
await Promise.resolve() // flush the refreshAllPointSymbols().then(...) microtask
|
|
301
|
+
// Sources start empty — every feature the draw control still holds (map-independent, in
|
|
302
|
+
// its own JS store) is pushed back through the public API to force a full render, now
|
|
303
|
+
// with every point's image ids already resolved for the new style.
|
|
304
|
+
expect(result.draw.set).toHaveBeenCalledWith(result.draw.getAll())
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
test('does nothing when the draw source survived the style reload', async () => {
|
|
308
|
+
const { map, eventBus, result } = setup({ hasDrawSource: true })
|
|
309
|
+
|
|
310
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
311
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
312
|
+
handlerFor(map.once, 'idle')()
|
|
313
|
+
await Promise.resolve()
|
|
314
|
+
|
|
315
|
+
expect(map.addSource).not.toHaveBeenCalled()
|
|
316
|
+
expect(map.addLayer).not.toHaveBeenCalled()
|
|
317
|
+
expect(result.draw.set).not.toHaveBeenCalled()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// A selected point's highlight ring (plugins/interact's useHighlightSync) only re-applies
|
|
321
|
+
// on MAP_DATA_CHANGE — this nudges it explicitly once refreshAllPointSymbols has actually
|
|
322
|
+
// finished, rather than relying on 'styledata'/'sourcedata' firing it as a side effect.
|
|
323
|
+
test('emits MAP_DATA_CHANGE once point symbols have actually finished re-resolving after a style change', async () => {
|
|
324
|
+
const { map, eventBus } = setup()
|
|
325
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
326
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
327
|
+
handlerFor(map.once, 'idle')()
|
|
328
|
+
await Promise.resolve() // flush the refreshAllPointSymbols().then(...) microtask
|
|
329
|
+
expect(eventBus.emit).toHaveBeenCalledWith(EVENTS.MAP_DATA_CHANGE)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// The highlight ring is a standalone layer added from outside mapbox-gl-draw's own render
|
|
333
|
+
// cycle (unlike a vertex's active ring, which is just a filter on a layer mapbox-gl-draw
|
|
334
|
+
// itself always keeps painted) — nothing guarantees MapLibre schedules a real paint pass to
|
|
335
|
+
// pick it up right after settling. Confirmed by repro: the ring, and separately this app's
|
|
336
|
+
// own queryRenderedFeatures-based hover cursor (same painted-tile-buffer dependency), stayed
|
|
337
|
+
// stale until an unrelated map drag forced a render.
|
|
338
|
+
test('triggers a repaint once point symbols have finished re-resolving after a style change', async () => {
|
|
339
|
+
const { map, eventBus } = setup()
|
|
340
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
341
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
342
|
+
handlerFor(map.once, 'idle')()
|
|
343
|
+
expect(map.triggerRepaint).not.toHaveBeenCalled()
|
|
344
|
+
await Promise.resolve()
|
|
345
|
+
expect(map.triggerRepaint).toHaveBeenCalled()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('MAP_STYLE_CHANGE idle handler tolerates a missing edit container', () => {
|
|
349
|
+
const { map, eventBus } = setup()
|
|
350
|
+
|
|
351
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
352
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
224
353
|
handlerFor(map.once, 'idle')()
|
|
225
354
|
|
|
226
355
|
expect(applyTouchVertexColors).toHaveBeenCalledWith(undefined, 'dark', {})
|
|
227
356
|
})
|
|
228
357
|
|
|
229
|
-
test('
|
|
358
|
+
test('MAP_STYLE_CHANGE passes pluginConfig through to restyling and touch colours', () => {
|
|
230
359
|
const pluginConfig = { editStroke: '#custom' }
|
|
231
360
|
const { map, eventBus } = setup({ pluginConfig })
|
|
232
361
|
|
|
233
362
|
handlerFor(eventBus.on, EVENTS.MAP_SET_STYLE)('dark')
|
|
363
|
+
handlerFor(eventBus.on, EVENTS.MAP_STYLE_CHANGE)()
|
|
234
364
|
handlerFor(map.once, 'idle')()
|
|
235
365
|
|
|
236
366
|
expect(updateDrawStyles).toHaveBeenCalledWith(map, 'dark', pluginConfig)
|
|
@@ -249,6 +379,37 @@ describe('createMapboxDraw – event handlers', () => {
|
|
|
249
379
|
|
|
250
380
|
expect(map.fire).toHaveBeenCalledWith('draw.scalechange', { scale: MAP_SIZE_SCALES.medium })
|
|
251
381
|
})
|
|
382
|
+
|
|
383
|
+
// map.getPixelRatio() never updates itself from a plain map-size change — nothing in this
|
|
384
|
+
// app calls map.setPixelRatio() off MAP_SET_SIZE alone, so a point-symbol refresh here
|
|
385
|
+
// would run before the map actually knows about the new size. MAP_SET_PIXEL_RATIO (fired
|
|
386
|
+
// separately, after MAP_SET_SIZE, carrying the freshly computed value) owns that instead.
|
|
387
|
+
test('MAP_SET_SIZE does not refresh point symbols — MAP_SET_PIXEL_RATIO owns that', () => {
|
|
388
|
+
const { eventBus } = setup()
|
|
389
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_SIZE)('medium')
|
|
390
|
+
expect(refreshAllPointSymbols).not.toHaveBeenCalled()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('MAP_SET_PIXEL_RATIO refreshes point symbols with the freshly computed pixel ratio', () => {
|
|
394
|
+
const { map, eventBus, mapProvider, result } = setup()
|
|
395
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_PIXEL_RATIO)(3)
|
|
396
|
+
expect(refreshAllPointSymbols).toHaveBeenCalledWith({ draw: result.draw, mapProvider, map, pixelRatioOverride: 3 })
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
test('MAP_SET_PIXEL_RATIO emits MAP_DATA_CHANGE once point symbols have actually finished re-resolving', async () => {
|
|
400
|
+
const { eventBus } = setup()
|
|
401
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_PIXEL_RATIO)(3)
|
|
402
|
+
await Promise.resolve()
|
|
403
|
+
expect(eventBus.emit).toHaveBeenCalledWith(EVENTS.MAP_DATA_CHANGE)
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
test('MAP_SET_PIXEL_RATIO triggers a repaint once point symbols have finished re-resolving', async () => {
|
|
407
|
+
const { map, eventBus } = setup()
|
|
408
|
+
handlerFor(eventBus.on, EVENTS.MAP_SET_PIXEL_RATIO)(3)
|
|
409
|
+
expect(map.triggerRepaint).not.toHaveBeenCalled()
|
|
410
|
+
await Promise.resolve()
|
|
411
|
+
expect(map.triggerRepaint).toHaveBeenCalled()
|
|
412
|
+
})
|
|
252
413
|
})
|
|
253
414
|
|
|
254
415
|
describe('createMapboxDraw – cleanup', () => {
|
|
@@ -260,7 +421,9 @@ describe('createMapboxDraw – cleanup', () => {
|
|
|
260
421
|
|
|
261
422
|
expect(removeWorkaround).toHaveBeenCalledTimes(1)
|
|
262
423
|
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_SET_STYLE, expect.any(Function))
|
|
424
|
+
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_STYLE_CHANGE, expect.any(Function))
|
|
263
425
|
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_SET_SIZE, expect.any(Function))
|
|
426
|
+
expect(eventBus.off).toHaveBeenCalledWith(EVENTS.MAP_SET_PIXEL_RATIO, expect.any(Function))
|
|
264
427
|
expect(draw.changeMode).toHaveBeenCalledWith('disabled')
|
|
265
428
|
expect(mapProvider.draw).toBeNull()
|
|
266
429
|
expect(mapProvider._mapboxDrawInstance).toBe(draw)
|
|
@@ -229,6 +229,18 @@ describe('line mode mouse interactions', () => {
|
|
|
229
229
|
})
|
|
230
230
|
})
|
|
231
231
|
|
|
232
|
+
describe('_canPlaceVertex guard', () => {
|
|
233
|
+
test('allows placement when there is no sketch feature yet (nothing to validate against)', () => {
|
|
234
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
235
|
+
expect(ctx._canPlaceVertex({ ...state, polygon: null }, [1, 2])).toBe(true)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('allows placement when there is no candidate point yet', () => {
|
|
239
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
240
|
+
expect(ctx._canPlaceVertex(state, null)).toBe(true)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
232
244
|
describe('onCreate (draw.create re-id)', () => {
|
|
233
245
|
test('re-ids the created feature to the requested featureId', () => {
|
|
234
246
|
const { ctx } = setup(DrawPolygonMode)
|
|
@@ -70,7 +70,15 @@ export const createLifecycle = ({ ParentMode, featureProp, excludeFeatureIdFromS
|
|
|
70
70
|
onStop (state) {
|
|
71
71
|
ParentMode.onStop.call(this, state)
|
|
72
72
|
this._listeners.forEach(([t, e, h]) => t.removeEventListener ? t.removeEventListener(e, h) : t.off(e, h))
|
|
73
|
-
|
|
73
|
+
// A touch/keyboard session leaving draw mode is about to land in interact mode, which
|
|
74
|
+
// needs the same crosshair to select the just-placed feature — only a mouse session (which
|
|
75
|
+
// selects by direct click) has no further use for it. onCreate's mode change to 'disabled'
|
|
76
|
+
// is itself setTimeout-deferred, landing after interactPlugin's own re-enable has already
|
|
77
|
+
// re-shown the crosshair for touch/keyboard — hiding it here unconditionally would win that
|
|
78
|
+
// race and undo it.
|
|
79
|
+
if (!['touch', 'keyboard'].includes(state.interfaceType)) {
|
|
80
|
+
this._hideCrossHair(state)
|
|
81
|
+
}
|
|
74
82
|
// Sync the final interfaceType from draw mode back to app state so crosshair
|
|
75
83
|
// visibility is correct when exiting draw mode (e.g., if user switched from mouse to keyboard)
|
|
76
84
|
this.map.fire('draw.interfacetypechange', { interfaceType: state.interfaceType })
|
|
@@ -33,11 +33,11 @@ describe('setup and crosshair', () => {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
describe('onStop', () => {
|
|
36
|
-
test('removes listeners, hides the crosshair and reports the final interface type', () => {
|
|
37
|
-
const { ctx, state, marker, container } = setup(DrawPolygonMode, { interfaceType: '
|
|
36
|
+
test('mouse session: removes listeners, hides the crosshair and reports the final interface type', () => {
|
|
37
|
+
const { ctx, state, marker, container } = setup(DrawPolygonMode, { interfaceType: 'mouse' })
|
|
38
38
|
ctx.onStop(state)
|
|
39
39
|
expect(marker.style.display).toBe('none')
|
|
40
|
-
expect(firedWith(ctx.map, 'draw.interfacetypechange')).toEqual([{ interfaceType: '
|
|
40
|
+
expect(firedWith(ctx.map, 'draw.interfacetypechange')).toEqual([{ interfaceType: 'mouse' }])
|
|
41
41
|
|
|
42
42
|
// Window/container/map listeners are gone: keydown no longer shows the crosshair
|
|
43
43
|
container.focus()
|
|
@@ -45,4 +45,25 @@ describe('onStop', () => {
|
|
|
45
45
|
expect(marker.style.display).toBe('none')
|
|
46
46
|
expect(ctx.map.off).toHaveBeenCalledWith('draw.create', ctx.createHandler)
|
|
47
47
|
})
|
|
48
|
+
|
|
49
|
+
// A touch/keyboard session leaving draw mode lands in interact mode, which needs the same
|
|
50
|
+
// crosshair to select the just-placed feature — unlike mouse, onStop must leave it visible.
|
|
51
|
+
test('keyboard session: still removes listeners and reports interface type, but leaves the crosshair visible', () => {
|
|
52
|
+
const { ctx, state, marker, container } = setup(DrawPolygonMode, { interfaceType: 'keyboard' })
|
|
53
|
+
ctx.onStop(state)
|
|
54
|
+
expect(marker.style.display).toBe('block')
|
|
55
|
+
expect(firedWith(ctx.map, 'draw.interfacetypechange')).toEqual([{ interfaceType: 'keyboard' }])
|
|
56
|
+
expect(ctx.map.off).toHaveBeenCalledWith('draw.create', ctx.createHandler)
|
|
57
|
+
|
|
58
|
+
container.focus()
|
|
59
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
|
|
60
|
+
expect(marker.style.display).toBe('block')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('touch session: leaves the crosshair visible too', () => {
|
|
64
|
+
const crossHair = { show: jest.fn(), hide: jest.fn() }
|
|
65
|
+
const { ctx, state } = setup(DrawPolygonMode, { interfaceType: 'touch', crossHair })
|
|
66
|
+
ctx.onStop(state)
|
|
67
|
+
expect(crossHair.hide).not.toHaveBeenCalled()
|
|
68
|
+
})
|
|
48
69
|
})
|
|
@@ -17,7 +17,7 @@ export const createPointerHandlers = ({ ParentMode, getFeature, getCoords }) =>
|
|
|
17
17
|
this.onMove(state, e)
|
|
18
18
|
},
|
|
19
19
|
|
|
20
|
-
// The global interface type (e.g. switching to touch and panning via
|
|
20
|
+
// The global interface type (e.g. switching to touch and panning via MoveControls)
|
|
21
21
|
// can change mid-session without any touch/pointer/key event ever landing on the
|
|
22
22
|
// map container, so this can't rely on onTouchStart/onPointerdown alone — refresh
|
|
23
23
|
// the rubber band immediately rather than waiting for the next incidental 'move'.
|
|
@@ -44,7 +44,7 @@ describe('touch and pointer interface', () => {
|
|
|
44
44
|
expect(marker.style.display).toBe('none')
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
test('draw.interfacetypechange (e.g. switching to touch and panning via
|
|
47
|
+
test('draw.interfacetypechange (e.g. switching to touch and panning via MoveControls mid-session) updates the interface and refreshes the rubber band immediately', () => {
|
|
48
48
|
const { ctx, state } = setup(DrawPolygonMode)
|
|
49
49
|
clickAt(ctx, state, 0, 0)
|
|
50
50
|
ctx.map.fire('draw.interfacetypechange', { interfaceType: 'touch' })
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import MapboxDraw from '@mapbox/mapbox-gl-draw'
|
|
2
|
+
import { createLifecycle } from './drawMode/lifecycle.js'
|
|
3
|
+
import { validatePlacement } from '../../../validation/validateGeometry.js'
|
|
4
|
+
import {
|
|
5
|
+
getSnapInstance, isSnapEnabled, isSnapActive, getSnapLngLat, triggerSnapAtPoint, triggerSnapAtCenter
|
|
6
|
+
} from '../utils/snapHelpers.js'
|
|
7
|
+
|
|
8
|
+
const MODE = 'draw_point'
|
|
9
|
+
|
|
10
|
+
// Only these keys signal a switch to keyboard drawing (matches drawMode/createDrawMode.js's
|
|
11
|
+
// INTERFACE_KEYS) — modifier keys and shortcut chords like cmd+z must not show the crosshair.
|
|
12
|
+
const INTERFACE_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Enter'])
|
|
13
|
+
|
|
14
|
+
// A point has no ring/rubber-band to build a candidate from — just the coordinate about to be
|
|
15
|
+
// placed. Mirrors validateGeometry.js's attemptPlacement, but that helper hardcodes
|
|
16
|
+
// Polygon/LineString via MODE_BY_GEOMETRY, so a Point candidate is built and validated directly.
|
|
17
|
+
const canPlace = (map, coord) => {
|
|
18
|
+
const candidate = { type: 'Feature', geometry: { type: 'Point', coordinates: coord }, properties: {} }
|
|
19
|
+
const { valid, reason } = validatePlacement(
|
|
20
|
+
candidate,
|
|
21
|
+
{ mode: MODE, vertexIndex: 0 },
|
|
22
|
+
{ onGeometryChange: map._drawGeometryValidator }
|
|
23
|
+
)
|
|
24
|
+
if (!valid) {
|
|
25
|
+
map.fire('draw.placementblocked', { feature: candidate, reason: reason ?? null, phase: 'place', mode: MODE, vertexIndex: 0 })
|
|
26
|
+
}
|
|
27
|
+
return valid
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ParentMode = MapboxDraw.modes.draw_point
|
|
31
|
+
|
|
32
|
+
// createLifecycle is the one createDrawMode cluster that IS reused here — its onSetup/onStop
|
|
33
|
+
// are genuinely geometry-agnostic (event binding/teardown, crosshair show/hide), not coupled
|
|
34
|
+
// to the ring-of-vertices shape the other clusters (clickHandlers, keyboardHandlers,
|
|
35
|
+
// pointerHandlers, undoHandlers, renderHelpers) are built around. See the mode's own handlers
|
|
36
|
+
// below for why those aren't reused.
|
|
37
|
+
const lifecycle = createLifecycle({ ParentMode, featureProp: 'point', excludeFeatureIdFromSetup: false })
|
|
38
|
+
|
|
39
|
+
const pointHandlers = {
|
|
40
|
+
// Neutralise the built-in's onKeyUp (Escape/Enter both cancel) — this mode's Escape/Enter
|
|
41
|
+
// handling lives entirely in onKeyup (lowercase, window-level) below, which fires for both
|
|
42
|
+
// keyboard-focused and mouse/touch sessions alike, so there is nothing left for the
|
|
43
|
+
// container-scoped capital-U hook to do.
|
|
44
|
+
onKeyUp () {},
|
|
45
|
+
|
|
46
|
+
_showCrossHair (state) {
|
|
47
|
+
if (state.crossHair) { state.crossHair.show() } else { state.vertexMarker.style.display = 'block' }
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
_hideCrossHair (state) {
|
|
51
|
+
if (state.crossHair) { state.crossHair.hide() } else { state.vertexMarker.style.display = 'none' }
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
_setInterface (state, type, show = true) {
|
|
55
|
+
state.interfaceType = type
|
|
56
|
+
if (show) { this._showCrossHair(state) }
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Re-id the freshly created feature to the caller's requested id — mapbox-gl-draw assigns
|
|
60
|
+
// its own id on creation, and the caller's featureId (passed through changeMode) must win.
|
|
61
|
+
// Mirrors drawMode/clickHandlers.js's reidCreatedFeature. Symbol-image resolution runs
|
|
62
|
+
// after the re-id (not before) so it writes to the feature under its final id — the
|
|
63
|
+
// resolver itself is injected by the adapter via changeMode's options so this mode has
|
|
64
|
+
// no direct dependency on symbolRegistry/mapProvider.
|
|
65
|
+
onCreate (state, e) {
|
|
66
|
+
const feature = e.features[0]
|
|
67
|
+
this._ctx.api.delete(feature.id)
|
|
68
|
+
feature.id = state.featureId
|
|
69
|
+
this._ctx.api.add(feature, { userProperties: true })
|
|
70
|
+
state.resolvePointSymbol?.(state.featureId, feature.properties)
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
onUndo () {}, // Nothing to undo before a single-click commit — satisfies createLifecycle's bind contract.
|
|
74
|
+
onPointerup () {}, // No vertex count to report — ditto.
|
|
75
|
+
|
|
76
|
+
// Keeps the snap indicator live at the crosshair as the map pans underneath it — the
|
|
77
|
+
// point-mode equivalent of drawMode/pointerHandlers.js's onMove, minus the rubber-band
|
|
78
|
+
// update (nothing to rubber-band for a single coordinate). Bound to MapLibre's own 'move'
|
|
79
|
+
// event by createLifecycle, so this fires continuously while panning; also called
|
|
80
|
+
// explicitly below wherever the interface type switches to touch/keyboard, so the
|
|
81
|
+
// indicator appears immediately rather than waiting for the first pan.
|
|
82
|
+
onMove (state) {
|
|
83
|
+
if (['touch', 'keyboard'].includes(state.interfaceType) && isSnapEnabled(state)) {
|
|
84
|
+
triggerSnapAtCenter(getSnapInstance(this.map), this.map)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
onBlur (state, e) {
|
|
89
|
+
if (e.target !== state.container) { this._hideCrossHair(state) }
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
onPointerdown (state, e) {
|
|
93
|
+
if (e.pointerType !== 'touch') { this._setInterface(state, 'mouse', false) }
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
onPointermove (state, e) {
|
|
97
|
+
if (e.pointerType !== 'touch') { this._hideCrossHair(state) }
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
onTouchStart (state) { this._setInterface(state, 'touch'); this.onMove(state) },
|
|
101
|
+
onTouchEnd (state) { this._setInterface(state, 'touch'); this.onMove(state) },
|
|
102
|
+
|
|
103
|
+
onInterfaceTypeChange (state, e) {
|
|
104
|
+
this._setInterface(state, e.interfaceType, ['touch', 'keyboard'].includes(e.interfaceType))
|
|
105
|
+
this.onMove(state)
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Continuously track a snap candidate under the real cursor (mouse only — touch/keyboard
|
|
109
|
+
// placement tracks the crosshair instead, triggered once at commit time in _placeAtCrossHair).
|
|
110
|
+
onMouseMove (state, e) {
|
|
111
|
+
if (isSnapEnabled(state)) {
|
|
112
|
+
triggerSnapAtPoint(getSnapInstance(this.map), this.map, e.point)
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
onVertexButtonClick (state, e) {
|
|
117
|
+
if (state.addVertexButtonId && !this.map._undoInProgress && e.target.closest(`#${state.addVertexButtonId}`)) {
|
|
118
|
+
this._placeAtCrossHair(state)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
onKeydown (state, e) {
|
|
123
|
+
if (document.activeElement !== state.container) { return }
|
|
124
|
+
if (e.key === 'Escape') { e.preventDefault(); return }
|
|
125
|
+
if (e.key === 'Enter') { state.isActive = true }
|
|
126
|
+
if (!INTERFACE_KEYS.has(e.key)) { return }
|
|
127
|
+
this._setInterface(state, 'keyboard')
|
|
128
|
+
this.onMove(state)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Window-level: fires regardless of which element has focus, so mouse/touch users can
|
|
132
|
+
// Escape a pending placement without ever focusing the map, and keyboard users placing via
|
|
133
|
+
// crosshair can commit with Enter. A point has no partial ring to reinitialize the way
|
|
134
|
+
// line/polygon's Escape does, so Escape always routes to draw.cancel, keyboard-focused or not.
|
|
135
|
+
onKeyup (state, e) {
|
|
136
|
+
if (e.key === 'Escape') {
|
|
137
|
+
this.map.fire('draw.cancel')
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
if (document.activeElement !== state.container) { return }
|
|
141
|
+
if (!INTERFACE_KEYS.has(e.key)) { return }
|
|
142
|
+
this._setInterface(state, 'keyboard')
|
|
143
|
+
this.onMove(state)
|
|
144
|
+
if (e.key === 'Enter' && state.isActive) { this._placeAtCrossHair(state) }
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
onClick (state, e) {
|
|
148
|
+
if (e.originalEvent.button > 0 || e.originalEvent.target !== this.map.getCanvas()) { return }
|
|
149
|
+
const snap = getSnapInstance(this.map)
|
|
150
|
+
const snapped = isSnapEnabled(state) && isSnapActive(snap) ? getSnapLngLat(snap) : null
|
|
151
|
+
this._commit(state, snapped || e.lngLat)
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
onTap (state, e) { this.onClick(state, e) },
|
|
155
|
+
|
|
156
|
+
// Touch "Add point" button and keyboard Enter both place at the crosshair (map centre),
|
|
157
|
+
// snapping to a candidate under it if one exists. Deliberately bypasses onClick — its
|
|
158
|
+
// button/canvas-target guards are real-mouse-click-only and would reject a synthetic commit,
|
|
159
|
+
// the same reason drawMode/clickHandlers.js's doClick calls ParentMode.onClick directly
|
|
160
|
+
// rather than going through its own wrapped onClick.
|
|
161
|
+
_placeAtCrossHair (state) {
|
|
162
|
+
const snap = getSnapInstance(this.map)
|
|
163
|
+
if (isSnapEnabled(state)) { triggerSnapAtCenter(snap, this.map) }
|
|
164
|
+
const snapped = isSnapEnabled(state) && isSnapActive(snap) ? getSnapLngLat(snap) : null
|
|
165
|
+
this._commit(state, snapped || this.map.getCenter())
|
|
166
|
+
// Unlike a real click (dispatched through mapbox-gl-draw's own mode-handler loop, which
|
|
167
|
+
// re-renders automatically after every call), this runs from our own window/container
|
|
168
|
+
// listeners outside that loop, so the render has to be requested explicitly.
|
|
169
|
+
this._ctx.store.render()
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Hard gate: a vetoed placement never appears, mirroring drawMode/clickHandlers.js's
|
|
173
|
+
// _canPlaceVertex. Shared by both the real-click and crosshair-commit paths.
|
|
174
|
+
_commit (state, lngLat) {
|
|
175
|
+
if (!canPlace(this.map, [lngLat.lng, lngLat.lat])) { return }
|
|
176
|
+
state.point.updateCoordinate('', lngLat.lng, lngLat.lat)
|
|
177
|
+
this.map.fire('draw.create', { features: [state.point.toGeoJSON()] })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Draw mode for placing a single point: mouse click, or crosshair + Enter/"Add point" button
|
|
183
|
+
* for touch/keyboard. Commits immediately on the first placement — unlike draw_line/draw_polygon,
|
|
184
|
+
* there is no rubber band, no multi-vertex undo, and no "Done" step; the mode self-exits via
|
|
185
|
+
* events.js's onCreate handler once mapbox-gl-draw fires 'draw.create'.
|
|
186
|
+
*
|
|
187
|
+
* Wraps mapbox-gl-draw's own built-in draw_point mode (MapboxDraw.modes.draw_point) for
|
|
188
|
+
* onSetup/onStop/toDisplayFeatures/onTrash — it already creates and tracks the in-progress
|
|
189
|
+
* point feature, and cleans up an uncommitted one on stop. Its onClick/onTap/onKeyUp are
|
|
190
|
+
* overridden outright: the built-in has no placement-veto hook, and treats both Escape and
|
|
191
|
+
* Enter as "abandon and revert to simple_select" — this app needs Enter to place, and a
|
|
192
|
+
* cancel path that goes through draw.cancel like every other mode's does.
|
|
193
|
+
*/
|
|
194
|
+
export const DrawPointMode = { ...ParentMode, ...lifecycle, ...pointHandlers }
|