@defra/interactive-map 0.0.4-alpha → 0.0.5-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 (202) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/index.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api.md +8 -0
  7. package/package.json +3 -2
  8. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -1
  9. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt +1 -0
  10. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -1
  11. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt +1 -0
  12. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  13. package/plugins/beta/datasets/src/datasets.js +55 -2
  14. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
  15. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
  16. package/plugins/beta/datasets/src/handleSetMapStyle.js +8 -1
  17. package/plugins/beta/datasets/src/mapLayers.js +41 -3
  18. package/plugins/beta/datasets/src/utils/bbox.js +113 -2
  19. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  20. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  21. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  22. package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
  23. package/plugins/beta/draw-ml/src/api/addFeature.js +15 -3
  24. package/plugins/beta/draw-ml/src/api/editFeature.js +16 -5
  25. package/plugins/beta/draw-ml/src/api/newLine.js +32 -7
  26. package/plugins/beta/draw-ml/src/api/newPolygon.js +33 -8
  27. package/plugins/beta/draw-ml/src/defaults.js +1 -0
  28. package/plugins/beta/draw-ml/src/draw.scss +23 -0
  29. package/plugins/beta/draw-ml/src/events.js +55 -113
  30. package/plugins/beta/draw-ml/src/manifest.js +64 -38
  31. package/plugins/beta/draw-ml/src/mapboxSnap.js +16 -12
  32. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +4 -3
  33. package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
  34. package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
  35. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
  36. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
  37. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
  38. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
  39. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +51 -406
  40. package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
  41. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  42. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  43. package/plugins/beta/frame/src/Frame.jsx +1 -1
  44. package/plugins/beta/map-styles/dist/css/index.css +1 -1
  45. package/plugins/beta/map-styles/dist/esm/index.js +1 -1
  46. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  47. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  48. package/plugins/beta/map-styles/src/mapStyles.scss +4 -1
  49. package/plugins/interact/dist/css/index.css +1 -1
  50. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  51. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  52. package/plugins/interact/dist/umd/index.js +1 -1
  53. package/plugins/interact/src/InteractInit.jsx +1 -1
  54. package/plugins/interact/src/defaults.js +1 -0
  55. package/plugins/interact/src/events.js +37 -52
  56. package/plugins/interact/src/events.test.js +35 -0
  57. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  58. package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -48
  59. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +64 -8
  60. package/plugins/interact/src/interact.scss +6 -0
  61. package/plugins/interact/src/reducer.js +25 -27
  62. package/plugins/interact/src/reducer.test.js +19 -6
  63. package/plugins/interact/src/utils/featureQueries.js +2 -2
  64. package/plugins/interact/src/utils/spatial.js +95 -0
  65. package/plugins/interact/src/utils/spatial.test.js +93 -0
  66. package/plugins/search/dist/css/index.css +1 -1
  67. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  68. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  69. package/plugins/search/dist/umd/index.js +1 -1
  70. package/plugins/search/src/Search.jsx +2 -2
  71. package/plugins/search/src/components/Form/Form.jsx +1 -1
  72. package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
  73. package/plugins/search/src/datasets.js +1 -1
  74. package/plugins/search/src/defaults.js +3 -0
  75. package/plugins/search/src/events/fetchSuggestions.js +48 -53
  76. package/plugins/search/src/events/formHandlers.js +3 -2
  77. package/plugins/search/src/events/index.js +1 -1
  78. package/plugins/search/src/events/inputHandlers.js +3 -1
  79. package/plugins/search/src/events/suggestionHandlers.js +12 -3
  80. package/plugins/search/src/search.scss +4 -1
  81. package/plugins/search/src/utils/parseOsNamesResults.js +12 -12
  82. package/providers/beta/esri/src/esriProvider.js +1 -1
  83. package/providers/beta/esri/src/index.js +1 -1
  84. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  85. package/providers/maplibre/dist/esm/index.js +1 -1
  86. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  87. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  88. package/providers/maplibre/dist/umd/index.js +1 -1
  89. package/providers/maplibre/src/defaults.js +3 -2
  90. package/providers/maplibre/src/index.js +22 -19
  91. package/providers/maplibre/src/maplibreProvider.js +13 -11
  92. package/providers/maplibre/src/utils/detectWebgl.js +1 -2
  93. package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
  94. package/providers/maplibre/src/utils/labels.js +174 -120
  95. package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
  96. package/providers/maplibre/src/utils/queryFeatures.js +146 -0
  97. package/providers/maplibre/src/utils/spatial.js +14 -17
  98. package/sonar-project.properties +39 -2
  99. package/src/App/components/Actions/Actions.jsx +2 -2
  100. package/src/App/components/Actions/Actions.module.scss +2 -2
  101. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
  102. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
  103. package/src/App/components/Logo/Logo.jsx +1 -1
  104. package/src/App/components/MapButton/MapButton.jsx +217 -41
  105. package/src/App/components/MapButton/MapButton.module.scss +32 -21
  106. package/src/App/components/MapButton/MapButton.test.jsx +128 -84
  107. package/src/App/components/Markers/Markers.jsx +1 -1
  108. package/src/App/components/Markers/Markers.module.scss +0 -5
  109. package/src/App/components/Panel/Panel.jsx +75 -45
  110. package/src/App/components/Panel/Panel.module.scss +18 -24
  111. package/src/App/components/Panel/Panel.test.jsx +18 -0
  112. package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
  113. package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
  114. package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
  115. package/src/App/components/Tooltip/Tooltip.jsx +4 -2
  116. package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
  117. package/src/App/components/Viewport/MapController.jsx +1 -1
  118. package/src/App/components/Viewport/MapStatus.jsx +1 -1
  119. package/src/App/components/Viewport/Viewport.jsx +2 -2
  120. package/src/App/components/Viewport/Viewport.module.scss +2 -2
  121. package/src/App/controls/keyboardActions.js +3 -2
  122. package/src/App/controls/keyboardShortcuts.js +4 -2
  123. package/src/App/hooks/useButtonStateEvaluator.js +58 -45
  124. package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
  125. package/src/App/hooks/useCrossHairAPI.js +49 -47
  126. package/src/App/hooks/useFocusVisible.js +2 -2
  127. package/src/App/hooks/useInterfaceAPI.js +16 -4
  128. package/src/App/hooks/useKeyboardHint.js +1 -1
  129. package/src/App/hooks/useKeyboardShortcuts.js +4 -2
  130. package/src/App/hooks/useMapAnnouncements.js +34 -32
  131. package/src/App/hooks/useMapEvents.js +12 -1
  132. package/src/App/hooks/useMapProviderOverrides.js +3 -3
  133. package/src/App/hooks/useMapStateSync.js +7 -1
  134. package/src/App/hooks/useMapURLSync.js +6 -1
  135. package/src/App/hooks/useMarkersAPI.js +88 -50
  136. package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
  137. package/src/App/hooks/useModalPanelBehaviour.js +46 -40
  138. package/src/App/hooks/useResizeObserver.js +2 -2
  139. package/src/App/initialiseApp.js +45 -38
  140. package/src/App/layout/Layout.jsx +6 -4
  141. package/src/App/layout/layout.module.scss +13 -17
  142. package/src/App/registry/keyboardShortcutRegistry.js +12 -2
  143. package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
  144. package/src/App/registry/mergeManifests.js +1 -1
  145. package/src/App/registry/panelRegistry.js +1 -1
  146. package/src/App/registry/panelRegistry.test.js +30 -1
  147. package/src/App/registry/pluginRegistry.js +14 -51
  148. package/src/App/registry/pluginRegistry.test.js +54 -6
  149. package/src/App/renderer/HtmlElementHost.jsx +186 -0
  150. package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
  151. package/src/App/renderer/mapButtons.js +11 -8
  152. package/src/App/renderer/mapButtons.test.js +6 -4
  153. package/src/App/renderer/mapControls.js +6 -0
  154. package/src/App/renderer/mapControls.test.js +10 -2
  155. package/src/App/renderer/mapPanels.js +39 -26
  156. package/src/App/renderer/mapPanels.test.js +29 -0
  157. package/src/App/renderer/pluginWrapper.test.js +28 -0
  158. package/src/App/renderer/slotHelpers.js +60 -0
  159. package/src/App/renderer/slotHelpers.test.js +82 -0
  160. package/src/App/store/AppProvider.jsx +3 -0
  161. package/src/App/store/AppProvider.test.jsx +40 -0
  162. package/src/App/store/PluginProvider.jsx +7 -5
  163. package/src/App/store/appActionsMap.js +25 -1
  164. package/src/App/store/appActionsMap.test.js +21 -0
  165. package/src/App/store/appReducer.js +1 -0
  166. package/src/App/store/mapActionsMap.js +12 -24
  167. package/src/App/store/mapReducer.js +1 -1
  168. package/src/InteractiveMap/InteractiveMap.js +79 -6
  169. package/src/InteractiveMap/InteractiveMap.test.js +164 -2
  170. package/src/InteractiveMap/behaviourController.js +12 -2
  171. package/src/InteractiveMap/behaviourController.test.js +82 -1
  172. package/src/InteractiveMap/deviceChecker.js +1 -1
  173. package/src/InteractiveMap/deviceChecker.test.js +2 -2
  174. package/src/InteractiveMap/historyManager.js +41 -5
  175. package/src/InteractiveMap/historyManager.test.js +37 -6
  176. package/src/InteractiveMap/polyfills.js +39 -0
  177. package/src/InteractiveMap/polyfills.test.js +127 -0
  178. package/src/config/appConfig.js +5 -5
  179. package/src/config/appConfig.test.js +107 -42
  180. package/src/config/defaults.js +1 -0
  181. package/src/index.umd.js +1 -1
  182. package/src/scss/main.scss +1 -0
  183. package/src/scss/settings/_dimensions.scss +6 -2
  184. package/src/services/eventBus.test.js +24 -1
  185. package/src/test-utils.js +1 -0
  186. package/src/types.js +5 -0
  187. package/src/utils/detectBreakpoint.js +78 -77
  188. package/src/utils/detectBreakpoint.test.js +50 -4
  189. package/src/utils/detectInterfaceType.js +4 -4
  190. package/src/utils/getIsFullscreen.js +3 -1
  191. package/src/utils/getSafeZoneInset.js +23 -7
  192. package/src/utils/getSafeZoneInset.test.js +18 -0
  193. package/src/utils/mapStateSync.js +3 -3
  194. package/src/utils/queryString.js +1 -1
  195. package/src/utils/stringToKebab.js +1 -1
  196. package/src/utils/toggleInertElements.js +37 -12
  197. package/webpack.dev.mjs +3 -4
  198. package/plugins/interact/src/utils/turfHelpers.js +0 -30
  199. package/plugins/interact/src/utils/turfHelpers.test.js +0 -30
  200. package/plugins/search/src/utils/fetchDataset.js +0 -23
  201. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
  202. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
