@defra/interactive-map 0.0.14-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.
Files changed (199) hide show
  1. package/assets/css/docusaurus.css +104 -0
  2. package/assets/images/favicon.svg +1 -0
  3. package/assets/images/hero.png +0 -0
  4. package/dist/css/index.css +1 -1
  5. package/dist/esm/im-core.js +1 -1
  6. package/dist/umd/im-core.js +1 -1
  7. package/dist/umd/index.js +1 -1
  8. package/docs/api/slot-map.svg +1 -0
  9. package/docs/api/slots.md +89 -6
  10. package/docs/api.md +1 -1
  11. package/docs/architecture.md +3 -1
  12. package/docs/{demo.mdx → examples.mdx} +1 -1
  13. package/docs/getting-started.md +1 -3
  14. package/docs/index.mdx +42 -0
  15. package/docs/plugins/interact.md +176 -55
  16. package/docs/plugins/map-styles.md +64 -7
  17. package/docs/plugins/search.md +207 -63
  18. package/docs/plugins.md +7 -15
  19. package/docusaurus.config.cjs +34 -34
  20. package/jest.setup.js +1 -1
  21. package/package.json +5 -4
  22. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  23. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  24. package/plugins/beta/datasets/src/DatasetsInit.jsx +1 -1
  25. package/plugins/beta/datasets/src/api/addDataset.js +1 -1
  26. package/plugins/beta/datasets/src/api/hideDataset.js +1 -1
  27. package/plugins/beta/datasets/src/api/hideFeatures.js +1 -1
  28. package/plugins/beta/datasets/src/api/removeDataset.js +1 -1
  29. package/plugins/beta/datasets/src/api/showDataset.js +1 -1
  30. package/plugins/beta/datasets/src/api/showFeatures.js +1 -1
  31. package/plugins/beta/datasets/src/datasets.js +4 -4
  32. package/plugins/beta/datasets/src/defaults.js +1 -1
  33. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +5 -5
  34. package/plugins/beta/datasets/src/handleSetMapStyle.js +1 -1
  35. package/plugins/beta/datasets/src/manifest.js +7 -7
  36. package/plugins/beta/datasets/src/mapLayers.js +2 -3
  37. package/plugins/beta/datasets/src/panels/Key.jsx +31 -29
  38. package/plugins/beta/datasets/src/panels/Layers.jsx +8 -9
  39. package/plugins/beta/datasets/src/utils/bbox.js +4 -4
  40. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  41. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  42. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  43. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  44. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  45. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  46. package/plugins/beta/draw-es/src/events.js +52 -20
  47. package/plugins/beta/draw-es/src/events.test.js +301 -0
  48. package/plugins/beta/draw-es/src/graphic.js +1 -1
  49. package/plugins/beta/draw-es/src/manifest.js +4 -4
  50. package/plugins/beta/draw-es/src/reducer.js +1 -1
  51. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  52. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  53. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  54. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  55. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  56. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  57. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  58. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  59. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  60. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  61. package/plugins/beta/draw-ml/src/events.js +8 -6
  62. package/plugins/beta/draw-ml/src/manifest.js +15 -15
  63. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  64. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  65. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  66. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  67. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  68. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  69. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  70. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  71. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  72. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  73. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  74. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  75. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  76. package/plugins/beta/frame/src/Frame.jsx +4 -4
  77. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  78. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  79. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  80. package/plugins/beta/frame/src/config.js +1 -1
  81. package/plugins/beta/frame/src/manifest.js +3 -3
  82. package/plugins/beta/frame/src/reducer.js +1 -1
  83. package/plugins/beta/frame/src/utils.js +1 -1
  84. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  85. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  86. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  87. package/plugins/beta/map-styles/src/manifest.js +2 -2
  88. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  89. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  90. package/plugins/beta/use-location/src/defaults.js +1 -1
  91. package/plugins/beta/use-location/src/events.js +3 -3
  92. package/plugins/interact/src/InteractInit.jsx +1 -2
  93. package/plugins/interact/src/api/enable.js +8 -5
  94. package/plugins/interact/src/api/enable.test.js +2 -2
  95. package/plugins/interact/src/api/selectFeature.js +4 -4
  96. package/plugins/interact/src/api/unselectFeature.js +5 -5
  97. package/plugins/interact/src/defaults.js +0 -1
  98. package/plugins/interact/src/events.test.js +15 -15
  99. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  100. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  101. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  102. package/plugins/interact/src/manifest.js +2 -2
  103. package/plugins/interact/src/manifest.test.js +3 -4
  104. package/plugins/interact/src/reducer.js +3 -3
  105. package/plugins/interact/src/reducer.test.js +0 -1
  106. package/plugins/interact/src/utils/spatial.js +10 -10
  107. package/plugins/interact/src/utils/spatial.test.js +14 -14
  108. package/plugins/search/dist/css/index.css +1 -1
  109. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  110. package/plugins/search/dist/esm/index.js +1 -1
  111. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  112. package/plugins/search/dist/umd/index.js +1 -1
  113. package/plugins/search/src/Search.jsx +7 -6
  114. package/plugins/search/src/Search.test.jsx +23 -23
  115. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  116. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  117. package/plugins/search/src/components/Form/Form.jsx +14 -14
  118. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  119. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  120. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  121. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  122. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  123. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  124. package/plugins/search/src/datasets.js +12 -13
  125. package/plugins/search/src/datasets.test.js +1 -1
  126. package/plugins/search/src/defaults.js +1 -1
  127. package/plugins/search/src/events/fetchSuggestions.js +4 -4
  128. package/plugins/search/src/events/fetchSuggestions.test.js +5 -5
  129. package/plugins/search/src/events/formHandlers.js +3 -3
  130. package/plugins/search/src/events/formHandlers.test.js +1 -1
  131. package/plugins/search/src/events/index.js +2 -2
  132. package/plugins/search/src/events/index.test.js +2 -2
  133. package/plugins/search/src/events/inputHandlers.js +4 -4
  134. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  135. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  136. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  137. package/plugins/search/src/index.js +2 -1
  138. package/plugins/search/src/index.test.js +3 -3
  139. package/plugins/search/src/manifest.js +6 -4
  140. package/plugins/search/src/reducer.js +1 -2
  141. package/plugins/search/src/reducer.test.js +2 -2
  142. package/plugins/search/src/search.scss +18 -6
  143. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  144. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  145. package/plugins/search/src/utils/updateMap.js +1 -1
  146. package/plugins/search/src/utils/updateMap.test.js +5 -5
  147. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  148. package/providers/beta/esri/src/esriProvider.js +5 -5
  149. package/providers/beta/esri/src/utils/coords.js +1 -1
  150. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  151. package/providers/beta/esri/src/utils/query.js +4 -4
  152. package/providers/beta/esri/src/utils/spatial.js +1 -2
  153. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  154. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  155. package/providers/maplibre/src/appEvents.test.js +1 -1
  156. package/providers/maplibre/src/index.js +1 -1
  157. package/providers/maplibre/src/index.test.js +3 -5
  158. package/providers/maplibre/src/mapEvents.test.js +15 -5
  159. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  160. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  161. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  162. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  163. package/providers/maplibre/src/utils/highlightFeatures.js +2 -2
  164. package/providers/maplibre/src/utils/highlightFeatures.test.js +12 -6
  165. package/providers/maplibre/src/utils/labels.js +19 -20
  166. package/providers/maplibre/src/utils/labels.test.js +15 -13
  167. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  168. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  169. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  170. package/providers/maplibre/src/utils/spatial.js +0 -1
  171. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  172. package/src/App/components/Panel/Panel.module.scss +1 -0
  173. package/src/App/hooks/useLayoutMeasurements.js +1 -10
  174. package/src/App/hooks/useLayoutMeasurements.test.js +2 -5
  175. package/src/App/hooks/useVisibleGeometry.js +7 -13
  176. package/src/App/hooks/useVisibleGeometry.test.js +72 -47
  177. package/src/App/layout/Layout.jsx +0 -3
  178. package/src/App/layout/Layout.test.jsx +0 -1
  179. package/src/App/layout/layout.module.scss +11 -77
  180. package/src/App/registry/pluginRegistry.js +17 -0
  181. package/src/App/registry/pluginRegistry.test.js +33 -0
  182. package/src/App/renderer/HtmlElementHost.jsx +0 -1
  183. package/src/App/renderer/HtmlElementHost.test.jsx +20 -11
  184. package/src/App/renderer/mapButtons.js +3 -2
  185. package/src/App/renderer/mapPanels.test.js +3 -3
  186. package/src/App/renderer/slotHelpers.js +2 -2
  187. package/src/App/renderer/slotHelpers.test.js +3 -3
  188. package/src/App/renderer/slots.js +0 -3
  189. package/src/App/store/AppProvider.jsx +0 -1
  190. package/src/App/store/appDispatchMiddleware.js +33 -1
  191. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  192. package/src/config/appConfig.js +4 -4
  193. package/src/utils/getSafeZoneInset.js +139 -42
  194. package/src/utils/getSafeZoneInset.test.js +298 -122
  195. package/src/utils/logger.js +6 -0
  196. package/src/utils/logger.test.js +32 -0
  197. package/webpack.dev.mjs +22 -18
  198. package/docs/govuk-prototype.md +0 -23
  199. package/docs/index.md +0 -19
