@defra/interactive-map 0.0.10-alpha → 0.0.11-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 (99) hide show
  1. package/README.md +1 -1
  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/button-definition.md +21 -3
  8. package/docs/api/panel-definition.md +10 -12
  9. package/docs/api.md +80 -7
  10. package/docs/demo.mdx +70 -0
  11. package/docs/index.md +0 -4
  12. package/docs/plugins/plugin-context.md +3 -3
  13. package/docs/plugins/plugin-manifest.md +1 -1
  14. package/docusaurus.config.cjs +55 -25
  15. package/package.json +12 -7
  16. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  17. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  18. package/plugins/beta/datasets/src/manifest.js +3 -3
  19. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  20. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  21. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  22. package/plugins/beta/map-styles/src/manifest.js +3 -3
  23. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
  24. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
  25. package/plugins/beta/use-location/src/manifest.js +7 -7
  26. package/plugins/search/dist/css/index.css +1 -1
  27. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  28. package/plugins/search/dist/esm/index.js +1 -1
  29. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  30. package/plugins/search/dist/umd/index.js +1 -1
  31. package/plugins/search/src/Search.jsx +9 -3
  32. package/plugins/search/src/Search.test.jsx +26 -6
  33. package/plugins/search/src/components/Form/Form.jsx +35 -7
  34. package/plugins/search/src/components/Form/Form.module.scss +27 -0
  35. package/plugins/search/src/components/Form/Form.test.jsx +99 -2
  36. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +28 -0
  37. package/plugins/search/src/components/SubmitButton/SubmitButton.module.scss +8 -0
  38. package/plugins/search/src/components/SubmitButton/SubmitButton.test.jsx +33 -0
  39. package/plugins/search/src/datasets.js +15 -11
  40. package/plugins/search/src/datasets.test.js +17 -2
  41. package/plugins/search/src/events/fetchSuggestions.js +1 -1
  42. package/plugins/search/src/index.js +1 -1
  43. package/plugins/search/src/index.test.js +4 -4
  44. package/plugins/search/src/reducer.js +9 -4
  45. package/plugins/search/src/reducer.test.js +12 -7
  46. package/plugins/search/src/search.scss +5 -1
  47. package/plugins/search/src/utils/parseOsNamesResults.js +18 -2
  48. package/plugins/search/src/utils/parseOsNamesResults.test.js +33 -15
  49. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  50. package/providers/beta/esri/src/appEvents.js +8 -2
  51. package/providers/beta/esri/src/esriProvider.js +6 -14
  52. package/providers/beta/esri/src/mapEvents.js +7 -1
  53. package/providers/beta/esri/src/utils/coords.js +33 -1
  54. package/providers/beta/esri/src/utils/coords.test.js +126 -0
  55. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  56. package/providers/maplibre/dist/esm/index.js +1 -1
  57. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  58. package/providers/maplibre/dist/umd/index.js +1 -1
  59. package/providers/maplibre/src/appEvents.js +10 -1
  60. package/providers/maplibre/src/appEvents.test.js +13 -4
  61. package/providers/maplibre/src/index.js +5 -13
  62. package/providers/maplibre/src/index.test.js +34 -15
  63. package/providers/maplibre/src/mapEvents.js +9 -1
  64. package/providers/maplibre/src/maplibreProvider.js +14 -15
  65. package/providers/maplibre/src/maplibreProvider.test.js +14 -1
  66. package/providers/maplibre/src/utils/spatial.js +11 -0
  67. package/providers/maplibre/src/utils/spatial.test.js +12 -0
  68. package/src/App/components/Actions/Actions.module.scss +5 -4
  69. package/src/App/components/MapButton/MapButton.jsx +4 -16
  70. package/src/App/components/MapButton/MapButton.module.scss +12 -12
  71. package/src/App/components/MapButton/MapButton.test.jsx +0 -9
  72. package/src/App/components/Panel/Panel.jsx +6 -6
  73. package/src/App/components/Panel/Panel.test.jsx +14 -15
  74. package/src/App/components/Viewport/MapController.jsx +2 -1
  75. package/src/App/hooks/useLayoutMeasurements.js +1 -1
  76. package/src/App/hooks/useLayoutMeasurements.test.js +1 -1
  77. package/src/App/hooks/useMapProviderOverrides.js +21 -1
  78. package/src/App/hooks/useMapProviderOverrides.test.js +51 -2
  79. package/src/App/layout/Layout.jsx +4 -4
  80. package/src/App/layout/layout.module.scss +1 -0
  81. package/src/App/registry/panelRegistry.js +1 -10
  82. package/src/App/registry/panelRegistry.test.js +6 -11
  83. package/src/App/renderer/HtmlElementHost.jsx +11 -3
  84. package/src/App/renderer/HtmlElementHost.test.jsx +89 -0
  85. package/src/App/renderer/mapButtons.js +128 -28
  86. package/src/App/renderer/mapButtons.test.js +119 -19
  87. package/src/App/store/MapProvider.jsx +18 -5
  88. package/src/App/store/MapProvider.test.jsx +56 -1
  89. package/src/App/store/appActionsMap.js +17 -9
  90. package/src/App/store/appActionsMap.test.js +33 -7
  91. package/src/App/store/mapActionsMap.js +4 -7
  92. package/src/InteractiveMap/InteractiveMap.js +18 -0
  93. package/src/InteractiveMap/InteractiveMap.test.js +12 -0
  94. package/src/config/appConfig.js +17 -15
  95. package/src/config/events.js +41 -4
  96. package/src/config/getInitialOpenPanels.js +2 -2
  97. package/src/config/getInitialOpenPanels.test.js +7 -7
  98. package/src/types.js +13 -11
  99. package/src/utils/getValueForStyle.js +1 -1
