@defra/interactive-map 0.0.15-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.
- package/assets/css/docusaurus.css +104 -0
- package/assets/images/favicon.svg +1 -0
- package/assets/images/hero.png +0 -0
- package/dist/esm/im-core.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/slot-map.svg +1 -0
- package/docs/api/slots.md +89 -6
- package/docs/api.md +1 -1
- package/docs/architecture.md +3 -1
- package/docs/{demo.mdx → examples.mdx} +1 -1
- package/docs/getting-started.md +1 -3
- package/docs/index.mdx +42 -0
- package/docs/plugins/interact.md +176 -55
- package/docs/plugins/map-styles.md +64 -7
- package/docs/plugins/search.md +207 -63
- package/docs/plugins.md +7 -15
- package/docusaurus.config.cjs +34 -34
- package/jest.setup.js +1 -1
- package/package.json +5 -4
- package/plugins/beta/datasets/src/DatasetsInit.jsx +1 -1
- package/plugins/beta/datasets/src/api/addDataset.js +1 -1
- package/plugins/beta/datasets/src/api/hideDataset.js +1 -1
- package/plugins/beta/datasets/src/api/hideFeatures.js +1 -1
- package/plugins/beta/datasets/src/api/removeDataset.js +1 -1
- package/plugins/beta/datasets/src/api/showDataset.js +1 -1
- package/plugins/beta/datasets/src/api/showFeatures.js +1 -1
- package/plugins/beta/datasets/src/datasets.js +4 -4
- package/plugins/beta/datasets/src/defaults.js +1 -1
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +5 -5
- package/plugins/beta/datasets/src/handleSetMapStyle.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +3 -3
- package/plugins/beta/datasets/src/mapLayers.js +2 -3
- package/plugins/beta/datasets/src/panels/Key.jsx +31 -29
- package/plugins/beta/datasets/src/panels/Layers.jsx +8 -9
- package/plugins/beta/datasets/src/utils/bbox.js +4 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
- package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
- package/plugins/beta/draw-es/src/events.js +52 -20
- package/plugins/beta/draw-es/src/events.test.js +301 -0
- package/plugins/beta/draw-es/src/graphic.js +1 -1
- package/plugins/beta/draw-es/src/manifest.js +4 -4
- package/plugins/beta/draw-es/src/reducer.js +1 -1
- package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
- package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
- package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
- package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
- package/plugins/beta/draw-ml/src/api/split.js +4 -4
- package/plugins/beta/draw-ml/src/defaults.js +1 -1
- package/plugins/beta/draw-ml/src/events.js +8 -6
- package/plugins/beta/draw-ml/src/manifest.js +15 -15
- package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
- package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
- package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
- package/plugins/beta/draw-ml/src/reducer.js +1 -1
- package/plugins/beta/draw-ml/src/undoStack.js +4 -4
- package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
- package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
- package/plugins/beta/frame/src/Frame.jsx +4 -4
- package/plugins/beta/frame/src/FrameInit.jsx +4 -4
- package/plugins/beta/frame/src/api/addFrame.js +1 -1
- package/plugins/beta/frame/src/api/editFeature.js +1 -1
- package/plugins/beta/frame/src/config.js +1 -1
- package/plugins/beta/frame/src/manifest.js +3 -3
- package/plugins/beta/frame/src/reducer.js +1 -1
- package/plugins/beta/frame/src/utils.js +1 -1
- package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
- package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
- package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
- package/plugins/beta/use-location/src/defaults.js +1 -1
- package/plugins/beta/use-location/src/events.js +3 -3
- package/plugins/interact/src/InteractInit.jsx +1 -2
- package/plugins/interact/src/api/enable.js +8 -5
- package/plugins/interact/src/api/enable.test.js +2 -2
- package/plugins/interact/src/api/selectFeature.js +4 -4
- package/plugins/interact/src/api/unselectFeature.js +5 -5
- package/plugins/interact/src/defaults.js +0 -1
- package/plugins/interact/src/events.test.js +15 -15
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
- package/plugins/interact/src/manifest.js +2 -2
- package/plugins/interact/src/manifest.test.js +3 -4
- package/plugins/interact/src/reducer.js +3 -3
- package/plugins/interact/src/reducer.test.js +0 -1
- package/plugins/interact/src/utils/spatial.js +10 -10
- package/plugins/interact/src/utils/spatial.test.js +14 -14
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +7 -6
- package/plugins/search/src/Search.test.jsx +23 -23
- package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
- package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
- package/plugins/search/src/components/Form/Form.jsx +14 -14
- package/plugins/search/src/components/Form/Form.test.jsx +11 -11
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
- package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
- package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
- package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
- package/plugins/search/src/datasets.js +12 -13
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +1 -1
- package/plugins/search/src/events/fetchSuggestions.js +3 -3
- package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
- package/plugins/search/src/events/formHandlers.js +3 -3
- package/plugins/search/src/events/formHandlers.test.js +1 -1
- package/plugins/search/src/events/index.js +2 -2
- package/plugins/search/src/events/index.test.js +2 -2
- package/plugins/search/src/events/inputHandlers.js +4 -4
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -2
- package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
- package/plugins/search/src/index.js +2 -1
- package/plugins/search/src/index.test.js +3 -3
- package/plugins/search/src/manifest.js +6 -4
- package/plugins/search/src/reducer.js +1 -2
- package/plugins/search/src/reducer.test.js +2 -2
- package/plugins/search/src/search.scss +10 -3
- package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
- package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
- package/plugins/search/src/utils/updateMap.js +1 -1
- package/plugins/search/src/utils/updateMap.test.js +5 -5
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +5 -5
- package/providers/beta/esri/src/utils/coords.js +1 -1
- package/providers/beta/esri/src/utils/esriFixes.js +1 -1
- package/providers/beta/esri/src/utils/query.js +4 -4
- package/providers/beta/esri/src/utils/spatial.js +1 -2
- package/providers/beta/esri/src/utils/spatial.test.js +4 -1
- package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
- package/providers/maplibre/src/appEvents.test.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +3 -5
- package/providers/maplibre/src/mapEvents.test.js +15 -5
- package/providers/maplibre/src/maplibreProvider.test.js +6 -2
- package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
- package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
- package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +2 -2
- package/providers/maplibre/src/utils/highlightFeatures.test.js +12 -6
- package/providers/maplibre/src/utils/labels.js +19 -20
- package/providers/maplibre/src/utils/labels.test.js +15 -13
- package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
- package/providers/maplibre/src/utils/queryFeatures.js +6 -6
- package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
- package/providers/maplibre/src/utils/spatial.js +0 -1
- package/providers/maplibre/src/utils/spatial.test.js +26 -27
- package/src/App/registry/pluginRegistry.js +17 -0
- package/src/App/registry/pluginRegistry.test.js +33 -0
- package/src/App/renderer/mapButtons.js +3 -2
- package/src/App/store/appDispatchMiddleware.js +33 -1
- package/src/App/store/appDispatchMiddleware.test.js +250 -222
- package/src/config/appConfig.js +2 -2
- package/src/utils/logger.js +6 -0
- package/src/utils/logger.test.js +32 -0
- package/webpack.dev.mjs +22 -18
- package/docs/govuk-prototype.md +0 -23
- package/docs/index.md +0 -19
|
@@ -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 } },
|
|
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 } },
|
|
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
|
+
})
|
|
@@ -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
|
|
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
|
|
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
|
+
})
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
// src/core/registry/pluginRegistry.js
|
|
2
2
|
import { registerIcon } from './iconRegistry.js'
|
|
3
3
|
import { registerKeyboardShortcut } from './keyboardShortcutRegistry.js'
|
|
4
|
+
import { allowedSlots } from '../renderer/slots.js'
|
|
5
|
+
import { logger } from '../../utils/logger.js'
|
|
4
6
|
|
|
5
7
|
const asArray = (value) => Array.isArray(value) ? value : [value]
|
|
6
8
|
|
|
9
|
+
const BREAKPOINTS = ['mobile', 'tablet', 'desktop']
|
|
10
|
+
|
|
11
|
+
function validateSlots (item, type) {
|
|
12
|
+
const allowed = allowedSlots[type]
|
|
13
|
+
BREAKPOINTS.forEach(bp => {
|
|
14
|
+
const slot = item[bp]?.slot
|
|
15
|
+
if (slot && !allowed.includes(slot) && !(type === 'panel' && slot.endsWith('-button'))) {
|
|
16
|
+
logger.warn(`${type} "${item.id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowed.join(', ')}.`)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export function createPluginRegistry ({ registerButton, registerPanel, registerControl }) {
|
|
8
22
|
const registeredPlugins = []
|
|
9
23
|
|
|
@@ -18,6 +32,7 @@ export function createPluginRegistry ({ registerButton, registerPanel, registerC
|
|
|
18
32
|
|
|
19
33
|
if (manifest.buttons) {
|
|
20
34
|
asArray(manifest.buttons).forEach(button => {
|
|
35
|
+
validateSlots(button, 'button')
|
|
21
36
|
registerButton({ [button.id]: { ...pluginConfig, ...button } })
|
|
22
37
|
// Flat button registry including any menu items (isMenuItem prevents slot rendering)
|
|
23
38
|
button?.menuItems?.forEach(menuItem => {
|
|
@@ -28,12 +43,14 @@ export function createPluginRegistry ({ registerButton, registerPanel, registerC
|
|
|
28
43
|
|
|
29
44
|
if (manifest.panels) {
|
|
30
45
|
asArray(manifest.panels).forEach(panel => {
|
|
46
|
+
validateSlots(panel, 'panel')
|
|
31
47
|
registerPanel({ [panel.id]: { ...pluginConfig, ...panel } })
|
|
32
48
|
})
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
if (manifest.controls) {
|
|
36
52
|
asArray(manifest.controls).forEach(control => {
|
|
53
|
+
validateSlots(control, 'control')
|
|
37
54
|
registerControl({ [control.id]: { ...pluginConfig, ...control } })
|
|
38
55
|
})
|
|
39
56
|
}
|
|
@@ -139,6 +139,39 @@ describe('pluginRegistry', () => {
|
|
|
139
139
|
expect(pluginRegistry.registeredPlugins).toEqual([pluginA, pluginB])
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
+
describe('slot validation', () => {
|
|
143
|
+
const INVALID_SLOT = 'invalid-slot'
|
|
144
|
+
|
|
145
|
+
beforeEach(() => jest.spyOn(console, 'warn').mockImplementation(() => {}))
|
|
146
|
+
afterEach(() => jest.restoreAllMocks())
|
|
147
|
+
|
|
148
|
+
it('warns when a manifest item has an invalid slot', () => {
|
|
149
|
+
const plugin = {
|
|
150
|
+
id: 'bad-plugin',
|
|
151
|
+
config: {},
|
|
152
|
+
manifest: {
|
|
153
|
+
buttons: [{ id: 'btn1', desktop: { slot: INVALID_SLOT } }],
|
|
154
|
+
panels: [{ id: 'panel1', desktop: { slot: INVALID_SLOT } }],
|
|
155
|
+
controls: [{ id: 'ctrl1', desktop: { slot: INVALID_SLOT } }]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
pluginRegistry.registerPlugin(plugin)
|
|
159
|
+
expect(console.warn).toHaveBeenCalledWith('[interactive-map]', expect.stringContaining(INVALID_SLOT))
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('does not warn for a panel with a button-adjacent slot', () => {
|
|
163
|
+
const plugin = {
|
|
164
|
+
id: 'adj-plugin',
|
|
165
|
+
config: {},
|
|
166
|
+
manifest: {
|
|
167
|
+
panels: [{ id: 'panel1', desktop: { slot: 'left-top-button' } }]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
pluginRegistry.registerPlugin(plugin)
|
|
171
|
+
expect(console.warn).not.toHaveBeenCalled()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
142
175
|
it('clears all registered plugins', () => {
|
|
143
176
|
const pluginA = { id: 'A', config: {}, manifest: {} }
|
|
144
177
|
const pluginB = { id: 'B', config: {}, manifest: {} }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/core/renderers/mapButtons.js
|
|
2
2
|
import { MapButton } from '../components/MapButton/MapButton.jsx'
|
|
3
3
|
import { allowedSlots } from './slots.js'
|
|
4
|
+
import { logger } from '../../utils/logger.js'
|
|
4
5
|
|
|
5
6
|
function getMatchingButtons ({ appState, buttonConfig, slot, evaluateProp }) {
|
|
6
7
|
const { breakpoint, mode } = appState
|
|
@@ -163,7 +164,7 @@ function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
|
163
164
|
|
|
164
165
|
/* istanbul ignore next */
|
|
165
166
|
if (process.env.NODE_ENV !== 'production' && typeof group === 'string') {
|
|
166
|
-
|
|
167
|
+
logger.warn(`Button "${buttonId}": group should be an object { name, label?, order? } — string groups are deprecated.`)
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
const name = resolveGroupName(group)
|
|
@@ -174,7 +175,7 @@ function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
|
174
175
|
const existing = groupMap.get(name)
|
|
175
176
|
/* istanbul ignore next */
|
|
176
177
|
if (process.env.NODE_ENV !== 'production' && existing.order !== order) {
|
|
177
|
-
|
|
178
|
+
logger.warn(`Group "${name}" has inconsistent order values (${existing.order} vs ${order}). Using the lower value.`)
|
|
178
179
|
existing.order = Math.min(existing.order, order)
|
|
179
180
|
}
|
|
180
181
|
} else {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// src/App/store/dispatchMiddleware.js
|
|
2
2
|
import { EVENTS as events } from '../../config/events.js'
|
|
3
|
-
import { defaultPanelConfig } from '../../config/appConfig.js'
|
|
3
|
+
import { defaultPanelConfig, defaultButtonConfig, defaultControlConfig } from '../../config/appConfig.js'
|
|
4
4
|
import { deepMerge } from '../../utils/deepMerge.js'
|
|
5
|
+
import { allowedSlots } from '../renderer/slots.js'
|
|
6
|
+
import { logger } from '../../utils/logger.js'
|
|
7
|
+
|
|
8
|
+
const BREAKPOINTS = ['mobile', 'tablet', 'desktop']
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Determines which panels were implicitly closed when opening a new panel
|
|
@@ -81,9 +85,37 @@ export function handleActionSideEffects (action, previousState, panelConfig, eve
|
|
|
81
85
|
})
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
if (type === 'ADD_BUTTON') {
|
|
89
|
+
const { id, config } = payload
|
|
90
|
+
const mergedConfig = deepMerge(defaultButtonConfig, config)
|
|
91
|
+
BREAKPOINTS.forEach(bp => {
|
|
92
|
+
const slot = mergedConfig[bp]?.slot
|
|
93
|
+
if (slot && !allowedSlots.button.includes(slot)) {
|
|
94
|
+
logger.warn(`button "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.button.join(', ')}.`)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (type === 'ADD_CONTROL') {
|
|
100
|
+
const { id, config } = payload
|
|
101
|
+
const mergedConfig = deepMerge(defaultControlConfig, config)
|
|
102
|
+
BREAKPOINTS.forEach(bp => {
|
|
103
|
+
const slot = mergedConfig[bp]?.slot
|
|
104
|
+
if (slot && !allowedSlots.control.includes(slot)) {
|
|
105
|
+
logger.warn(`control "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.control.join(', ')}.`)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
if (type === 'ADD_PANEL') {
|
|
85
111
|
const { id, config } = payload
|
|
86
112
|
const mergedConfig = deepMerge(defaultPanelConfig, config)
|
|
113
|
+
BREAKPOINTS.forEach(bp => {
|
|
114
|
+
const slot = mergedConfig[bp]?.slot
|
|
115
|
+
if (slot && !allowedSlots.panel.includes(slot) && !slot.endsWith('-button')) {
|
|
116
|
+
logger.warn(`panel "${id}" has invalid slot "${slot}" at breakpoint "${bp}". Allowed slots: ${allowedSlots.panel.join(', ')}.`)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
87
119
|
const bpConfig = mergedConfig[previousState.breakpoint]
|
|
88
120
|
if (bpConfig?.open) {
|
|
89
121
|
queueMicrotask(() => {
|