@defra/interactive-map 0.0.17-alpha → 0.0.19-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/css/docusaurus.css +58 -34
- 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/panel-definition.md +16 -0
- package/docs/api/symbol-config.md +160 -0
- package/docs/api/symbol-registry.md +115 -0
- package/docs/api.md +50 -23
- package/docs/assets/basic-map.jpg +0 -0
- package/docs/assets/button-first.jpg +0 -0
- package/docs/assets/maker-panel.jpg +0 -0
- package/docs/examples/add-marker-with-panel.mdx +59 -0
- package/docs/examples/basic-map.mdx +24 -0
- package/docs/examples/button-map.mdx +24 -0
- package/docs/examples/index.mdx +49 -0
- package/docs/index.mdx +1 -1
- package/docs/plugins/datasets.md +105 -9
- package/docs/plugins/interact.md +100 -44
- package/docs/plugins/search.md +15 -3
- package/docs/plugins.md +1 -1
- package/docusaurus.config.cjs +9 -1
- package/package.json +1 -1
- package/plugins/beta/datasets/dist/css/index.css +32 -14
- 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 +9 -4
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +57 -11
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +14 -8
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +155 -53
- 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 +1 -1
- package/plugins/beta/datasets/src/api/setData.js +4 -2
- package/plugins/beta/datasets/src/api/setStyle.js +2 -2
- 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 +13 -4
- package/plugins/beta/datasets/src/defaults.js +4 -2
- package/plugins/beta/datasets/src/index.js +2 -1
- package/plugins/beta/datasets/src/manifest.js +1 -1
- package/plugins/beta/datasets/src/panels/Key.jsx +11 -89
- package/plugins/beta/datasets/src/panels/Key.module.scss +24 -13
- package/plugins/beta/datasets/src/panels/Layers.module.scss +13 -7
- package/plugins/beta/datasets/src/reducer.js +6 -0
- package/plugins/beta/datasets/src/reducers/keyReducer.js +34 -0
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +8 -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 +3 -0
- 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/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/scale-bar/dist/css/index.css +1 -1
- package/plugins/beta/scale-bar/src/scaleBar.scss +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 +19 -8
- package/plugins/interact/src/InteractInit.test.js +26 -6
- package/plugins/interact/src/api/clear.js +1 -1
- package/plugins/interact/src/api/enable.test.js +7 -7
- package/plugins/interact/src/api/selectMarker.js +14 -0
- package/plugins/interact/src/api/selectMarker.test.js +25 -0
- package/plugins/interact/src/api/unselectMarker.js +14 -0
- package/plugins/interact/src/api/unselectMarker.test.js +14 -0
- package/plugins/interact/src/defaults.js +4 -6
- package/plugins/interact/src/events.js +27 -36
- package/plugins/interact/src/events.test.js +119 -90
- 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/manifest.js +10 -2
- package/plugins/interact/src/reducer.js +59 -5
- package/plugins/interact/src/reducer.test.js +100 -12
- 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/interact/src/utils/interactionModes.js +12 -0
- 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/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-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 +36 -7
- package/providers/maplibre/src/utils/highlightFeatures.test.js +153 -96
- 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/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/Panel/Panel.jsx +6 -6
- package/src/App/components/Panel/Panel.test.jsx +37 -0
- package/src/App/components/Viewport/Viewport.jsx +5 -15
- package/src/App/components/Viewport/Viewport.module.scss +2 -0
- package/src/App/components/Viewport/Viewport.test.jsx +16 -33
- package/src/App/hooks/useInterfaceAPI.js +7 -7
- package/src/App/hooks/useInterfaceAPI.test.js +162 -0
- package/src/App/hooks/useLayoutMeasurements.js +64 -72
- package/src/App/hooks/useMarkersAPI.js +2 -5
- package/src/App/hooks/useMarkersAPI.test.js +4 -4
- package/src/App/layout/Layout.jsx +3 -3
- package/src/App/layout/Layout.test.jsx +4 -2
- package/src/App/layout/layout.module.scss +1 -8
- package/src/App/renderer/HtmlElementHost.jsx +10 -5
- package/src/App/renderer/mapPanels.js +2 -1
- package/src/App/store/ServiceProvider.jsx +7 -5
- package/src/App/store/appActionsMap.js +4 -4
- package/src/App/store/appActionsMap.test.js +10 -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 +59 -11
- package/src/InteractiveMap/InteractiveMap.test.js +126 -4
- package/src/InteractiveMap/domStateManager.js +18 -6
- package/src/InteractiveMap/domStateManager.test.js +21 -0
- package/src/InteractiveMap/historyManager.js +28 -16
- package/src/InteractiveMap/historyManager.test.js +17 -0
- package/src/config/appConfig.js +2 -7
- package/src/config/appConfig.test.js +4 -15
- package/src/config/defaults.js +2 -3
- package/src/config/events.js +20 -21
- package/src/config/mapTheme.js +56 -0
- package/src/config/patternConfig.js +16 -0
- package/src/config/symbolConfig.js +80 -0
- package/src/scss/settings/_colors.scss +0 -9
- package/src/services/closeApp.js +1 -10
- package/src/services/closeApp.test.js +3 -43
- 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 +99 -12
- package/src/utils/mapStateSync.js +48 -10
- package/src/utils/mapStateSync.test.js +29 -9
- 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/docs/examples.mdx +0 -70
- package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +0 -48
- package/plugins/beta/datasets/src/styles/patterns.js +0 -157
|
@@ -19,7 +19,7 @@ describe('projectCoords', () => {
|
|
|
19
19
|
const mockProvider = { mapToScreen: jest.fn(() => ({ x: 100, y: 200 })) }
|
|
20
20
|
|
|
21
21
|
it('returns scaled coordinates when ready', () => {
|
|
22
|
-
expect(projectCoords({ lat: 1, lng: 1 }, mockProvider, 'medium', true)).toEqual({ x: 200, y:
|
|
22
|
+
expect(projectCoords({ lat: 1, lng: 1 }, mockProvider, 'medium', true)).toEqual({ x: 200, y: 400 })
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
it('returns zero when not ready or no provider', () => {
|
|
@@ -102,7 +102,7 @@ describe('useMarkers', () => {
|
|
|
102
102
|
|
|
103
103
|
const renderCallback = mockEventBus.on.mock.calls.find(call => call[0] === 'map:render')[1]
|
|
104
104
|
act(() => renderCallback())
|
|
105
|
-
expect(mockElement.style.transform).toBe('translate(200px,
|
|
105
|
+
expect(mockElement.style.transform).toBe('translate(200px, 400px)')
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
it('skips map:render when not ready or no provider (line 60)', () => {
|
|
@@ -126,7 +126,7 @@ describe('useMarkers', () => {
|
|
|
126
126
|
isMapReady: true
|
|
127
127
|
})
|
|
128
128
|
rerender()
|
|
129
|
-
expect(mockElement.style.transform).toBe('translate(300px,
|
|
129
|
+
expect(mockElement.style.transform).toBe('translate(300px, 600px)')
|
|
130
130
|
})
|
|
131
131
|
|
|
132
132
|
it('handles app:addmarker safely', () => {
|
|
@@ -137,7 +137,7 @@ describe('useMarkers', () => {
|
|
|
137
137
|
act(() => handleAddMarker(addPayload))
|
|
138
138
|
expect(mockDispatch).toHaveBeenCalledWith({
|
|
139
139
|
type: 'UPSERT_LOCATION_MARKER',
|
|
140
|
-
payload: { id: 'm1', coords: { lat: 1, lng: 1 }, label: 'Test', x: 200, y:
|
|
140
|
+
payload: { id: 'm1', coords: { lat: 1, lng: 1 }, label: 'Test', x: 200, y: 400, isVisible: true }
|
|
141
141
|
})
|
|
142
142
|
})
|
|
143
143
|
|
|
@@ -10,6 +10,7 @@ import { Attributions } from '../components/Attributions/Attributions'
|
|
|
10
10
|
import { layoutSlots } from '../renderer/slots'
|
|
11
11
|
import { SlotRenderer } from '../renderer/SlotRenderer'
|
|
12
12
|
import { HtmlElementHost } from '../renderer/HtmlElementHost'
|
|
13
|
+
import { getMapThemeVars } from '../../config/mapTheme.js'
|
|
13
14
|
|
|
14
15
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
15
16
|
// sonarjs/disable-next-line function-name
|
|
@@ -30,13 +31,12 @@ export const Layout = () => {
|
|
|
30
31
|
`im-o-app--${interfaceType}`,
|
|
31
32
|
`im-o-app--${isFullscreen ? 'fullscreen' : 'inline'}`,
|
|
32
33
|
`im-o-app--${mapStyle?.appColorScheme || preferredColorScheme}-app`,
|
|
33
|
-
`im-o-app--${mapStyle?.mapColorScheme || 'light'}-map`,
|
|
34
34
|
hasExclusiveControl && 'im-o-app--exclusive-control'
|
|
35
35
|
].filter(Boolean).join(' ')}
|
|
36
|
-
style={{ backgroundColor: mapStyle?.backgroundColor || undefined }}
|
|
36
|
+
style={{ backgroundColor: mapStyle?.backgroundColor || undefined, ...getMapThemeVars(mapStyle) }}
|
|
37
37
|
ref={layoutRefs.appContainerRef}
|
|
38
38
|
>
|
|
39
|
-
<Viewport
|
|
39
|
+
<Viewport />
|
|
40
40
|
<div className={`im-o-app__overlay${isLayoutReady ? '' : ' im-o-app__overlay--not-ready'}`}>
|
|
41
41
|
<div className='im-o-app__side' ref={layoutRefs.sideRef}>
|
|
42
42
|
<SlotRenderer slot={layoutSlots.SIDE} />
|
|
@@ -72,9 +72,11 @@ describe('Layout', () => {
|
|
|
72
72
|
expect(root.className).toContain('im-o-app--map')
|
|
73
73
|
expect(root.className).toContain('im-o-app--inline')
|
|
74
74
|
expect(root.className).toContain('im-o-app--light-app')
|
|
75
|
-
expect(root.className).toContain('im-o-app--dark-map')
|
|
76
75
|
expect(root.className).toContain('im-o-app--exclusive-control')
|
|
77
76
|
expect(root.style.backgroundColor).toBe('pink')
|
|
77
|
+
expect(root.style.getPropertyValue('--map-overlay-halo-color')).toBe('#0b0c0c')
|
|
78
|
+
expect(root.style.getPropertyValue('--map-overlay-selected-color')).toBe('#ffffff')
|
|
79
|
+
expect(root.style.getPropertyValue('--map-overlay-foreground-color')).toBe('#ffffff')
|
|
78
80
|
|
|
79
81
|
const overlay = root.querySelector('.im-o-app__overlay')
|
|
80
82
|
expect(overlay.className).not.toContain('not-ready')
|
|
@@ -123,7 +125,7 @@ describe('Layout', () => {
|
|
|
123
125
|
|
|
124
126
|
const root = document.getElementById('myApp-im-app')
|
|
125
127
|
expect(root.className).toContain('im-o-app--dark-app')
|
|
126
|
-
expect(root.className).toContain('im-o-app--light-map')
|
|
128
|
+
expect(root.className).not.toContain('im-o-app--light-map')
|
|
127
129
|
expect(root.style.backgroundColor).toBe('')
|
|
128
130
|
expect(root.className).not.toContain('exclusive-control')
|
|
129
131
|
})
|
|
@@ -436,16 +436,9 @@
|
|
|
436
436
|
// Inline border
|
|
437
437
|
.im-o-app--inline {
|
|
438
438
|
border: var(--app-border-width) solid var(--app-border-color);
|
|
439
|
+
box-sizing: border-box;
|
|
439
440
|
}
|
|
440
441
|
|
|
441
|
-
// Hide containers when keyboard hint is visible
|
|
442
|
-
.im-o-app__main--keyboard-hint-visible {
|
|
443
|
-
.im-o-app__top-col,
|
|
444
|
-
.im-o-app__right,
|
|
445
|
-
.im-o-app__right-bottom {
|
|
446
|
-
opacity: 0;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
442
|
|
|
450
443
|
// Avoid refresh jump if layout clacs are not ready
|
|
451
444
|
.im-o-app__overlay--not-ready {
|
|
@@ -39,11 +39,14 @@ export const getSlotRef = (slot, layoutRefs) => {
|
|
|
39
39
|
* (e.g. the banner slot swaps DOM nodes between mobile and desktop).
|
|
40
40
|
*/
|
|
41
41
|
export const useDomProjection = (wrapperRef, targetSlot, isVisible, layoutRefs, breakpoint) => {
|
|
42
|
+
const layoutRefsRef = useRef(layoutRefs)
|
|
43
|
+
layoutRefsRef.current = layoutRefs
|
|
44
|
+
|
|
42
45
|
useLayoutEffect(() => {
|
|
43
46
|
const wrapper = wrapperRef.current
|
|
44
47
|
|
|
45
48
|
if (isVisible) {
|
|
46
|
-
const slotRef = getSlotRef(targetSlot,
|
|
49
|
+
const slotRef = getSlotRef(targetSlot, layoutRefsRef.current)
|
|
47
50
|
if (slotRef?.current) {
|
|
48
51
|
const backdrop = slotRef.current.querySelector(':scope > .im-o-app__modal-backdrop')
|
|
49
52
|
if (backdrop) {
|
|
@@ -54,8 +57,8 @@ export const useDomProjection = (wrapperRef, targetSlot, isVisible, layoutRefs,
|
|
|
54
57
|
wrapper.style.display = ''
|
|
55
58
|
}
|
|
56
59
|
} else {
|
|
57
|
-
if (wrapper.parentElement ===
|
|
58
|
-
|
|
60
|
+
if (wrapper.parentElement === layoutRefsRef.current.modalRef?.current) {
|
|
61
|
+
layoutRefsRef.current.appContainerRef?.current?.appendChild(wrapper)
|
|
59
62
|
}
|
|
60
63
|
wrapper.style.display = 'none'
|
|
61
64
|
}
|
|
@@ -63,7 +66,7 @@ export const useDomProjection = (wrapperRef, targetSlot, isVisible, layoutRefs,
|
|
|
63
66
|
return () => {
|
|
64
67
|
wrapper.style.display = 'none'
|
|
65
68
|
}
|
|
66
|
-
}, [isVisible, targetSlot,
|
|
69
|
+
}, [isVisible, targetSlot, breakpoint, wrapperRef])
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/**
|
|
@@ -71,7 +74,7 @@ export const useDomProjection = (wrapperRef, targetSlot, isVisible, layoutRefs,
|
|
|
71
74
|
* The Panel component stays mounted for the lifetime of the registration.
|
|
72
75
|
* DOM projection moves it between slots; CSS hides it when closed.
|
|
73
76
|
*/
|
|
74
|
-
const PersistentPanel = ({ panelId, config, isOpen, openPanelProps, allowedModalPanelId, appState }) => {
|
|
77
|
+
const PersistentPanel = ({ panelId, config, isOpen, openPanelProps, focusOnOpen, allowedModalPanelId, appState }) => {
|
|
75
78
|
const panelRootRef = useRef(null)
|
|
76
79
|
const { breakpoint, mode, isFullscreen, layoutRefs } = appState
|
|
77
80
|
|
|
@@ -108,6 +111,7 @@ const PersistentPanel = ({ panelId, config, isOpen, openPanelProps, allowedModal
|
|
|
108
111
|
panelId={panelId}
|
|
109
112
|
panelConfig={config}
|
|
110
113
|
props={openPanelProps}
|
|
114
|
+
focusOnOpen={focusOnOpen}
|
|
111
115
|
html={config.html}
|
|
112
116
|
label={config.label}
|
|
113
117
|
isOpen={isOpen}
|
|
@@ -186,6 +190,7 @@ export const HtmlElementHost = () => {
|
|
|
186
190
|
config={config}
|
|
187
191
|
isOpen={!!openPanels[panelId]}
|
|
188
192
|
openPanelProps={openPanels[panelId]?.props}
|
|
193
|
+
focusOnOpen={openPanels[panelId]?.focusOnOpen}
|
|
189
194
|
allowedModalPanelId={allowedModalPanelId}
|
|
190
195
|
appState={appState}
|
|
191
196
|
/>
|
|
@@ -46,7 +46,7 @@ export function mapPanels ({ slot, appState, evaluateProp }) {
|
|
|
46
46
|
})
|
|
47
47
|
const allowedModalPanelId = modalPanels.length > 0 ? modalPanels[modalPanels.length - 1][0] : null
|
|
48
48
|
|
|
49
|
-
return openPanelEntries.map(([panelId, { props }]) => {
|
|
49
|
+
return openPanelEntries.map(([panelId, { props, focusOnOpen }]) => {
|
|
50
50
|
const config = panelConfig[panelId]
|
|
51
51
|
if (!config) {
|
|
52
52
|
return null
|
|
@@ -91,6 +91,7 @@ export function mapPanels ({ slot, appState, evaluateProp }) {
|
|
|
91
91
|
panelId={panelId}
|
|
92
92
|
panelConfig={config}
|
|
93
93
|
props={props}
|
|
94
|
+
focusOnOpen={focusOnOpen}
|
|
94
95
|
WrappedChild={WrappedChild}
|
|
95
96
|
label={evaluateProp(config.label, pluginId)}
|
|
96
97
|
html={pluginId ? evaluateProp(config.html, pluginId) : config.html}
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
// src/App/store/ServiceProvider.jsx
|
|
2
2
|
import React, { createContext, useMemo, useRef } from 'react'
|
|
3
|
-
import { EVENTS } from '../../config/events.js'
|
|
4
3
|
import { createAnnouncer } from '../../services/announcer.js'
|
|
5
4
|
import { reverseGeocode } from '../../services/reverseGeocode.js'
|
|
6
5
|
import { useConfig } from '../store/configContext.js'
|
|
7
6
|
import { closeApp } from '../../services/closeApp.js'
|
|
8
|
-
import {
|
|
7
|
+
import { symbolRegistry } from '../../services/symbolRegistry.js'
|
|
8
|
+
import { patternRegistry } from '../../services/patternRegistry.js'
|
|
9
9
|
|
|
10
10
|
export const ServiceContext = createContext(null)
|
|
11
11
|
|
|
12
12
|
export const ServiceProvider = ({ eventBus, children }) => {
|
|
13
|
-
const { id, handleExitClick } = useConfig()
|
|
13
|
+
const { id, handleExitClick, symbolDefaults: constructorSymbolDefaults } = useConfig()
|
|
14
14
|
const mapStatusRef = useRef(null)
|
|
15
15
|
const announce = useMemo(() => createAnnouncer(mapStatusRef), [])
|
|
16
16
|
|
|
17
|
+
symbolRegistry.setDefaults(constructorSymbolDefaults || {})
|
|
18
|
+
|
|
17
19
|
const services = useMemo(() => ({
|
|
18
20
|
announce,
|
|
19
21
|
reverseGeocode: (zoom, center) => reverseGeocode(zoom, center),
|
|
20
|
-
events: EVENTS,
|
|
21
22
|
eventBus,
|
|
22
23
|
mapStatusRef,
|
|
23
24
|
closeApp: () => closeApp(id, handleExitClick, eventBus),
|
|
24
|
-
|
|
25
|
+
symbolRegistry,
|
|
26
|
+
patternRegistry
|
|
25
27
|
}), [announce])
|
|
26
28
|
|
|
27
29
|
return (
|
|
@@ -7,7 +7,7 @@ import { registerPanel as registerPanelFn, addPanel as addPanelFn, removePanel a
|
|
|
7
7
|
import { registerControl as registerControlFn, addControl as addControlFn } from '../registry/controlRegistry.js'
|
|
8
8
|
|
|
9
9
|
// Interal helper
|
|
10
|
-
function buildOpenPanels (state, panelId, breakpoint, props) {
|
|
10
|
+
function buildOpenPanels (state, panelId, breakpoint, props, focusOnOpen) {
|
|
11
11
|
const panelConfig = state.panelConfig || state.panelRegistry.getPanelConfig()
|
|
12
12
|
const bpConfig = panelConfig[panelId]?.[breakpoint]
|
|
13
13
|
const isExclusiveNonModal = !!bpConfig.exclusive && !bpConfig.modal
|
|
@@ -23,7 +23,7 @@ function buildOpenPanels (state, panelId, breakpoint, props) {
|
|
|
23
23
|
return {
|
|
24
24
|
...(isExclusiveNonModal ? {} : filteredPanels),
|
|
25
25
|
...(isModal ? state.openPanels : {}),
|
|
26
|
-
[panelId]: { props }
|
|
26
|
+
[panelId]: { props, ...(focusOnOpen && { focusOnOpen: true }) }
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -109,12 +109,12 @@ const setInterfaceType = (state, payload) => {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const openPanel = (state, payload) => {
|
|
112
|
-
const { panelId, props = {} } = payload
|
|
112
|
+
const { panelId, props = {}, focusOnOpen } = payload
|
|
113
113
|
|
|
114
114
|
return {
|
|
115
115
|
...state,
|
|
116
116
|
previousOpenPanels: state.openPanels,
|
|
117
|
-
openPanels: buildOpenPanels(state, panelId, state.breakpoint, props)
|
|
117
|
+
openPanels: buildOpenPanels(state, panelId, state.breakpoint, props, focusOnOpen)
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -100,6 +100,16 @@ describe('actionsMap full coverage', () => {
|
|
|
100
100
|
expect(result.openPanels.panel3?.props).toEqual({})
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
+
test('OPEN_PANEL stores focusOnOpen when provided', () => {
|
|
104
|
+
const result = actionsMap.OPEN_PANEL(state, { panelId: 'panel2', focusOnOpen: true })
|
|
105
|
+
expect(result.openPanels.panel2?.focusOnOpen).toBe(true)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('OPEN_PANEL omits focusOnOpen when not provided', () => {
|
|
109
|
+
const result = actionsMap.OPEN_PANEL(state, { panelId: 'panel2' })
|
|
110
|
+
expect(result.openPanels.panel2?.focusOnOpen).toBeUndefined()
|
|
111
|
+
})
|
|
112
|
+
|
|
103
113
|
test('CLOSE_PANEL removes a panel', () => {
|
|
104
114
|
const result = actionsMap.CLOSE_PANEL(state, 'panel1')
|
|
105
115
|
expect(result.openPanels.panel1).toBeUndefined()
|
|
@@ -12,12 +12,10 @@ const setMapReady = (state) => ({
|
|
|
12
12
|
isMapReady: true
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
const setMapStyle = (state, payload) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}
|
|
15
|
+
const setMapStyle = (state, payload) => ({
|
|
16
|
+
...state,
|
|
17
|
+
mapStyle: payload
|
|
18
|
+
})
|
|
21
19
|
|
|
22
20
|
const setMapSize = (state, payload) => {
|
|
23
21
|
return {
|
|
@@ -46,8 +46,9 @@ describe('actionsMap', () => {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
test('SET_MAP_STYLE sets mapStyle', () => {
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const mapStyle = { id: 'satellite' }
|
|
50
|
+
const result = actionsMap.SET_MAP_STYLE(state, mapStyle)
|
|
51
|
+
expect(result.mapStyle).toBe(mapStyle)
|
|
51
52
|
expect(result.otherProp).toBe(state.otherProp)
|
|
52
53
|
})
|
|
53
54
|
|
|
@@ -17,10 +17,11 @@ export const initialState = (config) => {
|
|
|
17
17
|
// Does a plugin handle map styles
|
|
18
18
|
const pluginHandlesMapStyles = !!registeredPlugins?.find(plugin => plugin.config?.handlesMapStyle)
|
|
19
19
|
|
|
20
|
+
const initialMapStyle = pluginHandlesMapStyles ? null : mapStyle
|
|
20
21
|
return {
|
|
21
22
|
isMapReady: false,
|
|
22
23
|
mapProvider: null,
|
|
23
|
-
mapStyle:
|
|
24
|
+
mapStyle: initialMapStyle,
|
|
24
25
|
mapSize,
|
|
25
26
|
center,
|
|
26
27
|
zoom,
|
|
@@ -103,7 +103,9 @@ export default class InteractiveMap {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
_handleButtonClick (e) {
|
|
106
|
-
|
|
106
|
+
if (this.config.manageHistoryState) {
|
|
107
|
+
history.pushState({ isBack: true }, '', e.currentTarget.getAttribute('href'))
|
|
108
|
+
}
|
|
107
109
|
if (this._isHidden) {
|
|
108
110
|
this.showApp()
|
|
109
111
|
} else {
|
|
@@ -132,11 +134,20 @@ export default class InteractiveMap {
|
|
|
132
134
|
this.removeApp()
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
if (!this.config.manageHistoryState) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
138
140
|
|
|
139
|
-
history
|
|
141
|
+
// If this history entry was pushed by the map's open button, go back so the
|
|
142
|
+
// ?mv= entry is preserved as a forward entry (browser forward re-opens the map).
|
|
143
|
+
// Otherwise (direct URL / bookmark), just strip the param in place.
|
|
144
|
+
if (history.state?.isBack) {
|
|
145
|
+
history.back()
|
|
146
|
+
} else {
|
|
147
|
+
const key = this.config.mapViewParamKey
|
|
148
|
+
const newUrl = this._removeMapParamFromUrl(location.href, key)
|
|
149
|
+
history.replaceState(history.state, '', newUrl)
|
|
150
|
+
}
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
/**
|
|
@@ -190,6 +201,7 @@ export default class InteractiveMap {
|
|
|
190
201
|
})
|
|
191
202
|
|
|
192
203
|
updateDOMState(this)
|
|
204
|
+
this.eventBus.emit(events.APP_OPENED, { statePreserved: false })
|
|
193
205
|
} catch (err) {
|
|
194
206
|
renderError(this.rootEl, this.config.genericErrorText)
|
|
195
207
|
console.error(err)
|
|
@@ -213,8 +225,9 @@ export default class InteractiveMap {
|
|
|
213
225
|
this._openButton.focus()
|
|
214
226
|
}
|
|
215
227
|
|
|
216
|
-
updateDOMState(this)
|
|
228
|
+
updateDOMState(this, { isFullscreen: false })
|
|
217
229
|
|
|
230
|
+
this.eventBus.emit(events.APP_CLOSED, { statePreserved: false })
|
|
218
231
|
this.eventBus.emit(events.MAP_DESTROY, { mapId: this.id })
|
|
219
232
|
}
|
|
220
233
|
|
|
@@ -243,10 +256,10 @@ export default class InteractiveMap {
|
|
|
243
256
|
// Reset page title (remove prepended map title)
|
|
244
257
|
const parts = document.title.split(': ')
|
|
245
258
|
if (parts.length > 1) {
|
|
246
|
-
document.title = parts
|
|
259
|
+
document.title = parts.at(-1)
|
|
247
260
|
}
|
|
248
261
|
|
|
249
|
-
this.eventBus.emit(events.
|
|
262
|
+
this.eventBus.emit(events.APP_CLOSED, { statePreserved: true })
|
|
250
263
|
}
|
|
251
264
|
|
|
252
265
|
/**
|
|
@@ -265,7 +278,7 @@ export default class InteractiveMap {
|
|
|
265
278
|
|
|
266
279
|
updateDOMState(this)
|
|
267
280
|
|
|
268
|
-
this.eventBus.emit(events.
|
|
281
|
+
this.eventBus.emit(events.APP_OPENED, { statePreserved: true })
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
/**
|
|
@@ -365,6 +378,10 @@ export default class InteractiveMap {
|
|
|
365
378
|
/**
|
|
366
379
|
* Add a panel to the UI.
|
|
367
380
|
*
|
|
381
|
+
* Focus is moved to the panel on open by default. Set `focus: false` in the
|
|
382
|
+
* config to suppress this — useful when adding panels on page load where
|
|
383
|
+
* stealing focus would be disruptive.
|
|
384
|
+
*
|
|
368
385
|
* @param {string} id - Unique panel identifier.
|
|
369
386
|
* @param {PanelDefinition} config - Panel configuration.
|
|
370
387
|
*/
|
|
@@ -384,10 +401,15 @@ export default class InteractiveMap {
|
|
|
384
401
|
/**
|
|
385
402
|
* Show a panel.
|
|
386
403
|
*
|
|
404
|
+
* Focus is moved to the panel by default. Set `focus: false` in options to
|
|
405
|
+
* suppress this — useful when showing a panel and you want focus to remain on the button.
|
|
406
|
+
*
|
|
387
407
|
* @param {string} id - Panel identifier to show.
|
|
408
|
+
* @param {object} [options]
|
|
409
|
+
* @param {boolean} [options.focus=true] - Whether to move focus to the panel.
|
|
388
410
|
*/
|
|
389
|
-
showPanel (id) {
|
|
390
|
-
this.eventBus.emit(events.APP_SHOW_PANEL, id)
|
|
411
|
+
showPanel (id, { focus = true } = {}) {
|
|
412
|
+
this.eventBus.emit(events.APP_SHOW_PANEL, { id, focus })
|
|
391
413
|
}
|
|
392
414
|
|
|
393
415
|
/**
|
|
@@ -426,4 +448,30 @@ export default class InteractiveMap {
|
|
|
426
448
|
setView (opts) {
|
|
427
449
|
this.eventBus.emit(events.MAP_SET_VIEW, opts)
|
|
428
450
|
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Programmatically open the map.
|
|
454
|
+
*
|
|
455
|
+
* Equivalent to the user clicking the open button. If the map has been hidden (e.g. in hybrid mode),
|
|
456
|
+
* it will be shown; otherwise the app will be loaded for the first time.
|
|
457
|
+
*/
|
|
458
|
+
open () {
|
|
459
|
+
if (this._isHidden) {
|
|
460
|
+
this.showApp()
|
|
461
|
+
} else if (this._root) {
|
|
462
|
+
// App is already open — no-op
|
|
463
|
+
} else {
|
|
464
|
+
this.loadApp()
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Programmatically close the map.
|
|
470
|
+
*
|
|
471
|
+
* Triggers the same logic as the exit button. If `preserveStateOnClose` is true, the map is hidden
|
|
472
|
+
* but not destroyed; otherwise the app is removed entirely.
|
|
473
|
+
*/
|
|
474
|
+
close () {
|
|
475
|
+
this._handleExitClick()
|
|
476
|
+
}
|
|
429
477
|
}
|
|
@@ -117,7 +117,7 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
117
117
|
})
|
|
118
118
|
|
|
119
119
|
it('open button click calls _handleButtonClick / loadApp', async () => {
|
|
120
|
-
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
120
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', manageHistoryState: true, mapProvider: mapProviderMock })
|
|
121
121
|
const loadSpy = jest.spyOn(map, 'loadApp').mockResolvedValue()
|
|
122
122
|
const pushStateSpy = jest.spyOn(history, 'pushState').mockImplementation(() => {})
|
|
123
123
|
const fakeEvent = { currentTarget: { getAttribute: jest.fn().mockReturnValue('/?mv=map') } }
|
|
@@ -195,7 +195,7 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
195
195
|
expect(map.unmount).toHaveBeenCalled()
|
|
196
196
|
expect(mockButtonInstance.removeAttribute).toHaveBeenCalledWith('style')
|
|
197
197
|
expect(mockButtonInstance.focus).toHaveBeenCalled()
|
|
198
|
-
expect(updateDOMState).toHaveBeenCalledWith(map)
|
|
198
|
+
expect(updateDOMState).toHaveBeenCalledWith(map, { isFullscreen: false })
|
|
199
199
|
})
|
|
200
200
|
|
|
201
201
|
it('skips unmount if _root is falsy or unmount is not a function', () => {
|
|
@@ -275,6 +275,7 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
275
275
|
behaviour: 'buttonFirst',
|
|
276
276
|
mapProvider: mapProviderMock,
|
|
277
277
|
mapViewParamKey: 'mv',
|
|
278
|
+
manageHistoryState: true,
|
|
278
279
|
preserveStateOnClose: false
|
|
279
280
|
})
|
|
280
281
|
|
|
@@ -303,6 +304,7 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
303
304
|
behaviour: 'buttonFirst',
|
|
304
305
|
mapProvider: mapProviderMock,
|
|
305
306
|
mapViewParamKey: 'mv',
|
|
307
|
+
manageHistoryState: true,
|
|
306
308
|
preserveStateOnClose: true
|
|
307
309
|
})
|
|
308
310
|
|
|
@@ -321,7 +323,7 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
321
323
|
})
|
|
322
324
|
|
|
323
325
|
it('_handleButtonClick calls showApp when map is hidden', async () => {
|
|
324
|
-
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
326
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', manageHistoryState: true, mapProvider: mapProviderMock })
|
|
325
327
|
map._isHidden = true
|
|
326
328
|
const showAppSpy = jest.spyOn(map, 'showApp').mockImplementation(() => {})
|
|
327
329
|
const loadAppSpy = jest.spyOn(map, 'loadApp').mockResolvedValue()
|
|
@@ -339,6 +341,126 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
339
341
|
pushStateSpy.mockRestore()
|
|
340
342
|
})
|
|
341
343
|
|
|
344
|
+
it('_handleButtonClick skips pushState when manageHistoryState is false', async () => {
|
|
345
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', manageHistoryState: false, mapProvider: mapProviderMock })
|
|
346
|
+
expect(map.config.manageHistoryState).toBe(false)
|
|
347
|
+
const pushStateSpy = jest.spyOn(history, 'pushState').mockImplementation(() => {})
|
|
348
|
+
const fakeEvent = { currentTarget: { getAttribute: jest.fn().mockReturnValue('/?mv=map') } }
|
|
349
|
+
|
|
350
|
+
await openButtonCallback(fakeEvent)
|
|
351
|
+
|
|
352
|
+
expect(pushStateSpy).not.toHaveBeenCalled()
|
|
353
|
+
pushStateSpy.mockRestore()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('_handleExitClick calls history.back() when history.state.isBack is true', () => {
|
|
357
|
+
const backSpy = jest.spyOn(history, 'back').mockImplementation(() => {})
|
|
358
|
+
Object.defineProperty(history, 'state', { value: { isBack: true }, writable: true, configurable: true })
|
|
359
|
+
|
|
360
|
+
const map = new InteractiveMap('map', {
|
|
361
|
+
behaviour: 'buttonFirst',
|
|
362
|
+
mapProvider: mapProviderMock,
|
|
363
|
+
mapViewParamKey: 'mv',
|
|
364
|
+
manageHistoryState: true,
|
|
365
|
+
preserveStateOnClose: false
|
|
366
|
+
})
|
|
367
|
+
jest.spyOn(map, 'removeApp').mockImplementation(() => {})
|
|
368
|
+
|
|
369
|
+
map._handleExitClick()
|
|
370
|
+
|
|
371
|
+
expect(backSpy).toHaveBeenCalled()
|
|
372
|
+
Object.defineProperty(history, 'state', { value: null, writable: true, configurable: true })
|
|
373
|
+
backSpy.mockRestore()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('_handleExitClick skips history when manageHistoryState is false', () => {
|
|
377
|
+
const backSpy = jest.spyOn(history, 'back').mockImplementation(() => {})
|
|
378
|
+
const replaceStateSpy = jest.spyOn(history, 'replaceState').mockImplementation(() => {})
|
|
379
|
+
|
|
380
|
+
const map = new InteractiveMap('map', {
|
|
381
|
+
behaviour: 'buttonFirst',
|
|
382
|
+
mapProvider: mapProviderMock,
|
|
383
|
+
mapViewParamKey: 'mv',
|
|
384
|
+
manageHistoryState: false,
|
|
385
|
+
preserveStateOnClose: false
|
|
386
|
+
})
|
|
387
|
+
jest.spyOn(map, 'removeApp').mockImplementation(() => {})
|
|
388
|
+
|
|
389
|
+
map._handleExitClick()
|
|
390
|
+
|
|
391
|
+
expect(backSpy).not.toHaveBeenCalled()
|
|
392
|
+
expect(replaceStateSpy).not.toHaveBeenCalled()
|
|
393
|
+
backSpy.mockRestore()
|
|
394
|
+
replaceStateSpy.mockRestore()
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('loadApp emits APP_OPENED with statePreserved: false', async () => {
|
|
398
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
399
|
+
await map.loadApp()
|
|
400
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: false })
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
it('removeApp emits APP_CLOSED with statePreserved: false', () => {
|
|
404
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
405
|
+
map._root = {}
|
|
406
|
+
map.unmount = jest.fn()
|
|
407
|
+
map.removeApp()
|
|
408
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:closed', { statePreserved: false })
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('hideApp emits APP_CLOSED with statePreserved: true', () => {
|
|
412
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
413
|
+
map.hideApp()
|
|
414
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:closed', { statePreserved: true })
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it('showApp emits APP_OPENED with statePreserved: true', () => {
|
|
418
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
419
|
+
map._isHidden = true
|
|
420
|
+
map.showApp()
|
|
421
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: true })
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('open() shows hidden map', () => {
|
|
425
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
426
|
+
const showSpy = jest.spyOn(map, 'showApp').mockImplementation(() => {})
|
|
427
|
+
map._isHidden = true
|
|
428
|
+
map.open()
|
|
429
|
+
expect(showSpy).toHaveBeenCalled()
|
|
430
|
+
showSpy.mockRestore()
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('open() loads map when not yet initialised', () => {
|
|
434
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
435
|
+
const loadSpy = jest.spyOn(map, 'loadApp').mockResolvedValue()
|
|
436
|
+
map._root = null
|
|
437
|
+
map._isHidden = false
|
|
438
|
+
map.open()
|
|
439
|
+
expect(loadSpy).toHaveBeenCalled()
|
|
440
|
+
loadSpy.mockRestore()
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('open() is a no-op when already open', () => {
|
|
444
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
445
|
+
const loadSpy = jest.spyOn(map, 'loadApp').mockResolvedValue()
|
|
446
|
+
const showSpy = jest.spyOn(map, 'showApp').mockImplementation(() => {})
|
|
447
|
+
map._root = {}
|
|
448
|
+
map._isHidden = false
|
|
449
|
+
map.open()
|
|
450
|
+
expect(loadSpy).not.toHaveBeenCalled()
|
|
451
|
+
expect(showSpy).not.toHaveBeenCalled()
|
|
452
|
+
loadSpy.mockRestore()
|
|
453
|
+
showSpy.mockRestore()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('close() delegates to _handleExitClick', () => {
|
|
457
|
+
const map = new InteractiveMap('map', { mapProvider: mapProviderMock })
|
|
458
|
+
const exitSpy = jest.spyOn(map, '_handleExitClick').mockImplementation(() => {})
|
|
459
|
+
map.close()
|
|
460
|
+
expect(exitSpy).toHaveBeenCalled()
|
|
461
|
+
exitSpy.mockRestore()
|
|
462
|
+
})
|
|
463
|
+
|
|
342
464
|
it('hideApp sets _isHidden and hides element', () => {
|
|
343
465
|
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
344
466
|
map._openButton = mockButtonInstance
|
|
@@ -497,7 +619,7 @@ describe('InteractiveMap Public API Methods', () => {
|
|
|
497
619
|
expect(map.eventBus.emit).toHaveBeenCalledWith('app:removepanel', 'panel1')
|
|
498
620
|
|
|
499
621
|
// New assertions for coverage
|
|
500
|
-
expect(map.eventBus.emit).toHaveBeenCalledWith('app:showpanel', 'panel2')
|
|
622
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:showpanel', { id: 'panel2', focus: true })
|
|
501
623
|
expect(map.eventBus.emit).toHaveBeenCalledWith('app:hidepanel', 'panel3')
|
|
502
624
|
})
|
|
503
625
|
|
|
@@ -14,11 +14,21 @@ function updatePageTitle ({ pageTitle, isFullscreen }) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function getIsFullscreen (config) {
|
|
17
|
-
const { id, behaviour } = config
|
|
18
|
-
const hasViewParam = getQueryParam(defaults.mapViewParamKey) === id
|
|
17
|
+
const { id, behaviour, manageHistoryState } = config
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (behaviour === 'mapOnly') {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (behaviour === 'buttonFirst') {
|
|
24
|
+
// When the SPA manages history, the app is always fullscreen when loaded
|
|
25
|
+
if (manageHistoryState === false) {
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
return getQueryParam(defaults.mapViewParamKey) === id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return isHybridFullscreen(config) && getQueryParam(defaults.mapViewParamKey) === id
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
// -----------------------------------------------------------------------------
|
|
@@ -36,12 +46,14 @@ function getIsFullscreen (config) {
|
|
|
36
46
|
* @param {string} mapInstance.config.behaviour - Behaviour mode ("mapOnly", "buttonFirst", "hybrid").
|
|
37
47
|
* @param {string|number} mapInstance.config.containerHeight - Height to use when not fullscreen.
|
|
38
48
|
* @param {HTMLElement} mapInstance.rootEl - Root element of the app.
|
|
49
|
+
* @param {Object} [options]
|
|
50
|
+
* @param {boolean} [options.isFullscreen] - Override the computed fullscreen state.
|
|
39
51
|
* @returns {void}
|
|
40
52
|
*/
|
|
41
|
-
function updateDOMState (mapInstance) {
|
|
53
|
+
function updateDOMState (mapInstance, { isFullscreen: isFullscreenOverride } = {}) {
|
|
42
54
|
const { config, rootEl } = mapInstance
|
|
43
55
|
const { pageTitle, behaviour, containerHeight } = config
|
|
44
|
-
const isFullscreen = getIsFullscreen(config)
|
|
56
|
+
const isFullscreen = isFullscreenOverride ?? getIsFullscreen(config)
|
|
45
57
|
|
|
46
58
|
if (['mapOnly', 'buttonFirst', 'hybrid'].includes(behaviour)) {
|
|
47
59
|
toggleInertElements({ containerEl: rootEl, isFullscreen })
|