@defra/interactive-map 0.0.31-alpha → 0.0.33-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 (131) hide show
  1. package/README.md +2 -2
  2. package/assets/images/bright-ofm-thumb.jpg +0 -0
  3. package/assets/images/dark-ofm-thumb.jpg +0 -0
  4. package/assets/images/road-ofm-thumb.jpg +0 -0
  5. package/assets/templates/add-polygons.njk +143 -0
  6. package/assets/templates/add-symbols.njk +119 -0
  7. package/assets/templates/{map.njk → basic-map.njk} +9 -17
  8. package/assets/templates/button-map.njk +56 -0
  9. package/assets/templates/draw-tools.njk +161 -0
  10. package/assets/templates/marker-panel.njk +91 -0
  11. package/assets/templates/place-marker.njk +104 -0
  12. package/assets/templates/search-control.njk +98 -0
  13. package/assets/templates/select-feature.njk +101 -0
  14. package/assets/templates/style-switcher.njk +87 -0
  15. package/assets/templates/toggle-marker-label.njk +74 -0
  16. package/dist/css/index.css +1 -1
  17. package/dist/esm/im-core.js +1 -1
  18. package/dist/esm/im-shell.js +1 -1
  19. package/dist/umd/im-core.js +1 -1
  20. package/dist/umd/index.js +1 -1
  21. package/docs/api/map-style-config.md +27 -3
  22. package/docs/api.md +55 -0
  23. package/docs/assets/css/docusaurus.scss +22 -6
  24. package/docs/assets/images/add-polygons.jpg +0 -0
  25. package/docs/assets/images/add-symbols.jpg +0 -0
  26. package/docs/assets/images/hero.png +0 -0
  27. package/docs/assets/images/place-marker.jpg +0 -0
  28. package/docs/assets/images/screenshot.jpg +0 -0
  29. package/docs/examples/add-marker-with-panel.mdx +1 -1
  30. package/docs/examples/add-polygons.mdx +225 -0
  31. package/docs/examples/add-symbols.mdx +177 -0
  32. package/docs/examples/{draw.mdx → draw-tools.mdx} +2 -2
  33. package/docs/examples/index.mdx +38 -2
  34. package/docs/examples/place-marker.mdx +151 -0
  35. package/docs/plugins/datasets.md +144 -96
  36. package/docs/plugins/interact.md +1 -4
  37. package/docusaurus.config.cjs +5 -2
  38. package/govuk-prototype-kit.config.json +52 -2
  39. package/package.json +1 -1
  40. package/plugins/beta/datasets/dist/css/index.css +1 -2
  41. package/plugins/beta/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  42. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  43. package/plugins/beta/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  44. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  45. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  46. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +27 -9
  47. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.test.js +46 -4
  48. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +10 -1
  49. package/plugins/beta/datasets/src/api/setFeatureVisibility.test.js +48 -6
  50. package/plugins/beta/datasets/src/components/LayersMenu/Layers.module.scss +12 -12
  51. package/plugins/beta/datasets/src/reducers/datasetsToMenu.js +11 -6
  52. package/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +49 -0
  53. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.js +8 -1
  54. package/plugins/beta/datasets/src/reducers/pluginState.js +1 -1
  55. package/plugins/beta/datasets/src/registry/dataset.js +7 -3
  56. package/plugins/beta/datasets/src/registry/dataset.test.js +14 -0
  57. package/plugins/beta/datasets/src/registry/dynamicGeoJson.js +1 -1
  58. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  59. package/plugins/beta/draw-es/src/api/addFeature.js +12 -3
  60. package/plugins/beta/draw-es/src/events.js +8 -3
  61. package/plugins/beta/draw-es/src/events.test.js +2 -0
  62. package/plugins/beta/draw-es/src/graphic.js +26 -2
  63. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  64. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  65. package/plugins/beta/draw-ml/src/DrawInit.jsx +19 -0
  66. package/plugins/beta/draw-ml/src/api/newLine.js +1 -0
  67. package/plugins/beta/draw-ml/src/api/newPolygon.js +1 -0
  68. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +52 -37
  69. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +1 -1
  70. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +5 -4
  71. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +53 -21
  72. package/plugins/beta/draw-ol/dist/esm/DrawMode.js +1 -1
  73. package/plugins/beta/draw-ol/dist/esm/EditMode.js +1 -1
  74. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  75. package/plugins/beta/draw-ol/src/core/OLDrawManager.js +3 -0
  76. package/plugins/beta/draw-ol/src/draw/DrawMode.js +57 -49
  77. package/plugins/beta/draw-ol/src/draw/drawInput.js +76 -32
  78. package/plugins/beta/draw-ol/src/edit/EditMode.js +4 -4
  79. package/plugins/beta/draw-ol/src/edit/keyboardHandler.js +4 -2
  80. package/plugins/beta/draw-ol/src/manifest.js +5 -1
  81. package/plugins/beta/draw-ol/src/reducer.js +1 -1
  82. package/plugins/beta/draw-ol/src/snap/snapEngine.js +75 -31
  83. package/plugins/beta/draw-ol/src/snap/snapGeometry.js +23 -46
  84. package/plugins/beta/draw-ol/src/snap/snapInteraction.js +30 -29
  85. package/plugins/beta/draw-ol/src/snap/snapManager.js +10 -1
  86. package/plugins/beta/draw-ol/src/utils/geometryHelpers.js +9 -3
  87. package/plugins/beta/draw-ol/src/utils/olCoords.js +2 -2
  88. package/plugins/beta/draw-ol/src/utils/spatial.js +4 -8
  89. package/plugins/beta/map-styles/src/mapStyles.scss +41 -41
  90. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  91. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  92. package/plugins/interact/dist/umd/index.js +1 -1
  93. package/plugins/interact/src/InteractInit.jsx +1 -3
  94. package/plugins/interact/src/InteractInit.test.js +0 -2
  95. package/plugins/interact/src/events.test.js +1 -1
  96. package/plugins/interact/src/hooks/useCrossHairVisibility.js +4 -10
  97. package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +5 -10
  98. package/plugins/interact/src/hooks/useHighlightSync.js +1 -10
  99. package/plugins/interact/src/hooks/useHighlightSync.test.js +2 -21
  100. package/plugins/interact/src/hooks/useInteractionHandlers.js +5 -11
  101. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -25
  102. package/plugins/interact/src/reducer.js +4 -22
  103. package/plugins/interact/src/reducer.test.js +11 -39
  104. package/plugins/search/dist/css/index.css +5 -2
  105. package/plugins/search/src/components/Form/Form.module.scss +5 -2
  106. package/plugins/search/src/search.scss +4 -4
  107. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  108. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  109. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  110. package/providers/beta/openlayers/src/utils/tileLayers.js +29 -2
  111. package/providers/beta/openlayers/src/utils/tileLayers.test.js +53 -1
  112. package/src/App/components/CrossHair/CrossHair.module.scss +8 -8
  113. package/src/App/components/MapButton/MapButton.jsx +14 -10
  114. package/src/App/components/MapButton/MapButton.module.scss +1 -1
  115. package/src/App/components/MapButton/MapButton.test.jsx +1 -1
  116. package/src/App/components/PopupMenu/PopupMenu.test.jsx +79 -0
  117. package/src/App/components/PopupMenu/usePopupMenu.js +61 -12
  118. package/src/App/hooks/useLayoutMeasurements.js +10 -2
  119. package/src/App/hooks/useResizeObserver.js +4 -2
  120. package/src/InteractiveMap/InteractiveMap.js +4 -4
  121. package/src/InteractiveMap/InteractiveMap.test.js +3 -3
  122. package/src/InteractiveMap/behaviourController.js +3 -1
  123. package/src/InteractiveMap/behaviourController.test.js +6 -3
  124. package/src/InteractiveMap/domStateManager.js +2 -0
  125. package/src/config/events.js +21 -3
  126. package/src/types.js +12 -3
  127. package/src/utils/findNextTabStop.js +3 -3
  128. package/src/utils/findNextTabStop.test.js +25 -1
  129. package/webpack.dev.mjs +1 -0
  130. package/docs/assets/images/screens-blue.jpg +0 -0
  131. package/docs/assets/images/screens-white.jpg +0 -0
