@defra/interactive-map 0.0.23-alpha → 0.0.24-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/images/light-raster-thumb.jpg +0 -0
- package/assets/images/outdoor-raster-thumb.jpg +0 -0
- package/assets/images/road-raster-thumb.jpg +0 -0
- package/docs/plugins/search.md +70 -0
- package/package.json +16 -2
- package/plugins/beta/draw-ml/dist/css/index.css +1 -21
- 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/api/editFeature.js +9 -4
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +11 -3
- package/plugins/interact/src/hooks/useAttachEvents.test.js +3 -0
- 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 +2 -1
- package/plugins/search/src/components/Form/Form.jsx +11 -7
- package/plugins/search/src/components/Form/Form.test.jsx +23 -1
- package/plugins/search/src/datasets.js +2 -2
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +7 -1
- package/plugins/search/src/events/fetchSuggestions.js +13 -10
- package/plugins/search/src/events/fetchSuggestions.test.js +14 -1
- package/plugins/search/src/events/formHandlers.js +2 -4
- package/plugins/search/src/events/formHandlers.test.js +3 -3
- package/plugins/search/src/events/inputHandlers.js +1 -1
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -1
- package/plugins/search/src/events/suggestionHandlers.test.js +7 -1
- package/plugins/search/src/index.js +3 -7
- package/plugins/search/src/reducer.js +10 -5
- package/plugins/search/src/reducer.test.js +2 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +3 -3
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -0
- package/providers/beta/openlayers/dist/esm/index.js +1 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +2 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/dist/umd/index.js +2 -0
- package/providers/beta/openlayers/dist/umd/index.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/src/appEvents.js +50 -0
- package/providers/beta/openlayers/src/appEvents.test.js +140 -0
- package/providers/beta/openlayers/src/defaults.js +36 -0
- package/providers/beta/openlayers/src/index.js +21 -0
- package/providers/beta/openlayers/src/mapEvents.js +132 -0
- package/providers/beta/openlayers/src/mapEvents.test.js +197 -0
- package/providers/beta/openlayers/src/openlayersProvider.js +234 -0
- package/providers/beta/openlayers/src/openlayersProvider.test.js +331 -0
- package/providers/beta/openlayers/src/utils/spatial.js +142 -0
- package/providers/beta/openlayers/src/utils/spatial.test.js +117 -0
- package/providers/beta/openlayers/src/utils/zoom.js +26 -0
- package/providers/beta/openlayers/src/utils/zoom.test.js +23 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +17 -7
- package/providers/maplibre/src/utils/highlightFeatures.test.js +17 -0
- package/providers/maplibre/src/utils/patternImages.js +14 -9
- package/providers/maplibre/src/utils/patternImages.test.js +2 -1
- package/rollup.esm.mjs +14 -3
- package/src/App/hooks/useFeatureItems.test.js +1 -0
- package/src/App/hooks/useLayoutMeasurements.test.js +0 -1
- package/src/App/hooks/useMarkersAPI.test.js +1 -0
- package/src/App/hooks/useModalPanelBehaviour.test.js +2 -1
- package/src/App/store/AppProvider.test.jsx +5 -0
- package/src/App/store/MapProvider.test.jsx +8 -1
- package/src/services/announcer.test.js +5 -7
- package/webpack.dev.mjs +6 -1
- package/webpack.umd.mjs +1 -0
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { useEffect } from 'react'
|
|
3
3
|
import { Suggestions } from '../Suggestions/Suggestions'
|
|
4
4
|
|
|
5
|
-
const getResultMessage = (count) => {
|
|
5
|
+
const getResultMessage = (count, hasSearchError, searchErrorMessage, noResultsMessage) => {
|
|
6
|
+
if (hasSearchError) {
|
|
7
|
+
return searchErrorMessage
|
|
8
|
+
}
|
|
6
9
|
if (count === 0) {
|
|
7
|
-
return
|
|
10
|
+
return noResultsMessage
|
|
8
11
|
}
|
|
9
12
|
const plural = count === 1 ? 'result' : 'results'
|
|
10
13
|
return `${count} ${plural} available`
|
|
@@ -25,7 +28,8 @@ export const Form = ({
|
|
|
25
28
|
services,
|
|
26
29
|
children // For SearchClose
|
|
27
30
|
}) => {
|
|
28
|
-
const { areSuggestionsVisible, hasFetchedSuggestions, suggestions = [] } = pluginState
|
|
31
|
+
const { areSuggestionsVisible, hasFetchedSuggestions, hasSearchError, suggestions = [] } = pluginState
|
|
32
|
+
const { noResultsMessage, searchErrorMessage } = pluginConfig
|
|
29
33
|
|
|
30
34
|
// Announce when a fetch has completed (hasFetchedSuggestions flips to true),
|
|
31
35
|
// not when the input is merely focused/clicked (SHOW_SUGGESTIONS resets it to false).
|
|
@@ -33,7 +37,7 @@ export const Form = ({
|
|
|
33
37
|
if (!areSuggestionsVisible || !hasFetchedSuggestions) {
|
|
34
38
|
return
|
|
35
39
|
}
|
|
36
|
-
services.announce(getResultMessage(suggestions.length))
|
|
40
|
+
services.announce(getResultMessage(suggestions.length, hasSearchError, searchErrorMessage, noResultsMessage))
|
|
37
41
|
}, [suggestions, hasFetchedSuggestions])
|
|
38
42
|
|
|
39
43
|
const classNames = [
|
|
@@ -42,7 +46,7 @@ export const Form = ({
|
|
|
42
46
|
'im-c-panel'
|
|
43
47
|
].filter(Boolean).join(' ')
|
|
44
48
|
|
|
45
|
-
const
|
|
49
|
+
const showStatus = areSuggestionsVisible && hasFetchedSuggestions && (hasSearchError || !suggestions.length)
|
|
46
50
|
|
|
47
51
|
return (
|
|
48
52
|
<form
|
|
@@ -89,9 +93,9 @@ export const Form = ({
|
|
|
89
93
|
{/* Close button passed as child */}
|
|
90
94
|
{children}
|
|
91
95
|
</div>
|
|
92
|
-
{
|
|
96
|
+
{showStatus && (
|
|
93
97
|
<div className='im-c-search__status' aria-hidden='true'>
|
|
94
|
-
|
|
98
|
+
{hasSearchError ? searchErrorMessage : noResultsMessage}
|
|
95
99
|
</div>
|
|
96
100
|
)}
|
|
97
101
|
<Suggestions
|
|
@@ -32,7 +32,9 @@ describe('Form', () => {
|
|
|
32
32
|
},
|
|
33
33
|
pluginConfig: {
|
|
34
34
|
expanded: false,
|
|
35
|
-
width: '400px'
|
|
35
|
+
width: '400px',
|
|
36
|
+
noResultsMessage: 'No results available',
|
|
37
|
+
searchErrorMessage: 'Sorry, there was a problem with search'
|
|
36
38
|
},
|
|
37
39
|
appState: {
|
|
38
40
|
breakpoint: 'desktop',
|
|
@@ -251,5 +253,25 @@ describe('Form', () => {
|
|
|
251
253
|
)
|
|
252
254
|
expect(baseProps.services.announce).not.toHaveBeenCalled()
|
|
253
255
|
})
|
|
256
|
+
|
|
257
|
+
it('shows the error message when hasSearchError is true', () => {
|
|
258
|
+
render(
|
|
259
|
+
<Form
|
|
260
|
+
{...baseProps}
|
|
261
|
+
pluginState={{ ...baseProps.pluginState, ...searchedState, suggestions: [], hasSearchError: true }}
|
|
262
|
+
/>
|
|
263
|
+
)
|
|
264
|
+
expect(screen.getByText('Sorry, there was a problem with search')).toBeInTheDocument()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('announces the error message when hasSearchError is true', () => {
|
|
268
|
+
render(
|
|
269
|
+
<Form
|
|
270
|
+
{...baseProps}
|
|
271
|
+
pluginState={{ ...baseProps.pluginState, ...searchedState, suggestions: [], hasSearchError: true }}
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
274
|
+
expect(baseProps.services.announce).toHaveBeenCalledWith('Sorry, there was a problem with search')
|
|
275
|
+
})
|
|
254
276
|
})
|
|
255
277
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/plugins/search/datasets.js
|
|
2
2
|
import { parseOsNamesResults } from './utils/parseOsNamesResults.js'
|
|
3
3
|
|
|
4
|
-
export function createDatasets ({ customDatasets = [], osNamesURL, crs, regions = ['england', 'scotland', 'wales'] }) {
|
|
4
|
+
export function createDatasets ({ customDatasets = [], osNamesURL, crs, regions = ['england', 'scotland', 'wales'], maxSuggestions }) {
|
|
5
5
|
if (!osNamesURL) {
|
|
6
6
|
return customDatasets
|
|
7
7
|
}
|
|
@@ -9,7 +9,7 @@ export function createDatasets ({ customDatasets = [], osNamesURL, crs, regions
|
|
|
9
9
|
const defaultDatasets = [{
|
|
10
10
|
name: 'osNames',
|
|
11
11
|
urlTemplate: osNamesURL,
|
|
12
|
-
parseResults: (json, query) => parseOsNamesResults(json, query, regions, crs),
|
|
12
|
+
parseResults: (json, query) => parseOsNamesResults(json, query, regions, crs, maxSuggestions),
|
|
13
13
|
includeRegex: /[a-zA-Z0-9]/,
|
|
14
14
|
excludeRegex: /^(?:[a-z]{2}\s*(?:\d{3}\s*\d{3}|\d{4}\s*\d{4}|\d{5}\s*\d{5})|\d+\s*,?\s*\d+)$/i // NOSONAR - complexity unavoidable for gridref/coordinate matching
|
|
15
15
|
}]
|
|
@@ -54,7 +54,7 @@ describe('createDatasets', () => {
|
|
|
54
54
|
const query = 'test query'
|
|
55
55
|
|
|
56
56
|
const result = datasets[0].parseResults(json, query)
|
|
57
|
-
expect(parseMock).toHaveBeenCalledWith(json, query, ['england', 'scotland', 'wales'], crs)
|
|
57
|
+
expect(parseMock).toHaveBeenCalledWith(json, query, ['england', 'scotland', 'wales'], crs, undefined)
|
|
58
58
|
expect(result).toBe('parsed')
|
|
59
59
|
parseMock.mockRestore()
|
|
60
60
|
})
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export const DEFAULTS = {
|
|
2
|
-
|
|
2
|
+
expanded: false,
|
|
3
|
+
showMarker: true,
|
|
4
|
+
placeholder: 'Search',
|
|
5
|
+
minSearchLength: 3,
|
|
6
|
+
maxSuggestions: 8,
|
|
7
|
+
noResultsMessage: 'No results available',
|
|
8
|
+
searchErrorMessage: 'Sorry, there was a problem with search'
|
|
3
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/plugins/search/events/fetchSuggestions.js
|
|
2
2
|
|
|
3
|
-
export const sanitiseQuery = (value) => value.
|
|
3
|
+
export const sanitiseQuery = (value) => value.replaceAll(/[^a-zA-Z0-9\s\-.,]/g, '').trim()
|
|
4
4
|
|
|
5
5
|
const toRequest = ({ url, options }) => new Request(url, options)
|
|
6
6
|
|
|
@@ -32,14 +32,14 @@ const fetchDatasetResults = async (ds, request, query) => {
|
|
|
32
32
|
|
|
33
33
|
if (!response.ok) {
|
|
34
34
|
console.error(`Fetch error for ${ds.label || 'dataset'}: ${response.status}`)
|
|
35
|
-
return
|
|
35
|
+
return { error: true }
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const json = await response.json()
|
|
39
39
|
return ds.parseResults(json, query)
|
|
40
40
|
} catch (err) {
|
|
41
41
|
console.error(`Network error for ${ds.label || 'dataset'}:`, err)
|
|
42
|
-
return
|
|
42
|
+
return { error: true }
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -53,23 +53,26 @@ export const fetchSuggestions = async (value, datasets, dispatch, transformReque
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
let finalResults = []
|
|
56
|
+
let hasError = false
|
|
56
57
|
|
|
57
58
|
for (const ds of activeDatasets) {
|
|
58
59
|
const request = await getRequestConfig(ds, sanitisedValue, transformRequest)
|
|
59
60
|
const results = await fetchDatasetResults(ds, request, sanitisedValue)
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
if (results?.error) {
|
|
63
|
+
hasError = true
|
|
64
|
+
} else if (results?.length) {
|
|
63
65
|
finalResults = [...finalResults, ...results]
|
|
66
|
+
} else {
|
|
67
|
+
// No action
|
|
68
|
+
}
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
break
|
|
68
|
-
}
|
|
70
|
+
if (ds.exclusive && finalResults.length) {
|
|
71
|
+
break
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: finalResults })
|
|
75
|
+
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: { results: finalResults, hasError } })
|
|
73
76
|
|
|
74
77
|
return {
|
|
75
78
|
results: finalResults,
|
|
@@ -43,7 +43,7 @@ describe('fetchSuggestions', () => {
|
|
|
43
43
|
expect(result.results).toEqual(['a', 'b'])
|
|
44
44
|
expect(dispatch).toHaveBeenCalledWith({
|
|
45
45
|
type: 'UPDATE_SUGGESTIONS',
|
|
46
|
-
payload: ['a', 'b']
|
|
46
|
+
payload: { results: ['a', 'b'], hasError: false }
|
|
47
47
|
})
|
|
48
48
|
})
|
|
49
49
|
|
|
@@ -162,6 +162,19 @@ describe('fetchSuggestions', () => {
|
|
|
162
162
|
expect(console.error).toHaveBeenCalled()
|
|
163
163
|
})
|
|
164
164
|
|
|
165
|
+
test('dataset returning empty results does not set hasError', async () => {
|
|
166
|
+
global.fetch.mockResolvedValueOnce({ ok: true, json: async () => ({}) })
|
|
167
|
+
|
|
168
|
+
const datasets = [{ urlTemplate: '/api?q={query}', parseResults: () => [] }]
|
|
169
|
+
const result = await fetchSuggestions('test', datasets, dispatch)
|
|
170
|
+
|
|
171
|
+
expect(result.results).toEqual([])
|
|
172
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
173
|
+
type: 'UPDATE_SUGGESTIONS',
|
|
174
|
+
payload: { results: [], hasError: false }
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
165
178
|
test('stops processing when exclusive dataset returns results', async () => {
|
|
166
179
|
global.fetch
|
|
167
180
|
.mockResolvedValueOnce({
|
|
@@ -23,7 +23,7 @@ export const createFormHandlers = ({
|
|
|
23
23
|
|
|
24
24
|
handleCloseClick (_e, buttonRef) {
|
|
25
25
|
dispatch({ type: 'TOGGLE_EXPANDED', payload: false })
|
|
26
|
-
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: [] })
|
|
26
|
+
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: { results: [], hasError: false } })
|
|
27
27
|
dispatch({ type: 'SET_VALUE', payload: '' })
|
|
28
28
|
setTimeout(() => buttonRef.current.focus(), 0)
|
|
29
29
|
markers.remove('search')
|
|
@@ -60,9 +60,7 @@ export const createFormHandlers = ({
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (newSuggestions.length) {
|
|
63
|
-
|
|
64
|
-
viewportRef.current?.focus()
|
|
65
|
-
}
|
|
63
|
+
viewportRef.current?.focus()
|
|
66
64
|
if (appState.breakpoint === 'mobile') {
|
|
67
65
|
dispatch({ type: 'TOGGLE_EXPANDED', payload: false })
|
|
68
66
|
services.eventBus.emit('search:close')
|
|
@@ -63,7 +63,7 @@ describe('createFormHandlers', () => {
|
|
|
63
63
|
handlers.handleCloseClick(null, buttonRef)
|
|
64
64
|
|
|
65
65
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_EXPANDED', payload: false })
|
|
66
|
-
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_SUGGESTIONS', payload: [] })
|
|
66
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_SUGGESTIONS', payload: { results: [], hasError: false } })
|
|
67
67
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_VALUE', payload: '' })
|
|
68
68
|
expect(markers.remove).toHaveBeenCalledWith('search')
|
|
69
69
|
|
|
@@ -122,7 +122,7 @@ describe('createFormHandlers', () => {
|
|
|
122
122
|
expect(updateMap).not.toHaveBeenCalled()
|
|
123
123
|
})
|
|
124
124
|
|
|
125
|
-
test('handleSubmit fetches suggestions and updates map
|
|
125
|
+
test('handleSubmit fetches suggestions and updates map', async () => {
|
|
126
126
|
fetchSuggestions.mockResolvedValueOnce({
|
|
127
127
|
sanitisedValue: 'rome',
|
|
128
128
|
results: [
|
|
@@ -132,7 +132,7 @@ describe('createFormHandlers', () => {
|
|
|
132
132
|
|
|
133
133
|
await handlers.handleSubmit(
|
|
134
134
|
{ preventDefault: jest.fn() },
|
|
135
|
-
{
|
|
135
|
+
{},
|
|
136
136
|
{
|
|
137
137
|
suggestions: [],
|
|
138
138
|
selectedIndex: -1,
|
|
@@ -19,7 +19,7 @@ export const createInputHandlers = ({ dispatch, debouncedFetchSuggestions }) =>
|
|
|
19
19
|
|
|
20
20
|
if (value.length < DEFAULTS.minSearchLength) {
|
|
21
21
|
debouncedFetchSuggestions.cancel()
|
|
22
|
-
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: [] })
|
|
22
|
+
dispatch({ type: 'UPDATE_SUGGESTIONS', payload: { results: [], hasError: false } })
|
|
23
23
|
dispatch({ type: 'HIDE_SUGGESTIONS' })
|
|
24
24
|
return
|
|
25
25
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { updateMap } from '../utils/updateMap.js'
|
|
2
2
|
|
|
3
|
-
export const createSuggestionHandlers = ({ dispatch, services, mapProvider, markers, showMarker, markerOptions }) => {
|
|
3
|
+
export const createSuggestionHandlers = ({ dispatch, services, mapProvider, markers, showMarker, markerOptions, viewportRef }) => {
|
|
4
4
|
const selectionMessage = (suggestions, index) =>
|
|
5
5
|
index >= 0
|
|
6
6
|
? `${suggestions[index]?.text}. ${index + 1} of ${suggestions.length} is highlighted`
|
|
@@ -17,6 +17,7 @@ export const createSuggestionHandlers = ({ dispatch, services, mapProvider, mark
|
|
|
17
17
|
services.eventBus.emit('search:close')
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
viewportRef.current?.focus()
|
|
20
21
|
updateMap({ mapProvider, bounds: suggestion.bounds, point: suggestion.point, markers, showMarker, markerOptions })
|
|
21
22
|
services.eventBus.emit('search:match', { query: suggestion.text, ...suggestion })
|
|
22
23
|
},
|
|
@@ -9,6 +9,7 @@ jest.mock('../utils/updateMap.js')
|
|
|
9
9
|
describe('createSuggestionHandlers', () => {
|
|
10
10
|
let dispatch
|
|
11
11
|
let services
|
|
12
|
+
let viewportRef
|
|
12
13
|
let handlers
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
@@ -19,13 +20,16 @@ describe('createSuggestionHandlers', () => {
|
|
|
19
20
|
announce: jest.fn()
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
viewportRef = { current: { focus: jest.fn() } }
|
|
24
|
+
|
|
22
25
|
handlers = createSuggestionHandlers({
|
|
23
26
|
dispatch,
|
|
24
27
|
services,
|
|
25
28
|
mapProvider: 'map',
|
|
26
29
|
markers: 'markers',
|
|
27
30
|
showMarker: true,
|
|
28
|
-
markerOptions: { backgroundColor: 'blue' }
|
|
31
|
+
markerOptions: { backgroundColor: 'blue' },
|
|
32
|
+
viewportRef
|
|
29
33
|
})
|
|
30
34
|
|
|
31
35
|
jest.clearAllMocks()
|
|
@@ -41,6 +45,7 @@ describe('createSuggestionHandlers', () => {
|
|
|
41
45
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_VALUE', payload: 'Paris' })
|
|
42
46
|
expect(dispatch).toHaveBeenCalledWith({ type: 'HIDE_SUGGESTIONS' })
|
|
43
47
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SELECTED', payload: -1 })
|
|
48
|
+
expect(viewportRef.current.focus).toHaveBeenCalled()
|
|
44
49
|
expect(updateMap).toHaveBeenCalledWith(expect.objectContaining({ bounds: 'b', point: 'p' }))
|
|
45
50
|
expect(services.eventBus.emit).toHaveBeenCalledWith(
|
|
46
51
|
'search:match',
|
|
@@ -55,6 +60,7 @@ describe('createSuggestionHandlers', () => {
|
|
|
55
60
|
|
|
56
61
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_EXPANDED', payload: false })
|
|
57
62
|
expect(services.eventBus.emit).toHaveBeenCalledWith('search:close')
|
|
63
|
+
expect(viewportRef.current.focus).toHaveBeenCalled()
|
|
58
64
|
})
|
|
59
65
|
|
|
60
66
|
// ---------- ArrowDown ----------
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// /plugins/search/index.js
|
|
2
2
|
import './search.scss'
|
|
3
|
+
import { DEFAULTS } from './defaults.js'
|
|
3
4
|
|
|
4
5
|
export default function createPlugin (options = {}) {
|
|
5
|
-
// If search is open then we need to overirde the mobile slot
|
|
6
|
-
if (options.expanded) {
|
|
7
|
-
options.manifest = { controls: [{ id: 'search', mobile: { slot: 'banner' } }] }
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
return {
|
|
11
|
-
|
|
12
|
-
placeholder: 'Search',
|
|
7
|
+
...DEFAULTS,
|
|
13
8
|
...options,
|
|
9
|
+
...(options.expanded && { manifest: { controls: [{ id: 'search', mobile: { slot: 'banner' } }] } }),
|
|
14
10
|
id: 'search',
|
|
15
11
|
load: async () => {
|
|
16
12
|
const module = (await import(/* webpackChunkName: "im-search-plugin" */ './manifest.js')).manifest
|
|
@@ -5,6 +5,7 @@ const initialState = {
|
|
|
5
5
|
suggestions: [],
|
|
6
6
|
areSuggestionsVisible: false,
|
|
7
7
|
hasFetchedSuggestions: false,
|
|
8
|
+
hasSearchError: false,
|
|
8
9
|
selectedIndex: -1
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -13,7 +14,8 @@ const toggleExpanded = (state, payload) => {
|
|
|
13
14
|
...state,
|
|
14
15
|
isExpanded: payload,
|
|
15
16
|
areSuggestionsVisible: payload,
|
|
16
|
-
hasFetchedSuggestions: false
|
|
17
|
+
hasFetchedSuggestions: false,
|
|
18
|
+
hasSearchError: false
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -44,8 +46,9 @@ const setValue = (state, payload) => {
|
|
|
44
46
|
const updateSuggestions = (state, payload) => {
|
|
45
47
|
return {
|
|
46
48
|
...state,
|
|
47
|
-
suggestions: payload,
|
|
48
|
-
hasFetchedSuggestions: true
|
|
49
|
+
suggestions: payload.results,
|
|
50
|
+
hasFetchedSuggestions: true,
|
|
51
|
+
hasSearchError: payload.hasError
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -53,7 +56,8 @@ const showSuggestions = (state) => {
|
|
|
53
56
|
return {
|
|
54
57
|
...state,
|
|
55
58
|
areSuggestionsVisible: true,
|
|
56
|
-
hasFetchedSuggestions: false
|
|
59
|
+
hasFetchedSuggestions: false,
|
|
60
|
+
hasSearchError: false
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -61,7 +65,8 @@ const hideSuggestions = (state) => {
|
|
|
61
65
|
return {
|
|
62
66
|
...state,
|
|
63
67
|
areSuggestionsVisible: false,
|
|
64
|
-
hasFetchedSuggestions: false
|
|
68
|
+
hasFetchedSuggestions: false,
|
|
69
|
+
hasSearchError: false
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -52,9 +52,10 @@ describe('search state actions', () => {
|
|
|
52
52
|
it('UPDATE_SUGGESTIONS updates the suggestions array and sets hasFetchedSuggestions', () => {
|
|
53
53
|
const state = { ...initialState }
|
|
54
54
|
const suggestions = [{ id: 1 }, { id: 2 }]
|
|
55
|
-
const newState = actions.UPDATE_SUGGESTIONS(state, suggestions)
|
|
55
|
+
const newState = actions.UPDATE_SUGGESTIONS(state, { results: suggestions, hasError: false })
|
|
56
56
|
expect(newState.suggestions).toEqual(suggestions)
|
|
57
57
|
expect(newState.hasFetchedSuggestions).toBe(true)
|
|
58
|
+
expect(newState.hasSearchError).toBe(false)
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
it('SHOW_SUGGESTIONS sets areSuggestionsVisible to true and resets hasFetchedSuggestions', () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import OsGridRef from 'geodesy/osgridref.js'
|
|
3
3
|
|
|
4
4
|
const POINT_BUFFER = 500
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_MAX_RESULTS = 8
|
|
6
6
|
|
|
7
7
|
const isPostcode = (value) => {
|
|
8
8
|
value = value.replace(/\s/g, '')
|
|
@@ -100,7 +100,7 @@ const label = (query, { NAME1, COUNTY_UNITARY, DISTRICT_BOROUGH, POSTCODE_DISTRI
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const parseOsNamesResults = (json, query, regions, crs) => {
|
|
103
|
+
const parseOsNamesResults = (json, query, regions, crs, maxSuggestions = DEFAULT_MAX_RESULTS) => {
|
|
104
104
|
if (!json || json.error || json.header?.totalresults === 0) {
|
|
105
105
|
return []
|
|
106
106
|
}
|
|
@@ -108,7 +108,7 @@ const parseOsNamesResults = (json, query, regions, crs) => {
|
|
|
108
108
|
results = removeTenuousResults(results, query)
|
|
109
109
|
results = removeDuplicates(results)
|
|
110
110
|
results = removeRegions(results, regions)
|
|
111
|
-
results = results.slice(0,
|
|
111
|
+
results = results.slice(0, maxSuggestions)
|
|
112
112
|
|
|
113
113
|
return results.map(l => ({
|
|
114
114
|
id: l.GAZETTEER_ENTRY.ID,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"@babel/runtime/helpers/asyncToGenerator";import t from"ol/Map.js";import o from"ol/View.js";import r from"ol/layer/Tile.js";import{defaults as n}from"ol/interaction/defaults.js";import i from"proj4";import{register as a}from"ol/proj/proj4.js";import s from"ol/source/XYZ.js";import l from"ol/tilegrid/TileGrid.js";import u from"ol/TileState.js";import{transformExtent as m}from"ol/proj.js";var p=[896,448,224,112,56,28,14,7,3.5,1.75,.875,.4375,.21875,.109375],c=[156543.03392800014,78271.51696400007,39135.758482000034,19567.879241000017,9783.939620500008,4891.969810250004,2445.984905125002,1222.992452562501,611.4962262812505,305.74811314062526,152.87405657031263,76.43702828515632,38.21851414257816,19.10925707128908,9.55462853564454,4.77731426782227,2.388657133911135,1.1943285669555674,.5971642834777837,.29858214173889186,.14929107086944593,.07464553543472296,.03732276771736148,.01866138385868074],v=[-238375,1376256],d=300,g=2,h=["showKeyboardHelp","selectControl","moveLarge","nudgeMap","zoomLarge","nudgeZoom"],f="uk",M="world";var w=(e,t)=>{var o=null,r=function(){for(var r=arguments.length,n=new Array(r),i=0;i<r;i++)n[i]=arguments[i];clearTimeout(o),o=setTimeout(()=>{e(...n)},t)};return r.cancel=()=>{o&&(clearTimeout(o),o=null)},r};function y(e){var{map:t,view:o,emit:r,eventBus:n,events:i,on:a,debouncers:s}=e,l=!1,u=w(()=>{l=!1,r(i.MAP_MOVE_END)},500);s.push(u);var m,p,c,v=(m=()=>r(i.MAP_MOVE),p=10,c=0,function(){var e=Date.now();e-c>=p&&(c=e,m(...arguments))});a(o,"change",()=>{l||(l=!0,n.emit(i.MAP_MOVE_START)),u()}),a(t,"postrender",()=>{l&&v()})}function b(e){var{map:t,source:o,events:r,eventBus:n,getZoom:i,getCenter:a,getBounds:s,getResolution:l}=e,u=!1,m=[],p=[],c=t.getView(),v=c.getMinZoom(),d=c.getMaxZoom(),g=e=>{var t=(()=>{if(u)return null;var e=i();return{center:a(),bounds:s(),resolution:l(),zoom:e,isAtMaxZoom:e+.01>=d,isAtMinZoom:e-.01<=v}})();t&&n.emit(e,t)},h=(e,t,o)=>{e.on(t,o),m.push([e,t,o])};!function(e){var{source:t,map:o,emit:r,eventBus:n,events:i,on:a}=e,s=!1;a(t,"tileloadend",()=>{s||(s=!0,n.emit(i.MAP_LOADED))}),o.once("rendercomplete",()=>r(i.MAP_FIRST_IDLE))}({source:o,map:t,emit:g,eventBus:n,events:r,on:h}),y({map:t,view:t.getView(),emit:g,eventBus:n,events:r,on:h,debouncers:p}),function(e){var{map:t,eventBus:o,events:r,on:n}=e,i=null;n(t,"pointerdown",e=>{i=e.pixel}),n(t,"click",e=>{if(i){var t=e.pixel[0]-i[0],n=e.pixel[1]-i[1];if(Math.hypot(t,n)>6)return}o.emit(r.MAP_CLICK,{point:{x:e.pixel[0],y:e.pixel[1]},coords:e.coordinate})})}({map:t,eventBus:n,events:r,on:h}),h(t,"postrender",()=>n.emit(r.MAP_RENDER));var f=w(()=>g(r.MAP_DATA_CHANGE),500);return p.push(f),h(o,"tileloadend",f),{remove(){u=!0,p.forEach(e=>e.cancel()),m.forEach(e=>{var[t,o,r]=e;return t.un(o,r)})}}}var S=e=>(t,o)=>{var r=e(o,"Tile")||{},n=r.url||o,i=r.headers||{};fetch(n,{headers:i}).then(e=>e.blob()).then(e=>{t.getImage().src=URL.createObjectURL(e)}).catch(()=>t.setState(u.ERROR))};function x(e,t){var o=new l({resolutions:p,origin:v,tileSize:256});return new s({projection:"EPSG:27700",tileGrid:o,tileUrlFunction:t=>{var[o,r,n]=t;return e.replace("{z}",o).replace("{x}",r).replace("{y}",n)},tileLoadFunction:t?S(t):void 0})}function E(e,t,o){if(null!==e)for(var r,n,i,a,s,l,u,m,p=0,c=0,v=e.type,d="FeatureCollection"===v,g="Feature"===v,h=d?e.features.length:1,f=0;f<h;f++){s=(m=!!(u=d?e.features[f].geometry:g?e.geometry:e)&&"GeometryCollection"===u.type)?u.geometries.length:1;for(var M=0;M<s;M++){var w=0,y=0;if(null!==(a=m?u.geometries[M]:u)){l=a.coordinates;var b=a.type;switch(p=0,b){case null:break;case"Point":if(!1===t(l,c,f,w,y))return!1;c++,w++;break;case"LineString":case"MultiPoint":for(r=0;r<l.length;r++){if(!1===t(l[r],c,f,w,y))return!1;c++,"MultiPoint"===b&&w++}"LineString"===b&&w++;break;case"Polygon":case"MultiLineString":for(r=0;r<l.length;r++){for(n=0;n<l[r].length-p;n++){if(!1===t(l[r][n],c,f,w,y))return!1;c++}"MultiLineString"===b&&w++,"Polygon"===b&&y++}"Polygon"===b&&w++;break;case"MultiPolygon":for(r=0;r<l.length;r++){for(y=0,n=0;n<l[r].length;n++){for(i=0;i<l[r][n].length-p;i++){if(!1===t(l[r][n][i],c,f,w,y))return!1;c++}y++}w++}break;case"GeometryCollection":for(r=0;r<a.geometries.length;r++)if(!1===E(a.geometries[r],t))return!1;break;default:throw new Error("Unknown Geometry Type")}}}}}var P=function(e,t={}){if(null!=e.bbox&&!0!==t.recompute)return e.bbox;const o=[1/0,1/0,-1/0,-1/0];return E(e,e=>{o[0]>e[0]&&(o[0]=e[0]),o[1]>e[1]&&(o[1]=e[1]),o[2]<e[0]&&(o[2]=e[0]),o[3]<e[1]&&(o[3]=e[1])}),o},A=e=>{var t=e/1609.344;if(t<.5)return"".concat(Math.round(e),"m");if(t<10){var o=Number.parseFloat(t.toFixed(1)),r=1===o?"mile":"miles";return"".concat(o," ").concat(r)}var n=Math.round(t);return"".concat(n," miles")},Z=(e,t,o)=>{var r=o.getTargetElement().getBoundingClientRect(),[n,i,a,s]=(e=>P(e))(e),l=[[n,i],[n,s],[a,i],[a,s]].map(e=>o.getPixelFromCoordinate(e)).filter(Boolean);if(!l.length)return!1;var u=Math.min(...l.map(e=>e[0])),m=Math.max(...l.map(e=>e[0])),p=Math.min(...l.map(e=>e[1])),c=Math.max(...l.map(e=>e[1])),v=t.left-r.left,d=t.top-r.top,g=t.right-r.left,h=t.bottom-r.top;return u<g&&m>v&&p<h&&c>d},C="EPSG:27700",_=e=>{if(e){var{top:t=0,right:o=0,bottom:r=0,left:n=0}=e;return[t,o,r,n]}};i.defs(C,"+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs"),a(i);class T{constructor(e){var{mapProviderConfig:t={},events:o,eventBus:r}=e;this.events=o,this.eventBus=r,this.capabilities={supportedShortcuts:h,supportsMapSizes:!1},Object.assign(this,t)}initMap(i){var a=this;return e(function*(){var e,{container:s,padding:l,mapStyle:u,mapSize:m,center:v,zoom:d,bounds:g,minZoom:h,maxZoom:w,transformRequest:y}=i;a.mapStyleId=null==u?void 0:u.id,a.mapSize=m;var{events:S,eventBus:E}=a,P=x(u.url,y),A=new r({source:P}),Z=(null!==(e=a.zoomAlignment)&&void 0!==e?e:f)===M?{resolutions:c,defaultMinZoom:6,maxZoom:20}:{resolutions:p,defaultMinZoom:0,maxZoom:p.length-1},T=new o({projection:C,center:v,zoom:null!=d?d:Z.defaultMinZoom,minZoom:Math.max(null!=h?h:Z.defaultMinZoom,Z.defaultMinZoom),maxZoom:null!=w?w:Z.maxZoom,resolutions:Z.resolutions,constrainResolution:!1,padding:_(l)}),z=new t({target:s,layers:[A],view:T,controls:[],interactions:n({doubleClickZoom:!1})});g&&z.once("rendercomplete",()=>{T.fit(g,{size:z.getSize(),duration:0})}),a.mapEventHandles=b({map:z,source:P,events:S,eventBus:E,getZoom:a.getZoom.bind(a),getCenter:a.getCenter.bind(a),getBounds:a.getBounds.bind(a),getResolution:a.getResolution.bind(a)}),a.appEventHandles=function(e){var{tileLayer:t,transformRequest:o,events:r,eventBus:n}=e,i=e=>{var i=x(e.url,o);t.setSource(i),n.emit(r.MAP_STYLE_CHANGE,{mapStyleId:e.id})};return n.on(r.MAP_SET_STYLE,i),{remove(){n.off(r.MAP_SET_STYLE,i)}}}({tileLayer:A,transformRequest:y,events:S,eventBus:E})||[],a.map=z,a.view=T,a.tileLayer=A,a.source=P,E.emit(S.MAP_READY,{map:a.map,mapStyleId:a.mapStyleId,mapSize:a.mapSize,crs:a.crs})})()}destroyMap(){var e,t;null===(e=this.mapEventHandles)||void 0===e||e.remove(),null===(t=this.appEventHandles)||void 0===t||t.remove(),this.mapEventHandles=null,this.appEventHandles=null,this.map&&(this.map.setTarget(null),this.map=null),this.view=null,this.tileLayer=null,this.source=null}setView(e){var{center:t,zoom:o}=e;this.view.animate({center:null!=t?t:this.view.getCenter(),zoom:null!=o?o:this.view.getZoom(),duration:d})}zoomIn(e){this.view.animate({zoom:this.view.getZoom()+e,duration:d})}zoomOut(e){this.view.animate({zoom:this.view.getZoom()-e,duration:d})}panBy(e){var t=this.view.getCenter(),o=this.view.getResolution();this.view.animate({center:[t[0]+e[0]*o,t[1]-e[1]*o],duration:d})}fitToBounds(e){var t,o=Array.isArray(e)?e:(t=P(e),m(t,"EPSG:4326","EPSG:27700"));this.view.fit(o,{duration:d})}setPadding(e){this.view.padding=_(e)}getCenter(){var e=this.view.getCenter(),t=g;return[Math.round(e[0]*Math.pow(10,t))/Math.pow(10,t),Math.round(e[1]*Math.pow(10,t))/Math.pow(10,t)]}getZoom(){return this.view.getZoom()}getBounds(){return this.view.calculateExtent(this.map.getSize()).map(e=>Math.round(100*e)/100)}getFeaturesAtPoint(e,t){return[]}getVisibleFeatures(e){return[]}getAreaDimensions(){return(e=>{if(!e)return"";var[t,o,r,n]=e,i=r-t;return"".concat(A(n-o)," by ").concat(A(i))})((e=>{var t=e.getSize(),o=e.getView().padding||[0,0,0,0],[r,n,i,a]=o,s=[a,t[1]-i],l=[t[0]-n,r],u=e.getCoordinateFromPixel(s),m=e.getCoordinateFromPixel(l);return u&&m?[u[0],u[1],m[0],m[1]]:null})(this.map))}getCardinalMove(e,t){return((e,t)=>{var o=t[0]-e[0],r=t[1]-e[1],n=[];return Math.abs(r)>1&&n.push("".concat(r>0?"north":"south"," ").concat(A(Math.abs(r)))),Math.abs(o)>1&&n.push("".concat(o>0?"east":"west"," ").concat(A(Math.abs(o)))),n.join(", ")})(e,t)}getResolution(){return this.view.getResolution()}mapToScreen(e){if(!this.map)return{x:0,y:0};var t=this.map.getPixelFromCoordinate(e);return t?{x:t[0],y:t[1]}:{x:0,y:0}}screenToMap(e){return this.map.getCoordinateFromPixel([e.x,e.y])}isGeometryObscured(e,t){return Z(e,t,this.map)}}export{T as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"@babel/runtime/helpers/asyncToGenerator";function r(){var r,{zoomAlignment:t}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{checkDeviceCapabilities:()=>({isSupported:!!document.createElement("canvas").getContext,error:"Canvas is not supported in this browser"}),load:(r=e(function*(){return{MapProvider:(yield import(/* webpackChunkName: "im-openlayers-provider" */ "./im-openlayers-provider.js")).default,mapProviderConfig:{zoomAlignment:t,crs:"EPSG:27700"}}}),function(){return r.apply(this,arguments)})}}export{r as default};
|