@defra/interactive-map 0.0.35-alpha → 0.0.37-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api.md +15 -1
- package/package.json +1 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/events.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +20 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +1 -0
- package/plugins/datasets/src/components/LayersMenu/Layers.module.scss +3 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenuGroupWrapper.jsx +3 -1
- package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +3 -1
- package/plugins/datasets/src/initialise/initialiseDatasets.js +2 -2
- package/plugins/datasets/src/reducers/pluginState.js +2 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +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/hooks/useMapItemList.js +2 -2
- package/providers/beta/openlayers/src/utils/tileLayers.test.js +1 -1
- package/providers/maplibre/src/utils/queryFeatures.test.js +6 -6
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/MoveControl/MoveControl.jsx +113 -0
- package/src/App/components/MoveControl/MoveControl.module.scss +121 -0
- package/src/App/components/MoveControl/MoveControl.test.jsx +196 -0
- package/src/App/components/Tabs/Tabs.jsx +1 -0
- package/src/App/components/Tabs/Tabs.test.jsx +11 -34
- package/src/App/controls/keyboardActions.js +4 -2
- package/src/App/hooks/useMarkersAPI.test.js +1 -1
- package/src/App/registry/pluginRegistry.test.js +2 -2
- package/src/App/renderer/mapButtons.test.js +8 -8
- package/src/App/renderer/mapControls.js +6 -1
- package/src/App/renderer/mapControls.test.js +24 -0
- package/src/App/renderer/slots.js +1 -0
- package/src/App/store/appActionsMap.js +8 -0
- package/src/App/store/appActionsMap.test.js +12 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/appReducer.test.js +1 -0
- package/src/config/appConfig.js +41 -2
- package/src/config/appConfig.test.js +45 -4
- package/src/config/defaults.js +1 -0
- package/src/scss/main.scss +1 -0
- package/src/types.js +9 -1
- package/src/utils/resolveNudgeStep.js +2 -0
- package/src/utils/resolveNudgeStep.test.js +11 -0
|
@@ -152,6 +152,13 @@ const toggleHasExclusiveControl = (state, payload) => {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
const toggleNudgeStep = (state) => {
|
|
156
|
+
return {
|
|
157
|
+
...state,
|
|
158
|
+
nudgeStepSize: state.nudgeStepSize === 'small' ? 'large' : 'small'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
const setPluginsEvaluated = (state) =>
|
|
156
163
|
state.arePluginsEvaluated ? state : { ...state, arePluginsEvaluated: true }
|
|
157
164
|
|
|
@@ -385,6 +392,7 @@ export const actionsMap = {
|
|
|
385
392
|
RESTORE_PREVIOUS_PANELS: restorePreviousPanels,
|
|
386
393
|
TOGGLE_APP_VISIBLE: toggleAppVisible,
|
|
387
394
|
TOGGLE_HAS_EXCLUSIVE_CONTROL: toggleHasExclusiveControl,
|
|
395
|
+
TOGGLE_NUDGE_STEP: toggleNudgeStep,
|
|
388
396
|
TOGGLE_BUTTON_DISABLED: toggleButtonDisabled,
|
|
389
397
|
TOGGLE_BUTTON_HIDDEN: toggleButtonHidden,
|
|
390
398
|
TOGGLE_BUTTON_PRESSED: toggleButtonPressed,
|
|
@@ -25,6 +25,7 @@ describe('actionsMap full coverage', () => {
|
|
|
25
25
|
openPanels: { panel1: { props: {} } },
|
|
26
26
|
previousOpenPanels: {},
|
|
27
27
|
hasExclusiveControl: false,
|
|
28
|
+
nudgeStepSize: 'small',
|
|
28
29
|
safeZoneInset: { top: 0, bottom: 0 },
|
|
29
30
|
isLayoutReady: false,
|
|
30
31
|
syncMapPadding: true,
|
|
@@ -138,6 +139,17 @@ describe('actionsMap full coverage', () => {
|
|
|
138
139
|
expect(result.hasExclusiveControl).toBe(true)
|
|
139
140
|
})
|
|
140
141
|
|
|
142
|
+
test('TOGGLE_NUDGE_STEP flips small to large', () => {
|
|
143
|
+
const result = actionsMap.TOGGLE_NUDGE_STEP(state)
|
|
144
|
+
expect(result.nudgeStepSize).toBe('large')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('TOGGLE_NUDGE_STEP flips large to small', () => {
|
|
148
|
+
const s = { ...state, nudgeStepSize: 'large' }
|
|
149
|
+
const result = actionsMap.TOGGLE_NUDGE_STEP(s)
|
|
150
|
+
expect(result.nudgeStepSize).toBe('small')
|
|
151
|
+
})
|
|
152
|
+
|
|
141
153
|
test('PLUGINS_EVALUATED is no-op when arePluginsEvaluated already true', () => {
|
|
142
154
|
const s = { ...state, arePluginsEvaluated: true }
|
|
143
155
|
expect(actionsMap.PLUGINS_EVALUATED(s)).toBe(s)
|
|
@@ -47,6 +47,7 @@ describe('initialState', () => {
|
|
|
47
47
|
expect(result.preferredColorScheme).toBe('dark')
|
|
48
48
|
expect(result.mode).toBe('edit')
|
|
49
49
|
expect(result.panelConfig).toEqual({ panel1: {} })
|
|
50
|
+
expect(result.nudgeStepSize).toBe('large')
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
test('uses appColorScheme when autoColorScheme is false', () => {
|
package/src/config/appConfig.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KeyboardHelp } from '../App/components/KeyboardHelp/KeyboardHelp.jsx'
|
|
2
|
+
import { MoveControl } from '../App/components/MoveControl/MoveControl.jsx'
|
|
2
3
|
|
|
3
4
|
const keyboardBasePanelSlots = {
|
|
4
5
|
slot: 'middle',
|
|
@@ -12,6 +13,10 @@ const buttonSlots = {
|
|
|
12
13
|
showLabel: false
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
const moveControlSlot = {
|
|
17
|
+
slot: 'right-bottom'
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
const exitButtonSlots = {
|
|
16
21
|
slot: 'top-left',
|
|
17
22
|
showLabel: false
|
|
@@ -72,7 +77,7 @@ export const defaultAppConfig = {
|
|
|
72
77
|
iconId: 'plus',
|
|
73
78
|
keepFocus: true,
|
|
74
79
|
onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomIn(appConfig.zoomDelta),
|
|
75
|
-
excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appState.interfaceType === 'touch',
|
|
80
|
+
excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appConfig.enableMoveControl || appState.interfaceType === 'touch',
|
|
76
81
|
enableWhen: ({ mapState }) => !mapState.isAtMaxZoom,
|
|
77
82
|
mobile: buttonSlots,
|
|
78
83
|
tablet: buttonSlots,
|
|
@@ -84,11 +89,26 @@ export const defaultAppConfig = {
|
|
|
84
89
|
iconId: 'minus',
|
|
85
90
|
keepFocus: true,
|
|
86
91
|
onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomOut(appConfig.zoomDelta),
|
|
87
|
-
excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appState.interfaceType === 'touch',
|
|
92
|
+
excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appConfig.enableMoveControl || appState.interfaceType === 'touch',
|
|
88
93
|
enableWhen: ({ mapState }) => !mapState.isAtMinZoom,
|
|
89
94
|
mobile: buttonSlots,
|
|
90
95
|
tablet: buttonSlots,
|
|
91
96
|
desktop: buttonSlots
|
|
97
|
+
}, {
|
|
98
|
+
id: 'moveControl',
|
|
99
|
+
label: 'Move and zoom controls',
|
|
100
|
+
iconId: 'move',
|
|
101
|
+
keepFocus: true,
|
|
102
|
+
isExpanded: false,
|
|
103
|
+
ariaControls: ({ appConfig }) => `${appConfig.id}-move-control-content`,
|
|
104
|
+
onClick: (_e, { appState }) => appState.dispatch({
|
|
105
|
+
type: 'TOGGLE_BUTTON_EXPANDED',
|
|
106
|
+
payload: { id: 'moveControl', isExpanded: !appState.expandedButtons.has('moveControl') }
|
|
107
|
+
}),
|
|
108
|
+
excludeWhen: ({ appConfig }) => !appConfig.enableMoveControl,
|
|
109
|
+
mobile: buttonSlots,
|
|
110
|
+
tablet: buttonSlots,
|
|
111
|
+
desktop: buttonSlots
|
|
92
112
|
}],
|
|
93
113
|
|
|
94
114
|
panels: [{
|
|
@@ -108,6 +128,16 @@ export const defaultAppConfig = {
|
|
|
108
128
|
render: (props) => <KeyboardHelp context={props?.context} />
|
|
109
129
|
}],
|
|
110
130
|
|
|
131
|
+
controls: [{
|
|
132
|
+
id: 'moveControl',
|
|
133
|
+
label: 'Move and zoom',
|
|
134
|
+
excludeWhen: ({ appConfig }) => !appConfig.enableMoveControl,
|
|
135
|
+
mobile: moveControlSlot,
|
|
136
|
+
tablet: moveControlSlot,
|
|
137
|
+
desktop: moveControlSlot,
|
|
138
|
+
render: MoveControl
|
|
139
|
+
}],
|
|
140
|
+
|
|
111
141
|
icons: [{
|
|
112
142
|
id: 'maximise',
|
|
113
143
|
svgContent: '<path d="M15 3h6v6"/><path d="m21 3-7 7"/><path d="m3 21 7-7"/><path d="M9 21H3v-6"/>'
|
|
@@ -123,6 +153,15 @@ export const defaultAppConfig = {
|
|
|
123
153
|
}, {
|
|
124
154
|
id: 'chevron',
|
|
125
155
|
svgContent: '<path d="m6 9 6 6 6-6"/>'
|
|
156
|
+
}, {
|
|
157
|
+
id: 'move',
|
|
158
|
+
svgContent: '<path d="M12 2v20"/><path d="m15 19-3 3-3-3"/><path d="m19 9 3 3-3 3"/><path d="M2 12h20"/><path d="m5 9-3 3 3 3"/><path d="m9 5 3-3 3 3"/>'
|
|
159
|
+
}, {
|
|
160
|
+
id: 'precision',
|
|
161
|
+
svgContent: '<circle cx="12" cy="12" r="10"/><line x1="22" x2="18" y1="12" y2="12"/><line x1="6" x2="2" y1="12" y2="12"/><line x1="12" x2="12" y1="6" y2="2"/><line x1="12" x2="12" y1="22" y2="18"/>'
|
|
162
|
+
}, {
|
|
163
|
+
id: 'precision-active',
|
|
164
|
+
svgContent: '<circle cx="12" cy="12" r="10"/><line x1="22" x2="16" y1="12" y2="12"/><line x1="8" x2="2" y1="12" y2="12"/><line x1="12" x2="12" y1="8" y2="2"/><line x1="12" x2="12" y1="22" y2="16"/><circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"/>'
|
|
126
165
|
}]
|
|
127
166
|
}
|
|
128
167
|
|
|
@@ -19,6 +19,7 @@ describe('defaultAppConfig', () => {
|
|
|
19
19
|
const journeyContinueBtn = buttons.find(b => b.id === 'journeyContinue')
|
|
20
20
|
const zoomInBtn = buttons.find(b => b.id === 'zoomIn')
|
|
21
21
|
const zoomOutBtn = buttons.find(b => b.id === 'zoomOut')
|
|
22
|
+
const moveControlBtn = buttons.find(b => b.id === 'moveControl')
|
|
22
23
|
|
|
23
24
|
// --- UI RENDER TESTS ---
|
|
24
25
|
it('renders KeyboardHelp panel', () => {
|
|
@@ -143,15 +144,21 @@ describe('defaultAppConfig', () => {
|
|
|
143
144
|
appState: { interfaceType: 'mouse' }
|
|
144
145
|
})).toBe(true)
|
|
145
146
|
|
|
146
|
-
// Branch B: Second part of OR is true (
|
|
147
|
+
// Branch B: Second part of OR is true (enableMoveControl)
|
|
147
148
|
expect(btn.excludeWhen({
|
|
148
|
-
appConfig: { enableZoomControls: true },
|
|
149
|
+
appConfig: { enableZoomControls: true, enableMoveControl: true },
|
|
150
|
+
appState: { interfaceType: 'mouse' }
|
|
151
|
+
})).toBe(true)
|
|
152
|
+
|
|
153
|
+
// Branch C: Third part of OR is true (interfaceType === 'touch')
|
|
154
|
+
expect(btn.excludeWhen({
|
|
155
|
+
appConfig: { enableZoomControls: true, enableMoveControl: false },
|
|
149
156
|
appState: { interfaceType: 'touch' }
|
|
150
157
|
})).toBe(true)
|
|
151
158
|
|
|
152
|
-
// Branch
|
|
159
|
+
// Branch D: All parts are false (Result: false)
|
|
153
160
|
expect(btn.excludeWhen({
|
|
154
|
-
appConfig: { enableZoomControls: true },
|
|
161
|
+
appConfig: { enableZoomControls: true, enableMoveControl: false },
|
|
155
162
|
appState: { interfaceType: 'mouse' }
|
|
156
163
|
})).toBe(false)
|
|
157
164
|
})
|
|
@@ -175,6 +182,40 @@ describe('defaultAppConfig', () => {
|
|
|
175
182
|
expect(mapProviderMock.zoomOut).toHaveBeenCalledWith(2)
|
|
176
183
|
})
|
|
177
184
|
|
|
185
|
+
// --- MOVE CONTROL TOGGLE BUTTON ---
|
|
186
|
+
it('covers all branches of moveControl excludeWhen', () => {
|
|
187
|
+
expect(moveControlBtn.excludeWhen({ appConfig: { enableMoveControl: false } })).toBe(true)
|
|
188
|
+
expect(moveControlBtn.excludeWhen({ appConfig: { enableMoveControl: true } })).toBe(false)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('moveControl ariaControls resolves the control element id', () => {
|
|
192
|
+
expect(moveControlBtn.ariaControls({ appConfig: { id: 'im' } })).toBe('im-move-control-content')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('moveControl onClick toggles TOGGLE_BUTTON_EXPANDED based on current state', () => {
|
|
196
|
+
const dispatch = jest.fn()
|
|
197
|
+
|
|
198
|
+
moveControlBtn.onClick({}, { appState: { dispatch, expandedButtons: new Set() } })
|
|
199
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
200
|
+
type: 'TOGGLE_BUTTON_EXPANDED',
|
|
201
|
+
payload: { id: 'moveControl', isExpanded: true }
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
dispatch.mockClear()
|
|
205
|
+
moveControlBtn.onClick({}, { appState: { dispatch, expandedButtons: new Set(['moveControl']) } })
|
|
206
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
207
|
+
type: 'TOGGLE_BUTTON_EXPANDED',
|
|
208
|
+
payload: { id: 'moveControl', isExpanded: false }
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// --- MOVE CONTROL ---
|
|
213
|
+
it('covers all branches of moveControl control excludeWhen', () => {
|
|
214
|
+
const control = defaultAppConfig.controls.find(c => c.id === 'moveControl')
|
|
215
|
+
expect(control.excludeWhen({ appConfig: { enableMoveControl: false } })).toBe(true)
|
|
216
|
+
expect(control.excludeWhen({ appConfig: { enableMoveControl: true } })).toBe(false)
|
|
217
|
+
})
|
|
218
|
+
|
|
178
219
|
// --- SUPPLEMENTARY CONFIGS ---
|
|
179
220
|
it('exports supplementary configs and constants', () => {
|
|
180
221
|
expect(defaultButtonConfig.label).toBe('Button')
|
package/src/config/defaults.js
CHANGED
|
@@ -16,6 +16,7 @@ const defaults = {
|
|
|
16
16
|
containerHeight: '600px',
|
|
17
17
|
deviceNotSupportedText: 'Your device is not supported. A map is available with a more up-to-date browser or device.',
|
|
18
18
|
enableFullscreen: false,
|
|
19
|
+
enableMoveControl: true,
|
|
19
20
|
enableZoomControls: true,
|
|
20
21
|
genericErrorText: 'There was a problem loading the map. Please try again later.',
|
|
21
22
|
hasExitButton: false,
|
package/src/scss/main.scss
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
@use '../App/components/Markers/Markers.module';
|
|
32
32
|
@use '../App/components/Viewport/Features.module';
|
|
33
33
|
@use '../App/components/Actions/Actions.module';
|
|
34
|
+
@use '../App/components/MoveControl/MoveControl.module';
|
|
34
35
|
|
|
35
36
|
// Components that dont have a corresponding React Component
|
|
36
37
|
@use 'components';
|
package/src/types.js
CHANGED
|
@@ -685,8 +685,16 @@
|
|
|
685
685
|
* @property {boolean} [enableFullscreen=false]
|
|
686
686
|
* Whether a toggle fullscreen button is displayed.
|
|
687
687
|
*
|
|
688
|
+
* @property {boolean} [enableMoveControl=true]
|
|
689
|
+
* Whether the move control is displayed — a button that reveals directional pan,
|
|
690
|
+
* zoom, and step-size buttons, providing a non-dragging alternative to panning
|
|
691
|
+
* and zooming the map (WCAG 2.5.7). Unlike enableZoomControls, it remains visible
|
|
692
|
+
* when the interface type is 'touch'. When enabled, enableZoomControls' buttons
|
|
693
|
+
* are hidden to avoid duplicating zoom controls.
|
|
694
|
+
*
|
|
688
695
|
* @property {boolean} [enableZoomControls=true]
|
|
689
|
-
* Whether zoom control buttons are displayed.
|
|
696
|
+
* Whether zoom control buttons are displayed. Not displayed when the interface
|
|
697
|
+
* type is 'touch', or when enableMoveControl is enabled.
|
|
690
698
|
*
|
|
691
699
|
* @property {[number, number, number, number]} [extent]
|
|
692
700
|
* Initial extent [minX, minY, maxX, maxY]. Equivalent to bounds; use whichever matches your map provider's terminology.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { resolveStepAmount } from './resolveNudgeStep.js'
|
|
2
|
+
|
|
3
|
+
describe('resolveStepAmount', () => {
|
|
4
|
+
it('returns the small value when isLargeStep is false', () => {
|
|
5
|
+
expect(resolveStepAmount(false, 5, 100)).toBe(5)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('returns the large value when isLargeStep is true', () => {
|
|
9
|
+
expect(resolveStepAmount(true, 5, 100)).toBe(100)
|
|
10
|
+
})
|
|
11
|
+
})
|