@defra/interactive-map 0.0.10-alpha → 0.0.12-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- 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 +21 -3
- package/docs/api/panel-definition.md +10 -12
- package/docs/api.md +80 -7
- package/docs/demo.mdx +70 -0
- package/docs/index.md +0 -4
- package/docs/plugins/plugin-context.md +3 -3
- package/docs/plugins/plugin-descriptor.md +37 -0
- package/docs/plugins/plugin-manifest.md +1 -1
- package/docusaurus.config.cjs +55 -25
- package/package.json +18 -9
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +3 -3
- 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/events.js +4 -14
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +1 -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/src/manifest.js +3 -3
- 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 +7 -7
- 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/src/InteractInit.jsx +28 -6
- package/plugins/interact/src/InteractInit.test.js +19 -5
- package/plugins/interact/src/events.js +17 -15
- package/plugins/interact/src/events.test.js +25 -16
- 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 +9 -3
- package/plugins/search/src/Search.test.jsx +26 -6
- package/plugins/search/src/components/Form/Form.jsx +35 -7
- package/plugins/search/src/components/Form/Form.module.scss +27 -0
- package/plugins/search/src/components/Form/Form.test.jsx +99 -2
- package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +28 -0
- package/plugins/search/src/components/SubmitButton/SubmitButton.module.scss +8 -0
- package/plugins/search/src/components/SubmitButton/SubmitButton.test.jsx +33 -0
- package/plugins/search/src/datasets.js +15 -11
- package/plugins/search/src/datasets.test.js +17 -2
- package/plugins/search/src/events/fetchSuggestions.js +1 -1
- package/plugins/search/src/index.js +1 -1
- package/plugins/search/src/index.test.js +4 -4
- package/plugins/search/src/reducer.js +9 -4
- package/plugins/search/src/reducer.test.js +12 -7
- package/plugins/search/src/search.scss +5 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +18 -2
- package/plugins/search/src/utils/parseOsNamesResults.test.js +33 -15
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/appEvents.js +8 -2
- package/providers/beta/esri/src/esriProvider.js +25 -17
- package/providers/beta/esri/src/mapEvents.js +41 -4
- package/providers/beta/esri/src/utils/coords.js +34 -1
- package/providers/beta/esri/src/utils/coords.test.js +126 -0
- package/providers/beta/esri/src/utils/spatial.js +47 -1
- package/providers/beta/esri/src/utils/spatial.test.js +55 -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/appEvents.js +10 -1
- package/providers/maplibre/src/appEvents.test.js +13 -4
- package/providers/maplibre/src/index.js +5 -13
- package/providers/maplibre/src/index.test.js +34 -15
- package/providers/maplibre/src/mapEvents.js +9 -1
- package/providers/maplibre/src/maplibreProvider.js +25 -15
- package/providers/maplibre/src/maplibreProvider.test.js +28 -2
- package/providers/maplibre/src/utils/spatial.js +51 -0
- package/providers/maplibre/src/utils/spatial.test.js +47 -0
- package/src/App/components/Actions/Actions.module.scss +5 -4
- package/src/App/components/MapButton/MapButton.jsx +4 -16
- package/src/App/components/MapButton/MapButton.module.scss +12 -12
- package/src/App/components/MapButton/MapButton.test.jsx +0 -9
- package/src/App/components/Panel/Panel.jsx +6 -6
- package/src/App/components/Panel/Panel.test.jsx +14 -15
- package/src/App/components/Viewport/MapController.jsx +6 -1
- package/src/App/hooks/useLayoutMeasurements.js +1 -1
- package/src/App/hooks/useLayoutMeasurements.test.js +1 -1
- package/src/App/hooks/useMapProviderOverrides.js +21 -1
- package/src/App/hooks/useMapProviderOverrides.test.js +51 -2
- package/src/App/hooks/useMarkersAPI.js +5 -3
- package/src/App/hooks/useModalPanelBehaviour.js +19 -2
- package/src/App/hooks/useModalPanelBehaviour.test.js +84 -60
- package/src/App/hooks/useVisibleGeometry.js +100 -0
- package/src/App/hooks/useVisibleGeometry.test.js +331 -0
- package/src/App/layout/Layout.jsx +5 -5
- package/src/App/layout/layout.module.scss +2 -4
- package/src/App/registry/panelRegistry.js +1 -10
- package/src/App/registry/panelRegistry.test.js +6 -11
- package/src/App/renderer/HtmlElementHost.jsx +12 -3
- package/src/App/renderer/HtmlElementHost.test.jsx +89 -0
- package/src/App/renderer/mapButtons.js +128 -28
- package/src/App/renderer/mapButtons.test.js +119 -19
- package/src/App/renderer/pluginWrapper.js +3 -2
- package/src/App/renderer/slots.js +1 -1
- package/src/App/store/AppProvider.jsx +1 -0
- package/src/App/store/MapProvider.jsx +18 -5
- package/src/App/store/MapProvider.test.jsx +56 -1
- package/src/App/store/appActionsMap.js +17 -9
- package/src/App/store/appActionsMap.test.js +33 -7
- package/src/App/store/appDispatchMiddleware.js +19 -0
- package/src/App/store/appDispatchMiddleware.test.js +56 -0
- package/src/App/store/mapActionsMap.js +4 -7
- package/src/InteractiveMap/InteractiveMap.js +18 -0
- package/src/InteractiveMap/InteractiveMap.test.js +12 -0
- package/src/config/appConfig.js +17 -15
- package/src/config/events.js +41 -4
- package/src/config/getInitialOpenPanels.js +2 -2
- package/src/config/getInitialOpenPanels.test.js +7 -7
- package/src/types.js +22 -11
- package/src/utils/getValueForStyle.js +1 -1
|
@@ -11,9 +11,10 @@ describe('actionsMap full coverage', () => {
|
|
|
11
11
|
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
const mockPanelConfig = {
|
|
14
|
-
panel1: { desktop: { exclusive: true, modal: false,
|
|
14
|
+
panel1: { desktop: { exclusive: true, modal: false, open: true }, mobile: { exclusive: true, modal: false } },
|
|
15
15
|
panel2: { desktop: { exclusive: false, modal: true }, mobile: { exclusive: false, modal: true } },
|
|
16
|
-
panel3: { desktop: { exclusive: false, modal: false }, mobile: { exclusive: false, modal: false } }
|
|
16
|
+
panel3: { desktop: { exclusive: false, modal: false }, mobile: { exclusive: false, modal: false } },
|
|
17
|
+
panel4: { desktop: { open: true, dismissible: false }, mobile: { open: true, dismissible: true } }
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
state = {
|
|
@@ -171,15 +172,15 @@ describe('actionsMap full coverage', () => {
|
|
|
171
172
|
expect(result.panelConfig.panelX).toBeDefined()
|
|
172
173
|
})
|
|
173
174
|
|
|
174
|
-
test('ADD_PANEL adds panelConfig and opens
|
|
175
|
-
const payload = { id: 'panelY', config: { desktop: {
|
|
175
|
+
test('ADD_PANEL adds panelConfig and opens panel when open=true', () => {
|
|
176
|
+
const payload = { id: 'panelY', config: { desktop: { open: true } } }
|
|
176
177
|
const result = actionsMap.ADD_PANEL(state, payload)
|
|
177
178
|
expect(result.panelConfig.panelY).toBeDefined()
|
|
178
179
|
expect(result.openPanels.panelY).toBeDefined()
|
|
179
180
|
})
|
|
180
181
|
|
|
181
|
-
test('ADD_PANEL does not open if
|
|
182
|
-
const payload = { id: 'panelZ', config: { desktop: {
|
|
182
|
+
test('ADD_PANEL does not open if open=false', () => {
|
|
183
|
+
const payload = { id: 'panelZ', config: { desktop: { open: false } } }
|
|
183
184
|
const result = actionsMap.ADD_PANEL(state, payload)
|
|
184
185
|
expect(result.panelConfig.panelZ).toBeDefined()
|
|
185
186
|
expect(result.openPanels.panelZ).toBeUndefined()
|
|
@@ -274,6 +275,31 @@ describe('actionsMap full coverage', () => {
|
|
|
274
275
|
expect(result.isFullscreen).toBe(true)
|
|
275
276
|
})
|
|
276
277
|
|
|
278
|
+
test('SET_BREAKPOINT restores non-dismissible open panel at new breakpoint', () => {
|
|
279
|
+
const tmp = { ...state, openPanels: {} }
|
|
280
|
+
const result = actionsMap.SET_BREAKPOINT(tmp, { breakpoint: 'desktop', behaviour: 'responsive', hybridWidth: null, maxMobileWidth: 640 })
|
|
281
|
+
expect(result.openPanels.panel4).toBeDefined()
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('SET_BREAKPOINT does not force-open a non-dismissible panel where it is dismissible', () => {
|
|
285
|
+
const tmp = { ...state, openPanels: {} }
|
|
286
|
+
const result = actionsMap.SET_BREAKPOINT(tmp, { breakpoint: 'mobile', behaviour: 'responsive', hybridWidth: null, maxMobileWidth: 640 })
|
|
287
|
+
expect(result.openPanels.panel4).toBeUndefined()
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('SET_BREAKPOINT preserves existing props when restoring a non-dismissible panel', () => {
|
|
291
|
+
const props = { myProp: 'value' }
|
|
292
|
+
const tmp = { ...state, openPanels: { panel4: { props } } }
|
|
293
|
+
const result = actionsMap.SET_BREAKPOINT(tmp, { breakpoint: 'desktop', behaviour: 'responsive', hybridWidth: null, maxMobileWidth: 640 })
|
|
294
|
+
expect(result.openPanels.panel4.props).toEqual(props)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test('SET_BREAKPOINT uses panelRegistry.getPanelConfig() when panelConfig missing', () => {
|
|
298
|
+
const tmp = { ...state, panelConfig: undefined, openPanels: {} }
|
|
299
|
+
const result = actionsMap.SET_BREAKPOINT(tmp, { breakpoint: 'desktop', behaviour: 'responsive', hybridWidth: null, maxMobileWidth: 640 })
|
|
300
|
+
expect(result.openPanels.panel4).toBeDefined()
|
|
301
|
+
})
|
|
302
|
+
|
|
277
303
|
test('SET_HYBRID_FULLSCREEN updates isFullscreen', () => {
|
|
278
304
|
const tmp = { ...state, isFullscreen: false }
|
|
279
305
|
const result = actionsMap.SET_HYBRID_FULLSCREEN(tmp, true)
|
|
@@ -294,7 +320,7 @@ describe('actionsMap full coverage', () => {
|
|
|
294
320
|
|
|
295
321
|
test('ADD_PANEL skips registry if panelRegistry missing', () => {
|
|
296
322
|
const tmp = { ...state, panelRegistry: undefined }
|
|
297
|
-
const payload = { id: 'panelY', config: { desktop: {
|
|
323
|
+
const payload = { id: 'panelY', config: { desktop: { open: true } } }
|
|
298
324
|
const result = actionsMap.ADD_PANEL(tmp, payload)
|
|
299
325
|
expect(result.panelConfig.panelY).toBeDefined()
|
|
300
326
|
expect(result.openPanels.panelY).toBeDefined()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/App/store/dispatchMiddleware.js
|
|
2
2
|
import { EVENTS as events } from '../../config/events.js'
|
|
3
|
+
import { defaultPanelConfig } from '../../config/appConfig.js'
|
|
4
|
+
import { deepMerge } from '../../utils/deepMerge.js'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Determines which panels were implicitly closed when opening a new panel
|
|
@@ -78,4 +80,21 @@ export function handleActionSideEffects (action, previousState, panelConfig, eve
|
|
|
78
80
|
eventBus.emit(events.APP_PANEL_OPENED, { panelId, props })
|
|
79
81
|
})
|
|
80
82
|
}
|
|
83
|
+
|
|
84
|
+
if (type === 'ADD_PANEL') {
|
|
85
|
+
const { id, config } = payload
|
|
86
|
+
const mergedConfig = deepMerge(defaultPanelConfig, config)
|
|
87
|
+
const bpConfig = mergedConfig[previousState.breakpoint]
|
|
88
|
+
if (bpConfig?.open) {
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
const slot = bpConfig.slot
|
|
91
|
+
const { visibleGeometry } = mergedConfig
|
|
92
|
+
const eventPayload = { panelId: id, slot }
|
|
93
|
+
if (visibleGeometry) {
|
|
94
|
+
eventPayload.visibleGeometry = visibleGeometry
|
|
95
|
+
}
|
|
96
|
+
eventBus.emit(events.APP_PANEL_OPENED, eventPayload)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
81
100
|
}
|
|
@@ -177,4 +177,60 @@ describe('appDispatchMiddleware', () => {
|
|
|
177
177
|
)
|
|
178
178
|
})
|
|
179
179
|
})
|
|
180
|
+
|
|
181
|
+
describe('ADD_PANEL', () => {
|
|
182
|
+
it('emits APP_PANEL_OPENED with slot when panel opens by default', async () => {
|
|
183
|
+
run(
|
|
184
|
+
{ type: 'ADD_PANEL', payload: { id: 'newPanel', config: {} } },
|
|
185
|
+
{ breakpoint: 'desktop' }
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
await flushMicrotasks()
|
|
189
|
+
|
|
190
|
+
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
191
|
+
events.APP_PANEL_OPENED,
|
|
192
|
+
{ panelId: 'newPanel', slot: 'inset' }
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('emits APP_PANEL_OPENED with visibleGeometry when provided in config', async () => {
|
|
197
|
+
const visibleGeometry = { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: {} }
|
|
198
|
+
run(
|
|
199
|
+
{ type: 'ADD_PANEL', payload: { id: 'geoPanel', config: { visibleGeometry } } },
|
|
200
|
+
{ breakpoint: 'desktop' }
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
await flushMicrotasks()
|
|
204
|
+
|
|
205
|
+
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
206
|
+
events.APP_PANEL_OPENED,
|
|
207
|
+
{ panelId: 'geoPanel', slot: 'inset', visibleGeometry }
|
|
208
|
+
)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('does not emit APP_PANEL_OPENED when breakpoint config sets open: false', async () => {
|
|
212
|
+
run(
|
|
213
|
+
{ type: 'ADD_PANEL', payload: { id: 'hiddenPanel', config: { desktop: { open: false } } } },
|
|
214
|
+
{ breakpoint: 'desktop' }
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
await flushMicrotasks()
|
|
218
|
+
|
|
219
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('emits APP_PANEL_OPENED with slot for mobile breakpoint', async () => {
|
|
223
|
+
run(
|
|
224
|
+
{ type: 'ADD_PANEL', payload: { id: 'mobilePanel', config: {} } },
|
|
225
|
+
{ breakpoint: 'mobile' }
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
await flushMicrotasks()
|
|
229
|
+
|
|
230
|
+
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
231
|
+
events.APP_PANEL_OPENED,
|
|
232
|
+
{ panelId: 'mobilePanel', slot: 'bottom' }
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
})
|
|
180
236
|
})
|
|
@@ -7,13 +7,10 @@ const mergePayload = (state, payload) => ({
|
|
|
7
7
|
...payload
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
const setMapReady = (state
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
mapProvider
|
|
15
|
-
}
|
|
16
|
-
}
|
|
10
|
+
const setMapReady = (state) => ({
|
|
11
|
+
...state,
|
|
12
|
+
isMapReady: true
|
|
13
|
+
})
|
|
17
14
|
|
|
18
15
|
const setMapStyle = (state, payload) => {
|
|
19
16
|
return {
|
|
@@ -404,4 +404,22 @@ export default class InteractiveMap {
|
|
|
404
404
|
addControl (id, config) {
|
|
405
405
|
this.eventBus.emit(events.APP_ADD_CONTROL, { id, config })
|
|
406
406
|
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Fit the map view to a bounding box or GeoJSON geometry, respecting the safe zone padding.
|
|
410
|
+
*
|
|
411
|
+
* @param {[number, number, number, number] | object} target - Bounds as [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs, or a GeoJSON Feature, FeatureCollection, or geometry.
|
|
412
|
+
*/
|
|
413
|
+
fitToBounds (target) {
|
|
414
|
+
this.eventBus.emit(events.MAP_FIT_TO_BOUNDS, target)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Set the map center and zoom, respecting the safe zone padding.
|
|
419
|
+
*
|
|
420
|
+
* @param {{ center?: [number, number], zoom?: number }} opts - View options.
|
|
421
|
+
*/
|
|
422
|
+
setView (opts) {
|
|
423
|
+
this.eventBus.emit(events.MAP_SET_VIEW, opts)
|
|
424
|
+
}
|
|
407
425
|
}
|
|
@@ -509,4 +509,16 @@ describe('InteractiveMap Public API Methods', () => {
|
|
|
509
509
|
{ id: 'btn-1', prop: 'disabled', value: true }
|
|
510
510
|
)
|
|
511
511
|
})
|
|
512
|
+
|
|
513
|
+
it('fitToBounds emits MAP_FIT_TO_BOUNDS with bbox', () => {
|
|
514
|
+
const bbox = [-0.489, 51.28, 0.236, 51.686]
|
|
515
|
+
map.fitToBounds(bbox)
|
|
516
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('map:fittobounds', bbox)
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
it('setView emits MAP_SET_VIEW with opts', () => {
|
|
520
|
+
const opts = { center: [-0.1276, 51.5074], zoom: 12 }
|
|
521
|
+
map.setView(opts)
|
|
522
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('map:setview', opts)
|
|
523
|
+
})
|
|
512
524
|
})
|
package/src/config/appConfig.js
CHANGED
|
@@ -2,8 +2,8 @@ import { KeyboardHelp } from '../App/components/KeyboardHelp/KeyboardHelp.jsx'
|
|
|
2
2
|
|
|
3
3
|
const keyboardBasePanelSlots = {
|
|
4
4
|
slot: 'middle',
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
open: false,
|
|
6
|
+
dismissible: true,
|
|
7
7
|
modal: true
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -42,7 +42,7 @@ export const defaultAppConfig = {
|
|
|
42
42
|
desktop: buttonSlots
|
|
43
43
|
}, {
|
|
44
44
|
id: 'zoomIn',
|
|
45
|
-
group: 'zoom',
|
|
45
|
+
group: { name: 'zoom', label: 'Zoom controls', order: 0 },
|
|
46
46
|
label: 'Zoom in',
|
|
47
47
|
iconId: 'plus',
|
|
48
48
|
onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomIn(appConfig.zoomDelta),
|
|
@@ -53,7 +53,7 @@ export const defaultAppConfig = {
|
|
|
53
53
|
desktop: buttonSlots
|
|
54
54
|
}, {
|
|
55
55
|
id: 'zoomOut',
|
|
56
|
-
group: 'zoom',
|
|
56
|
+
group: { name: 'zoom', label: 'Zoom controls', order: 0 },
|
|
57
57
|
label: 'Zoom out',
|
|
58
58
|
iconId: 'minus',
|
|
59
59
|
onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomOut(appConfig.zoomDelta),
|
|
@@ -99,7 +99,7 @@ export const defaultAppConfig = {
|
|
|
99
99
|
// Used by addButton
|
|
100
100
|
const defaultButtonSlots = {
|
|
101
101
|
slot: 'right-top',
|
|
102
|
-
showLabel:
|
|
102
|
+
showLabel: true
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export const defaultButtonConfig = {
|
|
@@ -111,25 +111,27 @@ export const defaultButtonConfig = {
|
|
|
111
111
|
|
|
112
112
|
// Used by addPanel
|
|
113
113
|
export const defaultPanelConfig = {
|
|
114
|
-
showLabel: true,
|
|
115
114
|
label: 'Panel',
|
|
116
115
|
mobile: {
|
|
117
116
|
slot: 'bottom',
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
modal: false
|
|
117
|
+
open: true,
|
|
118
|
+
dismissible: true,
|
|
119
|
+
modal: false,
|
|
120
|
+
showLabel: true
|
|
121
121
|
},
|
|
122
122
|
tablet: {
|
|
123
123
|
slot: 'inset',
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
modal: false
|
|
124
|
+
open: true,
|
|
125
|
+
dismissible: true,
|
|
126
|
+
modal: false,
|
|
127
|
+
showLabel: true
|
|
127
128
|
},
|
|
128
129
|
desktop: {
|
|
129
130
|
slot: 'inset',
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
modal: false
|
|
131
|
+
open: true,
|
|
132
|
+
dismissible: true,
|
|
133
|
+
modal: false,
|
|
134
|
+
showLabel: true
|
|
133
135
|
},
|
|
134
136
|
render: null,
|
|
135
137
|
html: null
|
package/src/config/events.js
CHANGED
|
@@ -87,12 +87,16 @@ export const EVENTS = {
|
|
|
87
87
|
// Map commands (internal / plugin authors)
|
|
88
88
|
// ============================================
|
|
89
89
|
|
|
90
|
-
/** @internal Set map style. Payload:
|
|
90
|
+
/** @internal Set map style. Payload: MapStyleConfig */
|
|
91
91
|
MAP_SET_STYLE: 'map:setstyle',
|
|
92
92
|
/** @internal Set map size. Payload: { width, height } */
|
|
93
93
|
MAP_SET_SIZE: 'map:setsize',
|
|
94
94
|
/** @internal Set pixel ratio. Payload: pixelRatio */
|
|
95
95
|
MAP_SET_PIXEL_RATIO: 'map:setpixelratio',
|
|
96
|
+
/** @internal Fit the map to a bounding box. Payload: [west, south, east, north] */
|
|
97
|
+
MAP_FIT_TO_BOUNDS: 'map:fittobounds',
|
|
98
|
+
/** @internal Set the map center and zoom. Payload: { center: [number, number], zoom?: number } */
|
|
99
|
+
MAP_SET_VIEW: 'map:setview',
|
|
96
100
|
|
|
97
101
|
// ============================================
|
|
98
102
|
// Map responses (advanced / subscribe)
|
|
@@ -101,15 +105,48 @@ export const EVENTS = {
|
|
|
101
105
|
/** @internal Emitted when map styles are initialized. */
|
|
102
106
|
MAP_INIT_MAP_STYLES: 'map:initmapstyles',
|
|
103
107
|
|
|
104
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Emitted when the map style has finished loading.
|
|
110
|
+
* Payload: `{ mapStyleId: string }`
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* map.on(EVENTS.MAP_STYLE_CHANGE, ({ mapStyleId }) => {
|
|
114
|
+
* console.log('Style changed to', mapStyleId)
|
|
115
|
+
* })
|
|
116
|
+
*/
|
|
105
117
|
MAP_STYLE_CHANGE: 'map:stylechange',
|
|
106
118
|
|
|
107
|
-
/** Emitted when the map
|
|
119
|
+
/** Emitted when the map has fully loaded. */
|
|
108
120
|
MAP_LOADED: 'map:loaded',
|
|
109
121
|
|
|
110
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Emitted when the map is ready for interaction and initial app state is settled.
|
|
124
|
+
*
|
|
125
|
+
* Payload:
|
|
126
|
+
* - `map` — the underlying map instance
|
|
127
|
+
* - `view` — the map view (ESRI SDK only)
|
|
128
|
+
* - `crs` — coordinate reference system string (e.g. `'EPSG:4326'`)
|
|
129
|
+
* - `mapStyleId` — the ID of the active map style (e.g. `'outdoor'`, `'dark'`)
|
|
130
|
+
* - `mapSize` — the active map size string (e.g. `'small'`, `'medium'`, `'large'`)
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* map.on(EVENTS.MAP_READY, ({ map, mapStyleId, mapSize }) => {
|
|
134
|
+
* console.log('Map ready, style:', mapStyleId, 'size:', mapSize)
|
|
135
|
+
* })
|
|
136
|
+
*/
|
|
111
137
|
MAP_READY: 'map:ready',
|
|
112
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Emitted when the map size changes.
|
|
141
|
+
* Payload: `{ mapSize: string }`
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* map.on(EVENTS.MAP_SIZE_CHANGE, ({ mapSize }) => {
|
|
145
|
+
* console.log('Map size changed to', mapSize)
|
|
146
|
+
* })
|
|
147
|
+
*/
|
|
148
|
+
MAP_SIZE_CHANGE: 'map:sizechange',
|
|
149
|
+
|
|
113
150
|
/** Emitted once after the map first becomes idle following initial load. */
|
|
114
151
|
MAP_FIRST_IDLE: 'map:firstidle',
|
|
115
152
|
|
|
@@ -5,9 +5,9 @@ export function getInitialOpenPanels (panelConfig, breakpoint, prevOpenPanels =
|
|
|
5
5
|
const configPanel = panelConfig[panelId]
|
|
6
6
|
const bpConfig = configPanel[breakpoint]
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const isOpen = bpConfig?.open ?? false
|
|
9
9
|
|
|
10
|
-
if (
|
|
10
|
+
if (isOpen) {
|
|
11
11
|
// Preserve any props that were already set in state
|
|
12
12
|
openPanels[panelId] = prevOpenPanels[panelId] || { props: {} }
|
|
13
13
|
}
|
|
@@ -8,17 +8,17 @@ describe('getInitialOpenPanels', () => {
|
|
|
8
8
|
expect(getInitialOpenPanels({}, breakpoint)).toEqual({})
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
it('includes panels with
|
|
11
|
+
it('includes panels with open = true and no prev state', () => {
|
|
12
12
|
const config = {
|
|
13
|
-
PanelA: { desktop: {
|
|
13
|
+
PanelA: { desktop: { open: true } }
|
|
14
14
|
}
|
|
15
15
|
const result = getInitialOpenPanels(config, breakpoint)
|
|
16
16
|
expect(result).toEqual({ PanelA: { props: {} } })
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
it('skips panels with
|
|
19
|
+
it('skips panels with open = false', () => {
|
|
20
20
|
const config = {
|
|
21
|
-
PanelA: { desktop: {
|
|
21
|
+
PanelA: { desktop: { open: false } }
|
|
22
22
|
}
|
|
23
23
|
const result = getInitialOpenPanels(config, breakpoint)
|
|
24
24
|
expect(result).toEqual({})
|
|
@@ -34,7 +34,7 @@ describe('getInitialOpenPanels', () => {
|
|
|
34
34
|
|
|
35
35
|
it('preserves prevOpenPanels state if exists', () => {
|
|
36
36
|
const config = {
|
|
37
|
-
PanelA: { desktop: {
|
|
37
|
+
PanelA: { desktop: { open: true } }
|
|
38
38
|
}
|
|
39
39
|
const prevOpenPanels = {
|
|
40
40
|
PanelA: { props: { some: 'value' } }
|
|
@@ -45,8 +45,8 @@ describe('getInitialOpenPanels', () => {
|
|
|
45
45
|
|
|
46
46
|
it('uses empty props object if no prevOpenPanels entry exists', () => {
|
|
47
47
|
const config = {
|
|
48
|
-
PanelA: { desktop: {
|
|
49
|
-
PanelB: { desktop: {
|
|
48
|
+
PanelA: { desktop: { open: true } },
|
|
49
|
+
PanelB: { desktop: { open: true } }
|
|
50
50
|
}
|
|
51
51
|
const prevOpenPanels = {
|
|
52
52
|
PanelA: { props: { existing: true } }
|
package/src/types.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* @property {number} [order]
|
|
17
17
|
* The order the button appears within its slot.
|
|
18
18
|
*
|
|
19
|
-
* @property {boolean} [showLabel]
|
|
20
|
-
* Whether to a
|
|
19
|
+
* @property {boolean} [showLabel=true]
|
|
20
|
+
* Whether to show a label. If false, a tooltip is generated from the label instead. Defaults to true.
|
|
21
21
|
*
|
|
22
22
|
* @property {string} slot
|
|
23
23
|
* The slot that the button should appear in at this breakpoint.
|
|
@@ -37,14 +37,19 @@
|
|
|
37
37
|
*
|
|
38
38
|
* @typedef {Object} PanelBreakpointConfig
|
|
39
39
|
*
|
|
40
|
-
* @property {boolean} [
|
|
41
|
-
* Whether panel can be dismissed.
|
|
40
|
+
* @property {boolean} [dismissible]
|
|
41
|
+
* Whether panel can be dismissed. When `false` and `open` is `true`, the panel is always visible at this
|
|
42
|
+
* breakpoint and any associated panel-toggle button is automatically suppressed.
|
|
42
43
|
*
|
|
43
44
|
* @property {boolean} [exclusive]
|
|
44
45
|
* Whether panel is exclusive. An exclusive panel will hide other panels when it is visible.
|
|
45
46
|
*
|
|
46
|
-
* @property {boolean} [
|
|
47
|
-
* Whether panel is
|
|
47
|
+
* @property {boolean} [open]
|
|
48
|
+
* Whether the panel is open. When `true` and combined with `dismissible: false`, the panel is always visible at this
|
|
49
|
+
* breakpoint and will be restored automatically when the breakpoint is entered.
|
|
50
|
+
*
|
|
51
|
+
* @property {boolean} [showLabel]
|
|
52
|
+
* Whether to show the panel heading. Defaults to true. The heading is visually hidden if false.
|
|
48
53
|
*
|
|
49
54
|
* @property {boolean} [modal]
|
|
50
55
|
* Whether panel is modal.
|
|
@@ -198,8 +203,8 @@
|
|
|
198
203
|
* @property {(offset: [number, number]) => void} panBy
|
|
199
204
|
* Pan map by pixel offset [x, y]. Positive x pans right, positive y pans down.
|
|
200
205
|
*
|
|
201
|
-
* @property {(bounds: [number, number, number, number]) => void} fitToBounds
|
|
202
|
-
* Fit map view to the specified bounds [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs
|
|
206
|
+
* @property {(bounds: [number, number, number, number] | object) => void} fitToBounds
|
|
207
|
+
* Fit map view to the specified bounds [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs, or a GeoJSON Feature, FeatureCollection, or geometry.
|
|
203
208
|
*
|
|
204
209
|
* @property {(padding: { top?: number, bottom?: number, left?: number, right?: number }) => void} setPadding
|
|
205
210
|
* Set map padding as pixel insets from the top, bottom, left and right edges of the map.
|
|
@@ -242,6 +247,10 @@
|
|
|
242
247
|
*
|
|
243
248
|
* @property {() => void} [clearHighlightedLabel]
|
|
244
249
|
* @experimental Clear any highlighted label.
|
|
250
|
+
*
|
|
251
|
+
* @property {(geojson: object, panelRect: DOMRect) => boolean} [isGeometryObscured]
|
|
252
|
+
* Returns true if the geometry's screen bounding box overlaps the given panel element rectangle.
|
|
253
|
+
* Used internally by useVisibleGeometry to decide whether to pan/zoom when a panel opens.
|
|
245
254
|
*/
|
|
246
255
|
|
|
247
256
|
/**
|
|
@@ -366,11 +375,13 @@
|
|
|
366
375
|
* @property {ComponentType} [render]
|
|
367
376
|
* Render component.
|
|
368
377
|
*
|
|
369
|
-
* @property {boolean} [showLabel]
|
|
370
|
-
* Whether to show the panel heading. The panel heading is visually hidden if false.
|
|
371
|
-
*
|
|
372
378
|
* @property {PanelBreakpointConfig} tablet
|
|
373
379
|
* Tablet breakpoint configuration.
|
|
380
|
+
*
|
|
381
|
+
* @property {object} [visibleGeometry]
|
|
382
|
+
* GeoJSON Feature, FeatureCollection, or geometry to keep visible when this panel opens.
|
|
383
|
+
* If any part of the geometry's bounding box is obscured by the safe zone after the panel opens,
|
|
384
|
+
* the map automatically adjusts: Point or MultiPoint geometry routes to setView(), all other types to fitToBounds().
|
|
374
385
|
*/
|
|
375
386
|
|
|
376
387
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Returns the appropriate color for the given mapStyleId.
|
|
3
3
|
* Supports:
|
|
4
4
|
* - Simple string colors (#fff, rgba(...))
|
|
5
|
-
* - Object maps of
|
|
5
|
+
* - Object maps of mapStyleId → color
|
|
6
6
|
*
|
|
7
7
|
* @param {string|object} colors - Color string or style-mapped color object
|
|
8
8
|
* @param {string} mapStyleId - Current style/theme identifier
|