@@ -12,7 +12,7 @@ let mockedQueries = {}
12
12
 
13
13
  class MockResizeObserver {
14
14
  constructor (callback) { this.callback = callback }
15
- observe (el) { MockResizeObserver.instance = this }
15
+ observe () { MockResizeObserver.instance = this }
16
16
  disconnect () { MockResizeObserver.instance = null }
17
17
  trigger (width, fallback) {
18
18
  this.callback([fallback ? { contentRect: { width } } : { borderBoxSize: [{ inlineSize: width }], contentRect: { width } }])
@@ -35,7 +35,9 @@ const mockMatchMedia = (query) => {
35
35
  window.matchMedia = jest.fn(mockMatchMedia)
36
36
 
37
37
  const triggerMQ = (query, matches) => {
38
- if (mockedQueries[query]) mockedQueries[query].matches = matches
38
+ if (mockedQueries[query]) {
39
+ mockedQueries[query].matches = matches
40
+ }
39
41
  mediaListeners[query]?.forEach(fn => fn({ matches }))
40
42
  }
41
43
 
@@ -68,6 +70,46 @@ describe('detectBreakpoint', () => {
68
70
  detector.destroy()
69
71
  })
70
72
 
73
+ /**
74
+ * Test to ensure coverage for the concurrency guard (Line 75).
75
+ * Validates that if multiple breakpoint changes occur before a RAF
76
+ * frame fires, only the most recent (current) state notifies listeners.
77
+ */
78
+ it('does not notify listeners if the breakpoint changed again before RAF fired', () => {
79
+ const listener = jest.fn()
80
+ const detector = createBreakpointDetector(cfg)
81
+ detector.subscribe(listener)
82
+
83
+ // 1. Initial flush to clear the "mount" notification
84
+ flushRAF()
85
+ listener.mockClear()
86
+
87
+ // 2. Trigger 'mobile'.
88
+ // This sets lastBreakpoint = 'mobile' and queues RAF #1 (type: 'mobile')
89
+ triggerMQ('(max-width: 768px)', true)
90
+
91
+ // 3. IMMEDIATELY switch to 'desktop' before flushing.
92
+ // IMPORTANT: We must turn mobile OFF so the detector sees 'desktop'.
93
+ triggerMQ('(max-width: 768px)', false)
94
+ triggerMQ('(min-width: 1024px)', true)
95
+
96
+ // At this point:
97
+ // - lastBreakpoint is now 'desktop'
98
+ // - There are 3 callbacks in the RAF queue:
99
+ // - RAF #1 from the mobile trigger (type: 'mobile')
100
+ // - RAF #2 from the mobile=false trigger (type: 'tablet')
101
+ // - RAF #3 from the desktop trigger (type: 'desktop')
102
+
103
+ flushRAF()
104
+
105
+ // RAF #1: ('mobile' === 'desktop') is false -> skip listener
106
+ // RAF #2: ('tablet' === 'desktop') is false -> skip listener
107
+ // RAF #3: ('desktop' === 'desktop') is true -> call listener
108
+ expect(listener).toHaveBeenCalledTimes(1)
109
+ expect(listener).toHaveBeenCalledWith('desktop')
110
+ expect(detector.getBreakpoint()).toBe('desktop')
111
+ })
112
+
71
113
  describe('viewport mode', () => {
72
114
  it.each([
73
115
  ['mobile', true, false],
@@ -76,8 +118,12 @@ describe('detectBreakpoint', () => {
76
118
  ])('detects %s', (name, mobile, desktop) => {
77
119
  window.matchMedia.mockImplementation(q => {
78
120
  const mq = mockMatchMedia(q)
79
- if (q === '(max-width: 768px)') mq.matches = mobile
80
- if (q === '(min-width: 1024px)') mq.matches = desktop
121
+ if (q === '(max-width: 768px)') {
122
+ mq.matches = mobile
123
+ }
124
+ if (q === '(min-width: 1024px)') {
125
+ mq.matches = desktop
126
+ }
81
127
  return mq
82
128
  })
83
129
  const detector = createBreakpointDetector(cfg)
@@ -53,14 +53,14 @@ function createInterfaceDetector () {
53
53
  }
54
54
  }
55
55
 
56
- globalThis.addEventListener('pointerdown', handlePointer, { passive: true })
57
- globalThis.addEventListener('keydown', handleKeyDown, { passive: true })
56
+ window.addEventListener('pointerdown', handlePointer, { passive: true })
57
+ window.addEventListener('keydown', handleKeyDown, { passive: true })
58
58
 
59
59
  // cleanup
60
60
  return () => {
61
61
  mql.removeEventListener('change', handleMediaChange)
62
- globalThis.removeEventListener('pointerdown', handlePointer)
63
- globalThis.removeEventListener('keydown', handleKeyDown)
62
+ window.removeEventListener('pointerdown', handlePointer)
63
+ window.removeEventListener('keydown', handleKeyDown)
64
64
  }
65
65
  }
66
66
 
@@ -10,7 +10,9 @@
10
10
  */
11
11
  export const isHybridFullscreen = (config) => {
12
12
  const { behaviour, hybridWidth, maxMobileWidth } = config
13
- if (behaviour !== 'hybrid') return false
13
+ if (behaviour !== 'hybrid') {
14
+ return false
15
+ }
14
16
  const threshold = hybridWidth ?? maxMobileWidth
15
17
  return window.matchMedia(`(max-width: ${threshold}px)`).matches
16
18
  }
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Calculates the safe zone inset — the unobscured region of the map viewport
3
+ * not hidden behind overlay panels, action bars or the footer. Used as padding
4
+ * for map operations like setCenter or fitBounds so the full extent is visible.
5
+ *
6
+ * The algorithm measures the available space around the inset panel and decides
7
+ * whether to push the safe area below or beside it, depending on whether the
8
+ * layout is landscape or portrait oriented.
9
+ *
10
+ * @param {Object} refs - React refs for the key layout elements.
11
+ * @param {React.RefObject} refs.mainRef - The main content area.
12
+ * @param {React.RefObject} refs.insetRef - The inset panel (e.g. search results).
13
+ * @param {React.RefObject} refs.rightRef - The right-hand button column.
14
+ * @param {React.RefObject} refs.actionsRef - The bottom action bar.
15
+ * @param {React.RefObject} refs.footerRef - The footer (logo, copyright etc).
16
+ * @returns {{ top: number, right: number, left: number, bottom: number } | undefined}
17
+ * Pixel insets from each edge of the main area, or undefined if any ref is missing.
18
+ */
1
19
  export const getSafeZoneInset = ({
2
20
  mainRef,
3
21
  insetRef,
@@ -5,16 +23,14 @@ export const getSafeZoneInset = ({
5
23
  actionsRef,
6
24
  footerRef
7
25
  }) => {
8
- const main = mainRef.current
9
- const inset = insetRef.current
10
- const right = rightRef.current
11
- const actions = actionsRef.current
12
- const footer = footerRef.current
26
+ const refs = [mainRef, insetRef, rightRef, actionsRef, footerRef]
13
27
 
14
- if (!main || !inset || !right || !actions || !footer) {
15
- return
28
+ if (refs.some(ref => !ref.current)) {
29
+ return undefined
16
30
  }
17
31
 
32
+ const [main, inset, right, actions, footer] = refs.map(ref => ref.current)
33
+
18
34
  const root = document.documentElement
19
35
  const dividerGap = Number.parseInt(getComputedStyle(root).getPropertyValue('--divider-gap'), 10)
20
36
 
@@ -68,4 +68,22 @@ describe('getSafeZoneInset', () => {
68
68
  expect(result.left).toBe(80)
69
69
  expect(result.right).toBe(80)
70
70
  })
71
+
72
+ /**
73
+ * Test to ensure coverage for the safety guardrail (Line 29).
74
+ * Validates that the function returns undefined if React refs are
75
+ * not yet attached to DOM elements.
76
+ */
77
+ it('returns undefined if any ref.current is null (unattached)', () => {
78
+ const unattachedRefs = {
79
+ mainRef: { current: null },
80
+ insetRef: { current: null },
81
+ rightRef: { current: null },
82
+ actionsRef: { current: null },
83
+ footerRef: { current: null }
84
+ }
85
+
86
+ const result = getSafeZoneInset(unattachedRefs)
87
+ expect(result).toBeUndefined()
88
+ })
71
89
  })
@@ -11,7 +11,7 @@ const getMapStateFromURL = (id, search) => {
11
11
  return { center: [lng, lat], zoom }
12
12
  }
13
13
 
14
- const setMapStateInURL = (id, state, currentHref = globalThis.location.href) => {
14
+ const setMapStateInURL = (id, state, currentHref = window.location.href) => {
15
15
  // Use the passed href or the global one
16
16
  const url = new URL(currentHref || 'http://localhost')
17
17
  const params = [...new URLSearchParams(url.search)].map(([key, value]) => `${key}=${value}`)
@@ -30,10 +30,10 @@ const setMapStateInURL = (id, state, currentHref = globalThis.location.href) =>
30
30
 
31
31
  const hash = url.hash || ''
32
32
  const newUrl = `${url.origin}${url.pathname}?${[...filteredParams, ...newParams].join('&')}${hash}`
33
- globalThis.history.replaceState(globalThis.history.state, '', newUrl)
33
+ window.history.replaceState(window.history.state, '', newUrl)
34
34
  }
35
35
 
36
- const getInitialMapState = ({ id, center, zoom, bounds }, search = globalThis.location.search) => {
36
+ const getInitialMapState = ({ id, center, zoom, bounds }, search = window.location.search) => {
37
37
  // Pass search string down to the internal function
38
38
  const savedState = getMapStateFromURL(id, search)
39
39
  if (savedState) {
@@ -1,4 +1,4 @@
1
- export const getQueryParam = (name, search = globalThis.location?.search) => {
1
+ export const getQueryParam = (name, search = window.location?.search) => {
2
2
  const urlParams = new URLSearchParams(search)
3
3
  return urlParams.get(name)
4
4
  }
@@ -1,3 +1,3 @@
1
1
  export function stringToKebab (str) {
2
- return str?.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
2
+ return str?.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
3
3
  }
@@ -1,31 +1,56 @@
1
+ /**
2
+ * Checks whether a sibling element should be marked as inert.
3
+ * @param {Element} sibling - The sibling element to evaluate
4
+ * @param {Element} el - The current element being walked up from
5
+ * @param {Element|null} containerEl - The container to preserve (not make inert)
6
+ * @param {Element} boundaryEl - The outermost boundary for the traversal
7
+ * @returns {boolean}
8
+ */
9
+ const shouldMakeInert = (sibling, el, containerEl, boundaryEl) =>
10
+ sibling !== el &&
11
+ !containerEl?.contains(sibling) &&
12
+ sibling.matches(':not([aria-hidden]):not([data-fm-inert])') &&
13
+ boundaryEl.contains(sibling)
14
+
15
+ /**
16
+ * Toggles inert state on elements outside a container for fullscreen/modal behaviour.
17
+ *
18
+ * When entering fullscreen, walks up from `containerEl` to `boundaryEl`, marking all
19
+ * sibling branches as `aria-hidden` and flagging them with `data-fm-inert` so they
20
+ * can be restored later. When exiting fullscreen, restores all previously inerted elements.
21
+ *
22
+ * @param {Object} options
23
+ * @param {Element|null} options.containerEl - The element to keep interactive
24
+ * @param {boolean} options.isFullscreen - Whether fullscreen mode is active
25
+ * @param {Element} [options.boundaryEl=document.body] - The outermost ancestor to traverse up to
26
+ */
1
27
  export function toggleInertElements ({ containerEl, isFullscreen, boundaryEl = document.body }) {
28
+ // Restore any previously inerted elements
2
29
  let inertElements = Array.from(boundaryEl.querySelectorAll('[data-fm-inert]'))
3
30
 
4
31
  if (containerEl) {
5
- inertElements = inertElements.filter(el => !containerEl.contains(el))
32
+ inertElements = inertElements.filter(inertEl => !containerEl.contains(inertEl))
6
33
  }
7
34
 
8
- inertElements.forEach(el => {
9
- el.removeAttribute('aria-hidden')
10
- el.removeAttribute('data-fm-inert')
35
+ inertElements.forEach(inertEl => {
36
+ inertEl.removeAttribute('aria-hidden')
37
+ delete inertEl.dataset.fmInert
11
38
  })
12
39
 
13
- if (!isFullscreen) return
40
+ if (!isFullscreen) {
41
+ return
42
+ }
14
43
 
44
+ // Entering fullscreen: blur active element and inert all sibling branches
15
45
  document.activeElement?.blur()
16
46
 
17
47
  let el = containerEl
18
48
  while (el?.parentNode && el !== boundaryEl && el !== document.body) {
19
49
  const parent = el.parentNode
20
50
  for (const sibling of parent.children) {
21
- if (
22
- sibling !== el &&
23
- (!containerEl || !containerEl.contains(sibling)) &&
24
- sibling.matches(':not([aria-hidden]):not([data-fm-inert])') &&
25
- boundaryEl.contains(sibling)
26
- ) {
51
+ if (shouldMakeInert(sibling, el, containerEl, boundaryEl)) {
27
52
  sibling.setAttribute('aria-hidden', 'true')
28
- sibling.setAttribute('data-fm-inert', '')
53
+ sibling.dataset.fmInert = ''
29
54
  }
30
55
  }
31
56
  el = parent
package/webpack.dev.mjs CHANGED
@@ -66,9 +66,8 @@ export default {
66
66
  OS_NAMES_URL: JSON.stringify(process.env.OS_NAMES_URL),
67
67
  OS_NEAREST_URL: JSON.stringify(process.env.OS_NEAREST_URL),
68
68
  // Data services
69
- PARCEL_TILE_SERVICE_URL: JSON.stringify(process.env.PARCEL_TILE_SERVICE_URL),
70
- GRIDREF_SERVICE_URL: JSON.stringify(process.env.GRIDREF_SERVICE_URL),
71
- PARCEL_SERVICE_URL: JSON.stringify(process.env.PARCEL_SERVICE_URL)
69
+ FARMING_TILE_SERVICE_URL: JSON.stringify(process.env.FARMING_TILE_SERVICE_URL),
70
+ FARMING_API_URL: JSON.stringify(process.env.FARMING_API_URL)
72
71
  }
73
72
  })
74
73
  ],
@@ -77,7 +76,7 @@ export default {
77
76
  {
78
77
  test: /\.jsx?$/,
79
78
  loader: 'babel-loader',
80
- exclude: /node_modules\/(?!(lucide-react))/
79
+ exclude: /node_modules/
81
80
  },{
82
81
  test: /\.s[ac]ss$/i,
83
82
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
@@ -1,30 +0,0 @@
1
- import { polygon, multiPolygon, lineString, multiLineString, point, multiPoint } from '@turf/helpers'
2
-
3
- /**
4
- * Convert a GeoJSON Feature or geometry-like object into a Turf geometry.
5
- *
6
- * @param {Object} featureOrGeom - Either a Feature with a `.geometry` property or a raw GeoJSON geometry object.
7
- * @returns {Object} Turf geometry (Polygon, LineString, Point, etc.)
8
- *
9
- * @throws Will throw if the geometry type is not supported.
10
- */
11
- export function toTurfGeometry(featureOrGeom) {
12
- const geom = featureOrGeom.geometry || featureOrGeom
13
-
14
- switch (geom.type) {
15
- case 'Polygon':
16
- return polygon(geom.coordinates);
17
- case 'MultiPolygon':
18
- return multiPolygon(geom.coordinates);
19
- case 'LineString':
20
- return lineString(geom.coordinates);
21
- case 'MultiLineString':
22
- return multiLineString(geom.coordinates);
23
- case 'Point':
24
- return point(geom.coordinates);
25
- case 'MultiPoint':
26
- return multiPoint(geom.coordinates);
27
- default:
28
- throw new Error(`Unsupported geometry type: ${geom.type}`)
29
- }
30
- }
@@ -1,30 +0,0 @@
1
- import { toTurfGeometry } from './turfHelpers.js'
2
- import { polygon, multiPolygon, lineString, multiLineString, point, multiPoint } from '@turf/helpers'
3
-
4
- describe('toTurfGeometry', () => {
5
- const geomTypes = [
6
- { type: 'Polygon', coords: [[[0,0],[1,0],[1,1],[0,0]]], fn: polygon },
7
- { type: 'MultiPolygon', coords: [[[[0,0],[1,0],[1,1],[0,0]]]], fn: multiPolygon },
8
- { type: 'LineString', coords: [[0,0],[1,1]], fn: lineString },
9
- { type: 'MultiLineString', coords: [[[0,0],[1,1]]], fn: multiLineString },
10
- { type: 'Point', coords: [0,0], fn: point },
11
- { type: 'MultiPoint', coords: [[0,0],[1,1]], fn: multiPoint }
12
- ]
13
-
14
- geomTypes.forEach(({ type, coords, fn }) => {
15
- it(`converts ${type} Feature`, () => {
16
- const feature = { type: 'Feature', geometry: { type, coordinates: coords } }
17
- expect(toTurfGeometry(feature)).toEqual(fn(coords))
18
- })
19
-
20
- it(`converts raw ${type} geometry`, () => {
21
- const geom = { type, coordinates: coords }
22
- expect(toTurfGeometry(geom)).toEqual(fn(coords))
23
- })
24
- })
25
-
26
- it('throws on unsupported geometry', () => {
27
- expect(() => toTurfGeometry({ type: 'Feature', geometry: { type: 'Circle', coordinates: [] } }))
28
- .toThrow('Unsupported geometry type: Circle')
29
- })
30
- })
@@ -1,23 +0,0 @@
1
- // src/plugins/search/utils/fetch.js
2
- export async function fetchDataset(url, options = {}, transformRequest) {
3
- let request = new Request(url, options)
4
-
5
- if (transformRequest) {
6
- try {
7
- request = await transformRequest(request)
8
- } catch (err) {
9
- console.error('Error transforming request:', err)
10
- return null
11
- }
12
- }
13
-
14
- const response = await fetch(request)
15
-
16
- if (!response.ok) {
17
- console.error('Fetch error', response.status)
18
- return null
19
- }
20
-
21
- const json = await response.json()
22
- return json
23
- }