@defra/interactive-map 0.0.15-alpha → 0.0.17-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 (263) 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/assets/images/slot-map.svg +264 -0
  5. package/dist/css/index.css +1 -1
  6. package/dist/esm/im-core.js +1 -1
  7. package/dist/esm/im-shell.js +1 -1
  8. package/dist/umd/im-core.js +1 -1
  9. package/dist/umd/index.js +1 -1
  10. package/docs/api/slots.md +90 -6
  11. package/docs/api.md +4 -4
  12. package/docs/architecture.md +3 -1
  13. package/docs/{demo.mdx → examples.mdx} +1 -1
  14. package/docs/getting-started.md +5 -4
  15. package/docs/index.mdx +42 -0
  16. package/docs/plugins/datasets.md +561 -0
  17. package/docs/plugins/interact.md +176 -55
  18. package/docs/plugins/map-styles.md +64 -7
  19. package/docs/plugins/search.md +207 -63
  20. package/docs/plugins.md +8 -16
  21. package/docusaurus.config.cjs +34 -34
  22. package/jest.setup.js +1 -1
  23. package/package.json +6 -5
  24. package/plugins/beta/datasets/dist/css/index.css +85 -15
  25. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  26. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  27. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  28. package/plugins/beta/datasets/src/DatasetsInit.jsx +24 -9
  29. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  30. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
  31. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
  32. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
  33. package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
  34. package/plugins/beta/datasets/src/api/addDataset.js +3 -9
  35. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  36. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  37. package/plugins/beta/datasets/src/api/removeDataset.js +3 -45
  38. package/plugins/beta/datasets/src/api/setData.js +8 -0
  39. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  40. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  41. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  42. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  43. package/plugins/beta/datasets/src/datasets.js +33 -59
  44. package/plugins/beta/datasets/src/defaults.js +43 -9
  45. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +39 -30
  46. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  47. package/plugins/beta/datasets/src/manifest.js +27 -19
  48. package/plugins/beta/datasets/src/panels/Key.jsx +129 -49
  49. package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
  50. package/plugins/beta/datasets/src/panels/Layers.jsx +131 -29
  51. package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
  52. package/plugins/beta/datasets/src/reducer.js +128 -9
  53. package/plugins/beta/datasets/src/styles/patterns.js +157 -0
  54. package/plugins/beta/datasets/src/utils/bbox.js +8 -6
  55. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  56. package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
  57. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  58. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  59. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  60. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  61. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  62. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  63. package/plugins/beta/draw-es/src/events.js +52 -20
  64. package/plugins/beta/draw-es/src/events.test.js +301 -0
  65. package/plugins/beta/draw-es/src/graphic.js +1 -1
  66. package/plugins/beta/draw-es/src/manifest.js +4 -4
  67. package/plugins/beta/draw-es/src/reducer.js +1 -1
  68. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  69. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  70. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  71. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  72. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  73. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  74. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  75. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  76. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  77. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  78. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  79. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  80. package/plugins/beta/draw-ml/src/events.js +8 -6
  81. package/plugins/beta/draw-ml/src/manifest.js +29 -29
  82. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  83. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  84. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  85. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  86. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  87. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  88. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  89. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  90. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  91. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  92. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  93. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  94. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  95. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  96. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  97. package/plugins/beta/frame/src/Frame.jsx +9 -9
  98. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  99. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  100. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  101. package/plugins/beta/frame/src/config.js +1 -1
  102. package/plugins/beta/frame/src/manifest.js +3 -3
  103. package/plugins/beta/frame/src/reducer.js +1 -1
  104. package/plugins/beta/frame/src/utils.js +1 -1
  105. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  106. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  107. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  108. package/plugins/beta/map-styles/src/manifest.js +1 -1
  109. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  110. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  111. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  112. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  113. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  114. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  115. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  116. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  117. package/plugins/beta/use-location/src/defaults.js +1 -1
  118. package/plugins/beta/use-location/src/events.js +3 -3
  119. package/plugins/interact/dist/css/index.css +1 -1
  120. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  121. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  122. package/plugins/interact/src/InteractInit.jsx +1 -2
  123. package/plugins/interact/src/api/enable.js +8 -5
  124. package/plugins/interact/src/api/enable.test.js +2 -2
  125. package/plugins/interact/src/api/selectFeature.js +4 -4
  126. package/plugins/interact/src/api/unselectFeature.js +5 -5
  127. package/plugins/interact/src/defaults.js +0 -1
  128. package/plugins/interact/src/events.test.js +15 -15
  129. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  130. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  131. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  132. package/plugins/interact/src/interact.scss +0 -7
  133. package/plugins/interact/src/manifest.js +15 -19
  134. package/plugins/interact/src/manifest.test.js +6 -5
  135. package/plugins/interact/src/reducer.js +3 -3
  136. package/plugins/interact/src/reducer.test.js +0 -1
  137. package/plugins/interact/src/utils/spatial.js +10 -10
  138. package/plugins/interact/src/utils/spatial.test.js +14 -14
  139. package/plugins/search/dist/css/index.css +1 -1
  140. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  141. package/plugins/search/dist/esm/index.js +1 -1
  142. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  143. package/plugins/search/dist/umd/index.js +1 -1
  144. package/plugins/search/src/Search.jsx +7 -6
  145. package/plugins/search/src/Search.test.jsx +23 -23
  146. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  147. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  148. package/plugins/search/src/components/Form/Form.jsx +14 -14
  149. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  150. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  151. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  152. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  153. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  154. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  155. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  156. package/plugins/search/src/datasets.js +12 -13
  157. package/plugins/search/src/datasets.test.js +1 -1
  158. package/plugins/search/src/defaults.js +1 -1
  159. package/plugins/search/src/events/fetchSuggestions.js +3 -3
  160. package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
  161. package/plugins/search/src/events/formHandlers.js +3 -3
  162. package/plugins/search/src/events/formHandlers.test.js +1 -1
  163. package/plugins/search/src/events/index.js +2 -2
  164. package/plugins/search/src/events/index.test.js +2 -2
  165. package/plugins/search/src/events/inputHandlers.js +4 -4
  166. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  167. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  168. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  169. package/plugins/search/src/index.js +2 -1
  170. package/plugins/search/src/index.test.js +3 -3
  171. package/plugins/search/src/manifest.js +6 -4
  172. package/plugins/search/src/reducer.js +1 -2
  173. package/plugins/search/src/reducer.test.js +2 -2
  174. package/plugins/search/src/search.scss +10 -3
  175. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  176. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  177. package/plugins/search/src/utils/updateMap.js +1 -1
  178. package/plugins/search/src/utils/updateMap.test.js +5 -5
  179. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  180. package/providers/beta/esri/src/esriProvider.js +5 -5
  181. package/providers/beta/esri/src/utils/coords.js +1 -1
  182. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  183. package/providers/beta/esri/src/utils/query.js +4 -4
  184. package/providers/beta/esri/src/utils/spatial.js +1 -2
  185. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  186. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  187. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  188. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  189. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  190. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  191. package/providers/maplibre/src/appEvents.test.js +1 -1
  192. package/providers/maplibre/src/index.js +1 -1
  193. package/providers/maplibre/src/index.test.js +3 -5
  194. package/providers/maplibre/src/mapEvents.test.js +15 -5
  195. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  196. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  197. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  198. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  199. package/providers/maplibre/src/utils/highlightFeatures.js +3 -2
  200. package/providers/maplibre/src/utils/highlightFeatures.test.js +13 -6
  201. package/providers/maplibre/src/utils/labels.js +19 -20
  202. package/providers/maplibre/src/utils/labels.test.js +15 -13
  203. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  204. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  205. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  206. package/providers/maplibre/src/utils/spatial.js +0 -1
  207. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  208. package/src/App/components/Actions/Actions.jsx +2 -2
  209. package/src/App/components/Actions/Actions.module.scss +0 -7
  210. package/src/App/components/Actions/Actions.test.jsx +1 -1
  211. package/src/App/components/Icon/Icon.jsx +3 -2
  212. package/src/App/components/Icon/Icon.module.scss +4 -0
  213. package/src/App/components/Icon/Icon.test.jsx +43 -4
  214. package/src/App/components/MapButton/MapButton.jsx +42 -17
  215. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  216. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  217. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  218. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  219. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  220. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  221. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  222. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  223. package/src/App/hooks/useInterfaceAPI.js +6 -0
  224. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  225. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  226. package/src/App/layout/Layout.jsx +12 -7
  227. package/src/App/layout/Layout.test.jsx +2 -2
  228. package/src/App/layout/layout.module.scss +67 -29
  229. package/src/App/registry/pluginRegistry.js +17 -0
  230. package/src/App/registry/pluginRegistry.test.js +33 -0
  231. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  232. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  233. package/src/App/renderer/mapButtons.js +3 -2
  234. package/src/App/renderer/mapPanels.test.js +2 -2
  235. package/src/App/renderer/slotHelpers.js +2 -2
  236. package/src/App/renderer/slotHelpers.test.js +5 -5
  237. package/src/App/renderer/slots.js +9 -5
  238. package/src/App/store/AppProvider.jsx +3 -1
  239. package/src/App/store/AppProvider.test.jsx +1 -1
  240. package/src/App/store/ServiceProvider.jsx +3 -1
  241. package/src/App/store/appActionsMap.js +16 -0
  242. package/src/App/store/appActionsMap.test.js +27 -0
  243. package/src/App/store/appDispatchMiddleware.js +33 -1
  244. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  245. package/src/App/store/appReducer.js +2 -0
  246. package/src/InteractiveMap/InteractiveMap.js +4 -0
  247. package/src/config/appConfig.js +7 -4
  248. package/src/config/events.js +28 -0
  249. package/src/scss/main.scss +1 -0
  250. package/src/scss/settings/_dimensions.scss +0 -1
  251. package/src/services/logger.js +6 -0
  252. package/src/services/logger.test.js +32 -0
  253. package/src/utils/getSafeZoneInset.js +9 -7
  254. package/src/utils/getSafeZoneInset.test.js +10 -10
  255. package/webpack.dev.mjs +23 -19
  256. package/docs/govuk-prototype.md +0 -23
  257. package/docs/index.md +0 -19
  258. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  259. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  260. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  261. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  262. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  263. package/plugins/beta/datasets/src/mapLayers.js +0 -165
