@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.
Files changed (130) hide show
  1. package/assets/images/slot-map.svg +264 -0
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/slots.md +16 -15
  8. package/docs/api.md +3 -3
  9. package/docs/getting-started.md +4 -1
  10. package/docs/plugins/datasets.md +561 -0
  11. package/docs/plugins.md +1 -1
  12. package/package.json +2 -2
  13. package/plugins/beta/datasets/dist/css/index.css +85 -15
  14. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  15. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  16. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  17. package/plugins/beta/datasets/src/DatasetsInit.jsx +23 -8
  18. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  19. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
  20. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
  21. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
  22. package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
  23. package/plugins/beta/datasets/src/api/addDataset.js +2 -8
  24. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  25. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  26. package/plugins/beta/datasets/src/api/removeDataset.js +2 -44
  27. package/plugins/beta/datasets/src/api/setData.js +8 -0
  28. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  29. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  30. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  31. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  32. package/plugins/beta/datasets/src/datasets.js +29 -55
  33. package/plugins/beta/datasets/src/defaults.js +42 -8
  34. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +34 -25
  35. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  36. package/plugins/beta/datasets/src/manifest.js +24 -16
  37. package/plugins/beta/datasets/src/panels/Key.jsx +128 -50
  38. package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
  39. package/plugins/beta/datasets/src/panels/Layers.jsx +132 -29
  40. package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
  41. package/plugins/beta/datasets/src/reducer.js +128 -9
  42. package/plugins/beta/datasets/src/styles/patterns.js +157 -0
  43. package/plugins/beta/datasets/src/utils/bbox.js +7 -5
  44. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  45. package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
  46. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  47. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  48. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  49. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  50. package/plugins/beta/draw-ml/src/manifest.js +16 -16
  51. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  52. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  53. package/plugins/beta/frame/src/Frame.jsx +5 -5
  54. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  55. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  56. package/plugins/beta/map-styles/src/manifest.js +1 -1
  57. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  58. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  59. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  60. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  61. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  62. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  63. package/plugins/interact/dist/css/index.css +1 -1
  64. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  65. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  66. package/plugins/interact/src/interact.scss +0 -7
  67. package/plugins/interact/src/manifest.js +14 -18
  68. package/plugins/interact/src/manifest.test.js +3 -1
  69. package/plugins/search/dist/css/index.css +1 -1
  70. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  71. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  72. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  73. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  74. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  75. package/providers/maplibre/src/utils/highlightFeatures.js +1 -0
  76. package/providers/maplibre/src/utils/highlightFeatures.test.js +1 -0
  77. package/src/App/components/Actions/Actions.jsx +2 -2
  78. package/src/App/components/Actions/Actions.module.scss +0 -7
  79. package/src/App/components/Actions/Actions.test.jsx +1 -1
  80. package/src/App/components/Icon/Icon.jsx +3 -2
  81. package/src/App/components/Icon/Icon.module.scss +4 -0
  82. package/src/App/components/Icon/Icon.test.jsx +43 -4
  83. package/src/App/components/MapButton/MapButton.jsx +42 -17
  84. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  85. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  86. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  87. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  88. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  89. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  90. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  91. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  92. package/src/App/hooks/useInterfaceAPI.js +6 -0
  93. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  94. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  95. package/src/App/layout/Layout.jsx +12 -7
  96. package/src/App/layout/Layout.test.jsx +2 -2
  97. package/src/App/layout/layout.module.scss +67 -29
  98. package/src/App/registry/pluginRegistry.js +1 -1
  99. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  100. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  101. package/src/App/renderer/mapButtons.js +1 -1
  102. package/src/App/renderer/mapPanels.test.js +2 -2
  103. package/src/App/renderer/slotHelpers.js +2 -2
  104. package/src/App/renderer/slotHelpers.test.js +5 -5
  105. package/src/App/renderer/slots.js +9 -5
  106. package/src/App/store/AppProvider.jsx +3 -1
  107. package/src/App/store/AppProvider.test.jsx +1 -1
  108. package/src/App/store/ServiceProvider.jsx +3 -1
  109. package/src/App/store/appActionsMap.js +16 -0
  110. package/src/App/store/appActionsMap.test.js +27 -0
  111. package/src/App/store/appDispatchMiddleware.js +1 -1
  112. package/src/App/store/appDispatchMiddleware.test.js +2 -2
  113. package/src/App/store/appReducer.js +2 -0
  114. package/src/InteractiveMap/InteractiveMap.js +4 -0
  115. package/src/config/appConfig.js +5 -2
  116. package/src/config/events.js +28 -0
  117. package/src/scss/main.scss +1 -0
  118. package/src/scss/settings/_dimensions.scss +0 -1
  119. package/src/utils/getSafeZoneInset.js +9 -7
  120. package/src/utils/getSafeZoneInset.test.js +10 -10
  121. package/webpack.dev.mjs +1 -1
  122. package/docs/api/slot-map.svg +0 -1
  123. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  124. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  125. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  126. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  127. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  128. package/plugins/beta/datasets/src/mapLayers.js +0 -164
  129. /package/src/{utils → services}/logger.js +0 -0
  130. /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 '../../utils/logger.js'
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: 'bottom' }, tablet: { slot: 'top-left' }, desktop: { slot: 'top-left' } } } },
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: 'bottom' }
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
  /**
@@ -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: 'bottom',
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: 'bottom'
147
+ slot: 'drawer'
145
148
  },
146
149
  tablet: {
147
150
  slot: 'top-left'
@@ -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 }
@@ -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';
@@ -9,7 +9,6 @@
9
9
  --button-padding: 8px;
10
10
  --button-icon-label-offset: 7px;
11
11
  --label-button-padding: 10px;
12
- --touch-button-size: 50px;
13
12
 
14
13
  // Borders and focus
15
14
  --app-border-width: 1px;
@@ -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.footerRef - Footer (logo, copyright, etc).
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, footerRef,
110
+ mainRef, leftRef, rightRef, actionsRef, bottomRef,
111
111
  leftTopRef, leftBottomRef, rightTopRef, rightBottomRef
112
112
  }) => {
113
- if ([mainRef, leftRef, rightRef, actionsRef, footerRef].some(ref => !ref?.current)) {
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 footer = footerRef.current
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 footerInset = main.offsetHeight - footer.offsetTop + gap // mirrors --left/right-offset-bottom CSS var
131
- const baseBottom = Math.max(main.offsetHeight - actions.offsetTop + gap, footerInset)
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, footerInset, gap)
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 FOOTER_TOP = 560
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 FOOTER_INSET = MAIN_HEIGHT - FOOTER_TOP + GAP // 48
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, footerRef
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
- footerRef = { current: { offsetTop: FOOTER_TOP } }
75
+ bottomRef = { current: { offsetTop: BOTTOM_TOP, offsetHeight: 0 } }
76
76
  })
77
77
 
78
- const base = () => ({ mainRef, leftRef, rightRef, actionsRef, footerRef })
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, footerRef })).toBeUndefined()
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, footerRef })).toEqual({
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 footer for base bottom', () => {
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(FOOTER_INSET + ABOVE_THRESHOLD + GAP, CAP_HEIGHT))
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(FOOTER_INSET + PANEL_H_TALL + GAP, CAP_HEIGHT))
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
- forms: path.join(__dirname, 'demo/js/forms.js'),
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
  },
@@ -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
- }