@defra/interactive-map 0.0.19-alpha → 0.0.21-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.
- package/README.md +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +22 -5
- package/docs/api/map-style-config.md +2 -11
- package/docs/api/marker-config.md +19 -5
- package/docs/api.md +75 -46
- package/docs/architecture.md +0 -2
- package/{assets → docs/assets}/css/docusaurus.css +78 -1
- package/docs/assets/images/basic-map.jpg +0 -0
- package/docs/assets/images/button-first.jpg +0 -0
- package/{assets → docs/assets}/images/hero.png +0 -0
- package/docs/assets/images/marker-panel.jpg +0 -0
- package/docs/examples/add-marker-with-panel.mdx +112 -48
- package/docs/examples/basic-map.mdx +43 -14
- package/docs/examples/button-map.mdx +43 -14
- package/docs/examples/index.mdx +5 -5
- package/docs/getting-started.md +80 -53
- package/docs/plugins/datasets.md +11 -11
- package/docusaurus.config.cjs +5 -4
- package/package.json +7 -3
- package/plugins/beta/datasets/adapters/maplibre/dist/esm/im-datasets-ml-adapter.js +1 -0
- package/plugins/beta/datasets/adapters/maplibre/dist/esm/index.js +1 -0
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/im-datasets-ml-adapter.js +2 -0
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/im-datasets-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/index.js +1 -0
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +4 -4
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +26 -7
- package/plugins/beta/datasets/src/defaults.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +1 -1
- package/plugins/beta/datasets/src/panels/Layers.jsx +4 -4
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +1 -1
- package/plugins/beta/draw-ml/dist/css/index.css +19 -2
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/maplibreProvider.js +5 -3
- package/providers/maplibre/src/maplibreProvider.test.js +44 -2
- package/providers/maplibre/src/utils/patternImages.js +16 -10
- package/providers/maplibre/src/utils/patternImages.test.js +50 -19
- package/rollup.esm.mjs +9 -0
- package/src/InteractiveMap/InteractiveMap.js +2 -2
- package/src/InteractiveMap/InteractiveMap.test.js +4 -4
- package/src/services/eventBus.js +61 -0
- package/src/services/eventBus.test.js +70 -0
- package/src/utils/patternUtils.js +8 -3
- package/src/utils/patternUtils.test.js +17 -0
- package/webpack.umd.mjs +1 -0
- package/docs/assets/basic-map.jpg +0 -0
- package/docs/assets/button-first.jpg +0 -0
- package/docs/assets/maker-panel.jpg +0 -0
- /package/docs/assets/{screens-blue.jpg → images/screens-blue.jpg} +0 -0
- /package/docs/assets/{screens-white.jpg → images/screens-white.jpg} +0 -0
- /package/{assets → docs/assets}/images/slot-map.svg +0 -0
package/rollup.esm.mjs
CHANGED
|
@@ -255,6 +255,15 @@ const ALL_BUILDS = [
|
|
|
255
255
|
outDir: 'plugins/beta/datasets/dist/esm',
|
|
256
256
|
manualChunks: (id) => { if (id.includes('/manifest')) return 'im-datasets-plugin' }
|
|
257
257
|
},
|
|
258
|
+
{
|
|
259
|
+
entryPath: './plugins/beta/datasets/src/adapters/maplibre/index.js',
|
|
260
|
+
outDir: 'plugins/beta/datasets/adapters/maplibre/dist/esm',
|
|
261
|
+
manualChunks: (id) => {
|
|
262
|
+
if (id.includes('maplibreLayerAdapter')) {
|
|
263
|
+
return 'im-datasets-ml-adapter'
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
258
267
|
{
|
|
259
268
|
entryPath: './plugins/beta/map-styles/src/index.js',
|
|
260
269
|
outDir: 'plugins/beta/map-styles/dist/esm',
|
|
@@ -437,7 +437,7 @@ export default class InteractiveMap {
|
|
|
437
437
|
* @param {[number, number, number, number] | object} target - Bounds as [west, south, east, north] or [minX, minY, maxX, maxY] depending on the crs, or a GeoJSON Feature, FeatureCollection, or geometry.
|
|
438
438
|
*/
|
|
439
439
|
fitToBounds (target) {
|
|
440
|
-
this.eventBus.
|
|
440
|
+
this.eventBus.emitWhenReady(events.MAP_FIT_TO_BOUNDS, target)
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
/**
|
|
@@ -446,7 +446,7 @@ export default class InteractiveMap {
|
|
|
446
446
|
* @param {{ center?: [number, number], zoom?: number }} opts - View options.
|
|
447
447
|
*/
|
|
448
448
|
setView (opts) {
|
|
449
|
-
this.eventBus.
|
|
449
|
+
this.eventBus.emitWhenReady(events.MAP_SET_VIEW, opts)
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
/**
|
|
@@ -39,8 +39,8 @@ jest.mock('../utils/detectInterfaceType.js', () => ({
|
|
|
39
39
|
}))
|
|
40
40
|
jest.mock('../services/reverseGeocode.js', () => ({ createReverseGeocode: jest.fn() }))
|
|
41
41
|
jest.mock('../services/eventBus.js', () => ({
|
|
42
|
-
createEventBus: jest.fn(() => ({ on: jest.fn(), off: jest.fn(), emit: jest.fn(), destroy: jest.fn() })),
|
|
43
|
-
default: { on: jest.fn(), off: jest.fn(), emit: jest.fn(), destroy: jest.fn() }
|
|
42
|
+
createEventBus: jest.fn(() => ({ on: jest.fn(), off: jest.fn(), emit: jest.fn(), emitWhenReady: jest.fn(), destroy: jest.fn() })),
|
|
43
|
+
default: { on: jest.fn(), off: jest.fn(), emit: jest.fn(), emitWhenReady: jest.fn(), destroy: jest.fn() }
|
|
44
44
|
}))
|
|
45
45
|
jest.mock('../App/initialiseApp.js', () => ({ initialiseApp: jest.fn() }))
|
|
46
46
|
|
|
@@ -635,12 +635,12 @@ describe('InteractiveMap Public API Methods', () => {
|
|
|
635
635
|
it('fitToBounds emits MAP_FIT_TO_BOUNDS with bbox', () => {
|
|
636
636
|
const bbox = [-0.489, 51.28, 0.236, 51.686]
|
|
637
637
|
map.fitToBounds(bbox)
|
|
638
|
-
expect(map.eventBus.
|
|
638
|
+
expect(map.eventBus.emitWhenReady).toHaveBeenCalledWith('map:fittobounds', bbox)
|
|
639
639
|
})
|
|
640
640
|
|
|
641
641
|
it('setView emits MAP_SET_VIEW with opts', () => {
|
|
642
642
|
const opts = { center: [-0.1276, 51.5074], zoom: 12 }
|
|
643
643
|
map.setView(opts)
|
|
644
|
-
expect(map.eventBus.
|
|
644
|
+
expect(map.eventBus.emitWhenReady).toHaveBeenCalledWith('map:setview', opts)
|
|
645
645
|
})
|
|
646
646
|
})
|
package/src/services/eventBus.js
CHANGED
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
class EventBus {
|
|
2
2
|
constructor () {
|
|
3
3
|
this.events = {}
|
|
4
|
+
this._queued = {}
|
|
4
5
|
}
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Register a handler for an event. If a value was queued via `emitWhenReady`
|
|
9
|
+
* before any listeners existed, the handler is called immediately with that
|
|
10
|
+
* value and the queue is cleared.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} eventName
|
|
13
|
+
* @param {Function} handler
|
|
14
|
+
* @returns {this}
|
|
15
|
+
*/
|
|
6
16
|
on (eventName, handler) {
|
|
7
17
|
if (!this.events[eventName]) {
|
|
8
18
|
this.events[eventName] = []
|
|
9
19
|
}
|
|
10
20
|
this.events[eventName].push(handler)
|
|
21
|
+
|
|
22
|
+
if (this._queued[eventName] !== undefined) {
|
|
23
|
+
try {
|
|
24
|
+
handler(...this._queued[eventName])
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error in event handler for '${eventName}':`, error)
|
|
27
|
+
}
|
|
28
|
+
delete this._queued[eventName]
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
return this
|
|
12
32
|
}
|
|
13
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Remove a handler for an event. Omit `handler` to remove all handlers.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} eventName
|
|
38
|
+
* @param {Function} [handler]
|
|
39
|
+
* @returns {this}
|
|
40
|
+
*/
|
|
14
41
|
off (eventName, handler) {
|
|
15
42
|
if (!this.events[eventName]) {
|
|
16
43
|
return this
|
|
@@ -23,6 +50,13 @@ class EventBus {
|
|
|
23
50
|
return this
|
|
24
51
|
}
|
|
25
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Emit an event, calling all registered handlers synchronously.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} eventName
|
|
57
|
+
* @param {...*} args
|
|
58
|
+
* @returns {this}
|
|
59
|
+
*/
|
|
26
60
|
emit (eventName, ...args) {
|
|
27
61
|
if (!this.events[eventName]) {
|
|
28
62
|
return this
|
|
@@ -38,13 +72,40 @@ class EventBus {
|
|
|
38
72
|
return this
|
|
39
73
|
}
|
|
40
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Like `emit`, but queues the args if no listeners are registered yet.
|
|
77
|
+
* The first listener to subscribe will receive the queued value immediately,
|
|
78
|
+
* after which the queue is cleared. Subsequent `emitWhenReady` calls for the
|
|
79
|
+
* same event before a listener arrives replace the queued value.
|
|
80
|
+
*
|
|
81
|
+
* Use this for events that may be emitted before the listener side is ready
|
|
82
|
+
* (e.g. called from `map:ready` before React effects have run).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} eventName
|
|
85
|
+
* @param {...*} args
|
|
86
|
+
* @returns {this}
|
|
87
|
+
*/
|
|
88
|
+
emitWhenReady (eventName, ...args) {
|
|
89
|
+
if (!this.events[eventName] || this.events[eventName].length === 0) {
|
|
90
|
+
this._queued[eventName] = args
|
|
91
|
+
return this
|
|
92
|
+
}
|
|
93
|
+
return this.emit(eventName, ...args)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove all handlers and clear any queued events.
|
|
98
|
+
*/
|
|
41
99
|
destroy () {
|
|
42
100
|
this.events = {}
|
|
101
|
+
this._queued = {}
|
|
43
102
|
}
|
|
44
103
|
}
|
|
45
104
|
|
|
46
105
|
/**
|
|
47
106
|
* Factory for map-local EventBus
|
|
107
|
+
*
|
|
108
|
+
* @returns {EventBus}
|
|
48
109
|
*/
|
|
49
110
|
export function createEventBus () {
|
|
50
111
|
return new EventBus()
|
|
@@ -94,6 +94,76 @@ describe('EventBus singleton', () => {
|
|
|
94
94
|
})
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
+
describe('emitWhenReady', () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
eventBus.destroy()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('fires immediately when listeners are already registered', () => {
|
|
103
|
+
const handler = jest.fn()
|
|
104
|
+
eventBus.on('ready', handler)
|
|
105
|
+
|
|
106
|
+
eventBus.emitWhenReady('ready', 'value')
|
|
107
|
+
|
|
108
|
+
expect(handler).toHaveBeenCalledWith('value')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('queues the call and replays to the first subscriber when no listeners exist', () => {
|
|
112
|
+
const handler = jest.fn()
|
|
113
|
+
|
|
114
|
+
eventBus.emitWhenReady('ready', 'value')
|
|
115
|
+
expect(handler).not.toHaveBeenCalled()
|
|
116
|
+
|
|
117
|
+
eventBus.on('ready', handler)
|
|
118
|
+
expect(handler).toHaveBeenCalledWith('value')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('clears the queue after replaying so subsequent subscribers do not receive it', () => {
|
|
122
|
+
const first = jest.fn()
|
|
123
|
+
const second = jest.fn()
|
|
124
|
+
|
|
125
|
+
eventBus.emitWhenReady('ready', 'value')
|
|
126
|
+
eventBus.on('ready', first)
|
|
127
|
+
eventBus.on('ready', second)
|
|
128
|
+
|
|
129
|
+
expect(first).toHaveBeenCalledWith('value')
|
|
130
|
+
expect(second).not.toHaveBeenCalled()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('replaces a queued value if emitWhenReady is called again before a subscriber arrives', () => {
|
|
134
|
+
const handler = jest.fn()
|
|
135
|
+
|
|
136
|
+
eventBus.emitWhenReady('ready', 'first')
|
|
137
|
+
eventBus.emitWhenReady('ready', 'second')
|
|
138
|
+
eventBus.on('ready', handler)
|
|
139
|
+
|
|
140
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
141
|
+
expect(handler).toHaveBeenCalledWith('second')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('catches and logs errors thrown by a handler during queued replay', () => {
|
|
145
|
+
const error = new Error('boom')
|
|
146
|
+
const handler = jest.fn(() => { throw error })
|
|
147
|
+
jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
148
|
+
|
|
149
|
+
eventBus.emitWhenReady('ready', 'value')
|
|
150
|
+
eventBus.on('ready', handler)
|
|
151
|
+
|
|
152
|
+
expect(console.error).toHaveBeenCalledWith("Error in event handler for 'ready':", error)
|
|
153
|
+
console.error.mockRestore()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('clears queued events on destroy', () => {
|
|
157
|
+
const handler = jest.fn()
|
|
158
|
+
|
|
159
|
+
eventBus.emitWhenReady('ready', 'value')
|
|
160
|
+
eventBus.destroy()
|
|
161
|
+
eventBus.on('ready', handler)
|
|
162
|
+
|
|
163
|
+
expect(handler).not.toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
97
167
|
describe('createEventBus factory', () => {
|
|
98
168
|
/**
|
|
99
169
|
* Test to ensure coverage for the factory function (Line 50).
|
|
@@ -52,21 +52,26 @@ export const getPatternInnerContent = (dataset, patternRegistry) => {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Returns a deterministic image ID for a pattern + resolved colour combination.
|
|
55
|
+
* Returns a deterministic image ID for a pattern + resolved colour + pixel ratio combination.
|
|
56
56
|
*
|
|
57
57
|
* @param {Object} dataset
|
|
58
58
|
* @param {string} mapStyleId
|
|
59
59
|
* @param {Object} patternRegistry
|
|
60
|
+
* @param {number} [pixelRatio=1]
|
|
60
61
|
* @returns {string|null}
|
|
61
62
|
*/
|
|
62
|
-
|
|
63
|
+
// Minimum oversampling — keeps 16×16 physical pixels as the floor so patterns remain crisp.
|
|
64
|
+
export const PATTERN_MIN_PIXEL_RATIO = 2
|
|
65
|
+
|
|
66
|
+
export const getPatternImageId = (dataset, mapStyleId, patternRegistry, pixelRatio = 1) => {
|
|
63
67
|
const innerContent = getPatternInnerContent(dataset, patternRegistry)
|
|
64
68
|
if (!innerContent) {
|
|
65
69
|
return null
|
|
66
70
|
}
|
|
67
71
|
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
68
72
|
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
69
|
-
|
|
73
|
+
const effectiveRatio = Math.max(PATTERN_MIN_PIXEL_RATIO, pixelRatio)
|
|
74
|
+
return `pattern-${hashString(innerContent + fg + bg)}-${effectiveRatio}x`
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
@@ -103,6 +103,23 @@ describe('getPatternImageId', () => {
|
|
|
103
103
|
expect(idA).not.toBe(idB)
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
test('floors effective ratio at PATTERN_MIN_PIXEL_RATIO so low-DPI ids match 2x', () => {
|
|
107
|
+
const dataset = { fillPattern: 'dot' }
|
|
108
|
+
const id1x = getPatternImageId(dataset, 'style-a', mockRegistry, 1)
|
|
109
|
+
const id2x = getPatternImageId(dataset, 'style-a', mockRegistry, 2)
|
|
110
|
+
expect(id1x).toBe(id2x)
|
|
111
|
+
expect(id1x).toMatch(/-2x$/)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('produces different ids for pixelRatios above the floor', () => {
|
|
115
|
+
const dataset = { fillPattern: 'dot' }
|
|
116
|
+
const id2x = getPatternImageId(dataset, 'style-a', mockRegistry, 2)
|
|
117
|
+
const id3x = getPatternImageId(dataset, 'style-a', mockRegistry, 3)
|
|
118
|
+
expect(id2x).not.toBe(id3x)
|
|
119
|
+
expect(id2x).toMatch(/-2x$/)
|
|
120
|
+
expect(id3x).toMatch(/-3x$/)
|
|
121
|
+
})
|
|
122
|
+
|
|
106
123
|
test('falls back to "black" foreground and "transparent" background when colours are absent', () => {
|
|
107
124
|
const id = getPatternImageId({ fillPattern: 'dot' }, 'style-a', mockRegistry)
|
|
108
125
|
const idExplicit = getPatternImageId(
|
package/webpack.umd.mjs
CHANGED
|
@@ -140,6 +140,7 @@ const ALL_BUILDS = [
|
|
|
140
140
|
{ entryPath: './plugins/search/src/index.js', libraryPath: 'searchPlugin', outDir: 'plugins/search/dist/umd' },
|
|
141
141
|
{ entryPath: './plugins/interact/src/index.js', libraryPath: 'interactPlugin', outDir: 'plugins/interact/dist/umd' },
|
|
142
142
|
{ entryPath: './plugins/beta/datasets/src/index.js', libraryPath: 'datasetsPlugin', outDir: 'plugins/beta/datasets/dist/umd' },
|
|
143
|
+
{ entryPath: './plugins/beta/datasets/src/adapters/maplibre/index.js', libraryPath: 'datasetsMaplibreAdapter', outDir: 'plugins/beta/datasets/adapters/maplibre/dist/umd' },
|
|
143
144
|
{ entryPath: './plugins/beta/map-styles/src/index.js', libraryPath: 'mapStylesPlugin', outDir: 'plugins/beta/map-styles/dist/umd' },
|
|
144
145
|
{ entryPath: './plugins/beta/draw-ml/src/index.js', libraryPath: 'drawMLPlugin', outDir: 'plugins/beta/draw-ml/dist/umd' },
|
|
145
146
|
{ entryPath: './plugins/beta/frame/src/index.js', libraryPath: 'framePlugin', outDir: 'plugins/beta/frame/dist/umd' }
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|