@@ -404,4 +404,22 @@ export default class InteractiveMap {
404
404
  addControl (id, config) {
405
405
  this.eventBus.emit(events.APP_ADD_CONTROL, { id, config })
406
406
  }
407
+
408
+ /**
409
+ * Fit the map view to a bounding box or GeoJSON geometry, respecting the safe zone padding.
410
+ *
411
+ * @param {[number, number, number, number] | object} bbox - Bounds as [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs, or a GeoJSON Feature, FeatureCollection, or geometry.
412
+ */
413
+ fitToBounds (bbox) {
414
+ this.eventBus.emit(events.MAP_FIT_TO_BOUNDS, bbox)
415
+ }
416
+
417
+ /**
418
+ * Set the map center and zoom, respecting the safe zone padding.
419
+ *
420
+ * @param {{ center?: [number, number], zoom?: number }} opts - View options.
421
+ */
422
+ setView (opts) {
423
+ this.eventBus.emit(events.MAP_SET_VIEW, opts)
424
+ }
407
425
  }
@@ -509,4 +509,16 @@ describe('InteractiveMap Public API Methods', () => {
509
509
  { id: 'btn-1', prop: 'disabled', value: true }
510
510
  )
511
511
  })
512
+
513
+ it('fitToBounds emits MAP_FIT_TO_BOUNDS with bbox', () => {
514
+ const bbox = [-0.489, 51.28, 0.236, 51.686]
515
+ map.fitToBounds(bbox)
516
+ expect(map.eventBus.emit).toHaveBeenCalledWith('map:fittobounds', bbox)
517
+ })
518
+
519
+ it('setView emits MAP_SET_VIEW with opts', () => {
520
+ const opts = { center: [-0.1276, 51.5074], zoom: 12 }
521
+ map.setView(opts)
522
+ expect(map.eventBus.emit).toHaveBeenCalledWith('map:setview', opts)
523
+ })
512
524
  })
@@ -2,8 +2,8 @@ import { KeyboardHelp } from '../App/components/KeyboardHelp/KeyboardHelp.jsx'
2
2
 
3
3
  const keyboardBasePanelSlots = {
4
4
  slot: 'middle',
5
- initiallyOpen: false,
6
- dismissable: true,
5
+ open: false,
6
+ dismissible: true,
7
7
  modal: true
8
8
  }
