@defra/interactive-map 0.0.14-alpha → 0.0.16-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 (199) hide show
  1. package/assets/css/docusaurus.css +104 -0
  2. package/assets/images/favicon.svg +1 -0
  3. package/assets/images/hero.png +0 -0
  4. package/dist/css/index.css +1 -1
  5. package/dist/esm/im-core.js +1 -1
  6. package/dist/umd/im-core.js +1 -1
  7. package/dist/umd/index.js +1 -1
  8. package/docs/api/slot-map.svg +1 -0
  9. package/docs/api/slots.md +89 -6
  10. package/docs/api.md +1 -1
  11. package/docs/architecture.md +3 -1
  12. package/docs/{demo.mdx → examples.mdx} +1 -1
  13. package/docs/getting-started.md +1 -3
  14. package/docs/index.mdx +42 -0
  15. package/docs/plugins/interact.md +176 -55
  16. package/docs/plugins/map-styles.md +64 -7
  17. package/docs/plugins/search.md +207 -63
  18. package/docs/plugins.md +7 -15
  19. package/docusaurus.config.cjs +34 -34
  20. package/jest.setup.js +1 -1
  21. package/package.json +5 -4
  22. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  23. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  24. package/plugins/beta/datasets/src/DatasetsInit.jsx +1 -1
  25. package/plugins/beta/datasets/src/api/addDataset.js +1 -1
  26. package/plugins/beta/datasets/src/api/hideDataset.js +1 -1
  27. package/plugins/beta/datasets/src/api/hideFeatures.js +1 -1
  28. package/plugins/beta/datasets/src/api/removeDataset.js +1 -1
  29. package/plugins/beta/datasets/src/api/showDataset.js +1 -1
  30. package/plugins/beta/datasets/src/api/showFeatures.js +1 -1
  31. package/plugins/beta/datasets/src/datasets.js +4 -4
  32. package/plugins/beta/datasets/src/defaults.js +1 -1
  33. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +5 -5
  34. package/plugins/beta/datasets/src/handleSetMapStyle.js +1 -1
  35. package/plugins/beta/datasets/src/manifest.js +7 -7
  36. package/plugins/beta/datasets/src/mapLayers.js +2 -3
  37. package/plugins/beta/datasets/src/panels/Key.jsx +31 -29
  38. package/plugins/beta/datasets/src/panels/Layers.jsx +8 -9
  39. package/plugins/beta/datasets/src/utils/bbox.js +4 -4
  40. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  41. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  42. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  43. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  44. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  45. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  46. package/plugins/beta/draw-es/src/events.js +52 -20
  47. package/plugins/beta/draw-es/src/events.test.js +301 -0
  48. package/plugins/beta/draw-es/src/graphic.js +1 -1
  49. package/plugins/beta/draw-es/src/manifest.js +4 -4
  50. package/plugins/beta/draw-es/src/reducer.js +1 -1
  51. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  52. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  53. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  54. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  55. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  56. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  57. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  58. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  59. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  60. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  61. package/plugins/beta/draw-ml/src/events.js +8 -6
  62. package/plugins/beta/draw-ml/src/manifest.js +15 -15
  63. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  64. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  65. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  66. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  67. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  68. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  69. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  70. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  71. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  72. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  73. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  74. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  75. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  76. package/plugins/beta/frame/src/Frame.jsx +4 -4
  77. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  78. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  79. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  80. package/plugins/beta/frame/src/config.js +1 -1
  81. package/plugins/beta/frame/src/manifest.js +3 -3
  82. package/plugins/beta/frame/src/reducer.js +1 -1
  83. package/plugins/beta/frame/src/utils.js +1 -1
  84. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  85. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  86. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  87. package/plugins/beta/map-styles/src/manifest.js +2 -2
  88. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  89. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  90. package/plugins/beta/use-location/src/defaults.js +1 -1
  91. package/plugins/beta/use-location/src/events.js +3 -3
  92. package/plugins/interact/src/InteractInit.jsx +1 -2
  93. package/plugins/interact/src/api/enable.js +8 -5
  94. package/plugins/interact/src/api/enable.test.js +2 -2
  95. package/plugins/interact/src/api/selectFeature.js +4 -4
  96. package/plugins/interact/src/api/unselectFeature.js +5 -5
  97. package/plugins/interact/src/defaults.js +0 -1
  98. package/plugins/interact/src/events.test.js +15 -15
  99. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  100. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  101. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  102. package/plugins/interact/src/manifest.js +2 -2
  103. package/plugins/interact/src/manifest.test.js +3 -4
  104. package/plugins/interact/src/reducer.js +3 -3
  105. package/plugins/interact/src/reducer.test.js +0 -1
  106. package/plugins/interact/src/utils/spatial.js +10 -10
  107. package/plugins/interact/src/utils/spatial.test.js +14 -14
  108. package/plugins/search/dist/css/index.css +1 -1
  109. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  110. package/plugins/search/dist/esm/index.js +1 -1
  111. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  112. package/plugins/search/dist/umd/index.js +1 -1
  113. package/plugins/search/src/Search.jsx +7 -6
  114. package/plugins/search/src/Search.test.jsx +23 -23
  115. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  116. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  117. package/plugins/search/src/components/Form/Form.jsx +14 -14
  118. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  119. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  120. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  121. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  122. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  123. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  124. package/plugins/search/src/datasets.js +12 -13
  125. package/plugins/search/src/datasets.test.js +1 -1
  126. package/plugins/search/src/defaults.js +1 -1
  127. package/plugins/search/src/events/fetchSuggestions.js +4 -4
  128. package/plugins/search/src/events/fetchSuggestions.test.js +5 -5
  129. package/plugins/search/src/events/formHandlers.js +3 -3
  130. package/plugins/search/src/events/formHandlers.test.js +1 -1
  131. package/plugins/search/src/events/index.js +2 -2
  132. package/plugins/search/src/events/index.test.js +2 -2
  133. package/plugins/search/src/events/inputHandlers.js +4 -4
  134. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  135. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  136. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  137. package/plugins/search/src/index.js +2 -1
  138. package/plugins/search/src/index.test.js +3 -3
  139. package/plugins/search/src/manifest.js +6 -4
  140. package/plugins/search/src/reducer.js +1 -2
  141. package/plugins/search/src/reducer.test.js +2 -2
  142. package/plugins/search/src/search.scss +18 -6
  143. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  144. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  145. package/plugins/search/src/utils/updateMap.js +1 -1
  146. package/plugins/search/src/utils/updateMap.test.js +5 -5
  147. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  148. package/providers/beta/esri/src/esriProvider.js +5 -5
  149. package/providers/beta/esri/src/utils/coords.js +1 -1
  150. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  151. package/providers/beta/esri/src/utils/query.js +4 -4
  152. package/providers/beta/esri/src/utils/spatial.js +1 -2
  153. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  154. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  155. package/providers/maplibre/src/appEvents.test.js +1 -1
  156. package/providers/maplibre/src/index.js +1 -1
  157. package/providers/maplibre/src/index.test.js +3 -5
  158. package/providers/maplibre/src/mapEvents.test.js +15 -5
  159. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  160. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  161. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  162. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  163. package/providers/maplibre/src/utils/highlightFeatures.js +2 -2
  164. package/providers/maplibre/src/utils/highlightFeatures.test.js +12 -6
  165. package/providers/maplibre/src/utils/labels.js +19 -20
  166. package/providers/maplibre/src/utils/labels.test.js +15 -13
  167. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  168. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  169. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  170. package/providers/maplibre/src/utils/spatial.js +0 -1
  171. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  172. package/src/App/components/Panel/Panel.module.scss +1 -0
  173. package/src/App/hooks/useLayoutMeasurements.js +1 -10
  174. package/src/App/hooks/useLayoutMeasurements.test.js +2 -5
  175. package/src/App/hooks/useVisibleGeometry.js +7 -13
  176. package/src/App/hooks/useVisibleGeometry.test.js +72 -47
  177. package/src/App/layout/Layout.jsx +0 -3
  178. package/src/App/layout/Layout.test.jsx +0 -1
  179. package/src/App/layout/layout.module.scss +11 -77
  180. package/src/App/registry/pluginRegistry.js +17 -0
  181. package/src/App/registry/pluginRegistry.test.js +33 -0
  182. package/src/App/renderer/HtmlElementHost.jsx +0 -1
  183. package/src/App/renderer/HtmlElementHost.test.jsx +20 -11
  184. package/src/App/renderer/mapButtons.js +3 -2
  185. package/src/App/renderer/mapPanels.test.js +3 -3
  186. package/src/App/renderer/slotHelpers.js +2 -2
  187. package/src/App/renderer/slotHelpers.test.js +3 -3
  188. package/src/App/renderer/slots.js +0 -3
  189. package/src/App/store/AppProvider.jsx +0 -1
  190. package/src/App/store/appDispatchMiddleware.js +33 -1
  191. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  192. package/src/config/appConfig.js +4 -4
  193. package/src/utils/getSafeZoneInset.js +139 -42
  194. package/src/utils/getSafeZoneInset.test.js +298 -122
  195. package/src/utils/logger.js +6 -0
  196. package/src/utils/logger.test.js +32 -0
  197. package/webpack.dev.mjs +22 -18
  198. package/docs/govuk-prototype.md +0 -23
  199. package/docs/index.md +0 -19