@@ -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
+ }
@@ -27,7 +27,7 @@ const getRequestConfig = async (ds, query, transformRequest) => {
27
27
  const fetchDatasetResults = async (ds, request, query) => {
28
28
  try {
29
29
  const response = await fetch(request)
30
-
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
+ }
@@ -209,4 +209,4 @@ describe('fetchSuggestions', () => {
209
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
  }
@@ -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 })
@@ -10,11 +10,11 @@ describe('updateMap', () => {
10
10
 
11
11
  beforeEach(() => {
12
12
  mapProvider = {
13
- fitToBounds: jest.fn(),
13
+ fitToBounds: jest.fn()
14
14
  }
15
15
 
16
16
  markers = {
17
- add: jest.fn(),
17
+ add: jest.fn()
18
18
  }
19
19
  })
20
20
 
@@ -25,7 +25,7 @@ describe('updateMap', () => {
25
25
  point,
26
26
  markers,
27
27
  showMarker: true,
28
- markerColor: 'red',
28
+ markerColor: 'red'
29
29
  })
30
30
 
31
31
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(bounds)
@@ -43,10 +43,10 @@ describe('updateMap', () => {
43
43
  point,
44
44
  markers,
45
45
  showMarker: false,
46
- markerColor: 'blue',
46
+ markerColor: 'blue'
47
47
  })
48
48
 
49
49
  expect(mapProvider.fitToBounds).toHaveBeenCalledWith(bounds)
50
50
  expect(markers.add).not.toHaveBeenCalled()
51
51
  })
52
- })
52
+ })