9
9
 
@@ -42,7 +42,7 @@ export const defaultAppConfig = {
42
42
  desktop: buttonSlots
43
43
  }, {
44
44
  id: 'zoomIn',
45
- group: 'zoom',
45
+ group: { name: 'zoom', label: 'Zoom controls', order: 0 },
46
46
  label: 'Zoom in',
47
47
  iconId: 'plus',
48
48
  onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomIn(appConfig.zoomDelta),
@@ -53,7 +53,7 @@ export const defaultAppConfig = {
53
53
  desktop: buttonSlots
54
54
  }, {
55
55
  id: 'zoomOut',
56
- group: 'zoom',
56
+ group: { name: 'zoom', label: 'Zoom controls', order: 0 },
57
57
  label: 'Zoom out',
58
58
  iconId: 'minus',
59
59
  onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomOut(appConfig.zoomDelta),
@@ -99,7 +99,7 @@ export const defaultAppConfig = {
99
99
  // Used by addButton
100
100
  const defaultButtonSlots = {
101
101
  slot: 'right-top',
102
- showLabel: false
102
+ showLabel: true
103
103
  }
104
104
 
105
105
  export const defaultButtonConfig = {
@@ -111,25 +111,27 @@ export const defaultButtonConfig = {
111
111
 
112
112
  // Used by addPanel
113
113
  export const defaultPanelConfig = {
114
- showLabel: true,
115
114
  label: 'Panel',
116
115
  mobile: {
117
116
  slot: 'bottom',
118
- initiallyOpen: true,
119
- dismissable: true,
120
- modal: false
117
+ open: true,
118
+ dismissible: true,
119
+ modal: false,
120
+ showLabel: true
121
121
  },
122
122
  tablet: {
123
123
  slot: 'inset',
124
- initiallyOpen: true,
125
- dismissable: true,
126
- modal: false
124
+ open: true,
125
+ dismissible: true,
126
+ modal: false,
127
+ showLabel: true
127
128
  },
128
129
  desktop: {
129
130
  slot: 'inset',
130
- initiallyOpen: true,
131
- dismissable: true,
132
- modal: false
131
+ open: true,
132
+ dismissible: true,
133
+ modal: false,
134
+ showLabel: true
133
135
  },
134
136
  render: null,
135
137
  html: null
@@ -87,12 +87,16 @@ export const EVENTS = {
87
87
  // Map commands (internal / plugin authors)
88
88
  // ============================================
89
89
 
90
- /** @internal Set map style. Payload: styleId */
90
+ /** @internal Set map style. Payload: MapStyleConfig */
91
91
  MAP_SET_STYLE: 'map:setstyle',
92
92
  /** @internal Set map size. Payload: { width, height } */
93
93
  MAP_SET_SIZE: 'map:setsize',
94
94
  /** @internal Set pixel ratio. Payload: pixelRatio */
95
95
  MAP_SET_PIXEL_RATIO: 'map:setpixelratio',
96
+ /** @internal Fit the map to a bounding box. Payload: [west, south, east, north] */
97
+ MAP_FIT_TO_BOUNDS: 'map:fittobounds',
98
+ /** @internal Set the map center and zoom. Payload: { center: [number, number], zoom?: number } */
99
+ MAP_SET_VIEW: 'map:setview',
96
100
 
97
101
  // ============================================
98
102
  // Map responses (advanced / subscribe)
@@ -101,15 +105,48 @@ export const EVENTS = {
101
105
  /** @internal Emitted when map styles are initialized. */
102
106
  MAP_INIT_MAP_STYLES: 'map:initmapstyles',
103
107
 
104
- /** Emitted when the map style changes. Payload: { styleId: string } */
108
+ /**
109
+ * Emitted when the map style has finished loading.
110
+ * Payload: `{ mapStyleId: string }`
111
+ *
112
+ * @example
113
+ * map.on(EVENTS.MAP_STYLE_CHANGE, ({ mapStyleId }) => {
114
+ * console.log('Style changed to', mapStyleId)
115
+ * })
116
+ */
105
117
  MAP_STYLE_CHANGE: 'map:stylechange',
106
118
 
107
- /** Emitted when the map style has fully loaded. */
119
+ /** Emitted when the map has fully loaded. */
108
120
  MAP_LOADED: 'map:loaded',
109
121
 
110
- /** Emitted when the map is ready for interaction. Payload: { map } */
122
+ /**
123
+ * Emitted when the map is ready for interaction and initial app state is settled.
124
+ *
125
+ * Payload:
126
+ * - `map` — the underlying map instance
127
+ * - `view` — the map view (ESRI SDK only)
128
+ * - `crs` — coordinate reference system string (e.g. `'EPSG:4326'`)
129
+ * - `mapStyleId` — the ID of the active map style (e.g. `'outdoor'`, `'dark'`)
130
+ * - `mapSize` — the active map size string (e.g. `'small'`, `'medium'`, `'large'`)
131
+ *
132
+ * @example
133
+ * map.on(EVENTS.MAP_READY, ({ map, mapStyleId, mapSize }) => {
134
+ * console.log('Map ready, style:', mapStyleId, 'size:', mapSize)
135
+ * })
136
+ */
111
137
  MAP_READY: 'map:ready',
112
138
 
139
+ /**
140
+ * Emitted when the map size changes.
141
+ * Payload: `{ mapSize: string }`
142
+ *
143
+ * @example
144
+ * map.on(EVENTS.MAP_SIZE_CHANGE, ({ mapSize }) => {
145
+ * console.log('Map size changed to', mapSize)
146
+ * })
147
+ */
148
+ MAP_SIZE_CHANGE: 'map:sizechange',
149
+
113
150
  /** Emitted once after the map first becomes idle following initial load. */
114
151
  MAP_FIRST_IDLE: 'map:firstidle',
115
152
 
@@ -5,9 +5,9 @@ export function getInitialOpenPanels (panelConfig, breakpoint, prevOpenPanels =
5
5
  const configPanel = panelConfig[panelId]
6
6
  const bpConfig = configPanel[breakpoint]
7
7
 
8
- const isInitiallyOpen = bpConfig?.initiallyOpen ?? false
8
+ const isOpen = bpConfig?.open ?? false
9
9
 
10
- if (isInitiallyOpen) {
10
+ if (isOpen) {
11
11
  // Preserve any props that were already set in state
12
12
  openPanels[panelId] = prevOpenPanels[panelId] || { props: {} }
13
13
  }
@@ -8,17 +8,17 @@ describe('getInitialOpenPanels', () => {
8
8
  expect(getInitialOpenPanels({}, breakpoint)).toEqual({})
9
9
  })
10
10
 
11
- it('includes panels with initiallyOpen = true and no prev state', () => {
11
+ it('includes panels with open = true and no prev state', () => {
12
12
  const config = {
13
- PanelA: { desktop: { initiallyOpen: true } }
13
+ PanelA: { desktop: { open: true } }
14
14
  }
15
15
  const result = getInitialOpenPanels(config, breakpoint)
16
16
  expect(result).toEqual({ PanelA: { props: {} } })
17
17
  })
18
18
 
19
- it('skips panels with initiallyOpen = false', () => {
19
+ it('skips panels with open = false', () => {
20
20
  const config = {
21
- PanelA: { desktop: { initiallyOpen: false } }
21
+ PanelA: { desktop: { open: false } }
22
22
  }
23
23
  const result = getInitialOpenPanels(config, breakpoint)
24
24
  expect(result).toEqual({})
@@ -34,7 +34,7 @@ describe('getInitialOpenPanels', () => {
34
34
 
35
35
  it('preserves prevOpenPanels state if exists', () => {
36
36
  const config = {
37
- PanelA: { desktop: { initiallyOpen: true } }
37
+ PanelA: { desktop: { open: true } }
38
38
  }
39
39
  const prevOpenPanels = {
40
40
  PanelA: { props: { some: 'value' } }
@@ -45,8 +45,8 @@ describe('getInitialOpenPanels', () => {
45
45
 
46
46
  it('uses empty props object if no prevOpenPanels entry exists', () => {
47
47
  const config = {
48
- PanelA: { desktop: { initiallyOpen: true } },
49
- PanelB: { desktop: { initiallyOpen: true } }
48
+ PanelA: { desktop: { open: true } },
49
+ PanelB: { desktop: { open: true } }
50
50
  }
51
51
  const prevOpenPanels = {
52
52
  PanelA: { props: { existing: true } }
package/src/types.js CHANGED
@@ -16,8 +16,8 @@
16
16
  * @property {number} [order]
17
17
  * The order the button appears within its slot.
18
18
  *
19
- * @property {boolean} [showLabel]
20
- * Whether to a show label, if false then a tooltip is generated from the the label.
19
+ * @property {boolean} [showLabel=true]
20
+ * Whether to show a label. If false, a tooltip is generated from the label instead. Defaults to true.
21
21
  *
22
22
  * @property {string} slot
23
23
  * The slot that the button should appear in at this breakpoint.
@@ -37,14 +37,19 @@
37
37
  *
38
38
  * @typedef {Object} PanelBreakpointConfig
39
39
  *
40
- * @property {boolean} [dismissable]
41
- * Whether panel can be dismissed.
40
+ * @property {boolean} [dismissible]
41
+ * Whether panel can be dismissed. When `false` and `open` is `true`, the panel is always visible at this
42
+ * breakpoint and any associated panel-toggle button is automatically suppressed.
42
43
  *
43
44
  * @property {boolean} [exclusive]
44
45
  * Whether panel is exclusive. An exclusive panel will hide other panels when it is visible.
45
46
  *
46
- * @property {boolean} [initiallyOpen]
47
- * Whether panel is initially open.
47
+ * @property {boolean} [open]
48
+ * Whether the panel is open. When `true` and combined with `dismissible: false`, the panel is always visible at this
49
+ * breakpoint and will be restored automatically when the breakpoint is entered.
50
+ *
51
+ * @property {boolean} [showLabel]
52
+ * Whether to show the panel heading. Defaults to true. The heading is visually hidden if false.
48
53
  *
49
54
  * @property {boolean} [modal]
50
55
  * Whether panel is modal.
@@ -198,8 +203,8 @@
198
203
  * @property {(offset: [number, number]) => void} panBy
199
204
  * Pan map by pixel offset [x, y]. Positive x pans right, positive y pans down.
200
205
  *
201
- * @property {(bounds: [number, number, number, number]) => void} fitToBounds
202
- * Fit map view to the specified bounds [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs of the map provider.
206
+ * @property {(bounds: [number, number, number, number] | object) => void} fitToBounds
207
+ * Fit map view to the specified bounds [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs, or a GeoJSON Feature, FeatureCollection, or geometry.
203
208
  *
204
209
  * @property {(padding: { top?: number, bottom?: number, left?: number, right?: number }) => void} setPadding
205
210
  * Set map padding as pixel insets from the top, bottom, left and right edges of the map.
@@ -366,9 +371,6 @@
366
371
  * @property {ComponentType} [render]
367
372
  * Render component.
368
373
  *
369
- * @property {boolean} [showLabel]
370
- * Whether to show the panel heading. The panel heading is visually hidden if false.
371
- *
372
374
  * @property {PanelBreakpointConfig} tablet
373
375
  * Tablet breakpoint configuration.
374
376
  */
@@ -2,7 +2,7 @@
2
2
  * Returns the appropriate color for the given mapStyleId.
3
3
  * Supports:
4
4
  * - Simple string colors (#fff, rgba(...))
5
- * - Object maps of styleId → color
5
+ * - Object maps of mapStyleId → color
6
6
  *
7
7
  * @param {string|object} colors - Color string or style-mapped color object
8
8
  * @param {string} mapStyleId - Current style/theme identifier