@@ -9,16 +9,12 @@ import { buildStylesMap } from '../utils/buildStylesMap.js'
9
9
  * (shown with the keyboard cursor ring) as separate arguments so the provider can style them
10
10
  * differently. Also re-applies highlights after a map style reload, since highlight layers
11
11
  * are removed when the base style changes.
12
- *
13
- * Dispatches UPDATE_SELECTED_BOUNDS with the bounding box returned by the provider so
14
- * downstream consumers (e.g. interact:done) receive up-to-date bounds.
15
12
  */
16
13
  export const useHighlightSync = ({
17
14
  mapProvider,
18
15
  mapStyle,
19
16
  pluginState,
20
17
  selectedFeatures,
21
- dispatch,
22
18
  events,
23
19
  eventBus
24
20
  }) => {
@@ -37,12 +33,7 @@ export const useHighlightSync = ({
37
33
  const activeFeatures = listboxActiveItem
38
34
  ? [{ featureId: listboxActiveItem.featureId, layerId: listboxActiveItem.layerId, idProperty: listboxActiveItem.idProperty, geometry: listboxActiveItem.geometry }]
39
35
  : []
40
- const bounds = mapProvider.updateHighlightedFeatures?.(selectedFeatures, activeFeatures, stylesMap)
41
-
42
- dispatch({
43
- type: 'UPDATE_SELECTED_BOUNDS',
44
- payload: bounds
45
- })
36
+ mapProvider.updateHighlightedFeatures?.(selectedFeatures, activeFeatures, stylesMap)
46
37
  }
47
38
 
48
39
  useEffect(() => {
@@ -30,7 +30,6 @@ beforeEach(() => {
30
30
  layers: [{ layerId: 'layer1' }]
31
31
  },
32
32
  selectedFeatures: [],
33
- dispatch: jest.fn(),
34
33
  events: { MAP_STYLE_CHANGE: STYLE_CHANGE, MAP_DATA_CHANGE: DATA_CHANGE },
35
34
  eventBus: {
36
35
  on: jest.fn((event, handler) => {
@@ -51,7 +50,7 @@ beforeEach(() => {
51
50
  // ─── useHighlightSync — highlighting ─────────────────────────────────────────
52
51
 
53
52
  describe('useHighlightSync — highlighting', () => {
54
- it('updates map highlights and dispatches bounds', () => {
53
+ it('updates map highlights', () => {
55
54
  mockDeps.selectedFeatures = [{ featureId: 'F1', layerId: 'layer1' }]
56
55
 
57
56
  render()
@@ -61,22 +60,6 @@ describe('useHighlightSync — highlighting', () => {
61
60
  [],
62
61
  expect.any(Object)
63
62
  )
64
- expect(mockDeps.dispatch).toHaveBeenCalledWith({
65
- type: 'UPDATE_SELECTED_BOUNDS',
66
- payload: { sw: [0, 0], ne: [1, 1] }
67
- })
68
- })
69
-
70
- it('dispatches null bounds when provider returns null', () => {
71
- mockDeps.selectedFeatures = [{ featureId: 'F1' }]
72
- mockDeps.mapProvider.updateHighlightedFeatures.mockReturnValue(null)
73
-
74
- render()
75
-
76
- expect(mockDeps.dispatch).toHaveBeenCalledWith({
77
- type: 'UPDATE_SELECTED_BOUNDS',
78
- payload: null
79
- })
80
63
  })
81
64
  })
82
65
 
@@ -144,9 +127,7 @@ describe('useHighlightSync — guards', () => {
144
127
  mockDeps.mapProvider = null
145
128
  mockDeps.selectedFeatures = [{ featureId: 'F1' }]
146
129
 
147
- render()
148
-
149
- expect(mockDeps.dispatch).not.toHaveBeenCalled()
130
+ expect(() => render()).not.toThrow()
150
131
  })
151
132
 
152
133
  it('does nothing when mapStyle is null', () => {
@@ -37,15 +37,10 @@ const findMarkerAtPoint = (markers, point, scale) => {
37
37
  return null
38
38
  }
39
39
 
40
- const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectedMarkers, selectionBounds) => {
40
+ const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectedMarkers) => {
41
41
  const lastEmittedSelectionChange = useRef(null)
42
42
 
43
43
  useEffect(() => {
44
- const awaitingBounds = selectedFeatures.length > 0 && !selectionBounds
45
- if (awaitingBounds) {
46
- return
47
- }
48
-
49
44
  const prev = lastEmittedSelectionChange.current
50
45
  const wasEmpty = prev === null || (prev.features.length === 0 && prev.markers.length === 0)
51
46
  if (wasEmpty && selectedFeatures.length === 0 && selectedMarkers.length === 0) {
@@ -53,14 +48,13 @@ const useSelectionChangeEmitter = (eventBus, selectedFeatures, selectedMarkers,
53
48
  }
54
49
 
55
50
  eventBus.emit('interact:selectionchange', {
56
- selectedFeatures,
51
+ selectedFeatures: selectedFeatures.map(({ featureId, layerId, idProperty, properties }) => ({ featureId, layerId, idProperty, properties })),
57
52
  selectedMarkers,
58
- selectionBounds,
59
53
  contiguous: areAllContiguous(selectedFeatures)
60
54
  })
61
55
 
62
56
  lastEmittedSelectionChange.current = { features: selectedFeatures, markers: selectedMarkers }
63
- }, [selectedFeatures, selectedMarkers, selectionBounds])
57
+ }, [selectedFeatures, selectedMarkers])
64
58
  }
65
59
 
66
60
  /**
@@ -220,7 +214,7 @@ export const useInteractionHandlers = ({ mapState, pluginState, services, mapPro
220
214
  const {
221
215
  dispatch, layers, interactionModes, multiSelect, contiguous,
222
216
  marker: markerOptions, tolerance, selectedFeatures, selectedMarkers,
223
- selectionBounds, deselectOnClickOutside
217
+ deselectOnClickOutside
224
218
  } = pluginState
225
219
  const { eventBus } = services
226
220
  const layerConfigMap = buildLayerConfigMap(layers)
@@ -272,6 +266,6 @@ export const useInteractionHandlers = ({ mapState, pluginState, services, mapPro
272
266
  scale
273
267
  })
274
268
 
275
- useSelectionChangeEmitter(eventBus, selectedFeatures, selectedMarkers, selectionBounds)
269
+ useSelectionChangeEmitter(eventBus, selectedFeatures, selectedMarkers)
276
270
  return { handleInteraction }
277
271
  }
@@ -65,7 +65,6 @@ const setup = (pluginOverrides = {}, markerItems = [], markerRefs = new Map()) =
65
65
  marker: { symbol: 'pin', backgroundColor: 'red' },
66
66
  selectedFeatures: [],
67
67
  selectedMarkers: [],
68
- selectionBounds: null,
69
68
  ...pluginOverrides
70
69
  },
71
70
  services: {
@@ -345,30 +344,12 @@ it('does not check markers when selectMarker is not in interactionModes', () =>
345
344
  /* Selection change event */
346
345
  /* ------------------------------------------------------------------ */
347
346
 
348
- it('does not emit selectionchange when features are selected but bounds not yet calculated', () => {
347
+ it('emits selectionchange when features are selected', () => {
349
348
  const deps = {
350
349
  mapState: { markers: { add: jest.fn(), remove: jest.fn(), items: [], markerRefs: new Map() } },
351
350
  pluginState: {
352
- selectedFeatures: [{ featureId: 'F1' }],
353
- selectedMarkers: [],
354
- selectionBounds: null
355
- },
356
- services: { eventBus: { emit: jest.fn() } },
357
- mapProvider: { getFeatureGeometry: jest.fn(() => null) }
358
- }
359
-
360
- renderHook(() => useInteractionHandlers(deps))
361
-
362
- expect(deps.services.eventBus.emit).not.toHaveBeenCalled()
363
- })
364
-
365
- it('emits selectionchange once when bounds exist', () => {
366
- const deps = {
367
- mapState: { markers: { add: jest.fn(), remove: jest.fn(), items: [], markerRefs: new Map() } },
368
- pluginState: {
369
- selectedFeatures: [{ featureId: 'F1' }],
370
- selectedMarkers: [],
371
- selectionBounds: { sw: [0, 0], ne: [1, 1] }
351
+ selectedFeatures: [{ featureId: 'F1', layerId: 'l1', idProperty: 'id', properties: { name: 'A' }, geometry: { type: 'Point', coordinates: [0, 0] } }],
352
+ selectedMarkers: []
372
353
  },
373
354
  services: { eventBus: { emit: jest.fn() } },
374
355
  mapProvider: { getFeatureGeometry: jest.fn(() => null) }
@@ -379,9 +360,8 @@ it('emits selectionchange once when bounds exist', () => {
379
360
  expect(deps.services.eventBus.emit).toHaveBeenCalledWith(
380
361
  'interact:selectionchange',
381
362
  expect.objectContaining({
382
- selectedFeatures: deps.pluginState.selectedFeatures,
363
+ selectedFeatures: [{ featureId: 'F1', layerId: 'l1', idProperty: 'id', properties: { name: 'A' } }],
383
364
  selectedMarkers: [],
384
- selectionBounds: deps.pluginState.selectionBounds,
385
365
  contiguous: false
386
366
  })
387
367
  )
@@ -394,7 +374,7 @@ it('skips emission when selection remains empty after being cleared', () => {
394
374
  const { rerender } = renderHook(
395
375
  ({ features }) => useInteractionHandlers({
396
376
  mapState: { markers: { items: [], markerRefs: new Map() } },
397
- pluginState: { selectedFeatures: features, selectedMarkers: [], selectionBounds: { b: 1 } },
377
+ pluginState: { selectedFeatures: features, selectedMarkers: [] },
398
378
  services: { eventBus },
399
379
  mapProvider: { getFeatureGeometry: jest.fn(() => null) }
400
380
  }),
@@ -7,7 +7,6 @@ const initialState = {
7
7
  deselectOnClickOutside: false,
8
8
  selectedFeatures: [],
9
9
  selectedMarkers: [],
10
- selectionBounds: null,
11
10
  closeOnAction: true, // Done or Cancel
12
11
  listboxActiveItem: null // { featureId, layerId, idProperty, geometry } | null — ring shown but no selectionchange
13
12
  }
@@ -26,7 +25,6 @@ const disable = (state) => {
26
25
  enabled: false,
27
26
  selectedFeatures: [],
28
27
  selectedMarkers: [],
29
- selectionBounds: null,
30
28
  listboxActiveItem: null
31
29
  }
32
30
  }
@@ -46,7 +44,7 @@ const toggleSelectedFeatures = (state, payload) => {
46
44
  // 1. Handle explicit unselect
47
45
  if (addToExisting === false) {
48
46
  const filtered = currentSelected.filter((_, i) => i !== existingIndex)
49
- return { ...state, selectedFeatures: filtered, selectionBounds: null }
47
+ return { ...state, selectedFeatures: filtered }
50
48
  }
51
49
 
52
50
  // Define the feature object once to avoid repetition
@@ -70,18 +68,7 @@ const toggleSelectedFeatures = (state, payload) => {
70
68
  nextSelected = isSameSingle ? [] : [featureObj]
71
69
  }
72
70
 
73
- return { ...state, selectedFeatures: nextSelected, selectedMarkers: multiSelect && !replaceAll ? state.selectedMarkers : [], selectionBounds: null }
74
- }
75
-
76
- // Update bounds (called from useEffect after map provider calculates them)
77
- const updateSelectedBounds = (state, payload) => {
78
- if (JSON.stringify(payload) === JSON.stringify(state.selectionBounds)) {
79
- return state
80
- }
81
- return {
82
- ...state,
83
- selectionBounds: payload
84
- }
71
+ return { ...state, selectedFeatures: nextSelected, selectedMarkers: multiSelect && !replaceAll ? state.selectedMarkers : [] }
85
72
  }
86
73
 
87
74
  const toggleSelectedMarkers = (state, { markerId, multiSelect }) => {
@@ -94,7 +81,6 @@ const toggleSelectedMarkers = (state, { markerId, multiSelect }) => {
94
81
  return {
95
82
  ...state,
96
83
  selectedFeatures: [],
97
- selectionBounds: null,
98
84
  selectedMarkers: exists && current.length === 1 ? [] : [markerId]
99
85
  }
100
86
  }
@@ -103,8 +89,7 @@ const clearSelectedFeatures = (state) => {
103
89
  return {
104
90
  ...state,
105
91
  selectedFeatures: [],
106
- selectedMarkers: [],
107
- selectionBounds: null
92
+ selectedMarkers: []
108
93
  }
109
94
  }
110
95
 
@@ -124,7 +109,6 @@ const selectMarker = (state, { markerId, multiSelect }) => {
124
109
  return {
125
110
  ...state,
126
111
  selectedFeatures: multiSelect ? state.selectedFeatures : [],
127
- selectionBounds: null,
128
112
  selectedMarkers: nextMarkers
129
113
  }
130
114
  }
@@ -145,8 +129,7 @@ const unselectMarker = (state, { markerId }) => {
145
129
 
146
130
  const setSelectedFeatures = (state, features) => ({
147
131
  ...state,
148
- selectedFeatures: features,
149
- selectionBounds: null
132
+ selectedFeatures: features
150
133
  })
151
134
 
152
135
  const actions = {
@@ -154,7 +137,6 @@ const actions = {
154
137
  DISABLE: disable,
155
138
  TOGGLE_SELECTED_FEATURES: toggleSelectedFeatures,
156
139
  TOGGLE_SELECTED_MARKERS: toggleSelectedMarkers,
157
- UPDATE_SELECTED_BOUNDS: updateSelectedBounds,
158
140
  CLEAR_SELECTED_FEATURES: clearSelectedFeatures,
159
141
  SET_SELECTED_FEATURES: setSelectedFeatures,
160
142
  SELECT_MARKER: selectMarker,
@@ -11,7 +11,6 @@ describe('initialState', () => {
11
11
  deselectOnClickOutside: false,
12
12
  selectedFeatures: [],
13
13
  selectedMarkers: [],
14
- selectionBounds: null,
15
14
  closeOnAction: true,
16
15
  listboxActiveItem: null
17
16
  })
@@ -33,7 +32,7 @@ describe('ENABLE/DISABLE actions', () => {
33
32
 
34
33
  it('DISABLE sets enabled to false, clears selection and markers, and preserves other state', () => {
35
34
  const marker = { symbol: 'pin', backgroundColor: 'red' }
36
- const state = { ...initialState, enabled: true, layers: [1], marker, selectedFeatures: [{ featureId: 'f1' }], selectedMarkers: ['m1'], selectionBounds: [0, 0, 1, 1] }
35
+ const state = { ...initialState, enabled: true, layers: [1], marker, selectedFeatures: [{ featureId: 'f1' }], selectedMarkers: ['m1'] }
37
36
  const result = actions.DISABLE(state)
38
37
 
39
38
  expect(result.enabled).toBe(false)
@@ -41,7 +40,6 @@ describe('ENABLE/DISABLE actions', () => {
41
40
  expect(result.marker).toEqual(marker)
42
41
  expect(result.selectedFeatures).toEqual([])
43
42
  expect(result.selectedMarkers).toEqual([])
44
- expect(result.selectionBounds).toBeNull()
45
43
  expect(result).not.toBe(state)
46
44
  })
47
45
  })
@@ -56,7 +54,7 @@ describe('TOGGLE_SELECTED_FEATURES action', () => {
56
54
  })
57
55
 
58
56
  it('handles single-select, multi-select, add/remove, and replaceAll', () => {
59
- let state = { ...initialState, selectionBounds: { sw: [0, 0], ne: [1, 1] } }
57
+ let state = { ...initialState }
60
58
 
61
59
  // Single-select: add
62
60
  state = actions.TOGGLE_SELECTED_FEATURES(state, createFeature('f1'))
@@ -66,33 +64,27 @@ describe('TOGGLE_SELECTED_FEATURES action', () => {
66
64
  state = actions.TOGGLE_SELECTED_FEATURES(state, createFeature('f2'))
67
65
  expect(state.selectedFeatures[0].featureId).toBe('f2')
68
66
 
69
- // Toggle off same - clears bounds
67
+ // Toggle off same
70
68
  state = actions.TOGGLE_SELECTED_FEATURES(state, createFeature('f2'))
71
69
  expect(state.selectedFeatures).toHaveLength(0)
72
- expect(state.selectionBounds).toBeNull()
73
70
 
74
71
  // Multi-select: add multiple
75
- state = { ...state, selectionBounds: { sw: [0, 0], ne: [1, 1] } }
76
72
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f1'), multiSelect: true })
77
73
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f2'), multiSelect: true })
78
74
  expect(state.selectedFeatures.map(f => f.featureId)).toEqual(['f1', 'f2'])
79
75
 
80
- // Multi-select: remove (not last) - clears bounds for recalculation
76
+ // Multi-select: remove (not last)
81
77
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f1'), multiSelect: true })
82
78
  expect(state.selectedFeatures.map(f => f.featureId)).toEqual(['f2'])
83
- expect(state.selectionBounds).toBeNull()
84
79
 
85
- // Multi-select: remove last - clears bounds
80
+ // Multi-select: remove last
86
81
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f2'), multiSelect: true })
87
82
  expect(state.selectedFeatures).toHaveLength(0)
88
- expect(state.selectionBounds).toBeNull()
89
83
 
90
- // addToExisting false removes feature - clears bounds when empty
91
- state = { ...state, selectionBounds: { sw: [0, 0], ne: [1, 1] } }
84
+ // addToExisting false removes feature
92
85
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f2'), multiSelect: true })
93
86
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f2'), addToExisting: false })
94
87
  expect(state.selectedFeatures).toHaveLength(0)
95
- expect(state.selectionBounds).toBeNull()
96
88
 
97
89
  // replaceAll replaces everything
98
90
  state = actions.TOGGLE_SELECTED_FEATURES(state, { ...createFeature('f3'), replaceAll: true })
@@ -131,26 +123,12 @@ describe('TOGGLE_SELECTED_FEATURES action', () => {
131
123
  })
132
124
  })
133
125
 
134
- describe('UPDATE_SELECTED_BOUNDS action', () => {
135
- it('updates selectionBounds correctly', () => {
136
- const state = { ...initialState, selectionBounds: { sw: [0, 0], ne: [1, 1] } }
137
- const newBounds = { sw: [0, 0], ne: [2, 2] }
138
- const result = actions.UPDATE_SELECTED_BOUNDS(state, newBounds)
139
- expect(result.selectionBounds).toEqual(newBounds)
140
-
141
- // unchanged bounds returns same state
142
- const result2 = actions.UPDATE_SELECTED_BOUNDS(state, { sw: [0, 0], ne: [1, 1] })
143
- expect(result2).toBe(state)
144
- })
145
- })
146
-
147
126
  describe('TOGGLE_SELECTED_MARKERS action', () => {
148
127
  it('selects a marker in single-select mode and clears features', () => {
149
- const state = { ...initialState, selectedFeatures: [{ featureId: 'f1' }], selectionBounds: { sw: [0, 0], ne: [1, 1] } }
128
+ const state = { ...initialState, selectedFeatures: [{ featureId: 'f1' }] }
150
129
  const result = actions.TOGGLE_SELECTED_MARKERS(state, { markerId: 'm1', multiSelect: false })
151
130
  expect(result.selectedMarkers).toEqual(['m1'])
152
131
  expect(result.selectedFeatures).toEqual([])
153
- expect(result.selectionBounds).toBeNull()
154
132
  })
155
133
 
156
134
  it('toggles off the only selected marker in single-select mode', () => {
@@ -174,17 +152,15 @@ describe('TOGGLE_SELECTED_MARKERS action', () => {
174
152
  })
175
153
 
176
154
  describe('CLEAR_SELECTED_FEATURES action', () => {
177
- it('resets features, markers and bounds', () => {
155
+ it('resets features and markers', () => {
178
156
  const state = {
179
157
  ...initialState,
180
158
  selectedFeatures: [1],
181
- selectedMarkers: ['m1'],
182
- selectionBounds: { sw: [0, 0], ne: [1, 1] }
159
+ selectedMarkers: ['m1']
183
160
  }
184
161
  const result = actions.CLEAR_SELECTED_FEATURES(state)
185
162
  expect(result.selectedFeatures).toEqual([])
186
163
  expect(result.selectedMarkers).toEqual([])
187
- expect(result.selectionBounds).toBeNull()
188
164
  expect(result).not.toBe(state)
189
165
  })
190
166
  })
@@ -209,7 +185,6 @@ describe('SELECT_MARKER action', () => {
209
185
  const result = actions.SELECT_MARKER(state, { markerId: 'm1', multiSelect: false })
210
186
  expect(result.selectedMarkers).toEqual(['m1'])
211
187
  expect(result.selectedFeatures).toEqual([])
212
- expect(result.selectionBounds).toBeNull()
213
188
  })
214
189
 
215
190
  it('adds a marker in multi-select mode without clearing selectedFeatures', () => {
@@ -241,16 +216,14 @@ describe('UNSELECT_MARKER action', () => {
241
216
  })
242
217
 
243
218
  describe('SET_SELECTED_FEATURES action', () => {
244
- it('replaces selectedFeatures and clears bounds', () => {
219
+ it('replaces selectedFeatures', () => {
245
220
  const state = {
246
221
  ...initialState,
247
- selectedFeatures: [{ featureId: 'A' }, { featureId: 'B' }, { featureId: 'C' }],
248
- selectionBounds: [0, 0, 1, 1]
222
+ selectedFeatures: [{ featureId: 'A' }, { featureId: 'B' }, { featureId: 'C' }]
249
223
  }
250
224
  const trimmed = [{ featureId: 'A' }]
251
225
  const result = actions.SET_SELECTED_FEATURES(state, trimmed)
252
226
  expect(result.selectedFeatures).toEqual(trimmed)
253
- expect(result.selectionBounds).toBeNull()
254
227
  expect(result).not.toBe(state)
255
228
  })
256
229
  })
@@ -262,7 +235,6 @@ describe('actions object', () => {
262
235
  'DISABLE',
263
236
  'TOGGLE_SELECTED_FEATURES',
264
237
  'TOGGLE_SELECTED_MARKERS',
265
- 'UPDATE_SELECTED_BOUNDS',
266
238
  'CLEAR_SELECTED_FEATURES',
267
239
  'SET_SELECTED_FEATURES',
268
240
  'SELECT_MARKER',
@@ -3,6 +3,11 @@
3
3
  padding: 0;
4
4
  min-height: auto;
5
5
  border-radius: 0;
6
+ filter: var(--search-drop-shadow);
7
+ -webkit-transform: translate3d(0, 0, 0);
8
+ -moz-transform: translate3d(0, 0, 0);
9
+ -ms-transform: translate3d(0, 0, 0);
10
+ transform: translate3d(0, 0, 0);
6
11
  }
7
12
  .im-c-search-form:before {
8
13
  border-radius: 0;
@@ -16,7 +21,6 @@
16
21
  right: 0;
17
22
  height: auto;
18
23
  overflow: visible;
19
- filter: var(--search-drop-shadow);
20
24
  }
21
25
 
22
26
  .im-o-app--tablet .im-c-search-form,
@@ -25,7 +29,6 @@
25
29
  overflow: visible;
26
30
  border-radius: var(--button-border-radius);
27
31
  box-shadow: none;
28
- filter: var(--search-drop-shadow);
29
32
  }
30
33
  .im-o-app--tablet .im-c-search-form:before,
31
34
  .im-o-app--desktop .im-c-search-form:before {
@@ -3,6 +3,11 @@
3
3
  padding: 0;
4
4
  min-height: auto;
5
5
  border-radius: 0;
6
+ filter: var(--search-drop-shadow);
7
+ -webkit-transform: translate3d(0, 0, 0);
8
+ -moz-transform: translate3d(0, 0, 0);
9
+ -ms-transform: translate3d(0, 0, 0);
10
+ transform: translate3d(0, 0, 0);
6
11
 
7
12
  &:before {
8
13
  border-radius: 0;
@@ -17,7 +22,6 @@
17
22
  right: 0;
18
23
  height: auto;
19
24
  overflow: visible;
20
- filter: var(--search-drop-shadow);
21
25
  }
22
26
 
23
27
  .im-o-app--tablet .im-c-search-form,
@@ -26,7 +30,6 @@
26
30
  overflow: visible;
27
31
  border-radius: var(--button-border-radius);
28
32
  box-shadow: none;
29
- filter: var(--search-drop-shadow);
30
33
 
31
34
  &:before {
32
35
  border-radius: var(--button-border-radius);
@@ -32,10 +32,10 @@
32
32
  content: '';
33
33
  z-index: 1;
34
34
  position: absolute;
35
- top: -1px;
36
- right: -1px;
37
- bottom: -1px;
38
- left: -1px;
35
+ top: -1px;
36
+ right: -1px;
37
+ bottom: -1px;
38
+ left: -1px;
39
39
  border-radius: var(--button-border-radius);
40
40
  border: 2px solid var(--foreground-color);
41
41
  pointer-events: none;