@defra/interactive-map 0.0.23-alpha → 0.0.25-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 (75) hide show
  1. package/assets/images/light-raster-thumb.jpg +0 -0
  2. package/assets/images/outdoor-raster-thumb.jpg +0 -0
  3. package/assets/images/road-raster-thumb.jpg +0 -0
  4. package/dist/css/index.css +1 -1
  5. package/docs/api/map-style-config.md +39 -2
  6. package/docs/plugins/search.md +70 -0
  7. package/package.json +17 -2
  8. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  9. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  10. package/plugins/beta/draw-ml/src/api/editFeature.js +9 -4
  11. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +11 -3
  12. package/plugins/beta/scale-bar/dist/css/index.css +31 -1
  13. package/plugins/interact/src/hooks/useAttachEvents.test.js +3 -0
  14. package/plugins/search/dist/css/index.css +250 -1
  15. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  16. package/plugins/search/dist/esm/index.js +1 -1
  17. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  18. package/plugins/search/dist/umd/index.js +1 -1
  19. package/plugins/search/src/Search.jsx +2 -1
  20. package/plugins/search/src/components/Form/Form.jsx +11 -7
  21. package/plugins/search/src/components/Form/Form.test.jsx +23 -1
  22. package/plugins/search/src/datasets.js +2 -2
  23. package/plugins/search/src/datasets.test.js +1 -1
  24. package/plugins/search/src/defaults.js +7 -1
  25. package/plugins/search/src/events/fetchSuggestions.js +13 -10
  26. package/plugins/search/src/events/fetchSuggestions.test.js +14 -1
  27. package/plugins/search/src/events/formHandlers.js +2 -4
  28. package/plugins/search/src/events/formHandlers.test.js +3 -3
  29. package/plugins/search/src/events/inputHandlers.js +1 -1
  30. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  31. package/plugins/search/src/events/suggestionHandlers.js +2 -1
  32. package/plugins/search/src/events/suggestionHandlers.test.js +7 -1
  33. package/plugins/search/src/index.js +3 -7
  34. package/plugins/search/src/reducer.js +10 -5
  35. package/plugins/search/src/reducer.test.js +2 -1
  36. package/plugins/search/src/utils/parseOsNamesResults.js +3 -3
  37. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -0
  38. package/providers/beta/openlayers/dist/esm/index.js +1 -0
  39. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +2 -0
  40. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js.LICENSE.txt +1 -0
  41. package/providers/beta/openlayers/dist/umd/index.js +2 -0
  42. package/providers/beta/openlayers/dist/umd/index.js.LICENSE.txt +1 -0
  43. package/providers/beta/openlayers/src/appEvents.js +39 -0
  44. package/providers/beta/openlayers/src/appEvents.test.js +117 -0
  45. package/providers/beta/openlayers/src/defaults.js +36 -0
  46. package/providers/beta/openlayers/src/index.js +21 -0
  47. package/providers/beta/openlayers/src/mapEvents.js +132 -0
  48. package/providers/beta/openlayers/src/mapEvents.test.js +198 -0
  49. package/providers/beta/openlayers/src/openlayersProvider.js +249 -0
  50. package/providers/beta/openlayers/src/openlayersProvider.test.js +386 -0
  51. package/providers/beta/openlayers/src/utils/spatial.js +146 -0
  52. package/providers/beta/openlayers/src/utils/spatial.test.js +119 -0
  53. package/providers/beta/openlayers/src/utils/tileLayers.js +133 -0
  54. package/providers/beta/openlayers/src/utils/tileLayers.test.js +251 -0
  55. package/providers/beta/openlayers/src/utils/zoom.js +26 -0
  56. package/providers/beta/openlayers/src/utils/zoom.test.js +23 -0
  57. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  58. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  59. package/providers/maplibre/src/utils/highlightFeatures.js +17 -7
  60. package/providers/maplibre/src/utils/highlightFeatures.test.js +17 -0
  61. package/providers/maplibre/src/utils/maplibreFixes.js +1 -1
  62. package/providers/maplibre/src/utils/patternImages.js +14 -9
  63. package/providers/maplibre/src/utils/patternImages.test.js +2 -1
  64. package/rollup.esm.mjs +14 -3
  65. package/src/App/hooks/useFeatureItems.test.js +1 -0
  66. package/src/App/hooks/useLayoutMeasurements.test.js +0 -1
  67. package/src/App/hooks/useMarkersAPI.test.js +1 -0
  68. package/src/App/hooks/useModalPanelBehaviour.test.js +2 -1
  69. package/src/App/layout/layout.module.scss +1 -0
  70. package/src/App/store/AppProvider.test.jsx +5 -0
  71. package/src/App/store/MapProvider.test.jsx +8 -1
  72. package/src/services/announcer.test.js +5 -7
  73. package/src/types.js +11 -1
  74. package/webpack.dev.mjs +14 -5
  75. package/webpack.umd.mjs +1 -0
