@defra/interactive-map 0.0.3-alpha → 0.0.5-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/README.md +16 -57
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +176 -0
- package/docs/api/context.md +124 -0
- package/docs/api/control-definition.md +72 -0
- package/docs/api/icon-definition.md +28 -0
- package/docs/api/map-style-config.md +76 -0
- package/docs/api/marker-config.md +51 -0
- package/docs/api/panel-definition.md +117 -0
- package/docs/api/slots.md +25 -0
- package/docs/api.md +721 -0
- package/docs/architecture.md +139 -0
- package/docs/building-a-plugin.md +149 -0
- package/docs/getting-started.md +46 -0
- package/docs/govuk-prototype.md +0 -0
- package/docs/plugins/interact.md +205 -0
- package/docs/plugins/map-styles.md +84 -0
- package/docs/plugins/plugin-context.md +67 -0
- package/docs/plugins/plugin-descriptor.md +71 -0
- package/docs/plugins/plugin-manifest.md +118 -0
- package/docs/plugins/scale-bar.md +62 -0
- package/docs/plugins/search.md +162 -0
- package/docs/plugins.md +116 -0
- package/govuk-prototype-kit.config.json +7 -7
- package/package.json +16 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -0
- package/plugins/beta/datasets/dist/esm/index.js +2 -0
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
- package/plugins/beta/datasets/dist/umd/index.js +2 -0
- package/plugins/beta/datasets/src/datasets.js +113 -0
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
- package/plugins/{datasets → beta/datasets}/src/handleSetMapStyle.js +8 -1
- package/plugins/{datasets → beta/datasets}/src/manifest.js +1 -1
- package/plugins/{datasets → beta/datasets}/src/mapLayers.js +42 -4
- package/plugins/{datasets → beta/datasets}/src/panels/Key.jsx +1 -1
- package/plugins/beta/datasets/src/utils/bbox.js +115 -0
- package/plugins/beta/draw-ml/dist/css/index.css +1 -0
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -0
- package/plugins/beta/draw-ml/dist/esm/index.js +2 -0
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -0
- package/plugins/beta/draw-ml/dist/umd/index.js +2 -0
- package/plugins/beta/draw-ml/src/api/addFeature.js +27 -0
- package/plugins/{draw-ml → beta/draw-ml}/src/api/editFeature.js +17 -6
- package/plugins/beta/draw-ml/src/api/merge.js +9 -0
- package/plugins/beta/draw-ml/src/api/newLine.js +68 -0
- package/plugins/beta/draw-ml/src/api/newPolygon.js +68 -0
- package/plugins/beta/draw-ml/src/api/split.js +81 -0
- package/plugins/{draw-ml → beta/draw-ml}/src/defaults.js +3 -0
- package/plugins/beta/draw-ml/src/draw.scss +32 -0
- package/plugins/beta/draw-ml/src/events.js +171 -0
- package/plugins/beta/draw-ml/src/manifest.js +132 -0
- package/plugins/{draw-ml → beta/draw-ml}/src/mapboxDraw.js +51 -2
- package/plugins/{draw-ml → beta/draw-ml}/src/mapboxSnap.js +18 -12
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +497 -0
- package/plugins/beta/draw-ml/src/modes/drawLineMode.js +18 -0
- package/plugins/beta/draw-ml/src/modes/drawPolygonMode.js +16 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +437 -0
- package/plugins/{draw-ml → beta/draw-ml}/src/reducer.js +30 -2
- package/plugins/{draw-ml → beta/draw-ml}/src/styles.js +66 -36
- package/plugins/beta/draw-ml/src/undoStack.js +50 -0
- package/plugins/beta/draw-ml/src/utils/debounce.js +16 -0
- package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
- package/plugins/beta/draw-ml/src/utils/spatial.js +258 -0
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -0
- package/plugins/beta/frame/dist/esm/index.js +2 -0
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -0
- package/plugins/beta/frame/dist/umd/index.js +2 -0
- package/plugins/{frame → beta/frame}/src/Frame.jsx +1 -1
- package/plugins/{map-styles → beta/map-styles}/dist/css/index.css +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -0
- package/plugins/beta/map-styles/dist/esm/index.js +2 -0
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -0
- package/plugins/beta/map-styles/dist/umd/index.js +2 -0
- package/plugins/{map-styles → beta/map-styles}/src/MapStyles.jsx +1 -1
- package/plugins/{map-styles → beta/map-styles}/src/manifest.js +8 -10
- package/plugins/{map-styles → beta/map-styles}/src/mapStyles.scss +4 -1
- package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -0
- package/plugins/beta/scale-bar/dist/esm/index.js +2 -0
- package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -0
- package/plugins/beta/scale-bar/dist/umd/index.js +2 -0
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -0
- package/plugins/beta/use-location/dist/esm/index.js +2 -0
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -0
- package/plugins/beta/use-location/dist/umd/index.js +2 -0
- package/plugins/interact/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/esm/index.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/InteractInit.jsx +1 -1
- package/plugins/interact/src/InteractInit.test.js +97 -0
- package/plugins/interact/src/api/clear.test.js +18 -0
- package/plugins/interact/src/api/disable.test.js +12 -0
- package/plugins/interact/src/api/enable.test.js +48 -0
- package/plugins/interact/src/api/selectFeature.test.js +33 -0
- package/plugins/interact/src/api/unselectFeature.test.js +33 -0
- package/plugins/interact/src/defaults.js +2 -0
- package/plugins/interact/src/events.js +37 -52
- package/plugins/interact/src/events.test.js +186 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useHighlightSync.test.js +212 -0
- package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -41
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +316 -0
- package/plugins/interact/src/index.test.js +22 -0
- package/plugins/interact/src/interact.scss +6 -0
- package/plugins/interact/src/manifest.js +1 -1
- package/plugins/interact/src/manifest.test.js +94 -0
- package/plugins/interact/src/reducer.js +28 -21
- package/plugins/interact/src/reducer.test.js +155 -0
- package/plugins/interact/src/utils/buildStylesMap.test.js +57 -0
- package/plugins/interact/src/utils/featureQueries.js +2 -2
- package/plugins/interact/src/utils/featureQueries.test.js +56 -0
- package/plugins/interact/src/utils/spatial.js +95 -0
- package/plugins/interact/src/utils/spatial.test.js +93 -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 -2
- package/plugins/search/src/components/Form/Form.jsx +1 -1
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
- package/plugins/search/src/datasets.js +1 -1
- package/plugins/search/src/defaults.js +3 -0
- package/plugins/search/src/events/fetchSuggestions.js +48 -53
- package/plugins/search/src/events/formHandlers.js +3 -2
- package/plugins/search/src/events/index.js +1 -1
- package/plugins/search/src/events/inputHandlers.js +3 -1
- package/plugins/search/src/events/suggestionHandlers.js +12 -3
- package/plugins/search/src/search.scss +4 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +15 -19
- package/providers/{esri → beta/esri}/src/esriProvider.js +1 -1
- package/providers/{esri → beta/esri}/src/index.js +1 -1
- package/providers/{esri → beta/esri}/src/mapEvents.js +2 -2
- package/providers/beta/open-names/dist/esm/im-reverse-geocode.js +2 -0
- package/providers/beta/open-names/dist/esm/index.js +2 -0
- package/providers/beta/open-names/dist/umd/im-reverse-geocode.js +2 -0
- package/providers/beta/open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt +1 -0
- package/providers/beta/open-names/dist/umd/index.js +2 -0
- package/providers/beta/open-names/dist/umd/index.js.LICENSE.txt +1 -0
- package/providers/maplibre/dist/esm/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-framework.js.LICENSE.txt +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +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/defaults.js +3 -2
- package/providers/maplibre/src/index.js +36 -20
- package/providers/maplibre/src/maplibreProvider.js +140 -11
- package/providers/maplibre/src/utils/detectWebgl.js +1 -2
- package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
- package/providers/maplibre/src/utils/labels.js +174 -120
- package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
- package/providers/maplibre/src/utils/queryFeatures.js +146 -0
- package/providers/maplibre/src/utils/spatial.js +14 -17
- package/sonar-project.properties +40 -3
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +2 -2
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
- package/src/App/components/Logo/Logo.jsx +1 -1
- package/src/App/components/MapButton/MapButton.jsx +217 -41
- package/src/App/components/MapButton/MapButton.module.scss +32 -21
- package/src/App/components/MapButton/MapButton.test.jsx +128 -84
- package/src/App/components/Markers/Markers.jsx +1 -1
- package/src/App/components/Markers/Markers.module.scss +0 -5
- package/src/App/components/Panel/Panel.jsx +75 -45
- package/src/App/components/Panel/Panel.module.scss +18 -24
- package/src/App/components/Panel/Panel.test.jsx +18 -0
- package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
- package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
- package/src/App/components/Tooltip/Tooltip.jsx +4 -2
- package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/Viewport.jsx +2 -2
- package/src/App/components/Viewport/Viewport.module.scss +2 -2
- package/src/App/controls/keyboardActions.js +3 -2
- package/src/App/controls/keyboardShortcuts.js +4 -2
- package/src/App/hooks/useButtonStateEvaluator.js +58 -45
- package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
- package/src/App/hooks/useCrossHairAPI.js +49 -47
- package/src/App/hooks/useFocusVisible.js +2 -2
- package/src/App/hooks/useInterfaceAPI.js +36 -1
- package/src/App/hooks/useKeyboardHint.js +1 -1
- package/src/App/hooks/useKeyboardShortcuts.js +4 -2
- package/src/App/hooks/useMapAnnouncements.js +34 -32
- package/src/App/hooks/useMapEvents.js +12 -1
- package/src/App/hooks/useMapProviderOverrides.js +3 -3
- package/src/App/hooks/useMapStateSync.js +7 -1
- package/src/App/hooks/useMapURLSync.js +6 -1
- package/src/App/hooks/useMarkersAPI.js +88 -50
- package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
- package/src/App/hooks/useModalPanelBehaviour.js +46 -40
- package/src/App/hooks/useResizeObserver.js +2 -2
- package/src/App/initialiseApp.js +45 -38
- package/src/App/layout/Layout.jsx +6 -4
- package/src/App/layout/layout.module.scss +13 -17
- package/src/App/registry/keyboardShortcutRegistry.js +12 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
- package/src/App/registry/mergeManifests.js +1 -1
- package/src/App/registry/panelRegistry.js +1 -1
- package/src/App/registry/panelRegistry.test.js +30 -1
- package/src/App/registry/pluginRegistry.js +14 -51
- package/src/App/registry/pluginRegistry.test.js +54 -6
- package/src/App/renderer/HtmlElementHost.jsx +186 -0
- package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
- package/src/App/renderer/mapButtons.js +12 -9
- package/src/App/renderer/mapButtons.test.js +6 -4
- package/src/App/renderer/mapControls.js +6 -0
- package/src/App/renderer/mapControls.test.js +10 -2
- package/src/App/renderer/mapPanels.js +39 -26
- package/src/App/renderer/mapPanels.test.js +29 -0
- package/src/App/renderer/pluginWrapper.test.js +28 -0
- package/src/App/renderer/slotHelpers.js +60 -0
- package/src/App/renderer/slotHelpers.test.js +82 -0
- package/src/App/store/AppProvider.jsx +3 -0
- package/src/App/store/AppProvider.test.jsx +40 -0
- package/src/App/store/PluginProvider.jsx +7 -5
- package/src/App/store/appActionsMap.js +46 -1
- package/src/App/store/appActionsMap.test.js +21 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +12 -24
- package/src/App/store/mapReducer.js +1 -1
- package/src/InteractiveMap/InteractiveMap.js +194 -13
- package/src/InteractiveMap/InteractiveMap.test.js +164 -2
- package/src/InteractiveMap/behaviourController.js +12 -2
- package/src/InteractiveMap/behaviourController.test.js +82 -1
- package/src/InteractiveMap/deviceChecker.js +1 -1
- package/src/InteractiveMap/deviceChecker.test.js +2 -2
- package/src/InteractiveMap/historyManager.js +41 -5
- package/src/InteractiveMap/historyManager.test.js +37 -6
- package/src/InteractiveMap/polyfills.js +39 -0
- package/src/InteractiveMap/polyfills.test.js +127 -0
- package/src/config/appConfig.js +5 -5
- package/src/config/appConfig.test.js +107 -42
- package/src/config/defaults.js +30 -20
- package/src/config/events.js +110 -4
- package/src/index.js +5 -2
- package/src/index.umd.js +1 -1
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +6 -2
- package/src/scss/tools/_border-focus.scss +20 -4
- package/src/services/eventBus.test.js +24 -1
- package/src/test-utils.js +1 -0
- package/src/types.js +590 -0
- package/src/utils/detectBreakpoint.js +78 -77
- package/src/utils/detectBreakpoint.test.js +50 -4
- package/src/utils/detectInterfaceType.js +4 -4
- package/src/utils/getIsFullscreen.js +3 -1
- package/src/utils/getSafeZoneInset.js +23 -7
- package/src/utils/getSafeZoneInset.test.js +18 -0
- package/src/utils/mapStateSync.js +3 -3
- package/src/utils/queryString.js +1 -1
- package/src/utils/stringToKebab.js +1 -1
- package/src/utils/toggleInertElements.js +37 -12
- package/webpack.dev.mjs +3 -4
- package/webpack.esm.mjs +8 -8
- package/webpack.umd.mjs +8 -8
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +0 -1
- package/plugins/datasets/dist/esm/index.js +0 -2
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +0 -1
- package/plugins/datasets/dist/umd/index.js +0 -2
- package/plugins/datasets/src/datasets.js +0 -60
- package/plugins/datasets/src/utils/bbox.js +0 -4
- package/plugins/draw-ml/dist/css/index.css +0 -1
- package/plugins/draw-ml/dist/esm/im-draw-ml-plugin.js +0 -1
- package/plugins/draw-ml/dist/esm/index.js +0 -2
- package/plugins/draw-ml/dist/umd/im-draw-ml-plugin.js +0 -1
- package/plugins/draw-ml/dist/umd/index.js +0 -2
- package/plugins/draw-ml/src/api/addFeature.js +0 -15
- package/plugins/draw-ml/src/api/newPolygon.js +0 -43
- package/plugins/draw-ml/src/draw.scss +0 -9
- package/plugins/draw-ml/src/events.js +0 -127
- package/plugins/draw-ml/src/manifest.js +0 -100
- package/plugins/draw-ml/src/modes/drawVertexMode.js +0 -275
- package/plugins/draw-ml/src/modes/editVertexMode.js +0 -426
- package/plugins/draw-ml/src/utils.js +0 -102
- package/plugins/frame/dist/esm/im-frame-plugin.js +0 -1
- package/plugins/frame/dist/esm/index.js +0 -2
- package/plugins/frame/dist/umd/im-frame-plugin.js +0 -1
- package/plugins/frame/dist/umd/index.js +0 -2
- package/plugins/map-styles/dist/esm/im-map-styles-plugin.js +0 -1
- package/plugins/map-styles/dist/esm/index.js +0 -2
- package/plugins/map-styles/dist/umd/im-map-styles-plugin.js +0 -1
- package/plugins/map-styles/dist/umd/index.js +0 -2
- package/plugins/scale-bar/dist/esm/im-scale-bar-plugin.js +0 -1
- package/plugins/scale-bar/dist/esm/index.js +0 -2
- package/plugins/scale-bar/dist/umd/im-scale-bar-plugin.js +0 -1
- package/plugins/scale-bar/dist/umd/index.js +0 -2
- package/plugins/search/src/utils/fetchDataset.js +0 -23
- package/plugins/use-location/dist/esm/im-use-location-plugin.js +0 -1
- package/plugins/use-location/dist/esm/index.js +0 -2
- package/plugins/use-location/dist/umd/im-use-location-plugin.js +0 -1
- package/plugins/use-location/dist/umd/index.js +0 -2
- package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
- package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
- package/providers/open-names/dist/esm/im-reverse-geocode.js +0 -2
- package/providers/open-names/dist/esm/index.js +0 -2
- package/providers/open-names/dist/umd/im-reverse-geocode.js +0 -2
- package/providers/open-names/dist/umd/index.js +0 -2
- /package/docs/architecture/{ARCHITECTURE_DIAGRAMS.md → architecture-diagrams.md} +0 -0
- /package/plugins/{datasets → beta/datasets}/dist/css/index.css +0 -0
- /package/plugins/{datasets/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt} +0 -0
- /package/plugins/{datasets/dist/umd → beta/datasets/dist/esm}/index.js.LICENSE.txt +0 -0
- /package/plugins/{draw-ml/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt} +0 -0
- /package/plugins/{draw-ml → beta/datasets}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{datasets → beta/datasets}/src/DatasetsInit.jsx +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/addDataset.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/hideDataset.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/hideFeatures.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/removeDataset.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/showDataset.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/api/showFeatures.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/datasets.scss +0 -0
- /package/plugins/{datasets → beta/datasets}/src/defaults.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/index.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/panels/Key.module.scss +0 -0
- /package/plugins/{datasets → beta/datasets}/src/panels/Layers.jsx +0 -0
- /package/plugins/{datasets → beta/datasets}/src/panels/Layers.module.scss +0 -0
- /package/plugins/{datasets → beta/datasets}/src/reducer.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/utils/debounce.js +0 -0
- /package/plugins/{datasets → beta/datasets}/src/utils/filters.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/DrawInit.jsx +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/api/addFeature.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/api/deleteFeature.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/api/editFeature.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/api/newPolygon.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/events.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/graphic.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/index.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/manifest.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/reducer.js +0 -0
- /package/plugins/{draw-es → beta/draw-es}/src/sketchViewModel.js +0 -0
- /package/plugins/{frame → beta/draw-ml}/dist/esm/index.js.LICENSE.txt +0 -0
- /package/plugins/{frame → beta/draw-ml}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{draw-ml → beta/draw-ml}/src/DrawInit.jsx +0 -0
- /package/plugins/{draw-ml → beta/draw-ml}/src/api/deleteFeature.js +0 -0
- /package/plugins/{draw-ml → beta/draw-ml}/src/index.js +0 -0
- /package/plugins/{draw-ml → beta/draw-ml}/src/modes/disabledMode.js +0 -0
- /package/plugins/{draw-ml/src → beta/draw-ml/src/utils}/snapHelpers.js +0 -0
- /package/plugins/{frame → beta/frame}/dist/css/index.css +0 -0
- /package/plugins/{map-styles → beta/frame}/dist/esm/index.js.LICENSE.txt +0 -0
- /package/plugins/{map-styles → beta/frame}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{frame → beta/frame}/package.json +0 -0
- /package/plugins/{frame → beta/frame}/src/FrameInit.jsx +0 -0
- /package/plugins/{frame → beta/frame}/src/api/addFrame.js +0 -0
- /package/plugins/{frame → beta/frame}/src/api/editFeature.js +0 -0
- /package/plugins/{frame → beta/frame}/src/config.js +0 -0
- /package/plugins/{frame → beta/frame}/src/frame.scss +0 -0
- /package/plugins/{frame → beta/frame}/src/index.js +0 -0
- /package/plugins/{frame → beta/frame}/src/manifest.js +0 -0
- /package/plugins/{frame → beta/frame}/src/reducer.js +0 -0
- /package/plugins/{frame → beta/frame}/src/utils.js +0 -0
- /package/plugins/{scale-bar → beta/map-styles}/dist/esm/index.js.LICENSE.txt +0 -0
- /package/plugins/{scale-bar → beta/map-styles}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{map-styles → beta/map-styles}/src/MapStylesInit.jsx +0 -0
- /package/plugins/{map-styles → beta/map-styles}/src/config.js +0 -0
- /package/plugins/{map-styles → beta/map-styles}/src/index.js +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/dist/css/index.css +0 -0
- /package/plugins/{use-location → beta/scale-bar}/dist/esm/index.js.LICENSE.txt +0 -0
- /package/plugins/{use-location → beta/scale-bar}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/package.json +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.jsx +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.test.jsx +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/index.js +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/index.test.js +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/manifest.js +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/scaleBar.scss +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/utils.js +0 -0
- /package/plugins/{scale-bar → beta/scale-bar}/src/utils.test.js +0 -0
- /package/{providers/open-names → plugins/beta/use-location}/dist/esm/index.js.LICENSE.txt +0 -0
- /package/{providers/open-names → plugins/beta/use-location}/dist/umd/index.js.LICENSE.txt +0 -0
- /package/plugins/{use-location → beta/use-location}/src/UseLocation.jsx +0 -0
- /package/plugins/{use-location → beta/use-location}/src/UseLocationInit.jsx +0 -0
- /package/plugins/{use-location → beta/use-location}/src/defaults.js +0 -0
- /package/plugins/{use-location → beta/use-location}/src/events.js +0 -0
- /package/plugins/{use-location → beta/use-location}/src/index.js +0 -0
- /package/plugins/{use-location → beta/use-location}/src/manifest.js +0 -0
- /package/plugins/{use-location → beta/use-location}/src/reducer.js +0 -0
- /package/providers/{esri → beta/esri}/src/appEvents.js +0 -0
- /package/providers/{esri → beta/esri}/src/defaults.js +0 -0
- /package/providers/{esri → beta/esri}/src/esriProvider.scss +0 -0
- /package/providers/{esri → beta/esri}/src/utils/coords.js +0 -0
- /package/providers/{esri → beta/esri}/src/utils/detectWebGL.js +0 -0
- /package/providers/{esri → beta/esri}/src/utils/esriFixes.js +0 -0
- /package/providers/{esri → beta/esri}/src/utils/query.js +0 -0
- /package/providers/{esri → beta/esri}/src/utils/spatial.js +0 -0
- /package/providers/{open-names → beta/open-names}/dist/esm/im-reverse-geocode.js.LICENSE.txt +0 -0
- /package/providers/{open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt → beta/open-names/dist/esm/index.js.LICENSE.txt} +0 -0
- /package/providers/{open-names → beta/open-names}/src/index.js +0 -0
- /package/providers/{open-names → beta/open-names}/src/reverseGeocode.js +0 -0
- /package/providers/{open-names → beta/open-names}/src/utils/mapToLocationModel.js +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the squared distance from a point (p) to a line segment (v to w).
|
|
3
|
+
*/
|
|
4
|
+
const distToSegmentSquared = (p, v, w) => {
|
|
5
|
+
const l2 = (v.x - w.x) ** 2 + (v.y - w.y) ** 2
|
|
6
|
+
if (l2 === 0) {
|
|
7
|
+
return (p.x - v.x) ** 2 + (p.y - v.y) ** 2
|
|
8
|
+
}
|
|
9
|
+
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2
|
|
10
|
+
t = Math.max(0, Math.min(1, t))
|
|
11
|
+
return (p.x - (v.x + t * (w.x - v.x))) ** 2 + (p.y - (v.y + t * (w.y - v.y))) ** 2
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ray-casting algorithm to determine if a point is inside a polygon.
|
|
16
|
+
*/
|
|
17
|
+
const isPointInPolygon = (point, ring) => {
|
|
18
|
+
const [px, py] = point
|
|
19
|
+
let inside = false
|
|
20
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i++) {
|
|
21
|
+
const [xi, yi] = ring[i]
|
|
22
|
+
const [xj, yj] = ring[j]
|
|
23
|
+
const isAboveI = yi > py
|
|
24
|
+
const isAboveJ = yj > py
|
|
25
|
+
|
|
26
|
+
if (isAboveI === isAboveJ) {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
// 2. Calculate horizontal intersection
|
|
30
|
+
const intersectX = ((xj - xi) * (py - yi)) / (yj - yi) + xi
|
|
31
|
+
|
|
32
|
+
if (px < intersectX) {
|
|
33
|
+
inside = !inside;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return inside
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculates minimum squared pixel distance to the geometry.
|
|
41
|
+
*/
|
|
42
|
+
const getMinDistToGeometry = (map, point, geometry) => {
|
|
43
|
+
const { coordinates: coords, type } = geometry
|
|
44
|
+
let minSqDist = Infinity
|
|
45
|
+
const getScreenPt = (lngLat) => map.project(lngLat)
|
|
46
|
+
|
|
47
|
+
const processLine = (lineCoords) => {
|
|
48
|
+
for (let i = 0; i < lineCoords.length - 1; i++) {
|
|
49
|
+
const d2 = distToSegmentSquared(point, getScreenPt(lineCoords[i]), getScreenPt(lineCoords[i + 1]))
|
|
50
|
+
if (d2 < minSqDist) {
|
|
51
|
+
minSqDist = d2
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (type === 'Point') {
|
|
57
|
+
const p = getScreenPt(coords)
|
|
58
|
+
minSqDist = (point.x - p.x) ** 2 + (point.y - p.y) ** 2
|
|
59
|
+
} else if (type === 'LineString' || type === 'MultiPoint') {
|
|
60
|
+
if (type === 'LineString') {
|
|
61
|
+
processLine(coords)
|
|
62
|
+
} else {
|
|
63
|
+
coords.forEach((pt) => {
|
|
64
|
+
const p = getScreenPt(pt)
|
|
65
|
+
const d2 = (point.x - p.x) ** 2 + (point.y - p.y) ** 2
|
|
66
|
+
if (d2 < minSqDist) {
|
|
67
|
+
minSqDist = d2
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
} else if (type === 'Polygon' || type === 'MultiLineString') {
|
|
72
|
+
coords.forEach(processLine)
|
|
73
|
+
} else if (type === 'MultiPolygon') {
|
|
74
|
+
coords.forEach((poly) => poly.forEach(processLine))
|
|
75
|
+
} else {
|
|
76
|
+
// No action
|
|
77
|
+
}
|
|
78
|
+
return minSqDist
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Query features prioritizing Layer Order, then Containment for Polygons.
|
|
83
|
+
*/
|
|
84
|
+
export const queryFeatures = (map, point, options = {}) => {
|
|
85
|
+
const { radius = 10 } = options
|
|
86
|
+
const queryArea = [[point.x - radius, point.y - radius], [point.x + radius, point.y + radius]]
|
|
87
|
+
const rawFeatures = map.queryRenderedFeatures(queryArea)
|
|
88
|
+
if (rawFeatures.length === 0) {
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Identify layer visual hierarchy
|
|
93
|
+
const layerStack = []
|
|
94
|
+
rawFeatures.forEach(f => {
|
|
95
|
+
if (layerStack.includes(f.layer.id) === false) {
|
|
96
|
+
layerStack.push(f.layer.id)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Deduplicate Bottom-Up to favor data layers over highlight layers
|
|
101
|
+
const seenIds = new Set()
|
|
102
|
+
const uniqueFeatures = []
|
|
103
|
+
for (let i = rawFeatures.length - 1; i >= 0; i--) {
|
|
104
|
+
const f = rawFeatures[i]
|
|
105
|
+
const featureId = f.id === undefined ? JSON.stringify(f.properties) : f.id
|
|
106
|
+
if (seenIds.has(featureId) === false) {
|
|
107
|
+
seenIds.add(featureId)
|
|
108
|
+
uniqueFeatures.push(f)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const clickLngLat = map.unproject(point)
|
|
113
|
+
const clickPt = [clickLngLat.lng, clickLngLat.lat]
|
|
114
|
+
|
|
115
|
+
return uniqueFeatures
|
|
116
|
+
.map((f) => {
|
|
117
|
+
let score = 0
|
|
118
|
+
const type = f.geometry.type
|
|
119
|
+
const pixelDistSq = getMinDistToGeometry(map, point, f.geometry)
|
|
120
|
+
|
|
121
|
+
// PRIORITY 1: LAYER ORDER
|
|
122
|
+
const layerRank = layerStack.indexOf(f.layer.id)
|
|
123
|
+
score += (layerRank * 1000000)
|
|
124
|
+
|
|
125
|
+
// PRIORITY 2: CONTAINMENT (Polygon Special Treatment)
|
|
126
|
+
if (type.includes('Polygon')) {
|
|
127
|
+
const polys = type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates
|
|
128
|
+
const isInside = polys.some((ring) => isPointInPolygon(clickPt, ring[0]))
|
|
129
|
+
|
|
130
|
+
if (isInside === true) {
|
|
131
|
+
// Massive boost for polygons if we are actually inside them
|
|
132
|
+
score -= 500000 // NOSONAR - tolerance used only here
|
|
133
|
+
} else {
|
|
134
|
+
// If we are outside a polygon, it loses significantly to anything we ARE inside
|
|
135
|
+
score += 100000 // NOSONAR - tolerance used only here
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// PRIORITY 3: DISTANCE (Final Tie-breaker)
|
|
140
|
+
score += pixelDistSq
|
|
141
|
+
|
|
142
|
+
return { f, score }
|
|
143
|
+
})
|
|
144
|
+
.sort((a, b) => a.score - b.score)
|
|
145
|
+
.map(({ f }) => f)
|
|
146
|
+
}
|
|
@@ -123,26 +123,23 @@ const getCardinalMove = (from, to) => {
|
|
|
123
123
|
* @param {Array<[number, number]>} pixels - Array of pixel coordinates.
|
|
124
124
|
* @returns {number} Index of the closest pixel in the given direction.
|
|
125
125
|
*/
|
|
126
|
+
const isInDirection = (direction, dx, dy) => {
|
|
127
|
+
switch (direction) {
|
|
128
|
+
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
|
|
129
|
+
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
|
|
130
|
+
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
|
|
131
|
+
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
|
|
132
|
+
default: return false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
const spatialNavigate = (direction, start, pixels) => {
|
|
127
137
|
const [sx, sy] = start
|
|
128
138
|
|
|
129
139
|
// Direction filters
|
|
130
|
-
const candidates = pixels.filter(([x, y]) =>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const dx = x - sx
|
|
136
|
-
const dy = y - sy
|
|
137
|
-
|
|
138
|
-
switch (direction) {
|
|
139
|
-
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
|
|
140
|
-
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
|
|
141
|
-
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
|
|
142
|
-
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
|
|
143
|
-
default: return false
|
|
144
|
-
}
|
|
145
|
-
})
|
|
140
|
+
const candidates = pixels.filter(([x, y]) =>
|
|
141
|
+
(x !== sx || y !== sy) && isInDirection(direction, x - sx, y - sy)
|
|
142
|
+
)
|
|
146
143
|
|
|
147
144
|
if (!candidates.length) {
|
|
148
145
|
return pixels.findIndex(p => p[0] === sx && p[1] === sy)
|
|
@@ -169,7 +166,7 @@ const getResolution = (center, zoom) => {
|
|
|
169
166
|
const TILE_SIZE = 512
|
|
170
167
|
const lat = center.lat
|
|
171
168
|
const scale = Math.pow(2, zoom)
|
|
172
|
-
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE)
|
|
169
|
+
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE) // NOSONAR - 180 is degrees-to-radians conversion
|
|
173
170
|
return resolution
|
|
174
171
|
}
|
|
175
172
|
|
package/sonar-project.properties
CHANGED
|
@@ -11,13 +11,50 @@ sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
|
|
11
11
|
sonar.sourceEncoding=UTF-8
|
|
12
12
|
|
|
13
13
|
sonar.sources=src,plugins,providers
|
|
14
|
-
sonar.exclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**,**/dist/**,**/node_modules/**
|
|
14
|
+
sonar.exclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**,**/dist/**,**/node_modules/**,plugins/beta/**,providers/beta/**
|
|
15
15
|
sonar.tests=src,plugins,providers
|
|
16
16
|
sonar.test.inclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**
|
|
17
17
|
sonar.cpd.exclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Ignored rules
|
|
20
|
+
sonar.issue.ignore.multicriteria=reactPropsJs,reactPropsJsx,preferGlobalThisJs,preferGlobalThisJsx,preferAtJs,preferAtJsx,replaceAllJs,replaceAllJsx,stringReplaceAllJs,stringReplaceAllJsx,pascalCaseFunctionsJs,pascalCaseFunctionsJsx,pascalCaseFunctionsTs,pascalCaseFunctionsTsx
|
|
21
|
+
|
|
22
|
+
# S6774: React props validation - using TypeScript/JSDoc for prop types instead
|
|
20
23
|
sonar.issue.ignore.multicriteria.reactPropsJs.ruleKey=javascript:S6774
|
|
21
24
|
sonar.issue.ignore.multicriteria.reactPropsJs.resourceKey=**/*.js
|
|
22
25
|
sonar.issue.ignore.multicriteria.reactPropsJsx.ruleKey=javascript:S6774
|
|
23
|
-
sonar.issue.ignore.multicriteria.reactPropsJsx.resourceKey=**/*.jsx
|
|
26
|
+
sonar.issue.ignore.multicriteria.reactPropsJsx.resourceKey=**/*.jsx
|
|
27
|
+
|
|
28
|
+
# S7764: Prefer globalThis over window
|
|
29
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJs.ruleKey=javascript:S7764
|
|
30
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJs.resourceKey=**/*.js
|
|
31
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJsx.ruleKey=javascript:S7764
|
|
32
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJsx.resourceKey=**/*.jsx
|
|
33
|
+
|
|
34
|
+
# S7755: Prefer .at() over [index]
|
|
35
|
+
sonar.issue.ignore.multicriteria.preferAtJs.ruleKey=javascript:S7755
|
|
36
|
+
sonar.issue.ignore.multicriteria.preferAtJs.resourceKey=**/*.js
|
|
37
|
+
sonar.issue.ignore.multicriteria.preferAtJsx.ruleKey=javascript:S7755
|
|
38
|
+
sonar.issue.ignore.multicriteria.preferAtJsx.resourceKey=**/*.jsx
|
|
39
|
+
|
|
40
|
+
# S6316: Array.prototype.replaceAll may not be supported in all browsers
|
|
41
|
+
sonar.issue.ignore.multicriteria.replaceAllJs.ruleKey=javascript:S6316
|
|
42
|
+
sonar.issue.ignore.multicriteria.replaceAllJs.resourceKey=**/*.js
|
|
43
|
+
sonar.issue.ignore.multicriteria.replaceAllJsx.ruleKey=javascript:S6316
|
|
44
|
+
sonar.issue.ignore.multicriteria.replaceAllJsx.resourceKey=**/*.jsx
|
|
45
|
+
|
|
46
|
+
# S7781: Prefer .replaceAll() over .replace() with global regex
|
|
47
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJs.ruleKey=javascript:S7781
|
|
48
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJs.resourceKey=**/*.js
|
|
49
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJsx.ruleKey=javascript:S7781
|
|
50
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJsx.resourceKey=**/*.jsx
|
|
51
|
+
|
|
52
|
+
# S100: Function names should comply with a naming convention (PascalCase for React/Inits) - mustn't pics up React functions
|
|
53
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJs.ruleKey=javascript:S100
|
|
54
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJs.resourceKey=**/*.js
|
|
55
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJsx.ruleKey=javascript:S100
|
|
56
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJsx.resourceKey=**/*.jsx
|
|
57
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTs.ruleKey=javascript:S100
|
|
58
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTs.resourceKey=**/*.ts
|
|
59
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.ruleKey=javascript:S100
|
|
60
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.resourceKey=**/*.tsx
|
|
@@ -3,7 +3,7 @@ import { useApp } from '../../store/appContext'
|
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
5
5
|
// sonarjs/disable-next-line function-name
|
|
6
|
-
export const Actions = ({
|
|
6
|
+
export const Actions = ({ children }) => {
|
|
7
7
|
const { openPanels, panelConfig, breakpoint } = useApp()
|
|
8
8
|
|
|
9
9
|
const childArray = React.Children.toArray(children)
|
|
@@ -21,7 +21,7 @@ export const Actions = ({ slot, children }) => {
|
|
|
21
21
|
].filter(Boolean).join(' ')
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<div className={className} style={
|
|
24
|
+
<div className={className} style={visibleChild ? undefined : { display: 'none' }}>
|
|
25
25
|
{children}
|
|
26
26
|
</div>
|
|
27
27
|
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: row;
|
|
4
4
|
justify-content: center;
|
|
5
|
-
gap: var(--divider-gap);
|
|
5
|
+
gap: calc(var(--divider-gap) / 2);
|
|
6
6
|
padding: var(--panel-margin);
|
|
7
7
|
|
|
8
8
|
.im-c-button-wrapper--wide {
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
.im-o-app--tablet, .im-o-app--desktop {
|
|
24
24
|
.im-c-actions {
|
|
25
|
-
min-width:
|
|
25
|
+
min-width: 0;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// src/components/KeyboardHelp.jsx
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import { useConfig } from '../../store/configContext'
|
|
3
4
|
import { getKeyboardShortcuts } from '../../registry/keyboardShortcutRegistry.js'
|
|
4
5
|
|
|
5
6
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
6
7
|
// sonarjs/disable-next-line function-name
|
|
7
8
|
export const KeyboardHelp = () => {
|
|
8
|
-
const
|
|
9
|
+
const appConfig = useConfig()
|
|
10
|
+
const groups = getKeyboardShortcuts(appConfig).reduce((acc, shortcut) => {
|
|
9
11
|
acc[shortcut.group] = acc[shortcut.group] || []
|
|
10
12
|
acc[shortcut.group].push(shortcut)
|
|
11
13
|
return acc
|
|
@@ -3,12 +3,21 @@ import React from 'react'
|
|
|
3
3
|
import { render, screen, within } from '@testing-library/react'
|
|
4
4
|
import { KeyboardHelp } from './KeyboardHelp'
|
|
5
5
|
import { getKeyboardShortcuts } from '../../registry/keyboardShortcutRegistry.js'
|
|
6
|
+
import { useConfig } from '../../store/configContext'
|
|
6
7
|
|
|
7
8
|
jest.mock('../../registry/keyboardShortcutRegistry.js', () => ({
|
|
8
9
|
getKeyboardShortcuts: jest.fn()
|
|
9
10
|
}))
|
|
10
11
|
|
|
12
|
+
jest.mock('../../store/configContext', () => ({
|
|
13
|
+
useConfig: jest.fn()
|
|
14
|
+
}))
|
|
15
|
+
|
|
11
16
|
describe('KeyboardHelp', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
useConfig.mockReturnValue({})
|
|
19
|
+
})
|
|
20
|
+
|
|
12
21
|
afterEach(() => {
|
|
13
22
|
jest.clearAllMocks()
|
|
14
23
|
})
|
|
@@ -1,13 +1,169 @@
|
|
|
1
1
|
// components/MapButton.jsx
|
|
2
|
+
import React, { useState, useRef } from 'react'
|
|
2
3
|
import { stringToKebab } from '../../../utils/stringToKebab'
|
|
3
4
|
import { Tooltip } from '../Tooltip/Tooltip'
|
|
4
5
|
import { Icon } from '../Icon/Icon'
|
|
5
6
|
import { SlotRenderer } from '../../renderer/SlotRenderer'
|
|
7
|
+
import { PopupMenu } from '../PopupMenu/PopupMenu'
|
|
6
8
|
import { useConfig } from '../../store/configContext'
|
|
7
9
|
import { useApp } from '../../store/appContext'
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Builds CSS class names for the map button element.
|
|
13
|
+
* @param {string} buttonId - Unique identifier for the button (kebab-cased)
|
|
14
|
+
* @param {string} variant - Visual variant of the button (e.g., 'primary', 'secondary')
|
|
15
|
+
* @param {boolean} showLabel - Whether the button label is displayed
|
|
16
|
+
* @returns {string} Space-separated CSS class names
|
|
17
|
+
*/
|
|
18
|
+
const buildButtonClassNames = (buttonId, variant, showLabel) => [
|
|
19
|
+
'im-c-map-button',
|
|
20
|
+
buttonId && `im-c-map-button--${stringToKebab(buttonId)}`,
|
|
21
|
+
variant && `im-c-map-button--${variant}`,
|
|
22
|
+
showLabel && 'im-c-map-button--with-label'
|
|
23
|
+
].filter(Boolean).join(' ')
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds CSS class names for the wrapper div that contains the button.
|
|
27
|
+
* @param {string} buttonId - Unique identifier for the button
|
|
28
|
+
* @param {boolean} showLabel - Whether the button label is displayed
|
|
29
|
+
* @param {boolean} groupStart - Whether this button is at the start of a button group
|
|
30
|
+
* @param {boolean} groupMiddle - Whether this button is in the middle of a button group
|
|
31
|
+
* @param {boolean} groupEnd - Whether this button is at the end of a button group
|
|
32
|
+
* @returns {string} Space-separated CSS class names for the wrapper
|
|
33
|
+
*/
|
|
34
|
+
const buildWrapperClassNames = (buttonId, showLabel, groupStart, groupMiddle, groupEnd) => [
|
|
35
|
+
'im-c-button-wrapper',
|
|
36
|
+
buttonId && `im-c-button-wrapper--${stringToKebab(buttonId)}`,
|
|
37
|
+
showLabel && 'im-c-button-wrapper--wide',
|
|
38
|
+
groupStart && 'im-c-button-wrapper--group-start',
|
|
39
|
+
groupMiddle && 'im-c-button-wrapper--group-middle',
|
|
40
|
+
groupEnd && 'im-c-button-wrapper--group-end'
|
|
41
|
+
].filter(Boolean).join(' ')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handles spacebar key presses on anchor links by triggering a click event.
|
|
45
|
+
* This ensures anchor buttons behave like standard buttons when spacebar is pressed.
|
|
46
|
+
* @param {KeyboardEvent} e - The keyboard event
|
|
47
|
+
*/
|
|
48
|
+
const handleKeyUp = (e) => {
|
|
49
|
+
if (e.key === ' ' || e.key === 'Spacebar') {
|
|
50
|
+
e.preventDefault()
|
|
51
|
+
e.currentTarget.click()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Determines the controlled element (panel or popup menu) for ARIA attributes.
|
|
57
|
+
* @param {Object} options - Configuration options
|
|
58
|
+
* @param {string} options.idPrefix - Prefix for generated IDs
|
|
59
|
+
* @param {string} options.panelId - ID of the controlled panel (if applicable)
|
|
60
|
+
* @param {string} options.buttonId - Unique button identifier
|
|
61
|
+
* @param {boolean} options.hasMenu - Whether the button has a popup menu
|
|
62
|
+
* @returns {Object|null} Object with id and type ('panel' or 'popup'), or null if no controlled element
|
|
63
|
+
*/
|
|
64
|
+
const getControlledElement = ({ idPrefix, panelId, buttonId, hasMenu }) => {
|
|
65
|
+
if (panelId) {
|
|
66
|
+
return { id: `${idPrefix}-panel-${stringToKebab(panelId)}`, type: 'panel' }
|
|
67
|
+
}
|
|
68
|
+
if (hasMenu) {
|
|
69
|
+
return { id: `${idPrefix}-popup-${stringToKebab(buttonId)}`, type: 'popup' }
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Builds the complete props object for a button or anchor element.
|
|
76
|
+
* Handles ARIA attributes, event handlers, refs, and conditional styling.
|
|
77
|
+
* @param {Object} options - Configuration options
|
|
78
|
+
* @param {string} options.appId - Application identifier
|
|
79
|
+
* @param {string} options.buttonId - Unique button identifier
|
|
80
|
+
* @param {string} options.className - CSS classes for the button
|
|
81
|
+
* @param {Function} options.onClick - Click event handler
|
|
82
|
+
* @param {Function} options.onKeyUp - Key up event handler
|
|
83
|
+
* @param {Object} options.buttonRefs - React ref object for storing button references
|
|
84
|
+
* @param {boolean} options.isDisabled - Whether the button is disabled
|
|
85
|
+
* @param {boolean} options.isPressed - Whether the button is in pressed state
|
|
86
|
+
* @param {boolean} options.isExpanded - Whether content controlled by button is expanded
|
|
87
|
+
* @param {boolean} options.isPanelOpen - Whether the controlled panel is open
|
|
88
|
+
* @param {boolean} options.isPopupOpen - Whether the popup menu is open
|
|
89
|
+
* @param {Object|null} options.controlledElement - The element controlled by this button
|
|
90
|
+
* @param {string} options.href - URL for anchor element (if provided, renders as <a> instead of <button>)
|
|
91
|
+
* @returns {Object} Props object suitable for button or anchor element
|
|
92
|
+
*/
|
|
93
|
+
const buildButtonProps = ({
|
|
94
|
+
appId,
|
|
95
|
+
buttonId,
|
|
96
|
+
className,
|
|
97
|
+
onClick,
|
|
98
|
+
onKeyUp,
|
|
99
|
+
buttonRefs,
|
|
100
|
+
isDisabled,
|
|
101
|
+
isPressed,
|
|
102
|
+
isExpanded,
|
|
103
|
+
isPanelOpen,
|
|
104
|
+
isPopupOpen,
|
|
105
|
+
controlledElement,
|
|
106
|
+
href
|
|
107
|
+
}) => {
|
|
108
|
+
let ariaExpanded
|
|
109
|
+
if (controlledElement?.type === 'panel') {
|
|
110
|
+
ariaExpanded = String(isPanelOpen)
|
|
111
|
+
} else if (controlledElement?.type === 'popup') {
|
|
112
|
+
ariaExpanded = isPopupOpen
|
|
113
|
+
} else if (typeof isExpanded === 'boolean') {
|
|
114
|
+
ariaExpanded = isExpanded
|
|
115
|
+
} else {
|
|
116
|
+
// No action
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
id: `${appId}-${stringToKebab(buttonId)}`,
|
|
121
|
+
className,
|
|
122
|
+
onClick,
|
|
123
|
+
onKeyUp,
|
|
124
|
+
ref: (el) => {
|
|
125
|
+
if (buttonRefs.current && buttonId) {
|
|
126
|
+
buttonRefs.current[buttonId] = el
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
'aria-disabled': isDisabled || undefined,
|
|
130
|
+
'aria-expanded': ariaExpanded,
|
|
131
|
+
'aria-pressed': typeof isPressed === 'boolean' ? isPressed : undefined,
|
|
132
|
+
'aria-controls': controlledElement?.id,
|
|
133
|
+
'aria-haspopup': controlledElement?.type === 'popup' || undefined,
|
|
134
|
+
...(href
|
|
135
|
+
? { href, target: '_blank', onKeyUp: handleKeyUp, role: 'button' }
|
|
136
|
+
: { type: 'button' })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* MapButton component - A versatile button for map applications.
|
|
142
|
+
* Supports icons, labels, popups, panels, tooltips, and button grouping.
|
|
143
|
+
* Renders as either a <button> or <a> element depending on props.
|
|
144
|
+
* @component
|
|
145
|
+
* @param {Object} props - Component props
|
|
146
|
+
* @param {string} props.buttonId - Unique identifier for the button
|
|
147
|
+
* @param {string} [props.iconId] - Icon identifier to display
|
|
148
|
+
* @param {string} [props.iconSvgContent] - SVG content as an alternative to iconId
|
|
149
|
+
* @param {string} props.label - Button label/tooltip text
|
|
150
|
+
* @param {boolean} [props.showLabel=false] - Whether to display the label visually
|
|
151
|
+
* @param {boolean} [props.isDisabled=false] - Whether the button is disabled
|
|
152
|
+
* @param {boolean} [props.isPressed] - Whether the button is in pressed state (aria-pressed)
|
|
153
|
+
* @param {boolean} [props.isExpanded] - Whether content controlled by the button is expanded
|
|
154
|
+
* @param {boolean} [props.isHidden=false] - Whether to hide the button (CSS display: none)
|
|
155
|
+
* @param {boolean} [props.isPanelOpen=false] - Whether the controlled panel is open
|
|
156
|
+
* @param {string} [props.variant] - CSS variant class for styling (e.g., 'primary')
|
|
157
|
+
* @param {Function} [props.onClick] - Custom click handler
|
|
158
|
+
* @param {string} [props.panelId] - ID of the panel controlled by this button
|
|
159
|
+
* @param {Array<Object>} [props.menuItems] - Array of items for popup menu
|
|
160
|
+
* @param {string} [props.idPrefix=''] - Prefix for generated panel/popup IDs
|
|
161
|
+
* @param {string} [props.href] - URL for anchor element; if provided, renders as <a> instead of <button>
|
|
162
|
+
* @param {boolean} [props.groupStart=false] - Whether button is at start of button group
|
|
163
|
+
* @param {boolean} [props.groupMiddle=false] - Whether button is in middle of button group
|
|
164
|
+
* @param {boolean} [props.groupEnd=false] - Whether button is at end of button group
|
|
165
|
+
* @returns {JSX.Element} The rendered button component
|
|
166
|
+
*/
|
|
11
167
|
export const MapButton = ({
|
|
12
168
|
buttonId,
|
|
13
169
|
iconId,
|
|
@@ -15,13 +171,14 @@ export const MapButton = ({
|
|
|
15
171
|
label,
|
|
16
172
|
showLabel,
|
|
17
173
|
isDisabled,
|
|
18
|
-
isExpanded,
|
|
19
174
|
isPressed,
|
|
175
|
+
isExpanded,
|
|
20
176
|
isHidden,
|
|
21
|
-
|
|
177
|
+
isPanelOpen,
|
|
22
178
|
variant,
|
|
23
179
|
onClick,
|
|
24
180
|
panelId,
|
|
181
|
+
menuItems,
|
|
25
182
|
idPrefix,
|
|
26
183
|
href,
|
|
27
184
|
groupMiddle,
|
|
@@ -30,42 +187,65 @@ export const MapButton = ({
|
|
|
30
187
|
}) => {
|
|
31
188
|
const { id: appId } = useConfig()
|
|
32
189
|
const { buttonRefs } = useApp()
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
buttonId && `im-c-map-button--${stringToKebab(buttonId)}`,
|
|
37
|
-
variant && `im-c-map-button--${variant}`,
|
|
38
|
-
showLabel && 'im-c-map-button--with-label'
|
|
39
|
-
].filter(Boolean).join(' ')
|
|
190
|
+
const [isPopupOpen, setIsPopupOpen] = useState(false)
|
|
191
|
+
const [menuStartPos, setMenuStartPos] = useState(null)
|
|
192
|
+
const menuRef = useRef(null)
|
|
40
193
|
|
|
41
194
|
const Element = href ? 'a' : 'button'
|
|
195
|
+
const hasMenu = menuItems?.length >= 1
|
|
196
|
+
const controlledElement = getControlledElement({ idPrefix, panelId, buttonId, hasMenu })
|
|
42
197
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Handles button click events.
|
|
200
|
+
* Toggles popup menu visibility if the button controls a popup.
|
|
201
|
+
* Calls the custom onClick handler if provided.
|
|
202
|
+
* @param {React.MouseEvent} e - The click event
|
|
203
|
+
*/
|
|
204
|
+
const handleButtonClick = (e) => {
|
|
205
|
+
if (isDisabled) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
if (controlledElement?.type === 'popup') {
|
|
209
|
+
const isKeyboard = e.nativeEvent.pointerType === ''
|
|
210
|
+
/* istanbul ignore next as pointerType can't be tested in jest */
|
|
211
|
+
setMenuStartPos(isKeyboard ? 'first' : null)
|
|
212
|
+
setIsPopupOpen((prev) => !prev)
|
|
213
|
+
}
|
|
214
|
+
if (onClick) {
|
|
215
|
+
onClick(e)
|
|
47
216
|
}
|
|
48
217
|
}
|
|
49
218
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
'aria-controls': panelId ? `${idPrefix}-panel-${stringToKebab(panelId)}` : undefined,
|
|
63
|
-
...(href
|
|
64
|
-
? { href, target: '_blank', onKeyUp: handleKeyUp, role: 'button' } // only <a>
|
|
65
|
-
: { type: 'button' } // only <button>
|
|
66
|
-
)
|
|
219
|
+
/**
|
|
220
|
+
* Handles key up events on buttons that control popup menus.
|
|
221
|
+
* ArrowDown opens the menu at the first item.
|
|
222
|
+
* ArrowUp opens the menu at the last item.
|
|
223
|
+
* @param {React.KeyboardEvent} e - The keyboard event
|
|
224
|
+
*/
|
|
225
|
+
const handleButtonKeyUp = e => {
|
|
226
|
+
if (hasMenu && ['ArrowDown', 'ArrowUp'].includes(e.key)) {
|
|
227
|
+
e.preventDefault()
|
|
228
|
+
setMenuStartPos(e.key === 'ArrowUp' ? 'last' : 'first')
|
|
229
|
+
setIsPopupOpen(true)
|
|
230
|
+
}
|
|
67
231
|
}
|
|
68
232
|
|
|
233
|
+
const buttonProps = buildButtonProps({
|
|
234
|
+
appId,
|
|
235
|
+
buttonId,
|
|
236
|
+
className: buildButtonClassNames(buttonId, variant, showLabel),
|
|
237
|
+
onClick: handleButtonClick,
|
|
238
|
+
onKeyUp: handleButtonKeyUp,
|
|
239
|
+
buttonRefs,
|
|
240
|
+
isDisabled,
|
|
241
|
+
isPressed,
|
|
242
|
+
isExpanded,
|
|
243
|
+
isPanelOpen,
|
|
244
|
+
isPopupOpen,
|
|
245
|
+
controlledElement,
|
|
246
|
+
href
|
|
247
|
+
})
|
|
248
|
+
|
|
69
249
|
const buttonEl = (
|
|
70
250
|
<Element {...buttonProps}>
|
|
71
251
|
{(iconId || iconSvgContent) && <Icon id={iconId} svgContent={iconSvgContent} />}
|
|
@@ -73,18 +253,14 @@ export const MapButton = ({
|
|
|
73
253
|
</Element>
|
|
74
254
|
)
|
|
75
255
|
|
|
76
|
-
const wrapperClassNames = [
|
|
77
|
-
'im-c-button-wrapper',
|
|
78
|
-
showLabel && ' im-c-button-wrapper--wide',
|
|
79
|
-
groupStart && 'im-c-button-wrapper--group-start',
|
|
80
|
-
groupMiddle && 'im-c-button-wrapper--group-middle',
|
|
81
|
-
groupEnd && 'im-c-button-wrapper--group-end'
|
|
82
|
-
].filter(Boolean).join(' ')
|
|
83
|
-
|
|
84
256
|
return (
|
|
85
|
-
<div
|
|
257
|
+
<div
|
|
258
|
+
className={buildWrapperClassNames(buttonId, showLabel, groupStart, groupMiddle, groupEnd)}
|
|
259
|
+
style={isHidden ? { display: 'none' } : undefined}
|
|
260
|
+
>
|
|
86
261
|
{showLabel ? buttonEl : <Tooltip content={label}>{buttonEl}</Tooltip>}
|
|
87
262
|
{panelId && <SlotRenderer slot={`${stringToKebab(buttonId)}-button`} />}
|
|
263
|
+
{isPopupOpen && <PopupMenu popupMenuId={controlledElement.id} buttonId={buttonId} startPos={menuStartPos} menuRef={menuRef} items={menuItems} setIsOpen={setIsPopupOpen} />}
|
|
88
264
|
</div>
|
|
89
265
|
)
|
|
90
266
|
}
|