@defra/interactive-map 0.0.22-alpha → 0.0.24-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/light-raster-thumb.jpg +0 -0
- package/assets/images/outdoor-raster-thumb.jpg +0 -0
- package/assets/images/road-raster-thumb.jpg +0 -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/button-definition.md +39 -0
- package/docs/api/context.md +4 -4
- package/docs/api/map-style-config.md +11 -2
- package/docs/api/marker-config.md +38 -7
- package/docs/api/panel-definition.md +7 -3
- package/docs/api/symbol-config.md +10 -26
- package/docs/api/symbol-registry.md +28 -13
- package/docs/api.md +24 -2
- package/docs/assets/css/{docusaurus.css → docusaurus.scss} +18 -12
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/examples/index.mdx +12 -0
- package/docs/examples/toggle-marker-label.mdx +89 -0
- package/docs/plugins/interact.md +4 -11
- package/docs/plugins/search.md +70 -0
- package/docusaurus.config.cjs +2 -1
- package/package.json +23 -9
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/beta/datasets/dist/css/index.css +2 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +5 -5
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +24 -29
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +2 -2
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +3 -3
- package/plugins/beta/datasets/src/components/KeySvg.jsx +2 -2
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +1 -2
- package/plugins/beta/datasets/src/components/svgProperties.js +2 -2
- package/plugins/beta/datasets/src/panels/Key.module.scss +2 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/css/index.css +1 -21
- 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/src/api/editFeature.js +9 -4
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +11 -3
- 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/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +1 -0
- 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 +23 -55
- package/plugins/interact/src/InteractInit.test.js +58 -80
- package/plugins/interact/src/defaults.js +1 -3
- package/plugins/interact/src/hooks/useAttachEvents.js +46 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +68 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +47 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +54 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +30 -8
- package/plugins/interact/src/hooks/useHighlightSync.test.js +127 -80
- package/plugins/interact/src/hooks/useInteractionHandlers.js +18 -17
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +22 -61
- package/plugins/interact/src/hooks/useMapItemList.js +187 -0
- package/plugins/interact/src/hooks/useMapItemList.test.js +584 -0
- package/plugins/interact/src/index.js +0 -2
- package/plugins/interact/src/index.test.js +0 -2
- package/plugins/interact/src/manifest.js +37 -6
- package/plugins/interact/src/reducer.js +8 -4
- package/plugins/interact/src/reducer.test.js +18 -3
- package/plugins/interact/src/utils/buildStylesMap.js +21 -10
- package/plugins/interact/src/utils/buildStylesMap.test.js +29 -29
- package/plugins/interact/src/utils/spatial.js +0 -15
- package/plugins/interact/src/utils/spatial.test.js +1 -13
- 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 -1
- package/plugins/search/src/components/CloseButton/CloseButton.module.scss +2 -0
- package/plugins/search/src/components/Form/Form.jsx +11 -7
- package/plugins/search/src/components/Form/Form.test.jsx +23 -1
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +1 -1
- package/plugins/search/src/components/OpenButton/OpenButton.module.scss +3 -0
- package/plugins/search/src/datasets.js +2 -2
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +7 -1
- package/plugins/search/src/events/fetchSuggestions.js +13 -10
- package/plugins/search/src/events/fetchSuggestions.test.js +14 -1
- package/plugins/search/src/events/formHandlers.js +2 -4
- package/plugins/search/src/events/formHandlers.test.js +3 -3
- package/plugins/search/src/events/inputHandlers.js +1 -1
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -1
- package/plugins/search/src/events/suggestionHandlers.test.js +7 -1
- package/plugins/search/src/index.js +3 -7
- package/plugins/search/src/reducer.js +10 -5
- package/plugins/search/src/reducer.test.js +2 -1
- package/plugins/search/src/search.scss +1 -0
- package/plugins/search/src/utils/parseOsNamesResults.js +3 -3
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -0
- package/providers/beta/openlayers/dist/esm/index.js +1 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +2 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/dist/umd/index.js +2 -0
- package/providers/beta/openlayers/dist/umd/index.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/src/appEvents.js +50 -0
- package/providers/beta/openlayers/src/appEvents.test.js +140 -0
- package/providers/beta/openlayers/src/defaults.js +36 -0
- package/providers/beta/openlayers/src/index.js +21 -0
- package/providers/beta/openlayers/src/mapEvents.js +132 -0
- package/providers/beta/openlayers/src/mapEvents.test.js +197 -0
- package/providers/beta/openlayers/src/openlayersProvider.js +234 -0
- package/providers/beta/openlayers/src/openlayersProvider.test.js +331 -0
- package/providers/beta/openlayers/src/utils/spatial.js +142 -0
- package/providers/beta/openlayers/src/utils/spatial.test.js +117 -0
- package/providers/beta/openlayers/src/utils/zoom.js +26 -0
- package/providers/beta/openlayers/src/utils/zoom.test.js +23 -0
- 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-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +9 -14
- package/providers/maplibre/src/mapEvents.js +11 -5
- package/providers/maplibre/src/mapEvents.test.js +14 -3
- package/providers/maplibre/src/maplibreProvider.js +18 -11
- package/providers/maplibre/src/maplibreProvider.test.js +43 -36
- package/providers/maplibre/src/utils/highlightFeatures.js +112 -57
- package/providers/maplibre/src/utils/highlightFeatures.test.js +179 -51
- package/providers/maplibre/src/utils/maplibreFixes.js +10 -0
- package/providers/maplibre/src/utils/maplibreFixes.test.js +31 -0
- package/providers/maplibre/src/utils/patternImages.js +22 -19
- package/providers/maplibre/src/utils/patternImages.test.js +146 -167
- package/providers/maplibre/src/utils/rasteriseToImageData.js +1 -4
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +23 -24
- package/providers/maplibre/src/utils/symbolImages.js +23 -86
- package/providers/maplibre/src/utils/symbolImages.test.js +60 -105
- package/rollup.esm.mjs +14 -3
- package/src/App/components/Actions/Actions.module.scss +1 -0
- package/src/App/components/Actions/Actions.test.jsx +17 -4
- package/src/App/components/Hints/Hints.jsx +52 -0
- package/src/App/components/Hints/Hints.module.scss +37 -0
- package/src/App/components/Hints/Hints.test.jsx +104 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +57 -19
- package/src/App/components/KeyboardHelp/KeyboardHelp.module.scss +10 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +140 -31
- package/src/App/components/Markers/LabelMarker.jsx +14 -0
- package/src/App/components/Markers/LabelMarker.test.jsx +36 -0
- package/src/App/components/Markers/Markers.jsx +44 -75
- package/src/App/components/Markers/Markers.module.scss +58 -0
- package/src/App/components/Markers/Markers.test.jsx +90 -113
- package/src/App/components/Markers/SymbolLabelMarker.jsx +43 -0
- package/src/App/components/Markers/SymbolLabelMarker.test.jsx +56 -0
- package/src/App/components/Markers/SymbolMarker.jsx +31 -0
- package/src/App/components/Markers/SymbolMarker.test.jsx +72 -0
- package/src/App/components/Panel/Panel.jsx +1 -1
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +117 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +132 -85
- package/src/App/components/Tabs/Tabs.jsx +68 -0
- package/src/App/components/Tabs/Tabs.module.scss +57 -0
- package/src/App/components/Tabs/Tabs.test.jsx +121 -0
- package/src/App/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/App/components/Viewport/Features.jsx +35 -0
- package/src/App/components/Viewport/Features.module.scss +31 -0
- package/src/App/components/Viewport/Features.test.jsx +124 -0
- package/src/App/components/Viewport/MapStatus.jsx +0 -4
- package/src/App/components/Viewport/Viewport.jsx +35 -29
- package/src/App/components/Viewport/Viewport.module.scss +7 -21
- package/src/App/components/Viewport/Viewport.test.jsx +108 -65
- package/src/App/controls/keyboardActions.js +9 -3
- package/src/App/controls/keyboardActions.test.js +122 -87
- package/src/App/controls/keyboardShortcuts.js +1 -14
- package/src/App/hooks/useFeatureFocus.js +199 -0
- package/src/App/hooks/useFeatureFocus.test.js +635 -0
- package/src/App/hooks/useFeatureItems.js +30 -0
- package/src/App/hooks/useFeatureItems.test.js +98 -0
- package/src/App/hooks/useFocusVisible.js +4 -7
- package/src/App/hooks/useFocusVisible.test.js +6 -6
- package/src/App/hooks/useKeyboardHint.js +9 -34
- package/src/App/hooks/useKeyboardHint.test.js +34 -84
- package/src/App/hooks/useKeyboardShortcuts.js +8 -5
- package/src/App/hooks/useKeyboardShortcuts.test.js +16 -12
- package/src/App/hooks/useLayoutMeasurements.js +10 -4
- package/src/App/hooks/useLayoutMeasurements.test.js +30 -1
- package/src/App/hooks/useMarkerCursor.js +33 -0
- package/src/App/hooks/useMarkerCursor.test.js +87 -0
- package/src/App/hooks/useMarkersAPI.js +32 -9
- package/src/App/hooks/useMarkersAPI.test.js +163 -94
- package/src/App/hooks/useModalPanelBehaviour.test.js +2 -1
- package/src/App/layout/Layout.jsx +2 -0
- package/src/App/layout/Layout.test.jsx +3 -0
- package/src/App/registry/keyboardShortcutRegistry.js +3 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +6 -8
- package/src/App/renderer/mapButtons.js +5 -1
- package/src/App/renderer/mapButtons.test.js +37 -1
- package/src/App/store/AppProvider.test.jsx +5 -0
- package/src/App/store/MapProvider.test.jsx +8 -1
- package/src/App/store/ServiceProvider.jsx +4 -1
- package/src/App/store/ServiceProvider.test.jsx +11 -0
- package/src/App/store/appActionsMap.js +5 -1
- package/src/App/store/appActionsMap.test.js +13 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +12 -0
- package/src/InteractiveMap/InteractiveMap.test.js +2 -0
- package/src/config/appConfig.js +3 -1
- package/src/config/appConfig.test.js +4 -0
- package/src/config/defaults.js +1 -1
- package/src/config/events.js +8 -0
- package/src/config/mapTheme.js +8 -4
- package/src/config/symbolConfig.js +23 -16
- package/src/scss/main.scss +3 -0
- package/src/scss/tools/_border-focus.scss +0 -1
- package/src/services/announcer.test.js +5 -7
- package/src/services/eventBus.js +15 -0
- package/src/services/eventBus.test.js +11 -0
- package/src/services/hints.js +47 -0
- package/src/services/hints.test.js +110 -0
- package/src/services/patternRegistry.js +69 -1
- package/src/services/patternRegistry.test.js +112 -1
- package/src/services/reverseGeocode.js +4 -0
- package/src/services/reverseGeocode.test.js +11 -1
- package/src/services/symbolRegistry.js +152 -17
- package/src/services/symbolRegistry.test.js +141 -31
- package/src/types.js +71 -14
- package/src/utils/constrainKeyboardFocus.js +5 -5
- package/src/utils/detectInterfaceType.js +27 -5
- package/src/utils/detectInterfaceType.test.js +29 -3
- package/src/utils/patternUtils.js +4 -67
- package/src/utils/patternUtils.test.js +1 -123
- package/src/utils/symbolUtils.js +11 -13
- package/src/utils/symbolUtils.test.js +18 -26
- package/webpack.dev.mjs +6 -1
- package/webpack.umd.mjs +1 -0
- package/plugins/interact/dist/css/index.css +0 -1
- package/plugins/interact/src/interact.scss +0 -24
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { render, cleanup } from '@testing-library/react'
|
|
2
|
+
import { render, cleanup, fireEvent } from '@testing-library/react'
|
|
3
3
|
import { Viewport } from './Viewport.jsx'
|
|
4
4
|
import { useConfig } from '../../store/configContext.js'
|
|
5
5
|
import { useApp } from '../../store/appContext.js'
|
|
@@ -9,9 +9,6 @@ import { useKeyboardHint } from '../../hooks/useKeyboardHint.js'
|
|
|
9
9
|
import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts.js'
|
|
10
10
|
import { useMapEvents } from '../../hooks/useMapEvents.js'
|
|
11
11
|
|
|
12
|
-
// ---------------------------
|
|
13
|
-
// Mock dependencies
|
|
14
|
-
// ---------------------------
|
|
15
12
|
jest.mock('../../store/configContext.js', () => ({ useConfig: jest.fn() }))
|
|
16
13
|
jest.mock('../../store/appContext.js', () => ({ useApp: jest.fn() }))
|
|
17
14
|
jest.mock('../../store/mapContext.js', () => ({ useMap: jest.fn() }))
|
|
@@ -22,60 +19,60 @@ jest.mock('../../hooks/useMapEvents.js', () => ({ useMapEvents: jest.fn() }))
|
|
|
22
19
|
jest.mock('../CrossHair/CrossHair', () => ({ CrossHair: jest.fn(() => <div data-testid='cross-hair' />) }))
|
|
23
20
|
jest.mock('../Markers/Markers', () => ({ Markers: jest.fn(() => <div data-testid='markers' />) }))
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const KEYBOARD_HINT_TEXT = 'Test keyboad hint text'
|
|
23
|
+
|
|
24
|
+
const mockMapProvider = { initMap: jest.fn(), updateMap: jest.fn(), clearHighlightedLabel: jest.fn() }
|
|
25
|
+
|
|
26
|
+
function setupHookMocks (mainEl, viewportEl) {
|
|
27
|
+
useConfig.mockReturnValue({
|
|
28
|
+
id: 'test-map',
|
|
29
|
+
mapLabel: 'Test Map',
|
|
30
|
+
keyboardHintText: KEYBOARD_HINT_TEXT,
|
|
31
|
+
mapProvider: mockMapProvider
|
|
32
|
+
})
|
|
33
|
+
useApp.mockReturnValue({
|
|
34
|
+
interfaceType: 'desktop',
|
|
35
|
+
mode: 'default',
|
|
36
|
+
previousMode: 'default',
|
|
37
|
+
layoutRefs: { mainRef: { current: mainEl }, viewportRef: { current: viewportEl }, safeZoneRef: { current: null } },
|
|
38
|
+
safeZoneInset: {},
|
|
39
|
+
dispatch: jest.fn()
|
|
40
|
+
})
|
|
41
|
+
useMap.mockReturnValue({ mapSize: 'medium', dispatch: jest.fn() })
|
|
42
|
+
useService.mockReturnValue({
|
|
43
|
+
announce: jest.fn(),
|
|
44
|
+
hints: { show: jest.fn(), dismiss: jest.fn(), subscribe: jest.fn(() => jest.fn()) },
|
|
45
|
+
eventBus: { on: jest.fn(), off: jest.fn(), emit: jest.fn() }
|
|
46
|
+
})
|
|
47
|
+
useKeyboardHint.mockImplementation(({ onViewportFocusChange }) => ({
|
|
48
|
+
handleFocus: () => onViewportFocusChange(true),
|
|
49
|
+
handleBlur: () => onViewportFocusChange(false)
|
|
50
|
+
}))
|
|
51
|
+
useKeyboardShortcuts.mockImplementation(() => {})
|
|
52
|
+
useMapEvents.mockImplementation(() => {})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderViewport () {
|
|
56
|
+
const { container, rerender } = render(<Viewport />)
|
|
57
|
+
const viewport = container.querySelector('.im-c-viewport')
|
|
58
|
+
const mapContainer = container.querySelector('.im-c-viewport__map-container')
|
|
59
|
+
const safeZone = container.querySelector('.im-c-viewport__safezone')
|
|
60
|
+
const crossHair = container.querySelector('[data-testid="cross-hair"]')
|
|
61
|
+
const markers = container.querySelector('[data-testid="markers"]')
|
|
62
|
+
return { container, viewport, mapContainer, safeZone, crossHair, markers, rerender }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe('Viewport rendering', () => {
|
|
66
|
+
let viewportEl, mainEl
|
|
29
67
|
|
|
30
68
|
beforeEach(() => {
|
|
31
69
|
cleanup()
|
|
32
70
|
jest.clearAllMocks()
|
|
33
|
-
|
|
34
71
|
viewportEl = document.createElement('div')
|
|
35
72
|
mainEl = document.createElement('div')
|
|
36
73
|
document.body.appendChild(viewportEl)
|
|
37
74
|
document.body.appendChild(mainEl)
|
|
38
|
-
|
|
39
|
-
// ---------------------------
|
|
40
|
-
// Hook mocks
|
|
41
|
-
// ---------------------------
|
|
42
|
-
useConfig.mockReturnValue({
|
|
43
|
-
id: 'test-map',
|
|
44
|
-
mapLabel: 'Test Map',
|
|
45
|
-
keyboardHintText: 'Press arrow keys',
|
|
46
|
-
mapProvider: mockMapProvider
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
useApp.mockReturnValue({
|
|
50
|
-
interfaceType: 'desktop',
|
|
51
|
-
mode: 'default',
|
|
52
|
-
previousMode: 'default',
|
|
53
|
-
layoutRefs: { mainRef: { current: mainEl }, viewportRef: { current: viewportEl }, safeZoneRef: { current: null } },
|
|
54
|
-
safeZoneInset: {}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
useMap.mockReturnValue({
|
|
58
|
-
mapSize: 'medium',
|
|
59
|
-
dispatch: jest.fn()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
useService.mockReturnValue({
|
|
63
|
-
announce: jest.fn(),
|
|
64
|
-
eventBus: {
|
|
65
|
-
on: jest.fn(),
|
|
66
|
-
off: jest.fn(),
|
|
67
|
-
emit: jest.fn()
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
useKeyboardHint.mockImplementation(({ onViewportFocusChange }) => ({
|
|
72
|
-
showHint: true,
|
|
73
|
-
handleFocus: () => onViewportFocusChange(true),
|
|
74
|
-
handleBlur: () => onViewportFocusChange(false)
|
|
75
|
-
}))
|
|
76
|
-
|
|
77
|
-
useKeyboardShortcuts.mockImplementation(() => {})
|
|
78
|
-
useMapEvents.mockImplementation(() => {})
|
|
75
|
+
setupHookMocks(mainEl, viewportEl)
|
|
79
76
|
})
|
|
80
77
|
|
|
81
78
|
afterEach(() => {
|
|
@@ -83,17 +80,6 @@ describe('Viewport', () => {
|
|
|
83
80
|
mainEl.remove()
|
|
84
81
|
})
|
|
85
82
|
|
|
86
|
-
const renderViewport = () => {
|
|
87
|
-
const { container, rerender, unmount } = render(<Viewport />)
|
|
88
|
-
const viewport = container.querySelector('.im-c-viewport')
|
|
89
|
-
const mapContainer = container.querySelector('.im-c-viewport__map-container')
|
|
90
|
-
const safeZone = container.querySelector('.im-c-viewport__safezone')
|
|
91
|
-
const keyboardHint = mainEl.querySelector('.im-c-viewport__keyboard-hint')
|
|
92
|
-
const crossHair = container.querySelector('[data-testid="cross-hair"]')
|
|
93
|
-
const markers = container.querySelector('[data-testid="markers"]')
|
|
94
|
-
return { viewport, mapContainer, safeZone, keyboardHint, crossHair, markers, rerender, unmount }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
83
|
it('renders viewport, map container, safe zone, CrossHair, and Markers', () => {
|
|
98
84
|
const { viewport, mapContainer, safeZone, crossHair, markers } = renderViewport()
|
|
99
85
|
expect(viewport).toBeInTheDocument()
|
|
@@ -109,10 +95,44 @@ describe('Viewport', () => {
|
|
|
109
95
|
expect(viewport).toHaveClass('im-c-viewport--medium')
|
|
110
96
|
})
|
|
111
97
|
|
|
112
|
-
it('
|
|
113
|
-
const {
|
|
114
|
-
expect(
|
|
115
|
-
|
|
98
|
+
it('sets aria-describedby to the shared hints container id', () => {
|
|
99
|
+
const { viewport } = renderViewport()
|
|
100
|
+
expect(viewport).toHaveAttribute('aria-describedby', 'test-map-keyboard-desc')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('calls hints.show() with keyboardHintText when viewport gains keyboard focus', () => {
|
|
104
|
+
const { hints } = useService()
|
|
105
|
+
renderViewport()
|
|
106
|
+
const { onViewportFocusChange } = useKeyboardHint.mock.calls[0][0]
|
|
107
|
+
onViewportFocusChange(true)
|
|
108
|
+
expect(hints.show).toHaveBeenCalledWith(KEYBOARD_HINT_TEXT, { duration: 0 })
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('calls hints.dismiss() when viewport loses focus', () => {
|
|
112
|
+
const { hints } = useService()
|
|
113
|
+
renderViewport()
|
|
114
|
+
const { onViewportFocusChange } = useKeyboardHint.mock.calls[0][0]
|
|
115
|
+
onViewportFocusChange(false)
|
|
116
|
+
expect(hints.dismiss).toHaveBeenCalled()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('Viewport interactions', () => {
|
|
121
|
+
let viewportEl, mainEl
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
cleanup()
|
|
125
|
+
jest.clearAllMocks()
|
|
126
|
+
viewportEl = document.createElement('div')
|
|
127
|
+
mainEl = document.createElement('div')
|
|
128
|
+
document.body.appendChild(viewportEl)
|
|
129
|
+
document.body.appendChild(mainEl)
|
|
130
|
+
setupHookMocks(mainEl, viewportEl)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
afterEach(() => {
|
|
134
|
+
viewportEl.remove()
|
|
135
|
+
mainEl.remove()
|
|
116
136
|
})
|
|
117
137
|
|
|
118
138
|
it('attaches keyboard shortcuts', () => {
|
|
@@ -120,6 +140,15 @@ describe('Viewport', () => {
|
|
|
120
140
|
expect(useKeyboardShortcuts).toHaveBeenCalled()
|
|
121
141
|
})
|
|
122
142
|
|
|
143
|
+
it('dispatches SET_LISTBOX_ACTIVE when interact:listboxcapable fires', () => {
|
|
144
|
+
renderViewport()
|
|
145
|
+
const { eventBus } = useService()
|
|
146
|
+
const [, handler] = eventBus.on.mock.calls.find(([event]) => event === 'interact:listboxcapable')
|
|
147
|
+
handler()
|
|
148
|
+
const { dispatch } = useApp()
|
|
149
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_LISTBOX_ACTIVE' })
|
|
150
|
+
})
|
|
151
|
+
|
|
123
152
|
it('calls mapProvider.clearHighlightedLabel on map:click', () => {
|
|
124
153
|
const clearMock = jest.fn()
|
|
125
154
|
useConfig.mockReturnValueOnce({ ...useConfig(), mapProvider: { ...mockMapProvider, clearHighlightedLabel: clearMock } })
|
|
@@ -128,6 +157,20 @@ describe('Viewport', () => {
|
|
|
128
157
|
expect(clearMock).toHaveBeenCalled()
|
|
129
158
|
})
|
|
130
159
|
|
|
160
|
+
it('calls hints.show() when the features listbox gains focus', () => {
|
|
161
|
+
const { hints } = useService()
|
|
162
|
+
const { container } = renderViewport()
|
|
163
|
+
fireEvent.focus(container.querySelector('[role="listbox"]'))
|
|
164
|
+
expect(hints.show).toHaveBeenCalledWith(KEYBOARD_HINT_TEXT, { duration: 0 })
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('calls hints.dismiss() when the features listbox loses focus', () => {
|
|
168
|
+
const { hints } = useService()
|
|
169
|
+
const { container } = renderViewport()
|
|
170
|
+
fireEvent.blur(container.querySelector('[role="listbox"]'))
|
|
171
|
+
expect(hints.dismiss).toHaveBeenCalled()
|
|
172
|
+
})
|
|
173
|
+
|
|
131
174
|
it('focuses viewport when mode changes', () => {
|
|
132
175
|
const { viewport, rerender } = renderViewport()
|
|
133
176
|
const focusMock = jest.spyOn(viewport, 'focus')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { reverseGeocode } from '../../services/reverseGeocode.js'
|
|
1
|
+
import { reverseGeocode, hasReverseGeocode } from '../../services/reverseGeocode.js'
|
|
2
|
+
import { logger } from '../../services/logger.js'
|
|
2
3
|
|
|
3
4
|
export const createKeyboardActions = (mapProvider, announce, {
|
|
4
5
|
containerRef,
|
|
@@ -13,12 +14,13 @@ export const createKeyboardActions = (mapProvider, announce, {
|
|
|
13
14
|
const getZoom = (shift) => (shift ? nudgeZoomDelta : zoomDelta)
|
|
14
15
|
|
|
15
16
|
return {
|
|
16
|
-
showKeyboardControls: () => {
|
|
17
|
+
showKeyboardControls: (e) => {
|
|
18
|
+
const context = e.target === containerRef.current ? 'viewport' : 'listbox'
|
|
17
19
|
dispatch({
|
|
18
20
|
type: 'OPEN_PANEL',
|
|
19
21
|
payload: {
|
|
20
22
|
panelId: 'keyboardHelp',
|
|
21
|
-
props: { triggeringElement:
|
|
23
|
+
props: { triggeringElement: e.target, context }
|
|
22
24
|
}
|
|
23
25
|
})
|
|
24
26
|
},
|
|
@@ -36,6 +38,10 @@ export const createKeyboardActions = (mapProvider, announce, {
|
|
|
36
38
|
zoomOut: (e) => mapProvider.zoomOut(getZoom(e.shiftKey)),
|
|
37
39
|
|
|
38
40
|
getInfo: async (_e) => {
|
|
41
|
+
if (!hasReverseGeocode()) {
|
|
42
|
+
logger.warn('Alt+I pressed but reverseGeocode has not been configured')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
39
45
|
const coord = mapProvider.getCenter()
|
|
40
46
|
const place = await reverseGeocode(mapProvider.getZoom(), coord)
|
|
41
47
|
const area = mapProvider.getAreaDimensions?.()
|
|
@@ -1,139 +1,174 @@
|
|
|
1
1
|
import { createKeyboardActions } from './keyboardActions.js'
|
|
2
|
-
import { reverseGeocode } from '../../services/reverseGeocode.js'
|
|
2
|
+
import { reverseGeocode, hasReverseGeocode } from '../../services/reverseGeocode.js'
|
|
3
|
+
import { logger } from '../../services/logger.js'
|
|
3
4
|
|
|
4
5
|
jest.mock('../../services/reverseGeocode.js', () => ({
|
|
5
|
-
reverseGeocode: jest.fn()
|
|
6
|
+
reverseGeocode: jest.fn(),
|
|
7
|
+
hasReverseGeocode: jest.fn()
|
|
6
8
|
}))
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
jest.mock('../../services/logger.js', () => ({
|
|
11
|
+
logger: { warn: jest.fn() }
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const PAN_DELTA = 10
|
|
15
|
+
const NUDGE_PAN_DELTA = 5
|
|
16
|
+
const ZOOM_DELTA = 2
|
|
17
|
+
const NUDGE_ZOOM_DELTA = 1
|
|
18
|
+
const ZOOM_LEVEL = 12
|
|
19
|
+
|
|
20
|
+
const makeMapProvider = () => ({
|
|
21
|
+
panBy: jest.fn(),
|
|
22
|
+
zoomIn: jest.fn(),
|
|
23
|
+
zoomOut: jest.fn(),
|
|
24
|
+
getCenter: jest.fn().mockReturnValue({ lng: 1, lat: 2 }),
|
|
25
|
+
getZoom: jest.fn().mockReturnValue(ZOOM_LEVEL),
|
|
26
|
+
getAreaDimensions: jest.fn().mockReturnValue('5km²'),
|
|
27
|
+
highlightNextLabel: jest.fn(),
|
|
28
|
+
highlightLabelAtCenter: jest.fn(),
|
|
29
|
+
clearHighlightedLabel: jest.fn()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const makeActions = (overrides = {}) => {
|
|
33
|
+
const mapProvider = makeMapProvider()
|
|
34
|
+
const announce = jest.fn()
|
|
35
|
+
const dispatch = jest.fn()
|
|
36
|
+
const containerEl = document.createElement('div')
|
|
37
|
+
const containerRef = { current: containerEl }
|
|
38
|
+
const actions = createKeyboardActions(mapProvider, announce, {
|
|
39
|
+
containerRef,
|
|
40
|
+
dispatch,
|
|
41
|
+
panDelta: PAN_DELTA,
|
|
42
|
+
nudgePanDelta: NUDGE_PAN_DELTA,
|
|
43
|
+
zoomDelta: ZOOM_DELTA,
|
|
44
|
+
nudgeZoomDelta: NUDGE_ZOOM_DELTA,
|
|
45
|
+
readMapText: true,
|
|
46
|
+
...overrides
|
|
20
47
|
})
|
|
48
|
+
return { actions, mapProvider, announce, dispatch, containerRef }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
document.body.querySelectorAll('[role="listbox"]').forEach(el => el.remove())
|
|
53
|
+
})
|
|
21
54
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
containerRef,
|
|
32
|
-
dispatch,
|
|
33
|
-
panDelta,
|
|
34
|
-
nudgePanDelta,
|
|
35
|
-
zoomDelta,
|
|
36
|
-
nudgeZoomDelta,
|
|
37
|
-
readMapText
|
|
55
|
+
beforeAll(() => {
|
|
56
|
+
Object.defineProperty(window, 'matchMedia', { // NOSONAR: window required for broader browser support
|
|
57
|
+
writable: true,
|
|
58
|
+
value: jest.fn().mockReturnValue({
|
|
59
|
+
matches: false,
|
|
60
|
+
addListener: jest.fn(),
|
|
61
|
+
removeListener: jest.fn(),
|
|
62
|
+
addEventListener: jest.fn(),
|
|
63
|
+
removeEventListener: jest.fn()
|
|
38
64
|
})
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
hasReverseGeocode.mockReturnValue(true)
|
|
70
|
+
})
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
highlightNextLabel: jest.fn(),
|
|
49
|
-
highlightLabelAtCenter: jest.fn(),
|
|
50
|
-
clearHighlightedLabel: jest.fn()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
announce = jest.fn()
|
|
54
|
-
dispatch = jest.fn()
|
|
55
|
-
containerRef = { current: {} }
|
|
56
|
-
readMapText = true
|
|
72
|
+
describe('navigation actions', () => {
|
|
73
|
+
test('showKeyboardControls uses viewport context when target is the container', () => {
|
|
74
|
+
const { actions, dispatch, containerRef } = makeActions()
|
|
75
|
+
actions.showKeyboardControls({ target: containerRef.current })
|
|
76
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
77
|
+
type: 'OPEN_PANEL',
|
|
78
|
+
payload: { panelId: 'keyboardHelp', props: { triggeringElement: containerRef.current, context: 'viewport' } }
|
|
79
|
+
})
|
|
57
80
|
})
|
|
58
81
|
|
|
59
|
-
test('showKeyboardControls
|
|
60
|
-
|
|
82
|
+
test('showKeyboardControls uses listbox context when target is not the container', () => {
|
|
83
|
+
const { actions, dispatch } = makeActions()
|
|
84
|
+
const target = document.createElement('div')
|
|
85
|
+
actions.showKeyboardControls({ target })
|
|
61
86
|
expect(dispatch).toHaveBeenCalledWith({
|
|
62
87
|
type: 'OPEN_PANEL',
|
|
63
|
-
payload: {
|
|
64
|
-
panelId: 'keyboardHelp',
|
|
65
|
-
props: { triggeringElement: containerRef.current }
|
|
66
|
-
}
|
|
88
|
+
payload: { panelId: 'keyboardHelp', props: { triggeringElement: target, context: 'listbox' } }
|
|
67
89
|
})
|
|
68
90
|
})
|
|
69
91
|
|
|
70
92
|
test.each([
|
|
71
|
-
['panUp', { shiftKey: false }, [0, -
|
|
72
|
-
['panDown', { shiftKey: true }, [0,
|
|
73
|
-
['panLeft', { shiftKey: false }, [-
|
|
74
|
-
['panRight', { shiftKey: true }, [
|
|
93
|
+
['panUp', { shiftKey: false }, [0, -PAN_DELTA]],
|
|
94
|
+
['panDown', { shiftKey: true }, [0, NUDGE_PAN_DELTA]],
|
|
95
|
+
['panLeft', { shiftKey: false }, [-PAN_DELTA, 0]],
|
|
96
|
+
['panRight', { shiftKey: true }, [NUDGE_PAN_DELTA, 0]]
|
|
75
97
|
])('%s uses correct deltas', (method, event, expected) => {
|
|
76
|
-
|
|
98
|
+
const { actions, mapProvider } = makeActions()
|
|
99
|
+
actions[method](event)
|
|
77
100
|
expect(mapProvider.panBy).toHaveBeenCalledWith(expected)
|
|
78
101
|
})
|
|
79
102
|
|
|
80
103
|
test.each([
|
|
81
|
-
['zoomIn', { shiftKey: false },
|
|
82
|
-
['zoomOut', { shiftKey: true },
|
|
104
|
+
['zoomIn', { shiftKey: false }, ZOOM_DELTA],
|
|
105
|
+
['zoomOut', { shiftKey: true }, NUDGE_ZOOM_DELTA]
|
|
83
106
|
])('%s uses correct deltas', (method, event, expected) => {
|
|
84
|
-
|
|
107
|
+
const { actions, mapProvider } = makeActions()
|
|
108
|
+
actions[method](event)
|
|
85
109
|
expect(mapProvider[method]).toHaveBeenCalledWith(expected)
|
|
86
110
|
})
|
|
87
111
|
|
|
88
|
-
test('
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
test('clearSelection calls clearHighlightedLabel', () => {
|
|
113
|
+
const { actions, mapProvider } = makeActions()
|
|
114
|
+
actions.clearSelection()
|
|
115
|
+
expect(mapProvider.clearHighlightedLabel).toHaveBeenCalled()
|
|
116
|
+
})
|
|
117
|
+
})
|
|
91
118
|
|
|
92
|
-
|
|
119
|
+
describe('getInfo', () => {
|
|
120
|
+
test('does nothing when reverseGeocode is not configured', async () => {
|
|
121
|
+
hasReverseGeocode.mockReturnValue(false)
|
|
122
|
+
const { actions, announce } = makeActions()
|
|
123
|
+
await actions.getInfo()
|
|
124
|
+
expect(reverseGeocode).not.toHaveBeenCalled()
|
|
125
|
+
expect(announce).not.toHaveBeenCalled()
|
|
126
|
+
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('reverseGeocode'))
|
|
127
|
+
})
|
|
93
128
|
|
|
129
|
+
test('performs reverse geocode and announces result with area', async () => {
|
|
130
|
+
reverseGeocode.mockResolvedValue('London')
|
|
131
|
+
const { actions, announce } = makeActions()
|
|
132
|
+
await actions.getInfo()
|
|
133
|
+
expect(reverseGeocode).toHaveBeenCalledWith(ZOOM_LEVEL, { lng: 1, lat: 2 })
|
|
94
134
|
expect(announce).toHaveBeenCalledWith('London. Covering 5km².', 'core')
|
|
95
135
|
})
|
|
96
136
|
|
|
97
|
-
test('
|
|
98
|
-
delete mapProvider.getAreaDimensions
|
|
137
|
+
test('announces result without area when getAreaDimensions is absent', async () => {
|
|
99
138
|
reverseGeocode.mockResolvedValue('Paris')
|
|
100
|
-
|
|
101
|
-
|
|
139
|
+
const { actions, mapProvider, announce } = makeActions()
|
|
140
|
+
delete mapProvider.getAreaDimensions
|
|
141
|
+
await actions.getInfo()
|
|
102
142
|
expect(announce).toHaveBeenCalledWith('Paris.', 'core')
|
|
103
143
|
})
|
|
144
|
+
})
|
|
104
145
|
|
|
146
|
+
describe('label actions', () => {
|
|
105
147
|
test('highlightNextLabel announces returned label', () => {
|
|
148
|
+
const { actions, mapProvider, announce } = makeActions()
|
|
106
149
|
mapProvider.highlightNextLabel.mockReturnValue('Label A')
|
|
107
|
-
|
|
108
|
-
|
|
150
|
+
actions.highlightNextLabel({ key: 'A' })
|
|
109
151
|
expect(announce).toHaveBeenCalledWith('Label A', 'core')
|
|
110
152
|
})
|
|
111
153
|
|
|
112
|
-
test('highlightNextLabel no-op when readMapText
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
154
|
+
test('highlightNextLabel is no-op when readMapText is false', () => {
|
|
155
|
+
const { actions, mapProvider, announce } = makeActions({ readMapText: false })
|
|
156
|
+
actions.highlightNextLabel({ key: 'X' })
|
|
116
157
|
expect(mapProvider.highlightNextLabel).not.toHaveBeenCalled()
|
|
117
158
|
expect(announce).not.toHaveBeenCalled()
|
|
118
159
|
})
|
|
119
160
|
|
|
120
|
-
test('highlightLabelAtCenter announces label', () => {
|
|
161
|
+
test('highlightLabelAtCenter announces returned label', () => {
|
|
162
|
+
const { actions, mapProvider, announce } = makeActions()
|
|
121
163
|
mapProvider.highlightLabelAtCenter.mockReturnValue('Center Label')
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
actions.highlightLabelAtCenter()
|
|
124
165
|
expect(announce).toHaveBeenCalledWith('Center Label', 'core')
|
|
125
166
|
})
|
|
126
167
|
|
|
127
|
-
test('highlightLabelAtCenter no-op when readMapText
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
168
|
+
test('highlightLabelAtCenter is no-op when readMapText is false', () => {
|
|
169
|
+
const { actions, mapProvider, announce } = makeActions({ readMapText: false })
|
|
170
|
+
actions.highlightLabelAtCenter()
|
|
131
171
|
expect(mapProvider.highlightLabelAtCenter).not.toHaveBeenCalled()
|
|
132
172
|
expect(announce).not.toHaveBeenCalled()
|
|
133
173
|
})
|
|
134
|
-
|
|
135
|
-
test('clearSelection calls correct method', () => {
|
|
136
|
-
create().clearSelection()
|
|
137
|
-
expect(mapProvider.clearHighlightedLabel).toHaveBeenCalled()
|
|
138
|
-
})
|
|
139
174
|
})
|
|
@@ -3,49 +3,37 @@
|
|
|
3
3
|
export const coreShortcuts = [
|
|
4
4
|
{
|
|
5
5
|
id: 'showKeyboardHelp',
|
|
6
|
-
group: 'General',
|
|
7
6
|
title: 'Show keyboard help',
|
|
8
7
|
command: '<kbd>Alt</kbd> + <kbd>K</kbd>',
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
id: 'selectControl',
|
|
13
|
-
group: 'General',
|
|
14
|
-
title: 'Select a map control',
|
|
15
|
-
command: '<kbd>Tab</kbd> or <kbd>Shift</kbd> + <kbd>Tab</kbd>',
|
|
8
|
+
context: 'global',
|
|
16
9
|
enabled: true
|
|
17
10
|
},
|
|
18
11
|
{
|
|
19
12
|
id: 'moveLarge',
|
|
20
|
-
group: 'Navigation',
|
|
21
13
|
title: 'Move in large steps',
|
|
22
14
|
command: '<kbd>←</kbd>, <kbd>↑</kbd>, <kbd>→</kbd> or <kbd>↓</kbd>',
|
|
23
15
|
enabled: true
|
|
24
16
|
},
|
|
25
17
|
{
|
|
26
18
|
id: 'nudgeMap',
|
|
27
|
-
group: 'Navigation',
|
|
28
19
|
title: 'Nudge map',
|
|
29
20
|
command: '<kbd>Shift</kbd> + <kbd>←</kbd>, <kbd>↑</kbd>, <kbd>→</kbd> or <kbd>↓</kbd>',
|
|
30
21
|
enabled: false
|
|
31
22
|
},
|
|
32
23
|
{
|
|
33
24
|
id: 'zoomLarge',
|
|
34
|
-
group: 'Navigation',
|
|
35
25
|
title: 'Zoom in large steps',
|
|
36
26
|
command: '<kbd>+</kbd> or <kbd>-</kbd>',
|
|
37
27
|
enabled: true
|
|
38
28
|
},
|
|
39
29
|
{
|
|
40
30
|
id: 'nudgeZoom',
|
|
41
|
-
group: 'Navigation',
|
|
42
31
|
title: 'Nudge zoom',
|
|
43
32
|
command: '<kbd>Shift</kbd> + <kbd>+</kbd> or <kbd>-</kbd>',
|
|
44
33
|
enabled: false
|
|
45
34
|
},
|
|
46
35
|
{
|
|
47
36
|
id: 'highlightLabelAtCenter',
|
|
48
|
-
group: 'Labels',
|
|
49
37
|
title: 'Highlight label at centre',
|
|
50
38
|
command: '<kbd>Alt</kbd> + <kbd>Enter</kbd>',
|
|
51
39
|
enabled: false,
|
|
@@ -53,7 +41,6 @@ export const coreShortcuts = [
|
|
|
53
41
|
},
|
|
54
42
|
{
|
|
55
43
|
id: 'highlightNextLabel',
|
|
56
|
-
group: 'Labels',
|
|
57
44
|
title: 'Highlight nearby label',
|
|
58
45
|
command: '<kbd>Alt</kbd> + <kbd>→</kbd>, <kbd>←</kbd>, <kbd>↑</kbd> or <kbd>↓</kbd>',
|
|
59
46
|
enabled: false,
|