@@ -61,14 +61,19 @@ export const addPatternsToMap = async (map, styleArray, mapStyleId, patternRegis
61
61
  }
62
62
 
63
63
  const effectiveRatio = getEffectivePixelRatio(pixelRatio)
64
- await Promise.all(styleArray.map(async (config) => {
65
- const imageId = patternRegistry.getPatternImageId(config, mapStyleId, pixelRatio)
66
- if (!imageId || map.hasImage(imageId)) {
67
- return
64
+ // Build a unique set of imagesToAdd callbacks to avoid redundant rasterisation and map.addImage calls
65
+ const addImages = styleArray.reduce((imagesToAdd, style) => {
66
+ const imageId = patternRegistry.getPatternImageId(style, mapStyleId, pixelRatio)
67
+ if (imageId && !imagesToAdd[imageId] && !map.hasImage(imageId)) {
68
+ imagesToAdd[imageId] = async () => {
69
+ const result = await rasterisePattern(style, mapStyleId, patternRegistry, pixelRatio)
70
+ if (result) {
71
+ map.addImage(result.imageId, result.imageData, { pixelRatio: effectiveRatio })
72
+ }
73
+ }
68
74
  }
69
- const result = await rasterisePattern(config, mapStyleId, patternRegistry, pixelRatio)
70
- if (result) {
71
- map.addImage(result.imageId, result.imageData, { pixelRatio: effectiveRatio })
72
- }
73
- }))
75
+ return imagesToAdd
76
+ }, {})
77
+ // Execute the unique set of addImage callbacks in parallel
78
+ await Promise.all(Object.values(addImages).map(addImage => addImage()))
74
79
  }
