@defra/interactive-map 0.0.16-alpha → 0.0.18-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/assets/images/slot-map.svg +264 -0
- 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/context.md +53 -7
- package/docs/api/map-style-config.md +41 -2
- package/docs/api/marker-config.md +53 -11
- package/docs/api/slots.md +16 -15
- package/docs/api/symbol-config.md +160 -0
- package/docs/api/symbol-registry.md +115 -0
- package/docs/api.md +25 -22
- package/docs/getting-started.md +4 -1
- package/docs/plugins/datasets.md +657 -0
- package/docs/plugins/interact.md +68 -43
- package/docs/plugins/search.md +15 -3
- package/docs/plugins.md +1 -1
- package/package.json +2 -2
- package/plugins/beta/datasets/dist/css/index.css +103 -15
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/esm/index.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/DatasetsInit.jsx +29 -9
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +159 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +75 -0
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +440 -0
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +27 -0
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +31 -0
- package/plugins/beta/datasets/src/api/addDataset.js +2 -8
- package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
- package/plugins/beta/datasets/src/api/getStyle.js +13 -0
- package/plugins/beta/datasets/src/api/removeDataset.js +2 -44
- package/plugins/beta/datasets/src/api/setData.js +10 -0
- package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
- package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
- package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
- package/plugins/beta/datasets/src/api/setStyle.js +22 -0
- package/plugins/beta/datasets/src/components/EmptyKey.jsx +7 -0
- package/plugins/beta/datasets/src/components/EmptyKey.test.jsx +21 -0
- package/plugins/beta/datasets/src/components/KeySvg.jsx +24 -0
- package/plugins/beta/datasets/src/components/KeySvgLine.jsx +19 -0
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +15 -0
- package/plugins/beta/datasets/src/components/KeySvgRect.jsx +22 -0
- package/plugins/beta/datasets/src/components/KeySvgSymbol.jsx +16 -0
- package/plugins/beta/datasets/src/components/svgProperties.js +20 -0
- package/plugins/beta/datasets/src/datasets.js +39 -56
- package/plugins/beta/datasets/src/defaults.js +44 -8
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +34 -25
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
- package/plugins/beta/datasets/src/index.js +2 -1
- package/plugins/beta/datasets/src/manifest.js +25 -17
- package/plugins/beta/datasets/src/panels/Key.jsx +51 -51
- package/plugins/beta/datasets/src/panels/Key.module.scss +59 -9
- package/plugins/beta/datasets/src/panels/Layers.jsx +132 -29
- package/plugins/beta/datasets/src/panels/Layers.module.scss +56 -8
- package/plugins/beta/datasets/src/reducer.js +134 -9
- package/plugins/beta/datasets/src/reducers/keyReducer.js +34 -0
- package/plugins/beta/datasets/src/utils/bbox.js +7 -5
- package/plugins/beta/datasets/src/utils/filters.js +5 -2
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +86 -0
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/DrawInit.jsx +3 -2
- package/plugins/beta/draw-ml/dist/css/index.css +21 -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-ml/dist/umd/index.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +4 -3
- package/plugins/beta/draw-ml/src/draw.scss +0 -7
- package/plugins/beta/draw-ml/src/manifest.js +16 -16
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +5 -5
- 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/beta/map-styles/src/MapStyles.jsx +5 -4
- package/plugins/beta/map-styles/src/MapStylesInit.jsx +5 -4
- package/plugins/beta/map-styles/src/manifest.js +1 -1
- package/plugins/beta/scale-bar/dist/css/index.css +1 -1
- package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/src/index.test.js +3 -3
- package/plugins/beta/scale-bar/src/manifest.js +3 -3
- package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
- package/plugins/interact/dist/css/index.css +1 -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/InteractInit.jsx +14 -5
- package/plugins/interact/src/InteractInit.test.js +26 -6
- package/plugins/interact/src/api/enable.test.js +7 -7
- package/plugins/interact/src/defaults.js +4 -6
- package/plugins/interact/src/events.js +9 -6
- package/plugins/interact/src/events.test.js +28 -4
- package/plugins/interact/src/hooks/useHighlightSync.js +3 -3
- package/plugins/interact/src/hooks/useHighlightSync.test.js +6 -6
- package/plugins/interact/src/hooks/useHoverCursor.js +10 -0
- package/plugins/interact/src/hooks/useHoverCursor.test.js +44 -0
- package/plugins/interact/src/hooks/useInteractionHandlers.js +111 -69
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +147 -32
- package/plugins/interact/src/interact.scss +0 -7
- package/plugins/interact/src/manifest.js +14 -18
- package/plugins/interact/src/manifest.test.js +3 -1
- package/plugins/interact/src/reducer.js +23 -4
- package/plugins/interact/src/reducer.test.js +60 -11
- package/plugins/interact/src/utils/buildStylesMap.js +17 -4
- package/plugins/interact/src/utils/buildStylesMap.test.js +16 -2
- package/plugins/interact/src/utils/featureQueries.js +11 -6
- package/plugins/interact/src/utils/featureQueries.test.js +8 -1
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/src/Search.jsx +3 -1
- package/plugins/search/src/components/Form/Form.module.scss +2 -1
- package/plugins/search/src/events/fetchSuggestions.js +6 -4
- package/plugins/search/src/events/fetchSuggestions.test.js +26 -4
- package/plugins/search/src/events/formHandlers.js +3 -3
- package/plugins/search/src/events/formHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -2
- package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
- package/plugins/search/src/utils/updateMap.js +3 -3
- package/plugins/search/src/utils/updateMap.test.js +3 -3
- package/providers/maplibre/dist/esm/im-maplibre-provider.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/appEvents.js +7 -0
- package/providers/maplibre/src/appEvents.test.js +18 -4
- package/providers/maplibre/src/maplibreProvider.js +52 -0
- package/providers/maplibre/src/maplibreProvider.test.js +105 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +37 -7
- package/providers/maplibre/src/utils/highlightFeatures.test.js +153 -95
- package/providers/maplibre/src/utils/hoverCursor.js +61 -0
- package/providers/maplibre/src/utils/hoverCursor.test.js +130 -0
- package/providers/maplibre/src/utils/patternImages.js +70 -0
- package/providers/maplibre/src/utils/patternImages.test.js +180 -0
- package/providers/maplibre/src/utils/queryFeatures.js +38 -16
- package/providers/maplibre/src/utils/queryFeatures.test.js +20 -3
- package/providers/maplibre/src/utils/rasteriseToImageData.js +30 -0
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +69 -0
- package/providers/maplibre/src/utils/symbolImages.js +147 -0
- package/providers/maplibre/src/utils/symbolImages.test.js +248 -0
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +0 -7
- package/src/App/components/Actions/Actions.test.jsx +1 -1
- package/src/App/components/Icon/Icon.jsx +3 -2
- package/src/App/components/Icon/Icon.module.scss +4 -0
- package/src/App/components/Icon/Icon.test.jsx +43 -4
- package/src/App/components/MapButton/MapButton.jsx +42 -17
- package/src/App/components/MapButton/MapButton.module.scss +4 -13
- package/src/App/components/MapButton/MapButton.test.jsx +27 -3
- package/src/App/components/Markers/Markers.jsx +122 -27
- package/src/App/components/Markers/Markers.module.scss +0 -10
- package/src/App/components/Markers/Markers.test.jsx +246 -0
- package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
- package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
- package/src/App/hooks/useButtonStateEvaluator.js +12 -2
- package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
- package/src/App/hooks/useInterfaceAPI.js +6 -0
- package/src/App/hooks/useInterfaceAPI.test.js +156 -0
- package/src/App/hooks/useLayoutMeasurements.js +84 -18
- package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
- package/src/App/hooks/useMarkersAPI.js +2 -5
- package/src/App/hooks/useMarkersAPI.test.js +4 -4
- package/src/App/layout/Layout.jsx +14 -9
- package/src/App/layout/Layout.test.jsx +6 -4
- package/src/App/layout/layout.module.scss +67 -29
- package/src/App/registry/pluginRegistry.js +1 -1
- package/src/App/renderer/HtmlElementHost.jsx +2 -1
- package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
- package/src/App/renderer/mapButtons.js +1 -1
- package/src/App/renderer/mapPanels.test.js +2 -2
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +5 -5
- package/src/App/renderer/slots.js +9 -5
- package/src/App/store/AppProvider.jsx +3 -1
- package/src/App/store/AppProvider.test.jsx +1 -1
- package/src/App/store/ServiceProvider.jsx +8 -4
- package/src/App/store/appActionsMap.js +16 -0
- package/src/App/store/appActionsMap.test.js +27 -0
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +2 -2
- package/src/App/store/appReducer.js +2 -0
- package/src/App/store/mapActionsMap.js +4 -6
- package/src/App/store/mapActionsMap.test.js +3 -2
- package/src/App/store/mapReducer.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +4 -0
- package/src/config/appConfig.js +5 -8
- package/src/config/appConfig.test.js +1 -2
- package/src/config/defaults.js +0 -2
- package/src/config/events.js +28 -0
- package/src/config/mapTheme.js +56 -0
- package/src/config/patternConfig.js +16 -0
- package/src/config/symbolConfig.js +80 -0
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_colors.scss +0 -9
- package/src/scss/settings/_dimensions.scss +0 -1
- package/src/services/patternRegistry.js +40 -0
- package/src/services/patternRegistry.test.js +48 -0
- package/src/services/symbolRegistry.js +113 -0
- package/src/services/symbolRegistry.test.js +262 -0
- package/src/types.js +93 -11
- package/src/utils/getSafeZoneInset.js +9 -7
- package/src/utils/getSafeZoneInset.test.js +10 -10
- package/src/utils/patternUtils.js +94 -0
- package/src/utils/patternUtils.test.js +160 -0
- package/src/utils/symbolUtils.js +85 -0
- package/src/utils/symbolUtils.test.js +156 -0
- package/webpack.dev.mjs +1 -1
- package/docs/api/slot-map.svg +0 -1
- package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
- package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
- package/plugins/beta/datasets/src/api/showDataset.js +0 -14
- package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
- package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
- package/plugins/beta/datasets/src/mapLayers.js +0 -164
- /package/src/{utils → services}/logger.js +0 -0
- /package/src/{utils → services}/logger.test.js +0 -0
|
@@ -23,7 +23,7 @@ describe('useHighlightSync', () => {
|
|
|
23
23
|
},
|
|
24
24
|
mapStyle: { id: 'default-style' },
|
|
25
25
|
pluginState: {
|
|
26
|
-
|
|
26
|
+
layers: [{ layerId: 'layer1' }]
|
|
27
27
|
},
|
|
28
28
|
selectedFeatures: [],
|
|
29
29
|
dispatch: jest.fn(),
|
|
@@ -93,21 +93,21 @@ describe('useHighlightSync', () => {
|
|
|
93
93
|
)
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
it('rebuilds styles when
|
|
96
|
+
it('rebuilds styles when layers change', () => {
|
|
97
97
|
mockDeps.selectedFeatures = [{ featureId: 'F1' }]
|
|
98
98
|
|
|
99
99
|
const { rerender } = renderHook(
|
|
100
|
-
({
|
|
100
|
+
({ layers }) =>
|
|
101
101
|
useHighlightSync({
|
|
102
102
|
...mockDeps,
|
|
103
|
-
pluginState: {
|
|
103
|
+
pluginState: { layers }
|
|
104
104
|
}),
|
|
105
|
-
{ initialProps: {
|
|
105
|
+
{ initialProps: { layers: [{ layerId: 'layer1' }] } }
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
buildStylesMap.mockClear()
|
|
109
109
|
|
|
110
|
-
rerender({
|
|
110
|
+
rerender({ layers: [{ layerId: 'layer1' }, { layerId: 'layer2' }] })
|
|
111
111
|
|
|
112
112
|
expect(buildStylesMap).toHaveBeenCalled()
|
|
113
113
|
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useHoverCursor = (mapProvider, enabled, interactionModes, layers) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const canSelect = enabled && interactionModes?.includes('selectFeature')
|
|
6
|
+
const layerIds = canSelect ? layers.map(l => l.layerId) : []
|
|
7
|
+
mapProvider.setHoverCursor?.(layerIds)
|
|
8
|
+
return () => mapProvider.setHoverCursor?.([])
|
|
9
|
+
}, [enabled, interactionModes, layers])
|
|
10
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
|
+
import { useHoverCursor } from './useHoverCursor.js'
|
|
3
|
+
|
|
4
|
+
const makeProvider = () => ({ setHoverCursor: jest.fn() })
|
|
5
|
+
|
|
6
|
+
describe('useHoverCursor', () => {
|
|
7
|
+
const dataLayers = [{ layerId: 'layer-a' }, { layerId: 'layer-b' }]
|
|
8
|
+
|
|
9
|
+
it('calls setHoverCursor with layer IDs when enabled with selectFeature mode', () => {
|
|
10
|
+
const mapProvider = makeProvider()
|
|
11
|
+
renderHook(() => useHoverCursor(mapProvider, true, ['selectFeature'], dataLayers))
|
|
12
|
+
expect(mapProvider.setHoverCursor).toHaveBeenCalledWith(['layer-a', 'layer-b'])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('calls setHoverCursor with layer IDs when selectFeature is combined with other interactionModes', () => {
|
|
16
|
+
const mapProvider = makeProvider()
|
|
17
|
+
renderHook(() => useHoverCursor(mapProvider, true, ['selectMarker', 'selectFeature'], dataLayers))
|
|
18
|
+
expect(mapProvider.setHoverCursor).toHaveBeenCalledWith(['layer-a', 'layer-b'])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('calls setHoverCursor with empty array when disabled', () => {
|
|
22
|
+
const mapProvider = makeProvider()
|
|
23
|
+
renderHook(() => useHoverCursor(mapProvider, false, ['selectFeature'], dataLayers))
|
|
24
|
+
expect(mapProvider.setHoverCursor).toHaveBeenCalledWith([])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('calls setHoverCursor with empty array when selectFeature is not in interactionModes', () => {
|
|
28
|
+
const mapProvider = makeProvider()
|
|
29
|
+
renderHook(() => useHoverCursor(mapProvider, true, ['selectMarker', 'placeMarker'], dataLayers))
|
|
30
|
+
expect(mapProvider.setHoverCursor).toHaveBeenCalledWith([])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('clears cursor on unmount', () => {
|
|
34
|
+
const mapProvider = makeProvider()
|
|
35
|
+
const { unmount } = renderHook(() => useHoverCursor(mapProvider, true, ['selectFeature'], dataLayers))
|
|
36
|
+
mapProvider.setHoverCursor.mockClear()
|
|
37
|
+
unmount()
|
|
38
|
+
expect(mapProvider.setHoverCursor).toHaveBeenCalledWith([])
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('does not throw when setHoverCursor is absent', () => {
|
|
42
|
+
expect(() => renderHook(() => useHoverCursor({}, true, ['selectFeature'], dataLayers))).not.toThrow()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -1,8 +1,42 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from 'react'
|
|
2
2
|
import { isContiguousWithAny, canSplitFeatures, areAllContiguous } from '../utils/spatial.js'
|
|
3
3
|
import { getFeaturesAtPoint, findMatchingFeature, buildLayerConfigMap } from '../utils/featureQueries.js'
|
|
4
|
+
import { scaleFactor } from '../../../../src/config/appConfig.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns the id of the first DOM marker whose visual bounds contain the given point.
|
|
8
|
+
*
|
|
9
|
+
* MAP_CLICK point is container-relative; getBoundingClientRect is viewport-relative.
|
|
10
|
+
* We convert by subtracting the parent element's top-left (markers share a parent with
|
|
11
|
+
* the map container, so parentElement.getBoundingClientRect() gives the offset).
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} markers - markers object from mapState (has .items and .markerRefs)
|
|
14
|
+
* @param {{ x: number, y: number }} point - container-relative pixel coordinates
|
|
15
|
+
* @param {number} scale - scaleFactor for the current mapSize (e.g. 1.5 for medium)
|
|
16
|
+
* @returns {string|null}
|
|
17
|
+
*/
|
|
18
|
+
const findMarkerAtPoint = (markers, point, scale) => {
|
|
19
|
+
for (const marker of markers.items) {
|
|
20
|
+
const el = markers.markerRefs?.get(marker.id)
|
|
21
|
+
if (!el) {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
const parent = el.parentElement
|
|
25
|
+
const parentRect = parent ? parent.getBoundingClientRect() : { left: 0, top: 0 }
|
|
26
|
+
const { left, top, right, bottom } = el.getBoundingClientRect()
|
|
27
|
+
const scaledX = point.x * scale
|
|
28
|
+
const scaledY = point.y * scale
|
|
29
|
+
if (
|
|
30
|
+
scaledX >= left - parentRect.left && scaledX <= right - parentRect.left &&
|
|
31
|
+
scaledY >= top - parentRect.top && scaledY <= bottom - parentRect.top
|
|
32
|
+
) {
|
|
33
|
+
return marker.id
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
4
38
|
|
|
5
|
-
const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectionBounds) => {
|
|
39
|
+
const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectedMarkers, selectionBounds) => {
|
|
6
40
|
const lastEmittedSelectionChange = useRef(null)
|
|
7
41
|
|
|
8
42
|
useEffect(() => {
|
|
@@ -14,105 +48,113 @@ const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectionBounds)
|
|
|
14
48
|
|
|
15
49
|
// Skip if selection was already empty and remains empty
|
|
16
50
|
const prev = lastEmittedSelectionChange.current
|
|
17
|
-
const wasEmpty = prev === null || prev.length === 0
|
|
18
|
-
if (wasEmpty && selectedFeatures.length === 0) {
|
|
51
|
+
const wasEmpty = prev === null || (prev.features.length === 0 && prev.markers.length === 0)
|
|
52
|
+
if (wasEmpty && selectedFeatures.length === 0 && selectedMarkers.length === 0) {
|
|
19
53
|
return
|
|
20
54
|
}
|
|
21
55
|
|
|
22
56
|
eventBus.emit('interact:selectionchange', {
|
|
23
57
|
selectedFeatures,
|
|
58
|
+
selectedMarkers,
|
|
24
59
|
selectionBounds,
|
|
25
60
|
canMerge: areAllContiguous(selectedFeatures),
|
|
26
61
|
canSplit: canSplitFeatures(selectedFeatures)
|
|
27
62
|
})
|
|
28
63
|
|
|
29
|
-
lastEmittedSelectionChange.current = selectedFeatures
|
|
30
|
-
}, [selectedFeatures, selectionBounds])
|
|
64
|
+
lastEmittedSelectionChange.current = { features: selectedFeatures, markers: selectedMarkers }
|
|
65
|
+
}, [selectedFeatures, selectedMarkers, selectionBounds])
|
|
31
66
|
}
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Core interaction hook. Processes map clicks in fixed priority order:
|
|
70
|
+
* selectMarker → selectFeature → placeMarker (fallback).
|
|
71
|
+
*
|
|
72
|
+
* Which steps are active is controlled by `pluginState.interactionModes`. Steps not
|
|
73
|
+
* present in the array are skipped entirely — e.g. omitting `'selectMarker'` means
|
|
74
|
+
* marker hit-testing is never performed.
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} deps
|
|
77
|
+
* @param {Object} deps.mapState - Map state including markers and mapSize
|
|
78
|
+
* @param {Object} deps.pluginState - Plugin state including interactionModes, layers, etc.
|
|
79
|
+
* @param {Object} deps.services - Services including eventBus
|
|
80
|
+
* @param {Object} deps.mapProvider - Map provider instance for feature queries
|
|
81
|
+
* @returns {{ handleInteraction: Function }}
|
|
82
|
+
*/
|
|
83
|
+
export const useInteractionHandlers = ({ mapState, pluginState, services, mapProvider }) => {
|
|
84
|
+
const { markers, mapSize } = mapState
|
|
85
|
+
const { dispatch, layers, interactionModes, multiSelect, contiguous, marker: markerOptions, tolerance, selectedFeatures, selectedMarkers, selectionBounds, deselectOnClickOutside } = pluginState
|
|
41
86
|
const { eventBus } = services
|
|
42
|
-
const layerConfigMap = buildLayerConfigMap(
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
87
|
+
const layerConfigMap = buildLayerConfigMap(layers)
|
|
88
|
+
const scale = scaleFactor[mapSize] ?? 1
|
|
89
|
+
const processFeatureMatch = useCallback(({ feature, config }) => {
|
|
90
|
+
markers.remove('location')
|
|
91
|
+
const isNewContiguous = contiguous && isContiguousWithAny(feature, selectedFeatures)
|
|
92
|
+
const featureId = feature.properties?.[config.idProperty] ?? feature.id
|
|
93
|
+
if (featureId == null) {
|
|
94
|
+
return
|
|
50
95
|
}
|
|
96
|
+
dispatch({
|
|
97
|
+
type: 'TOGGLE_SELECTED_FEATURES',
|
|
98
|
+
payload: {
|
|
99
|
+
featureId,
|
|
100
|
+
multiSelect,
|
|
101
|
+
layerId: config.layerId,
|
|
102
|
+
idProperty: config.idProperty,
|
|
103
|
+
properties: feature.properties,
|
|
104
|
+
geometry: feature.geometry,
|
|
105
|
+
replaceAll: contiguous && !isNewContiguous
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}, [markers, contiguous, selectedFeatures, dispatch, multiSelect])
|
|
51
109
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
// 1. Handle Feature Match
|
|
56
|
-
if (match) {
|
|
57
|
-
processFeatureMatch(match)
|
|
110
|
+
const processFallback = useCallback(({ coords }) => {
|
|
111
|
+
const canPlace = interactionModes.includes('placeMarker')
|
|
112
|
+
if (!canPlace && !deselectOnClickOutside) {
|
|
58
113
|
return
|
|
59
114
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (isMarkerMode) {
|
|
64
|
-
dispatch({ type: 'CLEAR_SELECTED_FEATURES' })
|
|
65
|
-
markers.add('location', coords, { color: markerColor })
|
|
115
|
+
dispatch({ type: 'CLEAR_SELECTED_FEATURES' })
|
|
116
|
+
if (canPlace) {
|
|
117
|
+
markers.add('location', coords, markerOptions)
|
|
66
118
|
eventBus.emit('interact:markerchange', { coords })
|
|
67
|
-
} else if (deselectOnClickOutside) {
|
|
68
|
-
dispatch({ type: 'CLEAR_SELECTED_FEATURES' })
|
|
69
|
-
} else {
|
|
70
|
-
// No action
|
|
71
119
|
}
|
|
120
|
+
}, [interactionModes, dispatch, markers, markerOptions, eventBus, deselectOnClickOutside])
|
|
72
121
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
markers
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!featureId) {
|
|
122
|
+
const handleInteraction = useCallback(({ point, coords }) => {
|
|
123
|
+
if (interactionModes.includes('selectMarker')) {
|
|
124
|
+
const markerHit = findMarkerAtPoint(markers, point, scale)
|
|
125
|
+
if (markerHit) {
|
|
126
|
+
dispatch({ type: 'TOGGLE_SELECTED_MARKERS', payload: { markerId: markerHit, multiSelect } })
|
|
80
127
|
return
|
|
81
128
|
}
|
|
129
|
+
}
|
|
82
130
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
})
|
|
131
|
+
if (interactionModes.includes('selectFeature') && layers.length > 0) {
|
|
132
|
+
const allFeatures = getFeaturesAtPoint(mapProvider, point, { radius: tolerance })
|
|
133
|
+
if (pluginState?.debug) {
|
|
134
|
+
console.log(`--- Features at ${coords} ---`, allFeatures)
|
|
135
|
+
}
|
|
136
|
+
const match = findMatchingFeature(allFeatures, layerConfigMap)
|
|
137
|
+
if (match) {
|
|
138
|
+
processFeatureMatch(match)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
95
141
|
}
|
|
142
|
+
|
|
143
|
+
processFallback({ coords })
|
|
96
144
|
}, [
|
|
97
145
|
mapProvider,
|
|
98
|
-
|
|
99
|
-
|
|
146
|
+
layers,
|
|
147
|
+
interactionModes,
|
|
100
148
|
multiSelect,
|
|
101
|
-
eventBus,
|
|
102
149
|
dispatch,
|
|
103
150
|
markers,
|
|
104
|
-
contiguous,
|
|
105
|
-
selectedFeatures,
|
|
106
151
|
layerConfigMap,
|
|
107
152
|
pluginState?.debug,
|
|
108
153
|
tolerance,
|
|
109
|
-
|
|
110
|
-
|
|
154
|
+
processFeatureMatch,
|
|
155
|
+
processFallback,
|
|
156
|
+
scale
|
|
111
157
|
])
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
handleInteraction
|
|
117
|
-
}
|
|
158
|
+
useSelectionChangeEmitter(eventBus, selectedFeatures, selectedMarkers, selectionBounds)
|
|
159
|
+
return { handleInteraction }
|
|
118
160
|
}
|
|
@@ -30,19 +30,27 @@ const click = result =>
|
|
|
30
30
|
})
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const makeMarkerEl = (rect) => ({
|
|
34
|
+
getBoundingClientRect: () => rect,
|
|
35
|
+
parentElement: {
|
|
36
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 })
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const setup = (pluginOverrides = {}, markerItems = [], markerRefs = new Map()) => {
|
|
34
41
|
const deps = {
|
|
35
42
|
mapState: {
|
|
36
|
-
markers: { add: jest.fn(), remove: jest.fn() }
|
|
43
|
+
markers: { add: jest.fn(), remove: jest.fn(), items: markerItems, markerRefs }
|
|
37
44
|
},
|
|
38
45
|
pluginState: {
|
|
39
46
|
dispatch: jest.fn(),
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
layers: [{ layerId: 'parcels', idProperty: 'parcelId' }],
|
|
48
|
+
interactionModes: ['selectMarker', 'selectFeature'],
|
|
42
49
|
multiSelect: false,
|
|
43
50
|
contiguous: false,
|
|
44
|
-
|
|
51
|
+
marker: { symbol: 'pin', backgroundColor: 'red' },
|
|
45
52
|
selectedFeatures: [],
|
|
53
|
+
selectedMarkers: [],
|
|
46
54
|
selectionBounds: null,
|
|
47
55
|
...pluginOverrides
|
|
48
56
|
},
|
|
@@ -73,12 +81,94 @@ beforeEach(() => {
|
|
|
73
81
|
})
|
|
74
82
|
|
|
75
83
|
/* ------------------------------------------------------------------ */
|
|
76
|
-
/*
|
|
84
|
+
/* DOM marker hit detection */
|
|
85
|
+
/* ------------------------------------------------------------------ */
|
|
86
|
+
|
|
87
|
+
describe('DOM marker hit detection', () => {
|
|
88
|
+
it('dispatches TOGGLE_SELECTED_MARKERS when click is within a marker bounds', () => {
|
|
89
|
+
const markerEl = makeMarkerEl({ left: 5, top: 15, right: 15, bottom: 25 })
|
|
90
|
+
const markerRefs = new Map([['marker-1', markerEl]])
|
|
91
|
+
const markerItems = [{ id: 'marker-1', coords: [1, 2] }]
|
|
92
|
+
|
|
93
|
+
const { result, deps } = setup({}, markerItems, markerRefs)
|
|
94
|
+
click(result)
|
|
95
|
+
|
|
96
|
+
expect(deps.pluginState.dispatch).toHaveBeenCalledWith({
|
|
97
|
+
type: 'TOGGLE_SELECTED_MARKERS',
|
|
98
|
+
payload: { markerId: 'marker-1', multiSelect: false }
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('markers take precedence over features when both hit', () => {
|
|
103
|
+
const markerEl = makeMarkerEl({ left: 5, top: 15, right: 15, bottom: 25 })
|
|
104
|
+
const markerRefs = new Map([['marker-1', markerEl]])
|
|
105
|
+
const markerItems = [{ id: 'marker-1', coords: [1, 2] }]
|
|
106
|
+
|
|
107
|
+
const { result, deps } = setup({}, markerItems, markerRefs)
|
|
108
|
+
click(result)
|
|
109
|
+
|
|
110
|
+
expect(deps.pluginState.dispatch).not.toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({ type: 'TOGGLE_SELECTED_FEATURES' })
|
|
112
|
+
)
|
|
113
|
+
expect(featureQueries.getFeaturesAtPoint).not.toHaveBeenCalled()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('skips markers with no ref and continues to next', () => {
|
|
117
|
+
// marker in items but not in markerRefs — should not throw, should fall through to features
|
|
118
|
+
const markerItems = [{ id: 'marker-no-ref', coords: [1, 2] }]
|
|
119
|
+
const markerRefs = new Map() // no entry for marker-no-ref
|
|
120
|
+
|
|
121
|
+
const { result, deps } = setup({}, markerItems, markerRefs)
|
|
122
|
+
click(result)
|
|
123
|
+
|
|
124
|
+
expect(featureQueries.getFeaturesAtPoint).toHaveBeenCalled()
|
|
125
|
+
expect(deps.pluginState.dispatch).toHaveBeenCalledWith(
|
|
126
|
+
expect.objectContaining({ type: 'TOGGLE_SELECTED_FEATURES' })
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('uses zero offset when marker element has no parentElement', () => {
|
|
131
|
+
// parentElement is null — fallback parentRect { left: 0, top: 0 } should be used
|
|
132
|
+
const markerEl = {
|
|
133
|
+
getBoundingClientRect: () => ({ left: 5, top: 15, right: 15, bottom: 25 }),
|
|
134
|
+
parentElement: null
|
|
135
|
+
}
|
|
136
|
+
const markerRefs = new Map([['marker-1', markerEl]])
|
|
137
|
+
const markerItems = [{ id: 'marker-1', coords: [1, 2] }]
|
|
138
|
+
|
|
139
|
+
const { result, deps } = setup({}, markerItems, markerRefs)
|
|
140
|
+
click(result)
|
|
141
|
+
|
|
142
|
+
// click point { x: 10, y: 20 } is within [5,15,15,25] with zero parent offset
|
|
143
|
+
expect(deps.pluginState.dispatch).toHaveBeenCalledWith({
|
|
144
|
+
type: 'TOGGLE_SELECTED_MARKERS',
|
|
145
|
+
payload: { markerId: 'marker-1', multiSelect: false }
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('falls through to feature selection when click misses all markers', () => {
|
|
150
|
+
// marker bounds don't include the click point { x: 10, y: 20 }
|
|
151
|
+
const markerEl = makeMarkerEl({ left: 50, top: 50, right: 80, bottom: 80 })
|
|
152
|
+
const markerRefs = new Map([['marker-1', markerEl]])
|
|
153
|
+
const markerItems = [{ id: 'marker-1', coords: [1, 2] }]
|
|
154
|
+
|
|
155
|
+
const { result, deps } = setup({}, markerItems, markerRefs)
|
|
156
|
+
click(result)
|
|
157
|
+
|
|
158
|
+
expect(featureQueries.getFeaturesAtPoint).toHaveBeenCalled()
|
|
159
|
+
expect(deps.pluginState.dispatch).toHaveBeenCalledWith(
|
|
160
|
+
expect.objectContaining({ type: 'TOGGLE_SELECTED_FEATURES' })
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
/* ------------------------------------------------------------------ */
|
|
166
|
+
/* placeMarker mode */
|
|
77
167
|
/* ------------------------------------------------------------------ */
|
|
78
168
|
|
|
79
|
-
describe('
|
|
169
|
+
describe('placeMarker mode', () => {
|
|
80
170
|
it('places marker, clears selection, and emits event', () => {
|
|
81
|
-
const { result, deps } = setup({
|
|
171
|
+
const { result, deps } = setup({ interactionModes: ['placeMarker'] })
|
|
82
172
|
|
|
83
173
|
click(result)
|
|
84
174
|
|
|
@@ -88,7 +178,7 @@ describe('marker mode', () => {
|
|
|
88
178
|
expect(deps.mapState.markers.add).toHaveBeenCalledWith(
|
|
89
179
|
'location',
|
|
90
180
|
[1, 2],
|
|
91
|
-
{
|
|
181
|
+
{ symbol: 'pin', backgroundColor: 'red' }
|
|
92
182
|
)
|
|
93
183
|
expect(deps.services.eventBus.emit).toHaveBeenCalledWith(
|
|
94
184
|
'interact:markerchange',
|
|
@@ -98,32 +188,30 @@ describe('marker mode', () => {
|
|
|
98
188
|
})
|
|
99
189
|
|
|
100
190
|
/* ------------------------------------------------------------------ */
|
|
101
|
-
/*
|
|
191
|
+
/* selectFeature mode */
|
|
102
192
|
/* ------------------------------------------------------------------ */
|
|
103
193
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const { result, deps } = setup({ interactionMode: mode })
|
|
194
|
+
it('selectFeature mode dispatches selection for matching feature', () => {
|
|
195
|
+
const { result, deps } = setup({ interactionModes: ['selectFeature'] })
|
|
107
196
|
|
|
108
|
-
|
|
197
|
+
click(result)
|
|
109
198
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
199
|
+
expect(deps.pluginState.dispatch).toHaveBeenCalledWith(
|
|
200
|
+
expect.objectContaining({
|
|
201
|
+
type: 'TOGGLE_SELECTED_FEATURES',
|
|
202
|
+
payload: expect.objectContaining({
|
|
203
|
+
featureId: 'P1',
|
|
204
|
+
layerId: 'parcels'
|
|
117
205
|
})
|
|
118
|
-
)
|
|
119
|
-
|
|
206
|
+
})
|
|
207
|
+
)
|
|
120
208
|
})
|
|
121
209
|
|
|
122
|
-
it('falls back to
|
|
210
|
+
it('falls back to placeMarker when selectFeature finds no match', () => {
|
|
123
211
|
featureQueries.getFeaturesAtPoint.mockReturnValue([])
|
|
124
212
|
featureQueries.findMatchingFeature.mockReturnValue(null)
|
|
125
213
|
|
|
126
|
-
const { result, deps } = setup({
|
|
214
|
+
const { result, deps } = setup({ interactionModes: ['selectFeature', 'placeMarker'] })
|
|
127
215
|
|
|
128
216
|
click(result)
|
|
129
217
|
|
|
@@ -248,32 +336,58 @@ describe('deselectOnClickOutside', () => {
|
|
|
248
336
|
})
|
|
249
337
|
|
|
250
338
|
/* ------------------------------------------------------------------ */
|
|
251
|
-
/*
|
|
339
|
+
/* placeMarker / selectFeature guards */
|
|
252
340
|
/* ------------------------------------------------------------------ */
|
|
253
341
|
|
|
254
|
-
it('
|
|
342
|
+
it('places marker with placeMarker mode even when no layers exist', () => {
|
|
255
343
|
featureQueries.getFeaturesAtPoint.mockReturnValue([])
|
|
256
344
|
featureQueries.findMatchingFeature.mockReturnValue(null)
|
|
257
345
|
|
|
258
346
|
const { result, deps } = setup({
|
|
259
|
-
|
|
260
|
-
|
|
347
|
+
interactionModes: ['selectFeature', 'placeMarker'],
|
|
348
|
+
layers: []
|
|
261
349
|
})
|
|
262
350
|
|
|
263
351
|
click(result)
|
|
264
352
|
|
|
353
|
+
expect(deps.mapState.markers.add).toHaveBeenCalled()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('does not place marker when placeMarker is not in interactionModes', () => {
|
|
357
|
+
featureQueries.getFeaturesAtPoint.mockReturnValue([])
|
|
358
|
+
featureQueries.findMatchingFeature.mockReturnValue(null)
|
|
359
|
+
|
|
360
|
+
const { result, deps } = setup({ interactionModes: ['selectFeature'] })
|
|
361
|
+
|
|
362
|
+
click(result)
|
|
363
|
+
|
|
265
364
|
expect(deps.mapState.markers.add).not.toHaveBeenCalled()
|
|
266
365
|
})
|
|
267
366
|
|
|
367
|
+
it('does not check markers when selectMarker is not in interactionModes', () => {
|
|
368
|
+
const markerEl = makeMarkerEl({ left: 5, top: 15, right: 15, bottom: 25 })
|
|
369
|
+
const markerRefs = new Map([['marker-1', markerEl]])
|
|
370
|
+
const markerItems = [{ id: 'marker-1', coords: [1, 2] }]
|
|
371
|
+
|
|
372
|
+
const { result, deps } = setup({ interactionModes: ['selectFeature'] }, markerItems, markerRefs)
|
|
373
|
+
click(result)
|
|
374
|
+
|
|
375
|
+
expect(deps.pluginState.dispatch).not.toHaveBeenCalledWith(
|
|
376
|
+
expect.objectContaining({ type: 'TOGGLE_SELECTED_MARKERS' })
|
|
377
|
+
)
|
|
378
|
+
expect(featureQueries.getFeaturesAtPoint).toHaveBeenCalled()
|
|
379
|
+
})
|
|
380
|
+
|
|
268
381
|
/* ------------------------------------------------------------------ */
|
|
269
382
|
/* Selection change event */
|
|
270
383
|
/* ------------------------------------------------------------------ */
|
|
271
384
|
|
|
272
385
|
it('emits selectionchange once when bounds exist', () => {
|
|
273
386
|
const deps = {
|
|
274
|
-
mapState: { markers: { add: jest.fn(), remove: jest.fn() } },
|
|
387
|
+
mapState: { markers: { add: jest.fn(), remove: jest.fn(), items: [], markerRefs: new Map() } },
|
|
275
388
|
pluginState: {
|
|
276
389
|
selectedFeatures: [{ featureId: 'F1' }],
|
|
390
|
+
selectedMarkers: [],
|
|
277
391
|
selectionBounds: { sw: [0, 0], ne: [1, 1] }
|
|
278
392
|
},
|
|
279
393
|
services: { eventBus: { emit: jest.fn() } },
|
|
@@ -286,6 +400,7 @@ it('emits selectionchange once when bounds exist', () => {
|
|
|
286
400
|
'interact:selectionchange',
|
|
287
401
|
expect.objectContaining({
|
|
288
402
|
selectedFeatures: deps.pluginState.selectedFeatures,
|
|
403
|
+
selectedMarkers: [],
|
|
289
404
|
selectionBounds: deps.pluginState.selectionBounds,
|
|
290
405
|
canMerge: false,
|
|
291
406
|
canSplit: false
|
|
@@ -299,8 +414,8 @@ it('skips emission when selection remains empty after being cleared', () => {
|
|
|
299
414
|
// 1. First render with a feature (prev is null, emission happens)
|
|
300
415
|
const { rerender } = renderHook(
|
|
301
416
|
({ features }) => useInteractionHandlers({
|
|
302
|
-
mapState: { markers: {} },
|
|
303
|
-
pluginState: { selectedFeatures: features, selectionBounds: { b: 1 } },
|
|
417
|
+
mapState: { markers: { items: [], markerRefs: new Map() } },
|
|
418
|
+
pluginState: { selectedFeatures: features, selectedMarkers: [], selectionBounds: { b: 1 } },
|
|
304
419
|
services: { eventBus },
|
|
305
420
|
mapProvider: {}
|
|
306
421
|
}),
|
|
@@ -21,11 +21,4 @@
|
|
|
21
21
|
.im-o-app--mobile .im-c-mark-location .im-c-map-button {
|
|
22
22
|
flex: 1 1 auto;
|
|
23
23
|
min-width: fit-content;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Position touch select button
|
|
27
|
-
.im-c-button-wrapper--select-at-target {
|
|
28
|
-
position: absolute;
|
|
29
|
-
bottom: calc(100% + var(--primary-gap));
|
|
30
|
-
z-index: 0;
|
|
31
24
|
}
|
|
@@ -16,24 +16,6 @@ export const manifest = {
|
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
buttons: [{
|
|
19
|
-
id: 'selectAtTarget',
|
|
20
|
-
label: 'Select',
|
|
21
|
-
iconId: 'select',
|
|
22
|
-
variant: 'touch',
|
|
23
|
-
hiddenWhen: ({ appState, pluginState }) => !pluginState.enabled || appState.interfaceType !== 'touch',
|
|
24
|
-
mobile: {
|
|
25
|
-
slot: 'actions',
|
|
26
|
-
showLabel: false
|
|
27
|
-
},
|
|
28
|
-
tablet: {
|
|
29
|
-
slot: 'actions',
|
|
30
|
-
showLabel: false
|
|
31
|
-
},
|
|
32
|
-
desktop: {
|
|
33
|
-
slot: 'actions',
|
|
34
|
-
showLabel: false
|
|
35
|
-
}
|
|
36
|
-
}, {
|
|
37
19
|
id: 'selectCancel',
|
|
38
20
|
label: 'Back',
|
|
39
21
|
variant: 'tertiary',
|
|
@@ -50,6 +32,20 @@ export const manifest = {
|
|
|
50
32
|
slot: 'actions',
|
|
51
33
|
showLabel: true
|
|
52
34
|
}
|
|
35
|
+
}, {
|
|
36
|
+
id: 'selectAtTarget',
|
|
37
|
+
label: 'Select',
|
|
38
|
+
variant: 'primary',
|
|
39
|
+
hiddenWhen: ({ appState, pluginState }) => !pluginState.enabled || appState.interfaceType !== 'touch',
|
|
40
|
+
mobile: {
|
|
41
|
+
slot: 'actions'
|
|
42
|
+
},
|
|
43
|
+
tablet: {
|
|
44
|
+
slot: 'actions'
|
|
45
|
+
},
|
|
46
|
+
desktop: {
|
|
47
|
+
slot: 'actions'
|
|
48
|
+
}
|
|
53
49
|
}, {
|
|
54
50
|
id: 'selectDone',
|
|
55
51
|
label: 'Continue',
|
|
@@ -25,7 +25,9 @@ describe('manifest', () => {
|
|
|
25
25
|
manifest.buttons.forEach(b => {
|
|
26
26
|
['mobile', 'tablet', 'desktop'].forEach(dev => {
|
|
27
27
|
expect(b[dev].slot).toBe('actions')
|
|
28
|
-
|
|
28
|
+
if (b[dev].showLabel !== undefined) {
|
|
29
|
+
expect(typeof b[dev].showLabel).toBe('boolean')
|
|
30
|
+
}
|
|
29
31
|
})
|
|
30
32
|
})
|
|
31
33
|
})
|