@@ -63,4 +63,4 @@ describe('applyPreventDefaultFix', () => {
63
63
  e.preventDefault()
64
64
  expect(spy).toHaveBeenCalled()
65
65
  })
66
- })
66
+ })
@@ -30,7 +30,7 @@ const isPointInPolygon = (point, ring) => {
30
30
  const intersectX = ((xj - xi) * (py - yi)) / (yj - yi) + xi
31
31
 
32
32
  if (px < intersectX) {
33
- inside = !inside;
33
+ inside = !inside
34
34
  }
35
35
  }
36
36
  return inside
@@ -43,7 +43,7 @@ const getMinDistToGeometry = (map, point, geometry) => {
43
43
  const { coordinates: coords, type } = geometry
44
44
  let minSqDist = Infinity
45
45
  const getScreenPt = (lngLat) => map.project(lngLat)
46
-
46
+
47
47
  const processLine = (lineCoords) => {
48
48
  for (let i = 0; i < lineCoords.length - 1; i++) {
49
49
  const d2 = distToSegmentSquared(point, getScreenPt(lineCoords[i]), getScreenPt(lineCoords[i + 1]))
@@ -52,7 +52,7 @@ const getMinDistToGeometry = (map, point, geometry) => {
52
52
  }
53
53
  }
54
54
  }
55
-
55
+
56
56
  if (type === 'Point') {
57
57
  const p = getScreenPt(coords)
58
58
  minSqDist = (point.x - p.x) ** 2 + (point.y - p.y) ** 2
@@ -117,7 +117,7 @@ export const queryFeatures = (map, point, options = {}) => {
117
117
  let score = 0
118
118
  const type = f.geometry.type
119
119
  const pixelDistSq = getMinDistToGeometry(map, point, f.geometry)
120
-
120
+
121
121
  // PRIORITY 1: LAYER ORDER
122
122
  const layerRank = layerStack.indexOf(f.layer.id)
123
123
  score += (layerRank * 1000000)
@@ -126,7 +126,7 @@ export const queryFeatures = (map, point, options = {}) => {
126
126
  if (type.includes('Polygon')) {
127
127
  const polys = type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates
128
128
  const isInside = polys.some((ring) => isPointInPolygon(clickPt, ring[0]))
129
-
129
+
130
130
  if (isInside === true) {
131
131
  // Massive boost for polygons if we are actually inside them
132
132
  score -= 500000 // NOSONAR - tolerance used only here
@@ -143,4 +143,4 @@ export const queryFeatures = (map, point, options = {}) => {
143
143
  })
144
144
  .sort((a, b) => a.score - b.score)
145
145
  .map(({ f }) => f)
146
- }
146
+ }
@@ -15,12 +15,12 @@ describe('queryFeatures coverage', () => {
15
15
  const cases = [
16
16
  { type: 'Point', coords: [0, 0], p: { x: 3, y: 4 } },
17
17
  { type: 'LineString', coords: [[0, 0], [10, 0]], p: { x: 5, y: 5 } }, // t=0.5
18
- { type: 'LineString', coords: [[0, 0], [0, 0]], p: { x: 1, y: 1 } }, // l2=0
18
+ { type: 'LineString', coords: [[0, 0], [0, 0]], p: { x: 1, y: 1 } }, // l2=0
19
19
  { type: 'LineString', coords: [[0, 0], [10, 0]], p: { x: -5, y: 0 } }, // t<0
20
20
  { type: 'MultiPoint', coords: [[0, 0], [10, 10]], p: { x: 1, y: 0 } },
21
21
  { type: 'MultiLineString', coords: [[[0, 0], [10, 0]]], p: { x: 5, y: 1 } },
22
22
  { type: 'Polygon', coords: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], p: { x: 5, y: 5 } }, // Inside
23
- { type: 'MultiPolygon', coords: [[[[0, 0], [10, 0], [10, 10], [0, 0]]]], p: { x: 20, y: 20 } }, // Outside
23
+ { type: 'MultiPolygon', coords: [[[[0, 0], [10, 0], [10, 10], [0, 0]]]], p: { x: 20, y: 20 } }, // Outside
24
24
  { type: 'Unknown', coords: [], p: { x: 0, y: 0 } }
25
25
  ]
26
26
 
@@ -31,21 +31,21 @@ describe('queryFeatures coverage', () => {
31
31
  })
32
32
 
33
33
  // 3. Hits Line 144 (.sort) and property-based ID fallback
34
- const f1 = {
35
- properties: { key: 'a' },
36
- layer: { id: 'layer-A' },
37
- geometry: { type: 'Point', coordinates: [10, 10] }
34
+ const f1 = {
35
+ properties: { key: 'a' },
36
+ layer: { id: 'layer-A' },
37
+ geometry: { type: 'Point', coordinates: [10, 10] }
38
38
  }
39
- const f2 = {
40
- id: 'b',
41
- layer: { id: 'layer-B' },
42
- geometry: { type: 'Point', coordinates: [0, 0] }
39
+ const f2 = {
40
+ id: 'b',
41
+ layer: { id: 'layer-B' },
42
+ geometry: { type: 'Point', coordinates: [0, 0] }
43
43
  }
44
-
44
+
45
45
  // map.queryRenderedFeatures returns multiple items to trigger .sort()
46
46
  const sortMap = { ...mockMap, queryRenderedFeatures: () => [f1, f2] }
47
47
  const result = queryFeatures(sortMap, { x: 0, y: 0 })
48
-
48
+
49
49
  expect(result.length).toBe(2)
50
50
  expect(result[0].layer.id).toBe('layer-A') // Sorted by layerStack index
51
51
 
@@ -57,4 +57,4 @@ describe('queryFeatures coverage', () => {
57
57
  const rayMap = { ...mockMap, queryRenderedFeatures: () => [polyFeat] }
58
58
  expect(queryFeatures(rayMap, { x: -1, y: 5 }).length).toBe(1)
59
59
  })
60
- })
60
+ })
@@ -186,7 +186,6 @@ const getPaddedBounds = (LngLatBounds, map) => {
186
186
  return new LngLatBounds(swLngLat, neLngLat)
187
187
  }
188
188
 
189
-
190
189
  /**
191
190
  * Get a flat bbox [west, south, east, north] from any GeoJSON object
192
191
  * (Feature, FeatureCollection, or geometry).
@@ -10,7 +10,6 @@ jest.mock('geodesy/latlon-spherical.js', () =>
10
10
  jest.mock('@turf/bbox', () => jest.fn(() => [-1, 50, 1, 52]))
11
11
 
12
12
  describe('spatial utils', () => {
13
-
14
13
  test('formatDimension hits all branches', () => {
15
14
  // < 0.5 miles
16
15
  expect(spatial.formatDimension(500)).toMatch(/m$/)
@@ -43,55 +42,55 @@ describe('spatial utils', () => {
43
42
  })
44
43
 
45
44
  test('north/south/east/west moves', () => {
46
- expect(spatial.getCardinalMove([0,0],[0,0.5])).toMatch(/north/)
47
- expect(spatial.getCardinalMove([0,0],[0,-0.5])).toMatch(/south/)
48
- expect(spatial.getCardinalMove([0,0],[0.5,0])).toMatch(/east/)
49
- expect(spatial.getCardinalMove([0,0],[-0.5,0])).toMatch(/west/)
50
- expect(spatial.getCardinalMove([0,0],[0.5,0.5])).toMatch(/north.*east|east.*north/)
51
- expect(spatial.getCardinalMove([0,0],[0.00001,0.00001])).toBe('')
45
+ expect(spatial.getCardinalMove([0, 0], [0, 0.5])).toMatch(/north/)
46
+ expect(spatial.getCardinalMove([0, 0], [0, -0.5])).toMatch(/south/)
47
+ expect(spatial.getCardinalMove([0, 0], [0.5, 0])).toMatch(/east/)
48
+ expect(spatial.getCardinalMove([0, 0], [-0.5, 0])).toMatch(/west/)
49
+ expect(spatial.getCardinalMove([0, 0], [0.5, 0.5])).toMatch(/north.*east|east.*north/)
50
+ expect(spatial.getCardinalMove([0, 0], [0.00001, 0.00001])).toBe('')
52
51
  })
53
52
 
54
53
  test('spatialNavigate all directions and fallback', () => {
55
- const pixels = [[0,0],[0,-1],[1,0],[0,1],[-1,0]]
56
- expect(spatial.spatialNavigate('ArrowUp',[0,0],pixels)).toBe(1)
57
- expect(spatial.spatialNavigate('ArrowDown',[0,0],pixels)).toBe(3)
58
- expect(spatial.spatialNavigate('ArrowLeft',[0,0],pixels)).toBe(4)
59
- expect(spatial.spatialNavigate('ArrowRight',[0,0],pixels)).toBe(2)
60
- expect(spatial.spatialNavigate('InvalidDir',[0,0],pixels)).toBe(0)
54
+ const pixels = [[0, 0], [0, -1], [1, 0], [0, 1], [-1, 0]]
55
+ expect(spatial.spatialNavigate('ArrowUp', [0, 0], pixels)).toBe(1)
56
+ expect(spatial.spatialNavigate('ArrowDown', [0, 0], pixels)).toBe(3)
57
+ expect(spatial.spatialNavigate('ArrowLeft', [0, 0], pixels)).toBe(4)
58
+ expect(spatial.spatialNavigate('ArrowRight', [0, 0], pixels)).toBe(2)
59
+ expect(spatial.spatialNavigate('InvalidDir', [0, 0], pixels)).toBe(0)
61
60
  })
62
61
 
63
62
  test('spatialNavigate finds closer candidates (hits dist < minDist)', () => {
64
- const start = [0,0]
65
- const pixels = [[0,0],[10,0],[2,0]]
63
+ const start = [0, 0]
64
+ const pixels = [[0, 0], [10, 0], [2, 0]]
66
65
  expect(spatial.spatialNavigate('ArrowRight', start, pixels)).toBe(2)
67
66
  })
68
67
 
69
68
  test('spatialNavigate skips farther candidate (dist >= minDist false branch)', () => {
70
69
  // Closer candidate first → second candidate fails dist < minDist
71
- const pixels = [[0,0],[2,0],[10,0]]
72
- expect(spatial.spatialNavigate('ArrowRight', [0,0], pixels)).toBe(1)
70
+ const pixels = [[0, 0], [2, 0], [10, 0]]
71
+ expect(spatial.spatialNavigate('ArrowRight', [0, 0], pixels)).toBe(1)
73
72
  })
74
73
 
75
74
  test('spatialNavigate diagonal with dx>dy', () => {
76
- const start = [0,0]
77
- const pixels = [[0,0],[3,1],[1,0]] // dx>dy
75
+ const start = [0, 0]
76
+ const pixels = [[0, 0], [3, 1], [1, 0]] // dx>dy
78
77
  expect(spatial.spatialNavigate('ArrowRight', start, pixels)).toBe(2)
79
78
  })
80
79
 
81
80
  test('getResolution returns positive value', () => {
82
- expect(spatial.getResolution({lat:0},1)).toBeGreaterThan(0)
81
+ expect(spatial.getResolution({ lat: 0 }, 1)).toBeGreaterThan(0)
83
82
  })
84
83
 
85
84
  test('getPaddedBounds returns bounds', () => {
86
85
  const map = {
87
- getContainer: () => ({ getBoundingClientRect: () => ({ width:100,height:200 }) }),
88
- getPadding: () => ({ top:1,right:2,bottom:3,left:4 }),
89
- unproject: p => ({ x:p[0], y:p[1] })
86
+ getContainer: () => ({ getBoundingClientRect: () => ({ width: 100, height: 200 }) }),
87
+ getPadding: () => ({ top: 1, right: 2, bottom: 3, left: 4 }),
88
+ unproject: p => ({ x: p[0], y: p[1] })
90
89
  }
91
- const LngLatBounds = function(sw,ne){
92
- return {sw,ne}
90
+ const LngLatBounds = function (sw, ne) {
91
+ return { sw, ne }
93
92
  }
94
- const bounds = spatial.getPaddedBounds(LngLatBounds,map)
93
+ const bounds = spatial.getPaddedBounds(LngLatBounds, map)
95
94
  expect(bounds.sw).toBeDefined()
96
95
  expect(bounds.ne).toBeDefined()
97
96
  })
@@ -140,4 +139,4 @@ describe('spatial utils', () => {
140
139
  expect(map.project).toHaveBeenCalledTimes(4)
141
140
  })
142
141
  })
143
- })
142
+ })
@@ -14,6 +14,7 @@
14
14
  display: flex;
15
15
  flex-direction: column;
16
16
  max-height: 100vh;
17
+ min-height: 0;
17
18
  min-width: var(--min-panel-width);
18
19
  pointer-events: auto;
19
20
  background-color: var(--background-color);
@@ -12,9 +12,6 @@ const topColWidth = (left, right) =>
12
12
  const subSlotMaxHeight = (columnHeight, siblingButtons, gap) =>
13
13
  columnHeight - (siblingButtons ? siblingButtons + gap : 0)
14
14
 
15
- const calcOffsetLeft = (bottomOffsetTop, gap, insetBottom, inset) =>
16
- bottomOffsetTop - gap > insetBottom ? 0 : inset.offsetLeft + inset.offsetWidth
17
-
18
15
  export function useLayoutMeasurements () {
19
16
  const { dispatch, breakpoint, layoutRefs } = useApp()
20
17
  const { mapSize, isMapReady } = useMap()
@@ -26,7 +23,6 @@ export function useLayoutMeasurements () {
26
23
  topRef,
27
24
  topLeftColRef,
28
25
  topRightColRef,
29
- insetRef,
30
26
  footerRef,
31
27
  actionsRef,
32
28
  leftTopRef,
@@ -44,11 +40,9 @@ export function useLayoutMeasurements () {
44
40
  const top = topRef.current
45
41
  const topLeftCol = topLeftColRef.current
46
42
  const topRightCol = topRightColRef.current
47
- const inset = insetRef.current
48
43
  const bottom = footerRef.current
49
- const actions = actionsRef.current
50
44
 
51
- if ([main, top, inset, bottom].some(r => !r)) {
45
+ if ([main, top, bottom].some(r => !r)) {
52
46
  return
53
47
  }
54
48
 
@@ -77,9 +71,6 @@ export function useLayoutMeasurements () {
77
71
  appContainer.style.setProperty('--left-bottom-panel-max-height', `${subSlotMaxHeight(leftColumnHeight, buttonHeight(leftTopRef), dividerGap)}px`)
78
72
  appContainer.style.setProperty('--right-top-panel-max-height', `${subSlotMaxHeight(rightColumnHeight, buttonHeight(rightBottomRef), dividerGap)}px`)
79
73
  appContainer.style.setProperty('--right-bottom-panel-max-height', `${subSlotMaxHeight(rightColumnHeight, buttonHeight(rightTopRef), dividerGap)}px`)
80
-
81
- // === Bottom left offset ===
82
- appContainer.style.setProperty('--offset-left', `${calcOffsetLeft(Math.min(bottom.offsetTop, actions.offsetTop), dividerGap, inset.offsetHeight + leftOffsetTop, inset)}px`)
83
74
  }
84
75
 
85
76
  // --------------------------------
@@ -24,7 +24,6 @@ const refs = (o = {}) => ({
24
24
  topRef: { current: o.top === null ? null : el({ offsetTop: 10, ...o.top }) },
25
25
  topLeftColRef: { current: el({ offsetHeight: 50, offsetWidth: 200, ...o.topLeftCol }) },
26
26
  topRightColRef: { current: el({ offsetHeight: 40, offsetWidth: 180, ...o.topRightCol }) },
27
- insetRef: { current: o.inset === null ? null : el({ offsetHeight: 100, offsetLeft: 20, offsetWidth: 300, ...o.inset }) },
28
27
  footerRef: { current: o.footer === null ? null : el({ offsetTop: 400, ...o.footer }) },
29
28
  actionsRef: { current: el({ offsetTop: 450, ...o.actions }) },
30
29
  leftTopRef: { current: el({ offsetHeight: 0, ...o.leftTop }) },
@@ -57,7 +56,7 @@ describe('useLayoutMeasurements', () => {
57
56
  })
58
57
 
59
58
  test('early return when required refs are null', () => {
60
- const { layoutRefs } = setup({ refs: { main: null, top: null, inset: null, footer: null } })
59
+ const { layoutRefs } = setup({ refs: { main: null, top: null, footer: null } })
61
60
  renderHook(() => useLayoutMeasurements())
62
61
  expect(layoutRefs.appContainerRef.current.style.setProperty).not.toHaveBeenCalled()
63
62
  })
@@ -66,13 +65,11 @@ describe('useLayoutMeasurements', () => {
66
65
  const { layoutRefs } = setup()
67
66
  renderHook(() => useLayoutMeasurements())
68
67
  const spy = layoutRefs.appContainerRef.current.style.setProperty
69
- ;['--offset-left', '--right-offset-top', '--right-offset-bottom', '--top-col-width']
68
+ ;['--right-offset-top', '--right-offset-bottom', '--top-col-width']
70
69
  .forEach(prop => expect(spy).toHaveBeenCalledWith(prop, expect.any(String)))
71
70
  })
72
71
 
73
72
  test.each([
74
- ['offset-left with overlap', { inset: { offsetHeight: 200, offsetLeft: 30, offsetWidth: 150 }, footer: { offsetTop: 100 }, actions: { offsetTop: 120 }, topLeftCol: { offsetHeight: 50 }, top: { offsetTop: 10 } }, '180px'],
75
- ['offset-left without overlap', { inset: { offsetHeight: 50, offsetLeft: 30, offsetWidth: 150 }, footer: { offsetTop: 200 }, actions: { offsetTop: 220 }, topLeftCol: { offsetHeight: 50 }, top: { offsetTop: 10 } }, '0px'],
76
73
  ['right-offset-top', { topRightCol: { offsetHeight: 80 }, top: { offsetTop: 15 } }, '95px'],
77
74
  ['right-offset-bottom', { main: { offsetHeight: 600 }, footer: { offsetTop: 500 } }, '108px'],
78
75
  // leftColumnHeight = 400 - (50+10) - 8 = 332; rightColumnHeight = 400 - (40+10) - 8 = 342
@@ -31,14 +31,8 @@ export const getPointCoordinates = (geojson) => {
31
31
  return null
32
32
  }
33
33
 
34
- const SLOT_REFS = {
35
- inset: 'insetRef',
36
- bottom: 'bottomRef',
37
- side: 'sideRef'
38
- }
39
-
40
34
  export const useVisibleGeometry = () => {
41
- const { mapProvider, eventBus } = useConfig()
35
+ const { id, mapProvider, eventBus } = useConfig()
42
36
  const { layoutRefs, panelConfig, panelRegistry, breakpoint } = useApp()
43
37
 
44
38
  const latestRef = useRef({ layoutRefs, panelConfig, panelRegistry, breakpoint })
@@ -49,14 +43,13 @@ export const useVisibleGeometry = () => {
49
43
  return undefined
50
44
  }
51
45
 
52
- const handlePanelOpened = ({ panelId, slot: eventSlot, visibleGeometry: eventVisibleGeometry }) => {
53
- const { panelConfig: config, panelRegistry: registry, layoutRefs: refs, breakpoint: bp } = latestRef.current
46
+ const handlePanelOpened = ({ panelId, visibleGeometry: eventVisibleGeometry }) => {
47
+ const { panelConfig: config, panelRegistry: registry } = latestRef.current
54
48
  const resolvedConfig = config?.[panelId] ? config : (registry?.getPanelConfig() ?? config)
55
49
  const visibleGeometry = eventVisibleGeometry ?? resolvedConfig?.[panelId]?.visibleGeometry
56
- const slot = eventSlot ?? resolvedConfig?.[panelId]?.[bp]?.slot
57
- const slotRef = refs[SLOT_REFS[slot]]
50
+ const panel = layoutRefs.appContainerRef.current?.querySelector(`#${id}-panel-${panelId}`)
58
51
 
59
- if (!visibleGeometry || !slotRef) {
52
+ if (!visibleGeometry) {
60
53
  return
61
54
  }
62
55
  if (typeof mapProvider.isGeometryObscured !== 'function') {
@@ -64,7 +57,8 @@ export const useVisibleGeometry = () => {
64
57
  }
65
58
 
66
59
  const waitForPanel = () => {
67
- const panelRect = slotRef.current?.getBoundingClientRect()
60
+ if (!panel) { return }
61
+ const panelRect = panel.getBoundingClientRect()
68
62
 
69
63
  if (!panelRect || panelRect.width === 0 || panelRect.height === 0) {
70
64
  // Not ready yet, check on the next animation frame
@@ -10,8 +10,16 @@ const pointFeature = { type: 'Feature', geometry: { type: 'Point', coordinates:
10
10
  const multiPointFeature = { type: 'Feature', geometry: { type: 'MultiPoint', coordinates: [[1, 51], [2, 52]] }, properties: {} }
11
11
  const polygonFeature = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 0]]] }, properties: {} }
12
12
 
13
- const insetPanelRect = { left: 600, top: 0, right: 1000, bottom: 800, width: 400, height: 800 }
14
- const bottomPanelRect = { left: 0, top: 500, right: 1000, bottom: 800, width: 1000, height: 300 }
13
+ const APP_ID = 'test'
14
+ const panelRect = { left: 600, top: 0, right: 1000, bottom: 800, width: 400, height: 800 }
15
+
16
+ // Creates a panel DOM element with id matching what useVisibleGeometry queries.
17
+ const makePanelEl = (panelId, rect = panelRect) => {
18
+ const el = document.createElement('div')
19
+ el.id = `${APP_ID}-panel-${panelId}`
20
+ el.getBoundingClientRect = jest.fn(() => rect)
21
+ return el
22
+ }
15
23
 
16
24
  const setup = (overrides = {}) => {
17
25
  const capturedHandlers = {}
@@ -27,19 +35,18 @@ const setup = (overrides = {}) => {
27
35
  ...overrides.eventBus
28
36
  }
29
37
 
30
- const insetEl = document.createElement('div')
31
- insetEl.getBoundingClientRect = jest.fn(() => insetPanelRect)
32
- const bottomEl = document.createElement('div')
33
- bottomEl.getBoundingClientRect = jest.fn(() => bottomPanelRect)
38
+ // appContainerRef holds panel elements that the hook queries by id
39
+ const appContainer = document.createElement('div')
40
+ const myPanelEl = makePanelEl('myPanel')
41
+ appContainer.appendChild(myPanelEl)
34
42
 
35
43
  const layoutRefs = {
36
44
  mainRef: { current: document.createElement('div') },
37
- insetRef: { current: insetEl },
38
- bottomRef: { current: bottomEl },
45
+ appContainerRef: { current: appContainer },
39
46
  ...overrides.layoutRefs
40
47
  }
41
48
  const panelConfig = {
42
- myPanel: { visibleGeometry: polygonFeature, desktop: { slot: 'inset' } },
49
+ myPanel: { visibleGeometry: polygonFeature, desktop: { slot: 'left-top' } },
43
50
  emptyPanel: {},
44
51
  ...overrides.panelConfig
45
52
  }
@@ -48,10 +55,10 @@ const setup = (overrides = {}) => {
48
55
  ...overrides.panelRegistry
49
56
  }
50
57
 
51
- useConfig.mockReturnValue({ mapProvider, eventBus, ...overrides.config })
58
+ useConfig.mockReturnValue({ id: APP_ID, mapProvider, eventBus, ...overrides.config })
52
59
  useApp.mockReturnValue({ layoutRefs, panelConfig, panelRegistry, breakpoint: 'desktop', ...overrides.app })
53
60
 
54
- return { mapProvider, eventBus, capturedHandlers, layoutRefs, panelConfig, insetEl, bottomEl }
61
+ return { mapProvider, eventBus, capturedHandlers, layoutRefs, panelConfig, myPanelEl, appContainer }
55
62
  }
56
63
 
57
64
  describe('useVisibleGeometry', () => {
@@ -102,12 +109,14 @@ describe('useVisibleGeometry', () => {
102
109
  expect(mapProvider.isGeometryObscured).not.toHaveBeenCalled()
103
110
  })
104
111
 
105
- test('does nothing when panel has visibleGeometry but no slot config', () => {
112
+ test('does nothing when panel element is not in the DOM', () => {
113
+ // Panel has visibleGeometry and slot config but its DOM element is not mounted yet
106
114
  const { mapProvider, capturedHandlers } = setup({
107
- panelConfig: { noSlotPanel: { visibleGeometry: polygonFeature } }
115
+ panelConfig: { noElPanel: { visibleGeometry: polygonFeature, desktop: { slot: 'left-top' } } }
108
116
  })
109
117
  renderHook(() => useVisibleGeometry())
110
- capturedHandlers['app:panelopened']({ panelId: 'noSlotPanel' })
118
+ capturedHandlers['app:panelopened']({ panelId: 'noElPanel' })
119
+ jest.runAllTimers()
111
120
  expect(mapProvider.isGeometryObscured).not.toHaveBeenCalled()
112
121
  })
113
122
 
@@ -118,19 +127,20 @@ describe('useVisibleGeometry', () => {
118
127
  expect(mapProvider.fitToBounds).not.toHaveBeenCalled()
119
128
  })
120
129
 
121
- test('does nothing when slot ref has zero dimensions (panel not visible)', () => {
122
- const zeroEl = document.createElement('div')
123
- zeroEl.getBoundingClientRect = jest.fn(() => ({ left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }))
130
+ test('does nothing when panel element has zero dimensions (panel not yet visible)', () => {
131
+ const zeroRect = { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }
132
+ const appContainer = document.createElement('div')
133
+ appContainer.appendChild(makePanelEl('myPanel', zeroRect))
124
134
  const { mapProvider, capturedHandlers } = setup({
125
135
  layoutRefs: {
126
136
  mainRef: { current: document.createElement('div') },
127
- insetRef: { current: zeroEl }
137
+ appContainerRef: { current: appContainer }
128
138
  }
129
139
  })
130
140
  renderHook(() => useVisibleGeometry())
131
141
  capturedHandlers['app:panelopened']({ panelId: 'myPanel' })
132
142
 
133
- // Run the current pending animation frame
143
+ // Run only the first pending animation frame — panel has zero size so it reschedules
134
144
  jest.runOnlyPendingTimers()
135
145
 
136
146
  expect(mapProvider.isGeometryObscured).not.toHaveBeenCalled()
@@ -151,14 +161,17 @@ describe('useVisibleGeometry', () => {
151
161
  capturedHandlers['app:panelopened']({ panelId: 'myPanel' })
152
162
  jest.runAllTimers()
153
163
 
154
- expect(mapProvider.isGeometryObscured).toHaveBeenCalledWith(polygonFeature, insetPanelRect)
164
+ expect(mapProvider.isGeometryObscured).toHaveBeenCalledWith(polygonFeature, panelRect)
155
165
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(polygonFeature)
156
166
  expect(mapProvider.setView).not.toHaveBeenCalled()
157
167
  })
158
168
 
159
169
  test('calls setView with center for Point geometry when obscured', () => {
170
+ const appContainer = document.createElement('div')
171
+ appContainer.appendChild(makePanelEl('pointPanel'))
160
172
  const { mapProvider, capturedHandlers } = setup({
161
- panelConfig: { pointPanel: { visibleGeometry: pointFeature, desktop: { slot: 'inset' } } }
173
+ panelConfig: { pointPanel: { visibleGeometry: pointFeature, desktop: { slot: 'left-top' } } },
174
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
162
175
  })
163
176
  renderHook(() => useVisibleGeometry())
164
177
  capturedHandlers['app:panelopened']({ panelId: 'pointPanel' })
@@ -169,8 +182,11 @@ describe('useVisibleGeometry', () => {
169
182
  })
170
183
 
171
184
  test('calls setView with first coordinate for MultiPoint geometry when obscured', () => {
185
+ const appContainer = document.createElement('div')
186
+ appContainer.appendChild(makePanelEl('mpPanel'))
172
187
  const { mapProvider, capturedHandlers } = setup({
173
- panelConfig: { mpPanel: { visibleGeometry: multiPointFeature, desktop: { slot: 'inset' } } }
188
+ panelConfig: { mpPanel: { visibleGeometry: multiPointFeature, desktop: { slot: 'left-top' } } },
189
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
174
190
  })
175
191
  renderHook(() => useVisibleGeometry())
176
192
  capturedHandlers['app:panelopened']({ panelId: 'mpPanel' })
@@ -182,8 +198,11 @@ describe('useVisibleGeometry', () => {
182
198
 
183
199
  test('calls fitToBounds for a raw non-Feature geometry (e.g. Polygon) when obscured', () => {
184
200
  const rawPolygon = { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 0]]] }
201
+ const appContainer = document.createElement('div')
202
+ appContainer.appendChild(makePanelEl('geoPanel'))
185
203
  const { mapProvider, capturedHandlers } = setup({
186
- panelConfig: { geoPanel: { visibleGeometry: rawPolygon, desktop: { slot: 'inset' } } }
204
+ panelConfig: { geoPanel: { visibleGeometry: rawPolygon, desktop: { slot: 'left-top' } } },
205
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
187
206
  })
188
207
  renderHook(() => useVisibleGeometry())
189
208
  capturedHandlers['app:panelopened']({ panelId: 'geoPanel' })
@@ -194,8 +213,11 @@ describe('useVisibleGeometry', () => {
194
213
 
195
214
  test('calls setView for a raw Point geometry (not Feature-wrapped) when obscured', () => {
196
215
  const rawPoint = { type: 'Point', coordinates: [1, 51] }
216
+ const appContainer = document.createElement('div')
217
+ appContainer.appendChild(makePanelEl('rawPointPanel'))
197
218
  const { mapProvider, capturedHandlers } = setup({
198
- panelConfig: { rawPointPanel: { visibleGeometry: rawPoint, desktop: { slot: 'inset' } } }
219
+ panelConfig: { rawPointPanel: { visibleGeometry: rawPoint, desktop: { slot: 'left-top' } } },
220
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
199
221
  })
200
222
  renderHook(() => useVisibleGeometry())
201
223
  capturedHandlers['app:panelopened']({ panelId: 'rawPointPanel' })
@@ -206,8 +228,11 @@ describe('useVisibleGeometry', () => {
206
228
 
207
229
  test('does not call setView when Point feature has null coordinates', () => {
208
230
  const nullCoordsFeature = { type: 'Feature', geometry: { type: 'Point', coordinates: null }, properties: {} }
231
+ const appContainer = document.createElement('div')
232
+ appContainer.appendChild(makePanelEl('nullPanel'))
209
233
  const { mapProvider, capturedHandlers } = setup({
210
- panelConfig: { nullPanel: { visibleGeometry: nullCoordsFeature, desktop: { slot: 'inset' } } }
234
+ panelConfig: { nullPanel: { visibleGeometry: nullCoordsFeature, desktop: { slot: 'left-top' } } },
235
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
211
236
  })
212
237
  renderHook(() => useVisibleGeometry())
213
238
  capturedHandlers['app:panelopened']({ panelId: 'nullPanel' })
@@ -216,26 +241,14 @@ describe('useVisibleGeometry', () => {
216
241
  expect(mapProvider.fitToBounds).not.toHaveBeenCalled()
217
242
  })
218
243
 
219
- test('uses bottom slot ref when panel is in bottom slot', () => {
220
- const { mapProvider, capturedHandlers } = setup({
221
- panelConfig: { bottomPanel: { visibleGeometry: polygonFeature, desktop: { slot: 'bottom' } } }
222
- })
223
- renderHook(() => useVisibleGeometry())
224
- capturedHandlers['app:panelopened']({ panelId: 'bottomPanel' })
225
- jest.runAllTimers()
226
-
227
- expect(mapProvider.isGeometryObscured).toHaveBeenCalledWith(polygonFeature, bottomPanelRect)
228
- expect(mapProvider.fitToBounds).toHaveBeenCalledWith(polygonFeature)
229
- })
230
-
231
244
  test('uses latest panelConfig via ref when it changes between renders', () => {
232
- const { mapProvider, capturedHandlers, insetEl } = setup()
245
+ const { mapProvider, capturedHandlers, appContainer } = setup()
233
246
  const { rerender } = renderHook(() => useVisibleGeometry())
234
247
 
235
248
  const updatedGeometry = { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
236
- const updatedPanelConfig = { myPanel: { visibleGeometry: updatedGeometry, desktop: { slot: 'inset' } } }
249
+ const updatedPanelConfig = { myPanel: { visibleGeometry: updatedGeometry, desktop: { slot: 'left-top' } } }
237
250
  useApp.mockReturnValue({
238
- layoutRefs: { mainRef: { current: document.createElement('div') }, insetRef: { current: insetEl } },
251
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } },
239
252
  panelConfig: updatedPanelConfig,
240
253
  panelRegistry: { getPanelConfig: jest.fn(() => updatedPanelConfig) },
241
254
  breakpoint: 'desktop'
@@ -249,20 +262,26 @@ describe('useVisibleGeometry', () => {
249
262
 
250
263
  test('uses slot from event payload when registry config lacks slot info', () => {
251
264
  const freshGeometry = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 0]]] }, properties: {} }
265
+ const appContainer = document.createElement('div')
266
+ appContainer.appendChild(makePanelEl('freshPanel'))
252
267
  const { mapProvider, capturedHandlers } = setup({
253
- panelRegistry: { getPanelConfig: jest.fn(() => ({ freshPanel: { visibleGeometry: freshGeometry } })) }
268
+ panelRegistry: { getPanelConfig: jest.fn(() => ({ freshPanel: { visibleGeometry: freshGeometry } })) },
269
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
254
270
  })
255
271
  renderHook(() => useVisibleGeometry())
256
272
  // Event includes slot (as middleware provides for ADD_PANEL); registry config has no slot info
257
- capturedHandlers['app:panelopened']({ panelId: 'freshPanel', slot: 'inset' })
273
+ capturedHandlers['app:panelopened']({ panelId: 'freshPanel', slot: 'left-top' })
258
274
  jest.runAllTimers()
259
275
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(freshGeometry)
260
276
  })
261
277
 
262
278
  test('falls back to panelRegistry for panels not yet in stale panelConfig', () => {
263
279
  const freshGeometry = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 0]]] }, properties: {} }
280
+ const appContainer = document.createElement('div')
281
+ appContainer.appendChild(makePanelEl('freshPanel'))
264
282
  const { mapProvider, capturedHandlers } = setup({
265
- panelRegistry: { getPanelConfig: jest.fn(() => ({ freshPanel: { visibleGeometry: freshGeometry, desktop: { slot: 'inset' } } })) }
283
+ panelRegistry: { getPanelConfig: jest.fn(() => ({ freshPanel: { visibleGeometry: freshGeometry, desktop: { slot: 'left-top' } } })) },
284
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
266
285
  })
267
286
  renderHook(() => useVisibleGeometry())
268
287
  capturedHandlers['app:panelopened']({ panelId: 'freshPanel' })
@@ -271,13 +290,16 @@ describe('useVisibleGeometry', () => {
271
290
  })
272
291
 
273
292
  test('falls back to config when panel not in panelConfig and registry returns null', () => {
293
+ const appContainer = document.createElement('div')
294
+ appContainer.appendChild(makePanelEl('missingPanel'))
274
295
  const { mapProvider, capturedHandlers } = setup({
275
296
  panelConfig: {}, // panel not present
276
- panelRegistry: { getPanelConfig: jest.fn(() => null) } // registry returns null
297
+ panelRegistry: { getPanelConfig: jest.fn(() => null) }, // registry returns null
298
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
277
299
  })
278
300
 
279
301
  renderHook(() => useVisibleGeometry())
280
- capturedHandlers['app:panelopened']({ panelId: 'missingPanel', visibleGeometry: polygonFeature, slot: 'inset' })
302
+ capturedHandlers['app:panelopened']({ panelId: 'missingPanel', visibleGeometry: polygonFeature, slot: 'left-top' })
281
303
  jest.runAllTimers()
282
304
  // Should still call fitToBounds using visibleGeometry from event payload
283
305
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(polygonFeature)
@@ -285,11 +307,14 @@ describe('useVisibleGeometry', () => {
285
307
 
286
308
  test('uses visibleGeometry from event payload directly, bypassing registry (ADD_PANEL first-click case)', () => {
287
309
  // Registry is empty — simulates first ADD_PANEL before React has processed the reducer
310
+ const appContainer = document.createElement('div')
311
+ appContainer.appendChild(makePanelEl('newPanel'))
288
312
  const { mapProvider, capturedHandlers } = setup({
289
- panelRegistry: { getPanelConfig: jest.fn(() => ({})) }
313
+ panelRegistry: { getPanelConfig: jest.fn(() => ({})) },
314
+ layoutRefs: { mainRef: { current: document.createElement('div') }, appContainerRef: { current: appContainer } }
290
315
  })
291
316
  renderHook(() => useVisibleGeometry())
292
- capturedHandlers['app:panelopened']({ panelId: 'newPanel', slot: 'inset', visibleGeometry: polygonFeature })
317
+ capturedHandlers['app:panelopened']({ panelId: 'newPanel', slot: 'left-top', visibleGeometry: polygonFeature })
293
318
  jest.runAllTimers()
294
319
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(polygonFeature)
295
320
  })
@@ -63,9 +63,6 @@ export const Layout = () => {
63
63
  <SlotRenderer slot={layoutSlots.TOP_RIGHT} />
64
64
  </div>
65
65
  </div>
66
- <div className='im-o-app__inset' ref={layoutRefs.insetRef}>
67
- <SlotRenderer slot={layoutSlots.INSET} />
68
- </div>
69
66
  <div className='im-o-app__left' ref={layoutRefs.leftRef}>
70
67
  <div className='im-o-app__left-top' ref={layoutRefs.leftTopRef}>
71
68
  <SlotRenderer slot={layoutSlots.LEFT_TOP} />
@@ -37,7 +37,6 @@ describe('Layout', () => {
37
37
  topRef: React.createRef(),
38
38
  topLeftColRef: React.createRef(),
39
39
  topRightColRef: React.createRef(),
40
- insetRef: React.createRef(),
41
40
  rightRef: React.createRef(),
42
41
  footerRef: React.createRef(),
43
42
  actionsRef: React.createRef()