@@ -7,13 +7,13 @@ import { Form } from './Form'
7
7
  jest.mock('../Suggestions/Suggestions', () => ({
8
8
  Suggestions: ({ handleSuggestionClick, id }) => (
9
9
  <button
10
- data-testid="suggestion"
10
+ data-testid='suggestion'
11
11
  onClick={() => handleSuggestionClick('clicked-suggestion')}
12
12
  id={`${id}-search-suggestions`}
13
13
  >
14
14
  Suggestion
15
15
  </button>
16
- ),
16
+ )
17
17
  }))
18
18
 
19
19
  describe('Form', () => {
@@ -28,15 +28,15 @@ describe('Form', () => {
28
28
  hasFetchedSuggestions: false,
29
29
  suggestions: [],
30
30
  selectedIndex: -1,
31
- hasKeyboardFocusWithin: false,
31
+ hasKeyboardFocusWithin: false
32
32
  },
33
33
  pluginConfig: {
34
34
  expanded: false,
35
- width: '400px',
35
+ width: '400px'
36
36
  },
37
37
  appState: {
38
38
  breakpoint: 'desktop',
39
- interfaceType: 'keyboard',
39
+ interfaceType: 'keyboard'
40
40
  },
41
41
  events: {
42
42
  handleSubmit: jest.fn(),
@@ -45,11 +45,11 @@ describe('Form', () => {
45
45
  handleInputFocus: jest.fn(),
46
46
  handleInputBlur: jest.fn(),
47
47
  handleInputKeyDown: jest.fn(),
48
- handleSuggestionClick: jest.fn(),
48
+ handleSuggestionClick: jest.fn()
49
49
  },
50
50
  services: {
51
- announce: jest.fn(),
52
- },
51
+ announce: jest.fn()
52
+ }
53
53
  }
54
54
 
55
55
  beforeEach(() => {
@@ -98,7 +98,7 @@ describe('Form', () => {
98
98
  pluginState={{
99
99
  ...baseProps.pluginState,
100
100
  suggestionsVisible: true,
101
- selectedIndex: 2,
101
+ selectedIndex: 2
102
102
  }}
103
103
  />
104
104
  )
@@ -148,7 +148,7 @@ describe('Form', () => {
148
148
  it('renders children passed into the input container (e.g., CloseButton)', () => {
149
149
  render(
150
150
  <Form {...baseProps}>
151
- <div data-testid="close-button" />
151
+ <div data-testid='close-button' />
152
152
  </Form>
153
153
  )
154
154
  expect(screen.getByTestId('close-button')).toBeInTheDocument()
@@ -252,4 +252,4 @@ describe('Form', () => {
252
252
  expect(baseProps.services.announce).not.toHaveBeenCalled()
253
253
  })
254
254
  })
255
- })
255
+ })
@@ -1,9 +1,9 @@
1
1
  // src/plugins/search/OpenButton.jsx
2
- export const OpenButton = ({ id, isExpanded, onClick, buttonRef, searchIcon }) => {
2
+ export const OpenButton = ({ id, isExpanded, onClick, buttonRef, searchIcon, showLabel }) => {
3
3
  return (
4
4
  <button
5
- aria-label="Open search"
6
- className="im-c-map-button"
5
+ aria-label='Open search'
6
+ className='im-c-map-button'
7
7
  onClick={onClick}
8
8
  aria-controls={`${id}-search-form`}
9
9
  ref={buttonRef}
@@ -11,20 +11,21 @@ export const OpenButton = ({ id, isExpanded, onClick, buttonRef, searchIcon }) =
11
11
  >
12
12
  {searchIcon && (
13
13
  <svg
14
- xmlns="http://www.w3.org/2000/svg"
15
- width="24"
16
- height="24"
17
- viewBox="0 0 24 24"
18
- fill="none"
19
- stroke="currentColor"
20
- strokeWidth="2"
21
- strokeLinecap="round"
22
- strokeLinejoin="round"
23
- aria-hidden="true"
24
- focusable="false"
14
+ xmlns='http://www.w3.org/2000/svg'
15
+ width='24'
16
+ height='24'
17
+ viewBox='0 0 24 24'
18
+ fill='none'
19
+ stroke='currentColor'
20
+ strokeWidth='2'
21
+ strokeLinecap='round'
22
+ strokeLinejoin='round'
23
+ aria-hidden='true'
24
+ focusable='false'
25
25
  dangerouslySetInnerHTML={{ __html: searchIcon }}
26
- />
26
+ />
27
27
  )}
28
+ {showLabel && <span>Search</span>}
28
29
  </button>
29
30
  )
30
31
  }
@@ -9,7 +9,7 @@ describe('OpenButton', () => {
9
9
  isExpanded: false,
10
10
  onClick: jest.fn(),
11
11
  buttonRef: { current: null },
12
- searchIcon: null,
12
+ searchIcon: null
13
13
  }
14
14
 
15
15
  beforeEach(() => {
@@ -44,4 +44,8 @@ describe('OpenButton', () => {
44
44
  const { container } = render(<OpenButton {...baseProps} />)
45
45
  expect(container.querySelector('svg')).toBeNull()
46
46
  })
47
- })
47
+ it('should show a label if showLabel is true', () => {
48
+ const { container } = render(<OpenButton {...baseProps} showLabel />)
49
+ expect(container.querySelector('span').innerHTML).toContain('Search')
50
+ })
51
+ })
@@ -2,26 +2,26 @@
2
2
  export const SubmitButton = ({ defaultExpanded, submitIcon }) => {
3
3
  return (
4
4
  <button
5
- aria-label="Search"
6
- className="im-c-map-button im-c-search-submit-button"
7
- type="submit"
5
+ aria-label='Search'
6
+ className='im-c-map-button im-c-search-submit-button'
7
+ type='submit'
8
8
  style={defaultExpanded ? undefined : { display: 'none' }}
9
9
  >
10
10
  {submitIcon && (
11
11
  <svg
12
- xmlns="http://www.w3.org/2000/svg"
13
- width="24"
14
- height="24"
15
- viewBox="0 0 24 24"
16
- fill="none"
17
- stroke="currentColor"
18
- strokeWidth="2"
19
- strokeLinecap="round"
20
- strokeLinejoin="round"
21
- aria-hidden="true"
22
- focusable="false"
12
+ xmlns='http://www.w3.org/2000/svg'
13
+ width='24'
14
+ height='24'
15
+ viewBox='0 0 24 24'
16
+ fill='none'
17
+ stroke='currentColor'
18
+ strokeWidth='2'
19
+ strokeLinecap='round'
20
+ strokeLinejoin='round'
21
+ aria-hidden='true'
22
+ focusable='false'
23
23
  dangerouslySetInnerHTML={{ __html: submitIcon }}
24
- />
24
+ />
25
25
  )}
26
26
  </button>
27
27
  )
@@ -3,23 +3,23 @@ export const Suggestions = ({ id, pluginState, handleSuggestionClick }) => {
3
3
  return (
4
4
  <ul // NOSONAR
5
5
  id={`${id}-search-suggestions`}
6
- role="listbox" // NOSONAR
6
+ role='listbox' // NOSONAR
7
7
  aria-labelledby={`${id}-search`} // Option A: label from input
8
- className="im-c-search-suggestions"
9
- style={!pluginState.areSuggestionsVisible || !pluginState.suggestions.length ? { display: 'none' } : undefined }
8
+ className='im-c-search-suggestions'
9
+ style={!pluginState.areSuggestionsVisible || !pluginState.suggestions.length ? { display: 'none' } : undefined}
10
10
  >
11
11
  {pluginState.suggestions.map((suggestion, i) => (
12
12
  <li // NOSONAR
13
13
  key={suggestion.id}
14
14
  id={`${id}-search-suggestion-${i}`}
15
- className="im-c-search-suggestions__item"
16
- role="option" // NOSONAR
15
+ className='im-c-search-suggestions__item'
16
+ role='option' // NOSONAR
17
17
  aria-selected={pluginState.selectedIndex === i}
18
18
  aria-setsize={pluginState.suggestions.length}
19
19
  aria-posinset={i + 1}
20
20
  onClick={() => handleSuggestionClick(suggestion)} // NOSONAR
21
21
  >
22
- <span className="im-c-search-suggestions__label" dangerouslySetInnerHTML={{ __html: suggestion.marked }} />
22
+ <span className='im-c-search-suggestions__label' dangerouslySetInnerHTML={{ __html: suggestion.marked }} />
23
23
  </li>
24
24
  ))}
25
25
  </ul>
@@ -10,11 +10,11 @@ describe('Suggestions', () => {
10
10
  areSuggestionsVisible: true,
11
11
  suggestions: [
12
12
  { id: '1', marked: 'First' },
13
- { id: '2', marked: 'Second' },
13
+ { id: '2', marked: 'Second' }
14
14
  ],
15
- selectedIndex: 0,
15
+ selectedIndex: 0
16
16
  },
17
- handleSuggestionClick: jest.fn(),
17
+ handleSuggestionClick: jest.fn()
18
18
  }
19
19
 
20
20
  beforeEach(() => {
@@ -76,4 +76,4 @@ describe('Suggestions', () => {
76
76
  baseProps.pluginState.suggestions[1]
77
77
  )
78
78
  })
79
- })
79
+ })
@@ -1,19 +1,18 @@
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'] }) {
5
+ if (!osNamesURL) {
6
+ return customDatasets
7
+ }
5
8
 
6
- if (!osNamesURL) {
7
- return customDatasets
8
- }
9
+ const defaultDatasets = [{
10
+ name: 'osNames',
11
+ urlTemplate: osNamesURL,
12
+ parseResults: (json, query) => parseOsNamesResults(json, query, regions, crs),
13
+ includeRegex: /[a-zA-Z0-9]/,
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
+ }]
9
16
 
10
- const defaultDatasets = [{
11
- name: 'osNames',
12
- urlTemplate: osNamesURL,
13
- parseResults: (json, query) => parseOsNamesResults(json, query, regions, crs),
14
- includeRegex: /[a-zA-Z0-9]/,
15
- 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
16
- }]
17
-
18
- return [...defaultDatasets, ...customDatasets]
17
+ return [...defaultDatasets, ...customDatasets]
19
18
  }
@@ -58,4 +58,4 @@ describe('createDatasets', () => {
58
58
  expect(result).toBe('parsed')
59
59
  parseMock.mockRestore()
60
60
  })
61
- })
61
+ })
@@ -1,3 +1,3 @@
1
1
  export const DEFAULTS = {
2
2
  minSearchLength: 3
3
- }
3
+ }
@@ -26,8 +26,8 @@ const getRequestConfig = async (ds, query, transformRequest) => {
26
26
  */
27
27
  const fetchDatasetResults = async (ds, request, query) => {
28
28
  try {
29
- const response = await fetch(request.url, request.options)
30
-
29
+ const response = await fetch(request)
30
+
31
31
  if (!response.ok) {
32
32
  console.error(`Fetch error for ${ds.label || 'dataset'}: ${response.status}`)
33
33
  return null
@@ -68,9 +68,9 @@ export const fetchSuggestions = async (value, datasets, dispatch, transformReque
68
68
  }
69
69
 
70
70
  dispatch({ type: 'UPDATE_SUGGESTIONS', payload: finalResults })
71
-
71
+
72
72
  return {
73
73
  results: finalResults,
74
74
  sanitisedValue
75
75
  }
76
- }
76
+ }
@@ -35,7 +35,7 @@ describe('fetchSuggestions', () => {
35
35
 
36
36
  const result = await fetchSuggestions('test', datasets, dispatch)
37
37
 
38
- expect(fetch).toHaveBeenCalledWith('/api?q=test', { method: 'GET' })
38
+ expect(fetch).toHaveBeenCalledWith({ url: '/api?q=test', options: { method: 'GET' } })
39
39
  expect(result.results).toEqual(['a', 'b'])
40
40
  expect(dispatch).toHaveBeenCalledWith({
41
41
  type: 'UPDATE_SUGGESTIONS',
@@ -77,7 +77,7 @@ describe('fetchSuggestions', () => {
77
77
 
78
78
  const result = await fetchSuggestions('abc', datasets, dispatch)
79
79
 
80
- expect(fetch).toHaveBeenCalledWith('/custom/abc', { method: 'POST' })
80
+ expect(fetch).toHaveBeenCalledWith({ url: '/custom/abc', options: { method: 'POST' } })
81
81
  expect(result.results).toEqual(['y'])
82
82
  })
83
83
 
@@ -101,7 +101,7 @@ describe('fetchSuggestions', () => {
101
101
 
102
102
  await fetchSuggestions('x', datasets, dispatch, transformRequest)
103
103
 
104
- expect(fetch).toHaveBeenCalledWith('/t?q=x', { method: 'PUT' })
104
+ expect(fetch).toHaveBeenCalledWith({ url: '/t?q=x', options: { method: 'PUT' } })
105
105
  })
106
106
 
107
107
  test('handles fetch HTTP error', async () => {
@@ -206,7 +206,7 @@ describe('fetchSuggestions', () => {
206
206
 
207
207
  const result = await fetchSuggestions('hi', datasets, dispatch)
208
208
 
209
- expect(fetch).toHaveBeenCalledWith('/default?q=hi', { method: 'GET' })
209
+ expect(fetch).toHaveBeenCalledWith({ url: '/default?q=hi', options: { method: 'GET' } })
210
210
  expect(result.results).toEqual(['ok'])
211
211
  })
212
- })
212
+ })
@@ -16,12 +16,12 @@ export const createFormHandlers = ({
16
16
  let lastFetchedValue = ''
17
17
 
18
18
  return {
19
- handleOpenClick() {
19
+ handleOpenClick () {
20
20
  dispatch({ type: 'TOGGLE_EXPANDED', payload: true })
21
21
  services.eventBus.emit('search:open')
22
22
  },
23
23
 
24
- handleCloseClick(_e, buttonRef) {
24
+ handleCloseClick (_e, buttonRef) {
25
25
  dispatch({ type: 'TOGGLE_EXPANDED', payload: false })
26
26
  dispatch({ type: 'UPDATE_SUGGESTIONS', payload: [] })
27
27
  dispatch({ type: 'SET_VALUE', payload: '' })
@@ -31,7 +31,7 @@ export const createFormHandlers = ({
31
31
  services.eventBus.emit('search:close')
32
32
  },
33
33
 
34
- async handleSubmit(e, appState, pluginState) {
34
+ async handleSubmit (e, appState, pluginState) {
35
35
  e.preventDefault()
36
36
  const { suggestions, selectedIndex, value } = pluginState
37
37
  const trimmedValue = value?.trim()
@@ -229,4 +229,4 @@ describe('createFormHandlers', () => {
229
229
 
230
230
  expect(fetchSuggestions).toHaveBeenCalledTimes(1)
231
231
  })
232
- })
232
+ })
@@ -6,7 +6,7 @@ import { createSuggestionHandlers } from './suggestionHandlers.js'
6
6
 
7
7
  const DEBOUNCE_FETCH_TIME = 350
8
8
 
9
- export function attachEvents(args) {
9
+ export function attachEvents (args) {
10
10
  const { dispatch, searchContainerRef } = args
11
11
 
12
12
  // Debounce data fetching
@@ -26,7 +26,7 @@ export function attachEvents(args) {
26
26
  ...inputHandlers,
27
27
  ...suggestionHandlers,
28
28
 
29
- handleOutside(e) {
29
+ handleOutside (e) {
30
30
  if (searchContainerRef.current.contains(e.target)) {
31
31
  return
32
32
  }
@@ -69,7 +69,7 @@ describe('attachEvents', () => {
69
69
  })
70
70
 
71
71
  test('debouncedFetchSuggestions calls fetchSuggestions with correct args', () => {
72
- const handlers = attachEvents(args)
72
+ attachEvents(args)
73
73
 
74
74
  // grab the debounced function passed to input handlers
75
75
  const { debouncedFetchSuggestions } =
@@ -115,4 +115,4 @@ describe('attachEvents', () => {
115
115
 
116
116
  expect(services.eventBus.emit).toHaveBeenCalledWith('search:close')
117
117
  })
118
- })
118
+ })
@@ -1,19 +1,19 @@
1
1
  import { DEFAULTS } from '../defaults.js'
2
2
 
3
3
  export const createInputHandlers = ({ dispatch, debouncedFetchSuggestions }) => ({
4
- handleInputClick() {
4
+ handleInputClick () {
5
5
  dispatch({ type: 'SHOW_SUGGESTIONS' })
6
6
  },
7
7
 
8
- handleInputFocus(interfaceType) {
8
+ handleInputFocus (interfaceType) {
9
9
  dispatch({ type: 'SET_KEYBOARD_FOCUS_WITHIN', payload: interfaceType === 'keyboard' })
10
10
  },
11
11
 
12
- handleInputBlur(interfaceType) {
12
+ handleInputBlur (interfaceType) {
13
13
  dispatch({ type: 'INPUT_BLUR', payload: interfaceType })
14
14
  },
15
15
 
16
- handleInputChange(e) {
16
+ handleInputChange (e) {
17
17
  const value = e.target.value
18
18
  dispatch({ type: 'SET_VALUE', payload: value })
19
19
 
@@ -101,4 +101,4 @@ describe('createInputHandlers', () => {
101
101
  expect(debouncedFetchSuggestions).toHaveBeenCalledWith(value)
102
102
  expect(debouncedFetchSuggestions.cancel).not.toHaveBeenCalled()
103
103
  })
104
- })
104
+ })
@@ -7,7 +7,7 @@ export const createSuggestionHandlers = ({ dispatch, services, mapProvider, mark
7
7
  : `${suggestions.length} suggestions available`
8
8
 
9
9
  return {
10
- handleSuggestionClick(suggestion, appState) {
10
+ handleSuggestionClick (suggestion, appState) {
11
11
  dispatch({ type: 'SET_VALUE', payload: suggestion.text })
12
12
  dispatch({ type: 'HIDE_SUGGESTIONS' })
13
13
  dispatch({ type: 'SET_SELECTED', payload: -1 })
@@ -21,7 +21,7 @@ export const createSuggestionHandlers = ({ dispatch, services, mapProvider, mark
21
21
  services.eventBus.emit('search:match', { query: suggestion.text, ...suggestion })
22
22
  },
23
23
 
24
- handleInputKeyDown(e, pluginState) {
24
+ handleInputKeyDown (e, pluginState) {
25
25
  const { suggestions, selectedIndex } = pluginState
26
26
 
27
27
  switch (e.key) {
@@ -163,4 +163,4 @@ describe('createSuggestionHandlers', () => {
163
163
  expect(dispatch).not.toHaveBeenCalled()
164
164
  expect(services.announce).not.toHaveBeenCalled()
165
165
  })
166
- })
166
+ })
@@ -4,11 +4,12 @@ import './search.scss'
4
4
  export default function createPlugin (options = {}) {
5
5
  // If search is open then we need to overirde the mobile slot
6
6
  if (options.expanded) {
7
- options.manifest = {controls: [{ id: 'search', mobile: { slot: 'banner' }}]}
7
+ options.manifest = { controls: [{ id: 'search', mobile: { slot: 'banner' } }] }
8
8
  }
9
9
 
10
10
  return {
11
11
  showMarker: true,
12
+ placeholder: 'Search',
12
13
  ...options,
13
14
  id: 'search',
14
15
  load: async () => {
@@ -1,10 +1,10 @@
1
1
  // /plugins/search/index.test.js
2
2
 
3
3
  // Mock SCSS import so Jest can run without parsing errors
4
- jest.mock('./search.scss', () => {})
5
-
6
4
  import createPlugin from './index'
7
5
 
6
+ jest.mock('./search.scss', () => {})
7
+
8
8
  describe('createPlugin', () => {
9
9
  beforeEach(() => {
10
10
  jest.resetModules()
@@ -44,4 +44,4 @@ describe('createPlugin', () => {
44
44
  const result = await plugin.load()
45
45
  expect(result).toEqual(manifestMock)
46
46
  })
47
- })
47
+ })
@@ -10,15 +10,17 @@ export const manifest = {
10
10
 
11
11
  controls: [{
12
12
  id: 'search',
13
- label: 'Search',
14
13
  mobile: {
15
- slot: 'top-right'
14
+ slot: 'top-right',
15
+ showLabel: false
16
16
  },
17
17
  tablet: {
18
- slot: 'top-left'
18
+ slot: 'top-left',
19
+ showLabel: false
19
20
  },
20
21
  desktop: {
21
- slot: 'top-left'
22
+ slot: 'top-left',
23
+ showLabel: false
22
24
  },
23
25
  render: Search
24
26
  }],
@@ -8,7 +8,6 @@ const initialState = {
8
8
  selectedIndex: -1
9
9
  }
10
10
 
11
-
12
11
  const toggleExpanded = (state, payload) => {
13
12
  return {
14
13
  ...state,
@@ -31,7 +30,7 @@ const inputBlur = (state, payload) => {
31
30
  ...state,
32
31
  hasKeyboardFocusWithin: false,
33
32
  areSuggestionsVisible: state.areSuggestionsVisible && payload !== 'keyboard',
34
- selectedIndex: -1,
33
+ selectedIndex: -1
35
34
  }
36
35
  }
37
36
 
@@ -73,7 +73,7 @@ describe('search state actions', () => {
73
73
 
74
74
  it('SET_SELECTED updates selectedIndex and visibility', () => {
75
75
  const state = { ...initialState, areSuggestionsVisible: false }
76
-
76
+
77
77
  const selected = actions.SET_SELECTED(state, 1)
78
78
  expect(selected.selectedIndex).toBe(1)
79
79
  expect(selected.areSuggestionsVisible).toBe(true)
@@ -82,4 +82,4 @@ describe('search state actions', () => {
82
82
  expect(deselected.selectedIndex).toBe(-1)
83
83
  expect(deselected.areSuggestionsVisible).toBe(false)
84
84
  })
85
- })
85
+ })
@@ -50,14 +50,21 @@
50
50
  }
51
51
  }
52
52
 
53
- .im-c-search__input {
53
+ // Input reset (extra specificity to reset govuk)
54
+ .im-o-app .im-c-search__input {
54
55
  width: 100%;
55
- border: 0;
56
- padding: var(--divider-gap) calc(var(--divider-gap) + 2px);
57
56
  color: var(--foreground-color);
58
57
  background-color: transparent;
58
+ font-size: 1rem;
59
+ border: 0;
60
+ padding: var(--divider-gap) calc(var(--divider-gap) + 2px);
59
61
  outline: 0;
60
62
 
63
+ &:focus {
64
+ outline: 3px solid transparent;
65
+ box-shadow: none;
66
+ }
67
+
61
68
  &::placeholder {
62
69
  color: var(--foreground-color);
63
70
  }
@@ -84,9 +91,14 @@
84
91
  opacity: 0;
85
92
  z-index: -1;
86
93
  }
87
- .im-o-app__inset .im-c-panel {
88
- opacity: 0;
89
- z-index: -1;
94
+ .im-o-app__left-top,
95
+ .im-o-app__left-bottom,
96
+ .im-o-app__right-top,
97
+ .im-o-app__right-bottom {
98
+ .im-c-panel {
99
+ opacity: 0;
100
+ z-index: -1;
101
+ }
90
102
  }
91
103
  }
92
104
 
@@ -6,7 +6,7 @@ const MAX_RESULTS = 8
6
6
 
7
7
  const isPostcode = (value) => {
8
8
  value = value.replace(/\s/g, '')
9
- const regex = /^(([A-Z]{1,2}\d[A-Z\d]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?\d[A-Z]{2}|BFPO ?\d{1,4}|(KY\d|MSR|VG|AI)[ -]?\d{4}|[A-Z]{2} ?\d{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$/i //NOSONAR
9
+ const regex = /^(([A-Z]{1,2}\d[A-Z\d]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?\d[A-Z]{2}|BFPO ?\d{1,4}|(KY\d|MSR|VG|AI)[ -]?\d{4}|[A-Z]{2} ?\d{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$/i // NOSONAR
10
10
  return regex.test(value)
11
11
  }
12
12
 
@@ -77,7 +77,6 @@ const bounds = (crs, { MBR_XMIN, MBR_YMIN, MBR_XMAX, MBR_YMAX, GEOMETRY_X, GEOME
77
77
  }
78
78
 
79
79
  const point = (crs, { GEOMETRY_X, GEOMETRY_Y }) => {
80
-
81
80
  // EPSG:27700 → return raw OSGB grid coordinates (meters)
82
81
  if (crs === 'EPSG:27700') {
83
82
  return [GEOMETRY_X, GEOMETRY_Y]
@@ -134,13 +134,13 @@ describe('osNamesUtils', () => {
134
134
  */
135
135
 
136
136
  test('uses NAME2 when NAME2_LANG is eng', () => {
137
- const entry = { GAZETTEER_ENTRY: { ...sampleEntry.GAZETTEER_ENTRY, NAME1: 'Caerdydd', NAME2: 'Cardiff', NAME2_LANG: 'eng' }}
137
+ const entry = { GAZETTEER_ENTRY: { ...sampleEntry.GAZETTEER_ENTRY, NAME1: 'Caerdydd', NAME2: 'Cardiff', NAME2_LANG: 'eng' } }
138
138
  const output = parseOsNamesResults({ results: [entry] }, 'Cardiff', regions, 'EPSG:27700')
139
139
  expect(output[0].text).toContain('Cardiff')
140
140
  })
141
141
 
142
142
  test('falls back to NAME1 when NAME2_LANG is not eng', () => {
143
- const entry = { GAZETTEER_ENTRY: { ...sampleEntry.GAZETTEER_ENTRY, NAME1: 'London', NAME2: 'Londres', NAME2_LANG: 'fra' }}
143
+ const entry = { GAZETTEER_ENTRY: { ...sampleEntry.GAZETTEER_ENTRY, NAME1: 'London', NAME2: 'Londres', NAME2_LANG: 'fra' } }
144
144
  const output = parseOsNamesResults({ results: [entry] }, 'London', regions, 'EPSG:27700')
145
145
  expect(output[0].text).toContain('London')
146
146
  })
@@ -10,7 +10,7 @@
10
10
  * @param {boolean} showMarker - Whether to display a marker
11
11
  * @param {boolean} showMarker - Marker colour
12
12
  */
13
- export function updateMap({ mapProvider, bounds, point, markers, showMarker, markerColor }) {
13
+ export function updateMap ({ mapProvider, bounds, point, markers, showMarker, markerColor }) {
14
14
  mapProvider.fitToBounds(bounds)
15
15
  if (showMarker) {
16
16
  markers.add('search', point, { color: markerColor })