@@ -76,7 +76,8 @@ describe('addPatternsToMap', () => {
76
76
 
77
77
  it('processes multiple configs in parallel', async () => {
78
78
  const map = makeMap()
79
- patternRegistry.register('dots', SVG_CONTENT)
79
+ patternRegistry.register('dots', "fillPatternForegroundColor: '#aabbcc', fillPatternBackgroundColor: '#112233'")
80
+ console.log('patternRegistry.list', patternRegistry.list())
80
81
  await addPatternsToMap(map, [{ fillPattern: 'stripes' }, { fillPattern: 'dots' }], OUTDOOR, patternRegistry)
81
82
  expect(map.addImage).toHaveBeenCalledTimes(2)
82
83
  })
package/rollup.esm.mjs CHANGED
@@ -82,7 +82,7 @@ const PREACT_EXTERNALS = [
82
82
  // dependency so it auto-installs for consumers and is resolved transparently.
83
83
  const BABEL_RUNTIME_EXTERNAL = /@babel\/runtime/
84
84
 
85
- const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null) => {
85
+ const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null, extraExternals = []) => {
86
86
  const esmDir = path.resolve(__dirname, outDir)
87
87
  // Use the parent dir as output.dir so CSS can be emitted to css/index.css
88
88
  // (a sibling subdir) without Rollup 4's ban on ".." in emitted file names.
@@ -108,7 +108,8 @@ const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null)
108
108
  // instance from their own node_modules rather than a 1 MB copy bundled
109
109
  // into the provider. (UMD keeps it bundled — no bundler available there.)
110
110
  'maplibre-gl',
111
- /^@arcgis\/core/
111
+ /^@arcgis\/core/,
112
+ ...extraExternals
112
113
  ],
113
114
 
114
115
  plugins: [
@@ -228,6 +229,16 @@ const ALL_BUILDS = [
228
229
  outDir: 'providers/beta/esri/dist/esm',
229
230
  manualChunks: (id) => { if (id.includes('/esriProvider')) return 'im-esri-provider' }
230
231
  },
232
+ {
233
+ entryPath: './providers/beta/openlayers/src/index.js',
234
+ outDir: 'providers/beta/openlayers/dist/esm',
235
+ extraExternals: [/^ol\//, 'proj4'],
236
+ manualChunks: (id) => {
237
+ if (id.includes('/openlayersProvider')) {
238
+ return 'im-openlayers-provider'
239
+ }
240
+ }
241
+ },
231
242
 
232
243
  // Plugins — each lazy-loads ./manifest.js; manualChunks names that split chunk
233
244
  {
@@ -293,5 +304,5 @@ const buildsToRun = BUILD_TARGET
293
304
  : ALL_BUILDS
294
305
 
295
306
  export default buildsToRun.map(b =>
296
- createESMConfig(b.entryPath, b.outDir, b.isCore || false, b.manualChunks || null)
307
+ createESMConfig(b.entryPath, b.outDir, b.isCore || false, b.manualChunks || null, b.extraExternals || [])
297
308
  )
@@ -47,6 +47,7 @@ describe('useFeatureItems — event subscription', () => {
47
47
  it('does not subscribe when eventBus is undefined', () => {
48
48
  const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
49
49
  renderHook(() => useFeatureItems(undefined))
50
+ expect(spy).not.toHaveBeenCalled()
50
51
  spy.mockRestore()
51
52
  })
52
53
  })
@@ -201,7 +201,6 @@ describe('useLayoutMeasurements', () => {
201
201
  const { dispatch } = setup({ app: { arePluginsEvaluated: false } })
202
202
  renderHook(() => useLayoutMeasurements())
203
203
  expect(dispatch).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'SET_SAFE_ZONE_INSET' }))
204
- expect(layoutRefs => layoutRefs).toBeDefined() // no layout calculation
205
204
  })
206
205
 
207
206
  test('re-dispatches safe zone when arePluginsEvaluated becomes true', () => {
@@ -117,6 +117,7 @@ describe('useMarkers — markerRef and positioning', () => {
117
117
  act(() => result.current.markerRef('m1')(ctx.mockElement))
118
118
  const renderCallback = ctx.mockEventBus.on.mock.calls.find(call => call[0] === 'map:render')?.[1]
119
119
  if (renderCallback) act(() => renderCallback())
120
+ expect(ctx.mockElement.style.transform).toBeUndefined()
120
121
  })
121
122
 
122
123
  it('updates positions on mapSize change', () => {
@@ -256,9 +256,10 @@ describe('useModalPanelBehaviour', () => {
256
256
  })
257
257
 
258
258
  it('handles null focus targets gracefully', () => {
259
+ refs.panel.current.focus = jest.fn()
259
260
  render(<TestComponent />)
260
261
  dispatchFocusIn(null)
261
- expect(true).toBe(true)
262
+ expect(refs.panel.current.focus).not.toHaveBeenCalled()
262
263
  })
263
264
  })
264
265
 
@@ -350,6 +350,7 @@
350
350
  .im-c-panel--side {
351
351
  width: 100%;
352
352
  max-width: 80%;
353
+ bottom: 0;
353
354
 
354
355
  @include tools.border-focus-base(
355
356
  $is-inset: true,
@@ -82,6 +82,9 @@ describe('AppProvider', () => {
82
82
  breakpointCb('sm')
83
83
  interfaceCb('mobile')
84
84
  })
85
+
86
+ expect(mockBreakpointDetector.subscribe).toHaveBeenCalledWith(expect.any(Function))
87
+ expect(detectInterface.subscribeToInterfaceChanges).toHaveBeenCalledWith(expect.any(Function))
85
88
  })
86
89
 
87
90
  test('handles eventBus setmode and revertmode', () => {
@@ -94,6 +97,8 @@ describe('AppProvider', () => {
94
97
  mockOptions.eventBus.off('app:setmode', capturedSetMode)
95
98
  mockOptions.eventBus.off('app:revertmode', capturedRevertMode)
96
99
  })
100
+ expect(mockOptions.eventBus.on).toHaveBeenCalledWith('app:setmode', capturedSetMode)
101
+ expect(mockOptions.eventBus.on).toHaveBeenCalledWith('app:revertmode', capturedRevertMode)
97
102
  })
98
103
 
99
104
  test('provides state, dispatch, and layoutRefs via context', () => {
@@ -186,9 +186,14 @@ describe('MapProvider', () => {
186
186
  })
187
187
 
188
188
  test('handles dispatch actions correctly', () => {
189
+ let contextValue
190
+ const Child = () => {
191
+ contextValue = React.useContext(MapContext)
192
+ return null
193
+ }
189
194
  render(
190
195
  <MapProvider options={{ id: 'map1', mapSize: '100x100', eventBus: mockEventBus }}>
191
- <div>Child</div>
196
+ <Child />
192
197
  </MapProvider>
193
198
  )
194
199
 
@@ -197,5 +202,7 @@ describe('MapProvider', () => {
197
202
  capturedHandlers['map:setstyle']({ id: 'style2' })
198
203
  capturedHandlers['map:setsize']('400x400')
199
204
  })
205
+
206
+ expect(contextValue.isMapReady).toBe(true)
200
207
  })
201
208
  })
@@ -24,21 +24,19 @@ describe('createAnnouncer', () => {
24
24
 
25
25
  it('does nothing if mapStatusRef.current is null', () => {
26
26
  const announceWithNull = createAnnouncer({ current: null })
27
- announceWithNull('Hello')
28
- jest.advanceTimersByTime(1000)
29
- expect(true).toBe(true) // just confirm no throw
27
+ expect(() => {
28
+ announceWithNull('Hello')
29
+ jest.advanceTimersByTime(1000)
30
+ }).not.toThrow()
30
31
  })
31
32
 
32
33
  it('returns early if mapStatusRef.current becomes null during setTimeout', () => {
33
34
  announce('Test message', 'plugin')
34
35
  expect(mapStatusRef.current.textContent).toBe('') // cleared immediately
35
36
 
36
- // Set current to null before setTimeout fires
37
37
  mapStatusRef.current = null
38
38
 
39
- jest.advanceTimersByTime(100)
40
- // Should not throw, just return early
41
- expect(true).toBe(true)
39
+ expect(() => jest.advanceTimersByTime(100)).not.toThrow()
42
40
  })
43
41
 
44
42
  it('sets textContent for a plugin message immediately', () => {
package/src/types.js CHANGED
@@ -403,8 +403,18 @@
403
403
  * Falls back to `#0b0c0c` (light) or `#ffffff` (dark).
404
404
  * Injected as the `--map-overlay-foreground-color` CSS custom property.
405
405
  *
406
+ * @property {'raster' | 'ogc-vt'} [type]
407
+ * Tile layer type. Controls how the map provider constructs the basemap source. Omit (or leave
408
+ * undefined) for the default Mapbox GL vector tile path. The OpenLayers provider supports all
409
+ * three types, enabling raster, standard vector tile, and OGC vector tile basemaps:
410
+ * - `'raster'` — XYZ raster tile source; `url` should be a tile URL template.
411
+ * - `'ogc-vt'` — OGC API - Tiles vector tile source; `url` should point to an OGC style endpoint
412
+ * that returns a Mapbox GL style document. **Currently only supported by the OpenLayers provider.**
413
+ *
406
414
  * @property {string} url
407
- * URL to the style.json (Mapbox Style Specification).
415
+ * URL for the style. For the default vector tile path and `'ogc-vt'`, this should be a URL that
416
+ * returns a Mapbox GL style document (Mapbox Style Specification). For `'raster'`, this should
417
+ * be an XYZ tile URL template with `{x}`, `{y}`, `{z}` placeholders.
408
418
  */
409
419
 
410
420
  /**
package/webpack.dev.mjs CHANGED
@@ -20,7 +20,9 @@ export default {
20
20
  index: path.join(__dirname, 'demo/js/index.js'),
21
21
  draw: path.join(__dirname, 'demo/js/draw.js'),
22
22
  farming: path.join(__dirname, 'demo/js/farming.js'),
23
- planning: path.join(__dirname, 'demo/js/planning.js')
23
+ planning: path.join(__dirname, 'demo/js/planning.js'),
24
+ 'planning-ol': path.join(__dirname, 'demo/js/planning-ol.js'),
25
+ gep: path.join(__dirname, 'demo/js/gep.js')
24
26
  },
25
27
  output: {
26
28
  path: path.resolve(__dirname, 'public'),
@@ -56,10 +58,10 @@ export default {
56
58
  new webpack.DefinePlugin({
57
59
  'process.env': {
58
60
  // OS Open Zoomstack
59
- OUTDOOR_URL: JSON.stringify(process.env.OUTDOOR_URL),
60
- NIGHT_URL: JSON.stringify(process.env.NIGHT_URL),
61
- DEUTERANOPIA_URL: JSON.stringify(process.env.DEUTERANOPIA_URL),
62
- TRITANOPIA_URL: JSON.stringify(process.env.TRITANOPIA_URL),
61
+ OZS_OUTDOOR_URL: JSON.stringify(process.env.OZS_OUTDOOR_URL),
62
+ OZS_NIGHT_URL: JSON.stringify(process.env.OZS_NIGHT_URL),
63
+ OZS_DEUTERANOPIA_URL: JSON.stringify(process.env.OZS_DEUTERANOPIA_URL),
64
+ OZS_TRITANOPIA_URL: JSON.stringify(process.env.OZS_TRITANOPIA_URL),
63
65
  // OS Vector Tile API (3857)
64
66
  VTS_OUTDOOR_URL: JSON.stringify(process.env.VTS_OUTDOOR_URL),
65
67
  VTS_DARK_URL: JSON.stringify(process.env.VTS_DARK_URL),
@@ -68,8 +70,15 @@ export default {
68
70
  VTS_OUTDOOR_URL_27700: JSON.stringify(process.env.VTS_OUTDOOR_URL_27700),
69
71
  VTS_DARK_URL_27700: JSON.stringify(process.env.VTS_DARK_URL_27700),
70
72
  VTS_BLACK_AND_WHITE_URL_27700: JSON.stringify(process.env.VTS_BLACK_AND_WHITE_URL_27700),
73
+ // OS NGD Tiles API (OGC, 27700)
74
+ NGD_OUTDOOR_URL_27700: JSON.stringify(process.env.NGD_OUTDOOR_URL_27700),
75
+ NGD_BLACK_AND_WHITE_URL_27700: JSON.stringify(process.env.NGD_BLACK_AND_WHITE_URL_27700),
71
76
  // Aerial photography
72
77
  AERIAL_URL: JSON.stringify(process.env.AERIAL_URL),
78
+ // OS Maps API (27700 raster)
79
+ MAPS_OUTDOOR_URL: JSON.stringify(process.env.MAPS_OUTDOOR_URL),
80
+ MAPS_ROAD_URL: JSON.stringify(process.env.MAPS_ROAD_URL),
81
+ MAPS_LIGHT_URL: JSON.stringify(process.env.MAPS_LIGHT_URL),
73
82
  // OS Auth
74
83
  OS_CLIENT_ID: JSON.stringify(process.env.OS_CLIENT_ID),
75
84
  OS_CLIENT_SECRET: JSON.stringify(process.env.OS_CLIENT_SECRET),
package/webpack.umd.mjs CHANGED
@@ -137,6 +137,7 @@ const ALL_BUILDS = [
137
137
  // Providers
138
138
  { entryPath: './providers/maplibre/src/index.js', libraryPath: 'maplibreProvider', outDir: 'providers/maplibre/dist/umd' },
139
139
  { entryPath: './providers/beta/open-names/src/index.js', libraryPath: 'openNamesProvider', outDir: 'providers/beta/open-names/dist/umd' },
140
+ { entryPath: './providers/beta/openlayers/src/index.js', libraryPath: 'openLayersProvider', outDir: 'providers/beta/openlayers/dist/umd' },
140
141
 
141
142
  // Plugins
142
143
  { entryPath: './plugins/beta/scale-bar/src/index.js', libraryPath: 'scaleBarPlugin', outDir: 'plugins/beta/scale-bar/dist/umd' },