@defra/interactive-map 0.0.16-alpha → 0.0.17-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/slot-map.svg +264 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/slots.md +16 -15
- package/docs/api.md +3 -3
- package/docs/getting-started.md +4 -1
- package/docs/plugins/datasets.md +561 -0
- package/docs/plugins.md +1 -1
- package/package.json +2 -2
- package/plugins/beta/datasets/dist/css/index.css +85 -15
- 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/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/DatasetsInit.jsx +23 -8
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
- package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
- package/plugins/beta/datasets/src/api/addDataset.js +2 -8
- package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
- package/plugins/beta/datasets/src/api/getStyle.js +13 -0
- package/plugins/beta/datasets/src/api/removeDataset.js +2 -44
- package/plugins/beta/datasets/src/api/setData.js +8 -0
- package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
- package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
- package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
- package/plugins/beta/datasets/src/api/setStyle.js +22 -0
- package/plugins/beta/datasets/src/datasets.js +29 -55
- package/plugins/beta/datasets/src/defaults.js +42 -8
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +34 -25
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
- package/plugins/beta/datasets/src/manifest.js +24 -16
- package/plugins/beta/datasets/src/panels/Key.jsx +128 -50
- package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
- package/plugins/beta/datasets/src/panels/Layers.jsx +132 -29
- package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
- package/plugins/beta/datasets/src/reducer.js +128 -9
- package/plugins/beta/datasets/src/styles/patterns.js +157 -0
- package/plugins/beta/datasets/src/utils/bbox.js +7 -5
- package/plugins/beta/datasets/src/utils/filters.js +5 -2
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
- package/plugins/beta/draw-ml/dist/css/index.css +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/draw.scss +0 -7
- package/plugins/beta/draw-ml/src/manifest.js +16 -16
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +5 -5
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/src/manifest.js +1 -1
- package/plugins/beta/scale-bar/dist/css/index.css +1 -1
- package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/src/index.test.js +3 -3
- package/plugins/beta/scale-bar/src/manifest.js +3 -3
- package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
- package/plugins/interact/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/src/interact.scss +0 -7
- package/plugins/interact/src/manifest.js +14 -18
- package/plugins/interact/src/manifest.test.js +3 -1
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/src/components/Form/Form.module.scss +2 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +1 -0
- package/providers/maplibre/src/utils/highlightFeatures.test.js +1 -0
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +0 -7
- package/src/App/components/Actions/Actions.test.jsx +1 -1
- package/src/App/components/Icon/Icon.jsx +3 -2
- package/src/App/components/Icon/Icon.module.scss +4 -0
- package/src/App/components/Icon/Icon.test.jsx +43 -4
- package/src/App/components/MapButton/MapButton.jsx +42 -17
- package/src/App/components/MapButton/MapButton.module.scss +4 -13
- package/src/App/components/MapButton/MapButton.test.jsx +27 -3
- package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
- package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
- package/src/App/hooks/useButtonStateEvaluator.js +12 -2
- package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
- package/src/App/hooks/useInterfaceAPI.js +6 -0
- package/src/App/hooks/useLayoutMeasurements.js +84 -18
- package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
- package/src/App/layout/Layout.jsx +12 -7
- package/src/App/layout/Layout.test.jsx +2 -2
- package/src/App/layout/layout.module.scss +67 -29
- package/src/App/registry/pluginRegistry.js +1 -1
- package/src/App/renderer/HtmlElementHost.jsx +2 -1
- package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
- package/src/App/renderer/mapButtons.js +1 -1
- package/src/App/renderer/mapPanels.test.js +2 -2
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +5 -5
- package/src/App/renderer/slots.js +9 -5
- package/src/App/store/AppProvider.jsx +3 -1
- package/src/App/store/AppProvider.test.jsx +1 -1
- package/src/App/store/ServiceProvider.jsx +3 -1
- package/src/App/store/appActionsMap.js +16 -0
- package/src/App/store/appActionsMap.test.js +27 -0
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +2 -2
- package/src/App/store/appReducer.js +2 -0
- package/src/InteractiveMap/InteractiveMap.js +4 -0
- package/src/config/appConfig.js +5 -2
- package/src/config/events.js +28 -0
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +0 -1
- package/src/utils/getSafeZoneInset.js +9 -7
- package/src/utils/getSafeZoneInset.test.js +10 -10
- package/webpack.dev.mjs +1 -1
- package/docs/api/slot-map.svg +0 -1
- package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
- package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
- package/plugins/beta/datasets/src/api/showDataset.js +0 -14
- package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
- package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
- package/plugins/beta/datasets/src/mapLayers.js +0 -164
- /package/src/{utils → services}/logger.js +0 -0
- /package/src/{utils → services}/logger.test.js +0 -0
|
@@ -152,6 +152,12 @@ const toggleHasExclusiveControl = (state, payload) => {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
const setPluginsEvaluated = (state) =>
|
|
156
|
+
state.arePluginsEvaluated ? state : { ...state, arePluginsEvaluated: true }
|
|
157
|
+
|
|
158
|
+
const clearPluginsEvaluated = (state) =>
|
|
159
|
+
state.arePluginsEvaluated ? { ...state, arePluginsEvaluated: false } : state
|
|
160
|
+
|
|
155
161
|
const setSafeZoneInset = (state, { safeZoneInset, syncMapPadding = true }) => {
|
|
156
162
|
return shallowEqual(state.safeZoneInset, safeZoneInset)
|
|
157
163
|
? state
|
|
@@ -163,6 +169,13 @@ const setSafeZoneInset = (state, { safeZoneInset, syncMapPadding = true }) => {
|
|
|
163
169
|
}
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
const toggleAppVisible = (state, payload) => {
|
|
173
|
+
return {
|
|
174
|
+
...state,
|
|
175
|
+
appVisible: payload
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
166
179
|
const toggleButtonDisabled = (state, payload) => {
|
|
167
180
|
const { id, isDisabled } = payload
|
|
168
181
|
const updated = new Set(state.disabledButtons)
|
|
@@ -358,12 +371,15 @@ export const actionsMap = {
|
|
|
358
371
|
SET_HYBRID_FULLSCREEN: setHybridFullscreen,
|
|
359
372
|
SET_INTERFACE_TYPE: setInterfaceType,
|
|
360
373
|
SET_MODE: setMode,
|
|
374
|
+
PLUGINS_EVALUATED: setPluginsEvaluated,
|
|
375
|
+
CLEAR_PLUGINS_EVALUATED: clearPluginsEvaluated,
|
|
361
376
|
SET_SAFE_ZONE_INSET: setSafeZoneInset,
|
|
362
377
|
REVERT_MODE: revertMode,
|
|
363
378
|
OPEN_PANEL: openPanel,
|
|
364
379
|
CLOSE_PANEL: closePanel,
|
|
365
380
|
CLOSE_ALL_PANELS: closeAllPanels,
|
|
366
381
|
RESTORE_PREVIOUS_PANELS: restorePreviousPanels,
|
|
382
|
+
TOGGLE_APP_VISIBLE: toggleAppVisible,
|
|
367
383
|
TOGGLE_HAS_EXCLUSIVE_CONTROL: toggleHasExclusiveControl,
|
|
368
384
|
TOGGLE_BUTTON_DISABLED: toggleButtonDisabled,
|
|
369
385
|
TOGGLE_BUTTON_HIDDEN: toggleButtonHidden,
|
|
@@ -128,6 +128,26 @@ describe('actionsMap full coverage', () => {
|
|
|
128
128
|
expect(result.hasExclusiveControl).toBe(true)
|
|
129
129
|
})
|
|
130
130
|
|
|
131
|
+
test('PLUGINS_EVALUATED is no-op when arePluginsEvaluated already true', () => {
|
|
132
|
+
const s = { ...state, arePluginsEvaluated: true }
|
|
133
|
+
expect(actionsMap.PLUGINS_EVALUATED(s)).toBe(s)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('PLUGINS_EVALUATED sets arePluginsEvaluated when false', () => {
|
|
137
|
+
const s = { ...state, arePluginsEvaluated: false }
|
|
138
|
+
expect(actionsMap.PLUGINS_EVALUATED(s).arePluginsEvaluated).toBe(true)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('CLEAR_PLUGINS_EVALUATED clears arePluginsEvaluated when true', () => {
|
|
142
|
+
const s = { ...state, arePluginsEvaluated: true }
|
|
143
|
+
expect(actionsMap.CLEAR_PLUGINS_EVALUATED(s).arePluginsEvaluated).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('CLEAR_PLUGINS_EVALUATED is no-op when arePluginsEvaluated already false', () => {
|
|
147
|
+
const s = { ...state, arePluginsEvaluated: false }
|
|
148
|
+
expect(actionsMap.CLEAR_PLUGINS_EVALUATED(s)).toBe(s)
|
|
149
|
+
})
|
|
150
|
+
|
|
131
151
|
test('SET_SAFE_ZONE_INSET branch true/false', () => {
|
|
132
152
|
shallowEqualModule.shallowEqual.mockReturnValueOnce(false)
|
|
133
153
|
const res1 = actionsMap.SET_SAFE_ZONE_INSET(state, { safeZoneInset: { top: 10, bottom: 10 } })
|
|
@@ -152,6 +172,13 @@ describe('actionsMap full coverage', () => {
|
|
|
152
172
|
expect(r2.hiddenButtons.has('btn3')).toBe(false)
|
|
153
173
|
})
|
|
154
174
|
|
|
175
|
+
test('TOGGLE_APP_VISIBLE sets appVisible to payload', () => {
|
|
176
|
+
const r1 = actionsMap.TOGGLE_APP_VISIBLE(state, true)
|
|
177
|
+
expect(r1.appVisible).toBe(true)
|
|
178
|
+
const r2 = actionsMap.TOGGLE_APP_VISIBLE(state, false)
|
|
179
|
+
expect(r2.appVisible).toBe(false)
|
|
180
|
+
})
|
|
181
|
+
|
|
155
182
|
test('TOGGLE_BUTTON_PRESSED adds/removes button', () => {
|
|
156
183
|
const r1 = actionsMap.TOGGLE_BUTTON_PRESSED(state, { id: 'btn6', isPressed: true })
|
|
157
184
|
expect(r1.pressedButtons.has('btn6')).toBe(true)
|
|
@@ -3,7 +3,7 @@ import { EVENTS as events } from '../../config/events.js'
|
|
|
3
3
|
import { defaultPanelConfig, defaultButtonConfig, defaultControlConfig } from '../../config/appConfig.js'
|
|
4
4
|
import { deepMerge } from '../../utils/deepMerge.js'
|
|
5
5
|
import { allowedSlots } from '../renderer/slots.js'
|
|
6
|
-
import { logger } from '../../
|
|
6
|
+
import { logger } from '../../services/logger.js'
|
|
7
7
|
|
|
8
8
|
const BREAKPOINTS = ['mobile', 'tablet', 'desktop']
|
|
9
9
|
|
|
@@ -180,7 +180,7 @@ describe('ADD_CONTROL', () => {
|
|
|
180
180
|
|
|
181
181
|
it('does not warn when control has a valid slot', () => {
|
|
182
182
|
run(
|
|
183
|
-
{ type: 'ADD_CONTROL', payload: { id: 'myCtrl', config: { mobile: { slot: '
|
|
183
|
+
{ type: 'ADD_CONTROL', payload: { id: 'myCtrl', config: { mobile: { slot: 'drawer' }, tablet: { slot: 'top-left' }, desktop: { slot: 'top-left' } } } },
|
|
184
184
|
{ breakpoint: 'desktop' }
|
|
185
185
|
)
|
|
186
186
|
expect(console.warn).not.toHaveBeenCalled()
|
|
@@ -238,7 +238,7 @@ describe('ADD_PANEL', () => {
|
|
|
238
238
|
|
|
239
239
|
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
240
240
|
events.APP_PANEL_OPENED,
|
|
241
|
-
{ panelId: 'mobilePanel', slot: '
|
|
241
|
+
{ panelId: 'mobilePanel', slot: 'drawer' }
|
|
242
242
|
)
|
|
243
243
|
})
|
|
244
244
|
|
|
@@ -29,7 +29,9 @@ export const initialState = (config) => {
|
|
|
29
29
|
const openPanels = getInitialOpenPanels(panelConfig, initialBreakpoint)
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
|
+
appVisible: null,
|
|
32
33
|
isLayoutReady: false,
|
|
34
|
+
arePluginsEvaluated: false,
|
|
33
35
|
breakpoint: initialBreakpoint,
|
|
34
36
|
interfaceType: initialInterfaceType,
|
|
35
37
|
preferredColorScheme: autoColorScheme ? preferredColorScheme : appColorScheme,
|
|
@@ -245,6 +245,8 @@ export default class InteractiveMap {
|
|
|
245
245
|
if (parts.length > 1) {
|
|
246
246
|
document.title = parts[parts.length - 1]
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
this.eventBus.emit(events.APP_HIDDEN)
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
/**
|
|
@@ -262,6 +264,8 @@ export default class InteractiveMap {
|
|
|
262
264
|
}
|
|
263
265
|
|
|
264
266
|
updateDOMState(this)
|
|
267
|
+
|
|
268
|
+
this.eventBus.emit(events.APP_VISIBLE)
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
/**
|
package/src/config/appConfig.js
CHANGED
|
@@ -93,6 +93,9 @@ export const defaultAppConfig = {
|
|
|
93
93
|
}, {
|
|
94
94
|
id: 'minus',
|
|
95
95
|
svgContent: '<path d="M5 12h14"/>'
|
|
96
|
+
}, {
|
|
97
|
+
id: 'chevron',
|
|
98
|
+
svgContent: '<path d="m6 9 6 6 6-6"/>'
|
|
96
99
|
}]
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -113,7 +116,7 @@ export const defaultButtonConfig = {
|
|
|
113
116
|
export const defaultPanelConfig = {
|
|
114
117
|
label: 'Panel',
|
|
115
118
|
mobile: {
|
|
116
|
-
slot: '
|
|
119
|
+
slot: 'drawer',
|
|
117
120
|
open: true,
|
|
118
121
|
dismissible: true,
|
|
119
122
|
modal: false,
|
|
@@ -141,7 +144,7 @@ export const defaultPanelConfig = {
|
|
|
141
144
|
export const defaultControlConfig = {
|
|
142
145
|
label: 'Control',
|
|
143
146
|
mobile: {
|
|
144
|
-
slot: '
|
|
147
|
+
slot: 'drawer'
|
|
145
148
|
},
|
|
146
149
|
tablet: {
|
|
147
150
|
slot: 'top-left'
|
package/src/config/events.js
CHANGED
|
@@ -61,6 +61,34 @@ export const EVENTS = {
|
|
|
61
61
|
*/
|
|
62
62
|
APP_READY: 'app:ready',
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Emitted when the map application becomes visible after being hidden.
|
|
66
|
+
*
|
|
67
|
+
* This can occur in 'hybrid behaviour' responsive scenarios where the map is already initialized
|
|
68
|
+
* (e.g. initialized inline on desktop) but was hidden and then shown again
|
|
69
|
+
* (e.g. resizing to mobile and opening the map).
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* - Only emitted when transitioning from hidden → visible.
|
|
73
|
+
* - Not fired on initial open.
|
|
74
|
+
* - The existing map state may be preserved depending on configuration.
|
|
75
|
+
*/
|
|
76
|
+
APP_VISIBLE: 'app:visible',
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Emitted when the map application becomes hidden.
|
|
80
|
+
*
|
|
81
|
+
* This can occur in 'hybrid behaviour' responsive scenarios where the map was initialized inline
|
|
82
|
+
* (e.g. visible on desktop) but then becomes hidden
|
|
83
|
+
* (e.g. resizing to mobile or closing the map view).
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* - Only emitted when transitioning from visible → hidden.
|
|
87
|
+
* - Not fired on initial load if the map starts hidden.
|
|
88
|
+
* - The map state may be preserved depending on configuration.
|
|
89
|
+
*/
|
|
90
|
+
APP_HIDDEN: 'app:hidden',
|
|
91
|
+
|
|
64
92
|
/**
|
|
65
93
|
* Emitted when a panel is opened.
|
|
66
94
|
* Payload: { panelId: string }
|
package/src/scss/main.scss
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
@use '../App/components/KeyboardHelp/KeyboardHelp.module';
|
|
20
20
|
@use '../App/components/MapButton/MapButton.module';
|
|
21
21
|
@use '../App/components/Panel/Panel.module';
|
|
22
|
+
@use '../App/components/Icon/Icon.module';
|
|
22
23
|
@use '../App/components/Viewport/Viewport.module';
|
|
23
24
|
@use '../App/components/Tooltip/Tooltip.module';
|
|
24
25
|
@use '../App/components/PopupMenu/PopupMenu.module';
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* @param {React.RefObject} refs.leftRef - Left button column.
|
|
28
28
|
* @param {React.RefObject} refs.rightRef - Right button column.
|
|
29
29
|
* @param {React.RefObject} refs.actionsRef - Bottom action bar.
|
|
30
|
-
* @param {React.RefObject} refs.
|
|
30
|
+
* @param {React.RefObject} refs.bottomRef - Bottom row (logo, copyright, etc).
|
|
31
31
|
* @param {React.RefObject} [refs.leftTopRef] - Top-left panel slot.
|
|
32
32
|
* @param {React.RefObject} [refs.leftBottomRef] - Bottom-left panel slot.
|
|
33
33
|
* @param {React.RefObject} [refs.rightTopRef] - Top-right panel slot.
|
|
@@ -107,15 +107,15 @@ const computeRow = (leftW, rightW, leftH, rightH, wThreshold, baseInset, gap) =>
|
|
|
107
107
|
leftW + rightW > wThreshold ? baseInset + Math.max(leftH, rightH) + gap : 0
|
|
108
108
|
|
|
109
109
|
export const getSafeZoneInset = ({
|
|
110
|
-
mainRef, leftRef, rightRef, actionsRef,
|
|
110
|
+
mainRef, leftRef, rightRef, actionsRef, bottomRef,
|
|
111
111
|
leftTopRef, leftBottomRef, rightTopRef, rightBottomRef
|
|
112
112
|
}) => {
|
|
113
|
-
if ([mainRef, leftRef, rightRef, actionsRef,
|
|
113
|
+
if ([mainRef, leftRef, rightRef, actionsRef, bottomRef].some(ref => !ref?.current)) {
|
|
114
114
|
return undefined
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const main = mainRef.current; const left = leftRef.current
|
|
118
|
-
const actions = actionsRef.current; const
|
|
118
|
+
const actions = actionsRef.current; const bottom = bottomRef.current
|
|
119
119
|
|
|
120
120
|
const gap = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--divider-gap'), 10)
|
|
121
121
|
|
|
@@ -127,8 +127,10 @@ export const getSafeZoneInset = ({
|
|
|
127
127
|
const baseLeft = main.offsetLeft + left.offsetLeft + colWidth + gap
|
|
128
128
|
const baseRight = left.offsetLeft + colWidth + gap
|
|
129
129
|
const baseTop = left.offsetTop
|
|
130
|
-
const
|
|
131
|
-
|
|
130
|
+
const bottomContainerPad = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
|
|
131
|
+
// Minimum: primary-gap above the bottom edge. Normally: divider-gap above the top of the bottom container.
|
|
132
|
+
const bottomInset = Math.max(bottomContainerPad, main.offsetHeight - bottom.offsetTop + gap)
|
|
133
|
+
const baseBottom = Math.max(main.offsetHeight - actions.offsetTop + gap, bottomInset)
|
|
132
134
|
|
|
133
135
|
const availableH = main.offsetHeight - baseTop - baseBottom
|
|
134
136
|
const availableW = main.offsetWidth - (baseLeft - main.offsetLeft) - baseRight
|
|
@@ -139,7 +141,7 @@ export const getSafeZoneInset = ({
|
|
|
139
141
|
const leftPanelInset = leftCol.panelInset
|
|
140
142
|
const rightPanelInset = rightCol.panelInset
|
|
141
143
|
const topPanelInset = computeRow(leftCol.rowA.w, rightCol.rowA.w, leftCol.rowA.h, rightCol.rowA.h, availableW / RATIO, baseTop, gap)
|
|
142
|
-
const bottomPanelInset = computeRow(leftCol.rowB.w, rightCol.rowB.w, leftCol.rowB.h, rightCol.rowB.h, availableW / RATIO,
|
|
144
|
+
const bottomPanelInset = computeRow(leftCol.rowB.w, rightCol.rowB.w, leftCol.rowB.h, rightCol.rowB.h, availableW / RATIO, bottomInset, gap)
|
|
143
145
|
|
|
144
146
|
const usableW = main.offsetWidth - 2 * gap
|
|
145
147
|
const usableH = main.offsetHeight - 2 * gap
|
|
@@ -7,7 +7,7 @@ const LEFT_WIDTH = 40
|
|
|
7
7
|
const LEFT_TOP = 60
|
|
8
8
|
const RIGHT_WIDTH = 40
|
|
9
9
|
const ACTIONS_TOP = 540
|
|
10
|
-
const
|
|
10
|
+
const BOTTOM_TOP = 560
|
|
11
11
|
const EARLY_ACTIONS_TOP = 500
|
|
12
12
|
const GAP = 8
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ const PANEL_H_TALL = 150
|
|
|
31
31
|
const PANEL_H_SHORT = 100
|
|
32
32
|
|
|
33
33
|
const ABOVE_CAP_TOP = 330 // 60+330+8=398 > CAP_HEIGHT ≈ 389.3 → capped
|
|
34
|
-
const
|
|
34
|
+
const BOTTOM_INSET = MAIN_HEIGHT - BOTTOM_TOP + GAP // 48: divider-gap above top of bottom container
|
|
35
35
|
const ABOVE_CAP_BOTTOM = 342 // 48+342+8=398 > CAP_HEIGHT → capped
|
|
36
36
|
|
|
37
37
|
const PANEL_W_STANDARD = 200
|
|
@@ -49,7 +49,7 @@ const CAP_HEIGHT = (MAIN_HEIGHT - 2 * GAP) * (MAX_RATIO - 1) / MAX_RATIO
|
|
|
49
49
|
|
|
50
50
|
// ─── Setup ──────────────────────────────────────────────────────────────────
|
|
51
51
|
|
|
52
|
-
let mainRef, leftRef, rightRef, actionsRef,
|
|
52
|
+
let mainRef, leftRef, rightRef, actionsRef, bottomRef
|
|
53
53
|
|
|
54
54
|
beforeAll(() => {
|
|
55
55
|
globalThis.getComputedStyle = jest.fn().mockReturnValue({ getPropertyValue: () => String(GAP) })
|
|
@@ -72,10 +72,10 @@ beforeEach(() => {
|
|
|
72
72
|
leftRef = colRef(LEFT_WIDTH, LEFT_LEFT, LEFT_TOP)
|
|
73
73
|
rightRef = colRef(RIGHT_WIDTH)
|
|
74
74
|
actionsRef = { current: { offsetTop: ACTIONS_TOP } }
|
|
75
|
-
|
|
75
|
+
bottomRef = { current: { offsetTop: BOTTOM_TOP, offsetHeight: 0 } }
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
const base = () => ({ mainRef, leftRef, rightRef, actionsRef,
|
|
78
|
+
const base = () => ({ mainRef, leftRef, rightRef, actionsRef, bottomRef })
|
|
79
79
|
|
|
80
80
|
// Slot container where an .im-c-panel is the first element child.
|
|
81
81
|
const panel = (offsetWidth, offsetHeight) => {
|
|
@@ -96,7 +96,7 @@ describe('getSafeZoneInset — missing refs', () => {
|
|
|
96
96
|
expect(getSafeZoneInset({ ...base(), leftRef: { current: null } })).toBeUndefined()
|
|
97
97
|
})
|
|
98
98
|
it('returns undefined when actionsRef is undefined', () => {
|
|
99
|
-
expect(getSafeZoneInset({ mainRef, leftRef, rightRef,
|
|
99
|
+
expect(getSafeZoneInset({ mainRef, leftRef, rightRef, bottomRef })).toBeUndefined()
|
|
100
100
|
})
|
|
101
101
|
})
|
|
102
102
|
|
|
@@ -120,7 +120,7 @@ describe('getSafeZoneInset — base structural insets', () => {
|
|
|
120
120
|
it('uses zero button width when column ref has no button group', () => {
|
|
121
121
|
const noGroupLeft = { current: { offsetWidth: 0, offsetLeft: LEFT_LEFT, offsetTop: LEFT_TOP, querySelector: () => null } }
|
|
122
122
|
const noGroupRight = { current: { offsetWidth: 0, offsetLeft: 0, offsetTop: 0, querySelector: () => null } }
|
|
123
|
-
expect(getSafeZoneInset({ mainRef, leftRef: noGroupLeft, rightRef: noGroupRight, actionsRef,
|
|
123
|
+
expect(getSafeZoneInset({ mainRef, leftRef: noGroupLeft, rightRef: noGroupRight, actionsRef, bottomRef })).toEqual({
|
|
124
124
|
left: LEFT_LEFT + GAP, right: LEFT_LEFT + GAP, top: LEFT_TOP, bottom: BASE_BOTTOM
|
|
125
125
|
})
|
|
126
126
|
})
|
|
@@ -133,7 +133,7 @@ describe('getSafeZoneInset — base structural insets', () => {
|
|
|
133
133
|
rightBottomRef: panel(PANEL_W_STANDARD, 0)
|
|
134
134
|
})).toEqual({ left: BASE_LEFT, right: BASE_RIGHT, top: BASE_TOP, bottom: BASE_BOTTOM })
|
|
135
135
|
})
|
|
136
|
-
it('uses max of actions and
|
|
136
|
+
it('uses max of actions and bottom for base bottom', () => {
|
|
137
137
|
actionsRef.current.offsetTop = EARLY_ACTIONS_TOP
|
|
138
138
|
expect(getSafeZoneInset(base()).bottom).toBe(MAIN_HEIGHT - EARLY_ACTIONS_TOP + GAP)
|
|
139
139
|
})
|
|
@@ -244,7 +244,7 @@ describe('getSafeZoneInset — bottom edge', () => {
|
|
|
244
244
|
})
|
|
245
245
|
it('triggers when a bottom panel width exceeds threshold', () => {
|
|
246
246
|
expect(getSafeZoneInset({ ...base(), leftBottomRef: panel(ABOVE_W_THRESHOLD, ABOVE_THRESHOLD) }).bottom)
|
|
247
|
-
.toBe(Math.min(
|
|
247
|
+
.toBe(Math.min(BOTTOM_INSET + ABOVE_THRESHOLD + GAP, CAP_HEIGHT))
|
|
248
248
|
})
|
|
249
249
|
it('column-primary wide-and-tall bottom panel triggers left inset, not bottom', () => {
|
|
250
250
|
// panel(400,342): h/availableH≈0.724 > w/availableW≈0.510 → column-primary
|
|
@@ -257,7 +257,7 @@ describe('getSafeZoneInset — bottom edge', () => {
|
|
|
257
257
|
...base(),
|
|
258
258
|
leftBottomRef: panel(COMBINED_ABOVE_W, PANEL_H_TALL),
|
|
259
259
|
rightBottomRef: panel(COMBINED_ABOVE_W, PANEL_H_SHORT)
|
|
260
|
-
}).bottom).toBe(Math.min(
|
|
260
|
+
}).bottom).toBe(Math.min(BOTTOM_INSET + PANEL_H_TALL + GAP, CAP_HEIGHT))
|
|
261
261
|
})
|
|
262
262
|
it('does not trigger when both bottom panels are below combined width threshold', () => {
|
|
263
263
|
expect(getSafeZoneInset({
|
package/webpack.dev.mjs
CHANGED
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
],
|
|
19
19
|
entry: {
|
|
20
20
|
index: path.join(__dirname, 'demo/js/index.js'),
|
|
21
|
-
|
|
21
|
+
draw: path.join(__dirname, 'demo/js/draw.js'),
|
|
22
22
|
farming: path.join(__dirname, 'demo/js/farming.js'),
|
|
23
23
|
planning: path.join(__dirname, 'demo/js/planning.js')
|
|
24
24
|
},
|
package/docs/api/slot-map.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 838 629" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" xmlns:v="https://vecta.io/nano"><defs><style>@font-face{font-family:"CourierNewPS-BoldMT";src:url(data:application/font-woff;charset=utf-8;base64,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) format("woff"); font-weight:bold;font-style:normal;}</style></defs><style><![CDATA[.B{font-family:CourierNewPS-BoldMT, Courier New, monospace}.C{font-weight:700}.D{font-size:18px}.E{fill:#0969da}.F{font-family:ArialMT, Arial, sans-serif}.G{font-size:14px}.H{fill:#484949}]]></style><g fill="none" stroke="#d1d9e0"><path d="M171 121v327m496-327v327M171 284H1m836 0H667"/><path d="M1 61h836" stroke-width=".999"/><path d="M1 1h836v627H1z" stroke-width=".993"/><path d="M1 568h836M1 121l836 .001M279 61v60m280-60v60"/><path d="M1 448h836M1 508h836"/><path d="M251 508v-60"/><path d="M294 368h250v60H294z" stroke-width="1.001"/></g><text x="386.349" y="27.083" class="B C D E">banner</text><text x="383.347" y="43.182" class="F G H">mobile only</text><text x="381.44" y="594.177" class="B C D E">actions</text><text x="383.839" y="610.276" class="F G H">mobile only</text><text x="381.351" y="394.259" class="B C D E">actions</text><text x="375.571" y="410.358" class="F G H">tablet/desktop</text><text x="110.175" y="481.538" class="F G H">Logo</text><text x="386.195" y="534.177" class="B C D E">bottom</text><text x="383.193" y="550.276" class="F G H">mobile only</text><text x="96.226" y="94.819" class="B C D E">top-left</text><text x="364.776" y="94.819" class="B C D E">top-middle</text><text x="648.825" y="94.819" class="B C D E">top-right</text><text x="685.986" y="368.985" class="B C D E">right-bottom</text><text x="686.61" y="481.819" class="B C D E">footer-right</text><text x="25.795" y="371.554" class="B C D E">left-bottom</text><text x="702.664" y="203.591" class="B C D E">right-top</text><text x="42.472" y="206.319" class="B C D E">left-top</text></svg>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export const hideDataset = ({ mapProvider, pluginState }, datasetId) => {
|
|
2
|
-
const map = mapProvider.map
|
|
3
|
-
|
|
4
|
-
// Update map layer visibility
|
|
5
|
-
if (map.getLayer(datasetId)) {
|
|
6
|
-
map.setLayoutProperty(datasetId, 'visibility', 'none')
|
|
7
|
-
}
|
|
8
|
-
if (map.getLayer(`${datasetId}-stroke`)) {
|
|
9
|
-
map.setLayoutProperty(`${datasetId}-stroke`, 'visibility', 'none')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Update state
|
|
13
|
-
pluginState.dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { id: datasetId, visibility: 'hidden' } })
|
|
14
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { applyExclusionFilter } from '../utils/filters.js'
|
|
2
|
-
|
|
3
|
-
export const hideFeatures = ({ mapProvider, pluginState }, { featureIds, idProperty, datasetId }) => {
|
|
4
|
-
const map = mapProvider.map
|
|
5
|
-
|
|
6
|
-
// Get dataset to access original filter and determine layer IDs
|
|
7
|
-
const dataset = pluginState.datasets?.find(d => d.id === datasetId)
|
|
8
|
-
if (!dataset) {
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const originalFilter = dataset.filter || null
|
|
13
|
-
const hasFill = !!dataset.fill
|
|
14
|
-
const hasStroke = !!dataset.stroke
|
|
15
|
-
const fillLayerId = hasFill ? datasetId : null
|
|
16
|
-
|
|
17
|
-
let strokeLayerId = null
|
|
18
|
-
if (hasStroke) {
|
|
19
|
-
strokeLayerId = hasFill ? `${datasetId}-stroke` : datasetId
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Get current hidden state and calculate all hidden IDs
|
|
23
|
-
const existingHidden = pluginState.hiddenFeatures[datasetId]
|
|
24
|
-
const allHiddenIds = existingHidden
|
|
25
|
-
? [...new Set([...existingHidden.ids, ...featureIds])]
|
|
26
|
-
: featureIds
|
|
27
|
-
|
|
28
|
-
// Update state (store by datasetId, not individual layer IDs)
|
|
29
|
-
pluginState.dispatch({
|
|
30
|
-
type: 'HIDE_FEATURES',
|
|
31
|
-
payload: { layerId: datasetId, idProperty, featureIds }
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
// Apply filter to both layers
|
|
35
|
-
if (fillLayerId) {
|
|
36
|
-
applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, allHiddenIds)
|
|
37
|
-
}
|
|
38
|
-
if (strokeLayerId) {
|
|
39
|
-
applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, allHiddenIds)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export const showDataset = ({ mapProvider, pluginState }, datasetId) => {
|
|
2
|
-
const map = mapProvider.map
|
|
3
|
-
|
|
4
|
-
// Update map layer visibility
|
|
5
|
-
if (map.getLayer(datasetId)) {
|
|
6
|
-
map.setLayoutProperty(datasetId, 'visibility', 'visible')
|
|
7
|
-
}
|
|
8
|
-
if (map.getLayer(`${datasetId}-stroke`)) {
|
|
9
|
-
map.setLayoutProperty(`${datasetId}-stroke`, 'visibility', 'visible')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Update state
|
|
13
|
-
pluginState.dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { id: datasetId, visibility: 'visible' } })
|
|
14
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { applyExclusionFilter } from '../utils/filters.js'
|
|
2
|
-
|
|
3
|
-
export const showFeatures = ({ mapProvider, pluginState }, { featureIds, idProperty, datasetId }) => {
|
|
4
|
-
const map = mapProvider.map
|
|
5
|
-
|
|
6
|
-
// Get current hidden state before update
|
|
7
|
-
const existingHidden = pluginState.hiddenFeatures[datasetId]
|
|
8
|
-
if (!existingHidden) {
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Get dataset to access original filter and determine layer IDs
|
|
13
|
-
const dataset = pluginState.datasets?.find(d => d.id === datasetId)
|
|
14
|
-
if (!dataset) {
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const originalFilter = dataset.filter || null
|
|
19
|
-
const hasFill = !!dataset.fill
|
|
20
|
-
const hasStroke = !!dataset.stroke
|
|
21
|
-
const fillLayerId = hasFill ? datasetId : null
|
|
22
|
-
|
|
23
|
-
let strokeLayerId = null
|
|
24
|
-
if (hasStroke) {
|
|
25
|
-
strokeLayerId = hasFill ? `${datasetId}-stroke` : datasetId
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Calculate remaining hidden IDs
|
|
29
|
-
const remainingHiddenIds = existingHidden.ids.filter(id => !featureIds.includes(id))
|
|
30
|
-
|
|
31
|
-
// Update state
|
|
32
|
-
pluginState.dispatch({
|
|
33
|
-
type: 'SHOW_FEATURES',
|
|
34
|
-
payload: { layerId: datasetId, featureIds }
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
// Apply filter to both layers (or restore original if nothing hidden)
|
|
38
|
-
if (fillLayerId) {
|
|
39
|
-
applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, remainingHiddenIds)
|
|
40
|
-
}
|
|
41
|
-
if (strokeLayerId) {
|
|
42
|
-
applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, remainingHiddenIds)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { addMapLayers } from './mapLayers.js'
|
|
2
|
-
import { applyExclusionFilter } from './utils/filters.js'
|
|
3
|
-
|
|
4
|
-
export const handleSetMapStyle = ({
|
|
5
|
-
map,
|
|
6
|
-
events,
|
|
7
|
-
eventBus,
|
|
8
|
-
getDatasets,
|
|
9
|
-
getHiddenFeatures,
|
|
10
|
-
getDynamicSources
|
|
11
|
-
}) => {
|
|
12
|
-
const onSetStyle = (e) => {
|
|
13
|
-
map.once('idle', () => {
|
|
14
|
-
const newStyleId = e.id
|
|
15
|
-
const datasets = getDatasets()
|
|
16
|
-
const hiddenFeatures = getHiddenFeatures()
|
|
17
|
-
const dynamicSources = getDynamicSources ? getDynamicSources() : new Map()
|
|
18
|
-
|
|
19
|
-
// Re-add all layers with correct colors for new style
|
|
20
|
-
datasets.forEach(dataset => {
|
|
21
|
-
addMapLayers(map, newStyleId, dataset)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// Reapply cached data for dynamic sources
|
|
25
|
-
dynamicSources.forEach(source => {
|
|
26
|
-
source.reapply()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// Reapply hidden features filters
|
|
30
|
-
Object.entries(hiddenFeatures).forEach(([datasetId, { idProperty, ids }]) => {
|
|
31
|
-
const dataset = datasets.find(d => d.id === datasetId)
|
|
32
|
-
if (!dataset) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const originalFilter = dataset.filter || null
|
|
37
|
-
const hasFill = !!dataset.fill
|
|
38
|
-
const hasStroke = !!dataset.stroke
|
|
39
|
-
const fillLayerId = hasFill ? datasetId : null
|
|
40
|
-
const strokeLayerId = hasStroke ? (hasFill ? `${datasetId}-stroke` : datasetId) : null
|
|
41
|
-
|
|
42
|
-
if (fillLayerId) {
|
|
43
|
-
applyExclusionFilter(map, fillLayerId, originalFilter, idProperty, ids)
|
|
44
|
-
}
|
|
45
|
-
if (strokeLayerId) {
|
|
46
|
-
applyExclusionFilter(map, strokeLayerId, originalFilter, idProperty, ids)
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
eventBus.on(events.MAP_SET_STYLE, onSetStyle)
|
|
53
|
-
return onSetStyle
|
|
54
|
-
}
|