@defra/interactive-map 0.0.3-alpha → 0.0.4-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 (268) hide show
  1. package/README.md +16 -57
  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/button-definition.md +176 -0
  7. package/docs/api/context.md +124 -0
  8. package/docs/api/control-definition.md +72 -0
  9. package/docs/api/icon-definition.md +28 -0
  10. package/docs/api/map-style-config.md +76 -0
  11. package/docs/api/marker-config.md +51 -0
  12. package/docs/api/panel-definition.md +117 -0
  13. package/docs/api/slots.md +25 -0
  14. package/docs/api.md +713 -0
  15. package/docs/architecture.md +139 -0
  16. package/docs/building-a-plugin.md +149 -0
  17. package/docs/getting-started.md +46 -0
  18. package/docs/govuk-prototype.md +0 -0
  19. package/docs/plugins/interact.md +205 -0
  20. package/docs/plugins/map-styles.md +84 -0
  21. package/docs/plugins/plugin-context.md +67 -0
  22. package/docs/plugins/plugin-descriptor.md +71 -0
  23. package/docs/plugins/plugin-manifest.md +118 -0
  24. package/docs/plugins/scale-bar.md +62 -0
  25. package/docs/plugins/search.md +162 -0
  26. package/docs/plugins.md +116 -0
  27. package/govuk-prototype-kit.config.json +7 -7
  28. package/package.json +14 -1
  29. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -0
  30. package/plugins/beta/datasets/dist/esm/index.js +2 -0
  31. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -0
  32. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  33. package/plugins/{datasets → beta/datasets}/src/manifest.js +1 -1
  34. package/plugins/{datasets → beta/datasets}/src/mapLayers.js +1 -1
  35. package/plugins/{datasets → beta/datasets}/src/panels/Key.jsx +1 -1
  36. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -0
  37. package/plugins/beta/draw-ml/dist/esm/index.js +2 -0
  38. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -0
  39. package/plugins/beta/draw-ml/dist/umd/index.js +2 -0
  40. package/plugins/{draw-ml → beta/draw-ml}/src/api/editFeature.js +1 -1
  41. package/plugins/beta/draw-ml/src/api/merge.js +9 -0
  42. package/plugins/beta/draw-ml/src/api/newLine.js +43 -0
  43. package/plugins/{draw-ml → beta/draw-ml}/src/api/newPolygon.js +5 -5
  44. package/plugins/beta/draw-ml/src/api/split.js +81 -0
  45. package/plugins/{draw-ml → beta/draw-ml}/src/defaults.js +2 -0
  46. package/plugins/{draw-ml → beta/draw-ml}/src/events.js +105 -3
  47. package/plugins/{draw-ml → beta/draw-ml}/src/manifest.js +17 -11
  48. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxDraw.js +51 -2
  49. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxSnap.js +2 -0
  50. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +496 -0
  51. package/plugins/beta/draw-ml/src/modes/drawLineMode.js +18 -0
  52. package/plugins/beta/draw-ml/src/modes/drawPolygonMode.js +16 -0
  53. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +792 -0
  54. package/plugins/{draw-ml → beta/draw-ml}/src/reducer.js +30 -2
  55. package/plugins/{draw-ml → beta/draw-ml}/src/styles.js +66 -36
  56. package/plugins/beta/draw-ml/src/undoStack.js +50 -0
  57. package/plugins/beta/draw-ml/src/utils/debounce.js +16 -0
  58. package/plugins/beta/draw-ml/src/utils/spatial.js +258 -0
  59. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -0
  60. package/plugins/beta/frame/dist/esm/index.js +2 -0
  61. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -0
  62. package/plugins/beta/frame/dist/umd/index.js +2 -0
  63. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -0
  64. package/plugins/beta/map-styles/dist/esm/index.js +2 -0
  65. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -0
  66. package/plugins/beta/map-styles/dist/umd/index.js +2 -0
  67. package/plugins/{map-styles → beta/map-styles}/src/MapStyles.jsx +1 -1
  68. package/plugins/{map-styles → beta/map-styles}/src/manifest.js +8 -10
  69. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -0
  70. package/plugins/beta/scale-bar/dist/esm/index.js +2 -0
  71. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -0
  72. package/plugins/beta/scale-bar/dist/umd/index.js +2 -0
  73. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -0
  74. package/plugins/beta/use-location/dist/esm/index.js +2 -0
  75. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -0
  76. package/plugins/beta/use-location/dist/umd/index.js +2 -0
  77. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  78. package/plugins/interact/dist/esm/index.js +1 -1
  79. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  80. package/plugins/interact/dist/umd/index.js +1 -1
  81. package/plugins/interact/src/InteractInit.test.js +97 -0
  82. package/plugins/interact/src/api/clear.test.js +18 -0
  83. package/plugins/interact/src/api/disable.test.js +12 -0
  84. package/plugins/interact/src/api/enable.test.js +48 -0
  85. package/plugins/interact/src/api/selectFeature.test.js +33 -0
  86. package/plugins/interact/src/api/unselectFeature.test.js +33 -0
  87. package/plugins/interact/src/defaults.js +1 -0
  88. package/plugins/interact/src/events.test.js +151 -0
  89. package/plugins/interact/src/hooks/useHighlightSync.test.js +212 -0
  90. package/plugins/interact/src/hooks/useInteractionHandlers.js +14 -7
  91. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +260 -0
  92. package/plugins/interact/src/index.test.js +22 -0
  93. package/plugins/interact/src/manifest.js +1 -1
  94. package/plugins/interact/src/manifest.test.js +94 -0
  95. package/plugins/interact/src/reducer.js +10 -1
  96. package/plugins/interact/src/reducer.test.js +142 -0
  97. package/plugins/interact/src/utils/buildStylesMap.test.js +57 -0
  98. package/plugins/interact/src/utils/featureQueries.test.js +56 -0
  99. package/plugins/interact/src/utils/turfHelpers.js +30 -0
  100. package/plugins/interact/src/utils/turfHelpers.test.js +30 -0
  101. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  102. package/plugins/search/dist/esm/index.js +1 -1
  103. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  104. package/plugins/search/dist/umd/index.js +1 -1
  105. package/plugins/search/src/utils/parseOsNamesResults.js +3 -7
  106. package/providers/{esri → beta/esri}/src/mapEvents.js +2 -2
  107. package/providers/beta/open-names/dist/esm/im-reverse-geocode.js +2 -0
  108. package/providers/beta/open-names/dist/esm/index.js +2 -0
  109. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js +2 -0
  110. package/providers/beta/open-names/dist/umd/index.js +2 -0
  111. package/providers/maplibre/dist/esm/im-maplibre-framework.js +1 -1
  112. package/providers/maplibre/dist/esm/im-maplibre-framework.js.LICENSE.txt +1 -1
  113. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +1 -1
  114. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  115. package/providers/maplibre/dist/esm/index.js +1 -1
  116. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  117. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  118. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +1 -1
  119. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  120. package/providers/maplibre/dist/umd/index.js +1 -1
  121. package/providers/maplibre/src/index.js +14 -1
  122. package/providers/maplibre/src/maplibreProvider.js +127 -0
  123. package/sonar-project.properties +1 -1
  124. package/src/App/hooks/useInterfaceAPI.js +24 -1
  125. package/src/App/renderer/mapButtons.js +1 -1
  126. package/src/App/store/appActionsMap.js +22 -1
  127. package/src/InteractiveMap/InteractiveMap.js +114 -6
  128. package/src/config/defaults.js +29 -20
  129. package/src/config/events.js +110 -4
  130. package/src/index.js +5 -2
  131. package/src/scss/tools/_border-focus.scss +20 -4
  132. package/src/types.js +585 -0
  133. package/webpack.esm.mjs +8 -8
  134. package/webpack.umd.mjs +8 -8
  135. package/plugins/datasets/dist/esm/im-datasets-plugin.js +0 -1
  136. package/plugins/datasets/dist/esm/index.js +0 -2
  137. package/plugins/datasets/dist/umd/im-datasets-plugin.js +0 -1
  138. package/plugins/datasets/dist/umd/index.js +0 -2
  139. package/plugins/draw-ml/dist/esm/im-draw-ml-plugin.js +0 -1
  140. package/plugins/draw-ml/dist/esm/index.js +0 -2
  141. package/plugins/draw-ml/dist/umd/im-draw-ml-plugin.js +0 -1
  142. package/plugins/draw-ml/dist/umd/index.js +0 -2
  143. package/plugins/draw-ml/src/modes/drawVertexMode.js +0 -275
  144. package/plugins/draw-ml/src/modes/editVertexMode.js +0 -426
  145. package/plugins/draw-ml/src/utils.js +0 -102
  146. package/plugins/frame/dist/esm/im-frame-plugin.js +0 -1
  147. package/plugins/frame/dist/esm/index.js +0 -2
  148. package/plugins/frame/dist/umd/im-frame-plugin.js +0 -1
  149. package/plugins/frame/dist/umd/index.js +0 -2
  150. package/plugins/map-styles/dist/esm/im-map-styles-plugin.js +0 -1
  151. package/plugins/map-styles/dist/esm/index.js +0 -2
  152. package/plugins/map-styles/dist/umd/im-map-styles-plugin.js +0 -1
  153. package/plugins/map-styles/dist/umd/index.js +0 -2
  154. package/plugins/scale-bar/dist/esm/im-scale-bar-plugin.js +0 -1
  155. package/plugins/scale-bar/dist/esm/index.js +0 -2
  156. package/plugins/scale-bar/dist/umd/im-scale-bar-plugin.js +0 -1
  157. package/plugins/scale-bar/dist/umd/index.js +0 -2
  158. package/plugins/use-location/dist/esm/im-use-location-plugin.js +0 -1
  159. package/plugins/use-location/dist/esm/index.js +0 -2
  160. package/plugins/use-location/dist/umd/im-use-location-plugin.js +0 -1
  161. package/plugins/use-location/dist/umd/index.js +0 -2
  162. package/providers/open-names/dist/esm/im-reverse-geocode.js +0 -2
  163. package/providers/open-names/dist/esm/index.js +0 -2
  164. package/providers/open-names/dist/umd/im-reverse-geocode.js +0 -2
  165. package/providers/open-names/dist/umd/index.js +0 -2
  166. /package/docs/architecture/{ARCHITECTURE_DIAGRAMS.md → architecture-diagrams.md} +0 -0
  167. /package/plugins/{datasets → beta/datasets}/dist/css/index.css +0 -0
  168. /package/plugins/{datasets → beta/datasets}/dist/esm/index.js.LICENSE.txt +0 -0
  169. /package/plugins/{datasets → beta/datasets}/dist/umd/index.js.LICENSE.txt +0 -0
  170. /package/plugins/{datasets → beta/datasets}/src/DatasetsInit.jsx +0 -0
  171. /package/plugins/{datasets → beta/datasets}/src/api/addDataset.js +0 -0
  172. /package/plugins/{datasets → beta/datasets}/src/api/hideDataset.js +0 -0
  173. /package/plugins/{datasets → beta/datasets}/src/api/hideFeatures.js +0 -0
  174. /package/plugins/{datasets → beta/datasets}/src/api/removeDataset.js +0 -0
  175. /package/plugins/{datasets → beta/datasets}/src/api/showDataset.js +0 -0
  176. /package/plugins/{datasets → beta/datasets}/src/api/showFeatures.js +0 -0
  177. /package/plugins/{datasets → beta/datasets}/src/datasets.js +0 -0
  178. /package/plugins/{datasets → beta/datasets}/src/datasets.scss +0 -0
  179. /package/plugins/{datasets → beta/datasets}/src/defaults.js +0 -0
  180. /package/plugins/{datasets → beta/datasets}/src/handleSetMapStyle.js +0 -0
  181. /package/plugins/{datasets → beta/datasets}/src/index.js +0 -0
  182. /package/plugins/{datasets → beta/datasets}/src/panels/Key.module.scss +0 -0
  183. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.jsx +0 -0
  184. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.module.scss +0 -0
  185. /package/plugins/{datasets → beta/datasets}/src/reducer.js +0 -0
  186. /package/plugins/{datasets → beta/datasets}/src/utils/bbox.js +0 -0
  187. /package/plugins/{datasets → beta/datasets}/src/utils/debounce.js +0 -0
  188. /package/plugins/{datasets → beta/datasets}/src/utils/filters.js +0 -0
  189. /package/plugins/{draw-es → beta/draw-es}/src/DrawInit.jsx +0 -0
  190. /package/plugins/{draw-es → beta/draw-es}/src/api/addFeature.js +0 -0
  191. /package/plugins/{draw-es → beta/draw-es}/src/api/deleteFeature.js +0 -0
  192. /package/plugins/{draw-es → beta/draw-es}/src/api/editFeature.js +0 -0
  193. /package/plugins/{draw-es → beta/draw-es}/src/api/newPolygon.js +0 -0
  194. /package/plugins/{draw-es → beta/draw-es}/src/events.js +0 -0
  195. /package/plugins/{draw-es → beta/draw-es}/src/graphic.js +0 -0
  196. /package/plugins/{draw-es → beta/draw-es}/src/index.js +0 -0
  197. /package/plugins/{draw-es → beta/draw-es}/src/manifest.js +0 -0
  198. /package/plugins/{draw-es → beta/draw-es}/src/reducer.js +0 -0
  199. /package/plugins/{draw-es → beta/draw-es}/src/sketchViewModel.js +0 -0
  200. /package/plugins/{draw-ml → beta/draw-ml}/dist/css/index.css +0 -0
  201. /package/plugins/{draw-ml → beta/draw-ml}/dist/esm/index.js.LICENSE.txt +0 -0
  202. /package/plugins/{draw-ml → beta/draw-ml}/dist/umd/index.js.LICENSE.txt +0 -0
  203. /package/plugins/{draw-ml → beta/draw-ml}/src/DrawInit.jsx +0 -0
  204. /package/plugins/{draw-ml → beta/draw-ml}/src/api/addFeature.js +0 -0
  205. /package/plugins/{draw-ml → beta/draw-ml}/src/api/deleteFeature.js +0 -0
  206. /package/plugins/{draw-ml → beta/draw-ml}/src/draw.scss +0 -0
  207. /package/plugins/{draw-ml → beta/draw-ml}/src/index.js +0 -0
  208. /package/plugins/{draw-ml → beta/draw-ml}/src/modes/disabledMode.js +0 -0
  209. /package/plugins/{draw-ml/src → beta/draw-ml/src/utils}/snapHelpers.js +0 -0
  210. /package/plugins/{frame → beta/frame}/dist/css/index.css +0 -0
  211. /package/plugins/{frame → beta/frame}/dist/esm/index.js.LICENSE.txt +0 -0
  212. /package/plugins/{frame → beta/frame}/dist/umd/index.js.LICENSE.txt +0 -0
  213. /package/plugins/{frame → beta/frame}/package.json +0 -0
  214. /package/plugins/{frame → beta/frame}/src/Frame.jsx +0 -0
  215. /package/plugins/{frame → beta/frame}/src/FrameInit.jsx +0 -0
  216. /package/plugins/{frame → beta/frame}/src/api/addFrame.js +0 -0
  217. /package/plugins/{frame → beta/frame}/src/api/editFeature.js +0 -0
  218. /package/plugins/{frame → beta/frame}/src/config.js +0 -0
  219. /package/plugins/{frame → beta/frame}/src/frame.scss +0 -0
  220. /package/plugins/{frame → beta/frame}/src/index.js +0 -0
  221. /package/plugins/{frame → beta/frame}/src/manifest.js +0 -0
  222. /package/plugins/{frame → beta/frame}/src/reducer.js +0 -0
  223. /package/plugins/{frame → beta/frame}/src/utils.js +0 -0
  224. /package/plugins/{map-styles → beta/map-styles}/dist/css/index.css +0 -0
  225. /package/plugins/{map-styles → beta/map-styles}/dist/esm/index.js.LICENSE.txt +0 -0
  226. /package/plugins/{map-styles → beta/map-styles}/dist/umd/index.js.LICENSE.txt +0 -0
  227. /package/plugins/{map-styles → beta/map-styles}/src/MapStylesInit.jsx +0 -0
  228. /package/plugins/{map-styles → beta/map-styles}/src/config.js +0 -0
  229. /package/plugins/{map-styles → beta/map-styles}/src/index.js +0 -0
  230. /package/plugins/{map-styles → beta/map-styles}/src/mapStyles.scss +0 -0
  231. /package/plugins/{scale-bar → beta/scale-bar}/dist/css/index.css +0 -0
  232. /package/plugins/{scale-bar → beta/scale-bar}/dist/esm/index.js.LICENSE.txt +0 -0
  233. /package/plugins/{scale-bar → beta/scale-bar}/dist/umd/index.js.LICENSE.txt +0 -0
  234. /package/plugins/{scale-bar → beta/scale-bar}/package.json +0 -0
  235. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.jsx +0 -0
  236. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.test.jsx +0 -0
  237. /package/plugins/{scale-bar → beta/scale-bar}/src/index.js +0 -0
  238. /package/plugins/{scale-bar → beta/scale-bar}/src/index.test.js +0 -0
  239. /package/plugins/{scale-bar → beta/scale-bar}/src/manifest.js +0 -0
  240. /package/plugins/{scale-bar → beta/scale-bar}/src/scaleBar.scss +0 -0
  241. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.js +0 -0
  242. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.test.js +0 -0
  243. /package/plugins/{use-location → beta/use-location}/dist/esm/index.js.LICENSE.txt +0 -0
  244. /package/plugins/{use-location → beta/use-location}/dist/umd/index.js.LICENSE.txt +0 -0
  245. /package/plugins/{use-location → beta/use-location}/src/UseLocation.jsx +0 -0
  246. /package/plugins/{use-location → beta/use-location}/src/UseLocationInit.jsx +0 -0
  247. /package/plugins/{use-location → beta/use-location}/src/defaults.js +0 -0
  248. /package/plugins/{use-location → beta/use-location}/src/events.js +0 -0
  249. /package/plugins/{use-location → beta/use-location}/src/index.js +0 -0
  250. /package/plugins/{use-location → beta/use-location}/src/manifest.js +0 -0
  251. /package/plugins/{use-location → beta/use-location}/src/reducer.js +0 -0
  252. /package/providers/{esri → beta/esri}/src/appEvents.js +0 -0
  253. /package/providers/{esri → beta/esri}/src/defaults.js +0 -0
  254. /package/providers/{esri → beta/esri}/src/esriProvider.js +0 -0
  255. /package/providers/{esri → beta/esri}/src/esriProvider.scss +0 -0
  256. /package/providers/{esri → beta/esri}/src/index.js +0 -0
  257. /package/providers/{esri → beta/esri}/src/utils/coords.js +0 -0
  258. /package/providers/{esri → beta/esri}/src/utils/detectWebGL.js +0 -0
  259. /package/providers/{esri → beta/esri}/src/utils/esriFixes.js +0 -0
  260. /package/providers/{esri → beta/esri}/src/utils/query.js +0 -0
  261. /package/providers/{esri → beta/esri}/src/utils/spatial.js +0 -0
  262. /package/providers/{open-names → beta/open-names}/dist/esm/im-reverse-geocode.js.LICENSE.txt +0 -0
  263. /package/providers/{open-names → beta/open-names}/dist/esm/index.js.LICENSE.txt +0 -0
  264. /package/providers/{open-names → beta/open-names}/dist/umd/im-reverse-geocode.js.LICENSE.txt +0 -0
  265. /package/providers/{open-names → beta/open-names}/dist/umd/index.js.LICENSE.txt +0 -0
  266. /package/providers/{open-names → beta/open-names}/src/index.js +0 -0
  267. /package/providers/{open-names → beta/open-names}/src/reverseGeocode.js +0 -0
  268. /package/providers/{open-names → beta/open-names}/src/utils/mapToLocationModel.js +0 -0
@@ -0,0 +1,97 @@
1
+ import { render } from '@testing-library/react'
2
+ import { InteractInit } from './InteractInit.jsx'
3
+ import { useInteractionHandlers } from './hooks/useInteractionHandlers.js'
4
+ import { useHighlightSync } from './hooks/useHighlightSync.js'
5
+ import { attachEvents } from './events.js'
6
+
7
+ jest.mock('./hooks/useInteractionHandlers.js')
8
+ jest.mock('./hooks/useHighlightSync.js')
9
+ jest.mock('./events.js')
10
+
11
+ describe('InteractInit', () => {
12
+ let props
13
+ let handleInteractionMock
14
+ let cleanupMock
15
+
16
+ beforeEach(() => {
17
+ handleInteractionMock = jest.fn()
18
+ cleanupMock = jest.fn()
19
+
20
+ useInteractionHandlers.mockReturnValue({ handleInteraction: handleInteractionMock })
21
+ useHighlightSync.mockReturnValue(undefined)
22
+ attachEvents.mockReturnValue(cleanupMock)
23
+
24
+ props = {
25
+ appState: { interfaceType: 'mouse' },
26
+ mapState: { crossHair: { fixAtCenter: jest.fn(), hide: jest.fn() }, mapStyle: {} },
27
+ services: { events: {}, eventBus: {}, closeApp: jest.fn() },
28
+ buttonConfig: {},
29
+ mapProvider: {},
30
+ pluginState: { dispatch: jest.fn(), enabled: true, selectedFeatures: [], selectionBounds: {} }
31
+ }
32
+ })
33
+
34
+ it('calls useInteractionHandlers with correct arguments', () => {
35
+ render(<InteractInit {...props} />)
36
+ expect(useInteractionHandlers).toHaveBeenCalledWith(expect.objectContaining({
37
+ appState: props.appState,
38
+ mapState: props.mapState,
39
+ pluginState: props.pluginState,
40
+ services: props.services,
41
+ mapProvider: props.mapProvider
42
+ }))
43
+ })
44
+
45
+ it('calls useHighlightSync with correct arguments', () => {
46
+ render(<InteractInit {...props} />)
47
+ expect(useHighlightSync).toHaveBeenCalledWith(expect.objectContaining({
48
+ mapProvider: props.mapProvider,
49
+ mapStyle: props.mapState.mapStyle,
50
+ pluginState: props.pluginState,
51
+ selectedFeatures: props.pluginState.selectedFeatures,
52
+ dispatch: props.pluginState.dispatch,
53
+ events: props.services.events,
54
+ eventBus: props.services.eventBus
55
+ }))
56
+ })
57
+
58
+ it('fixes or hides crossHair based on interfaceType and enabled', () => {
59
+ // enabled true + non-touch = hide
60
+ render(<InteractInit {...props} />)
61
+ expect(props.mapState.crossHair.hide).toHaveBeenCalled()
62
+ expect(props.mapState.crossHair.fixAtCenter).not.toHaveBeenCalled()
63
+
64
+ // touch interface
65
+ props.appState.interfaceType = 'touch'
66
+ render(<InteractInit {...props} />)
67
+ expect(props.mapState.crossHair.fixAtCenter).toHaveBeenCalled()
68
+ })
69
+
70
+ it('attaches events and returns cleanup', () => {
71
+ const { unmount } = render(<InteractInit {...props} />)
72
+ expect(attachEvents).toHaveBeenCalledWith(expect.objectContaining({
73
+ handleInteraction: handleInteractionMock,
74
+ appState: props.appState,
75
+ pluginState: props.pluginState,
76
+ mapState: props.mapState,
77
+ buttonConfig: props.buttonConfig,
78
+ events: props.services.events,
79
+ eventBus: props.services.eventBus,
80
+ closeApp: props.services.closeApp
81
+ }))
82
+
83
+ // simulate unmount
84
+ unmount()
85
+ expect(cleanupMock).toHaveBeenCalled()
86
+ })
87
+
88
+ it('does not attach events if plugin not enabled', () => {
89
+ const disabledProps = {
90
+ ...props,
91
+ pluginState: { ...props.pluginState, enabled: false } // fresh object
92
+ }
93
+ attachEvents.mockClear() // ensure previous calls don't interfere
94
+ render(<InteractInit {...disabledProps} />)
95
+ expect(attachEvents).not.toHaveBeenCalled()
96
+ })
97
+ })
@@ -0,0 +1,18 @@
1
+ import { clear } from './clear.js'
2
+
3
+ describe('clear', () => {
4
+ it('clears selected features and removes location marker', () => {
5
+ const dispatch = jest.fn()
6
+ const remove = jest.fn()
7
+
8
+ clear({
9
+ pluginState: { dispatch },
10
+ mapState: { markers: { remove } }
11
+ })
12
+
13
+ expect(dispatch).toHaveBeenCalledWith({
14
+ type: 'CLEAR_SELECTED_FEATURES'
15
+ })
16
+ expect(remove).toHaveBeenCalledWith('location')
17
+ })
18
+ })
@@ -0,0 +1,12 @@
1
+ import { disable } from './disable.js'
2
+
3
+ describe('disable', () => {
4
+ it('dispatches DISABLE action once without payload', () => {
5
+ const dispatch = jest.fn()
6
+
7
+ disable({ pluginState: { dispatch } })
8
+
9
+ expect(dispatch).toHaveBeenCalledTimes(1)
10
+ expect(dispatch).toHaveBeenCalledWith({ type: 'DISABLE' })
11
+ })
12
+ })
@@ -0,0 +1,48 @@
1
+ import { enable } from './enable.js'
2
+ import { DEFAULTS } from '../defaults.js'
3
+
4
+ describe('enable', () => {
5
+ let dispatch
6
+
7
+ beforeEach(() => {
8
+ dispatch = jest.fn()
9
+ })
10
+
11
+ it('dispatches ENABLE with merged payload correctly', () => {
12
+ const pluginConfig = { markerColor: 'blue' }
13
+ const options = { interactionMode: 'select', markerColor: 'green', dataLayers: [{ layerId: 'test' }] }
14
+
15
+ enable({ pluginState: { dispatch }, pluginConfig }, options)
16
+
17
+ expect(dispatch).toHaveBeenCalledTimes(1)
18
+ expect(dispatch).toHaveBeenCalledWith({
19
+ type: 'ENABLE',
20
+ payload: expect.objectContaining({
21
+ interactionMode: 'select', // options override DEFAULTS
22
+ multiSelect: DEFAULTS.multiSelect, // default preserved
23
+ markerColor: 'green', // options override pluginConfig
24
+ dataLayers: [{ layerId: 'test' }] // options passed
25
+ })
26
+ })
27
+ })
28
+
29
+ it('handles empty or undefined options', () => {
30
+ const pluginConfig = { markerColor: 'blue' }
31
+
32
+ enable({ pluginState: { dispatch }, pluginConfig }, {})
33
+ expect(dispatch).toHaveBeenCalledTimes(1)
34
+ expect(dispatch).toHaveBeenCalledWith({
35
+ type: 'ENABLE',
36
+ payload: expect.objectContaining({ ...DEFAULTS, ...pluginConfig })
37
+ })
38
+
39
+ dispatch.mockClear()
40
+
41
+ enable({ pluginState: { dispatch }, pluginConfig }, undefined)
42
+ expect(dispatch).toHaveBeenCalledTimes(1)
43
+ expect(dispatch).toHaveBeenCalledWith({
44
+ type: 'ENABLE',
45
+ payload: expect.objectContaining({ ...DEFAULTS, ...pluginConfig })
46
+ })
47
+ })
48
+ })
@@ -0,0 +1,33 @@
1
+ import { selectFeature } from './selectFeature.js'
2
+
3
+ describe('selectFeature', () => {
4
+ it('emits interact:selectFeature with correct payload', () => {
5
+ const emit = jest.fn()
6
+ const services = { eventBus: { emit } }
7
+
8
+ // Test with all params
9
+ selectFeature(
10
+ { services },
11
+ { featureId: 'f1', layerId: 'layer-abc', idProperty: 'objectId' }
12
+ )
13
+
14
+ expect(emit).toHaveBeenCalledTimes(1)
15
+ expect(emit).toHaveBeenCalledWith('interact:selectFeature', {
16
+ featureId: 'f1',
17
+ layerId: 'layer-abc',
18
+ idProperty: 'objectId'
19
+ })
20
+
21
+ emit.mockClear()
22
+
23
+ // Test missing optional params
24
+ selectFeature({ services }, { featureId: 'f2' })
25
+
26
+ expect(emit).toHaveBeenCalledTimes(1)
27
+ expect(emit).toHaveBeenCalledWith('interact:selectFeature', {
28
+ featureId: 'f2',
29
+ layerId: undefined,
30
+ idProperty: undefined
31
+ })
32
+ })
33
+ })
@@ -0,0 +1,33 @@
1
+ import { unselectFeature } from './unselectFeature.js'
2
+
3
+ describe('unselectFeature', () => {
4
+ it('emits interact:unselectFeature with correct payload', () => {
5
+ const emit = jest.fn()
6
+ const services = { eventBus: { emit } }
7
+
8
+ // Test with all params
9
+ unselectFeature(
10
+ { services },
11
+ { featureId: 'f1', layerId: 'layer-abc', idProperty: 'objectId' }
12
+ )
13
+
14
+ expect(emit).toHaveBeenCalledTimes(1)
15
+ expect(emit).toHaveBeenCalledWith('interact:unselectFeature', {
16
+ featureId: 'f1',
17
+ layerId: 'layer-abc',
18
+ idProperty: 'objectId'
19
+ })
20
+
21
+ emit.mockClear()
22
+
23
+ // Test missing optional params
24
+ unselectFeature({ services }, { featureId: 'f2' })
25
+
26
+ expect(emit).toHaveBeenCalledTimes(1)
27
+ expect(emit).toHaveBeenCalledWith('interact:unselectFeature', {
28
+ featureId: 'f2',
29
+ layerId: undefined,
30
+ idProperty: undefined
31
+ })
32
+ })
33
+ })
@@ -1,6 +1,7 @@
1
1
  export const DEFAULTS = {
2
2
  interactionMode: 'marker',
3
3
  multiSelect: false,
4
+ contiguous: false,
4
5
  markerColor: 'rgba(212,53,28,1)',
5
6
  selectedStroke: 'rgba(212,53,28,1)',
6
7
  selectedFill: 'rgba(255, 0, 0, 0.1)',
@@ -0,0 +1,151 @@
1
+ import { attachEvents } from './events.js'
2
+
3
+ describe('attachEvents', () => {
4
+ let createParams, cleanup
5
+
6
+ beforeEach(() => {
7
+ // factory function to create fresh params for each test
8
+ createParams = () => ({
9
+ appState: { layoutRefs: { viewportRef: { current: document.body } }, disabledButtons: new Set() },
10
+ mapState: {
11
+ markers: { remove: jest.fn(), getMarker: jest.fn(() => null) },
12
+ crossHair: { getDetail: jest.fn(() => ({ point: { x: 0, y: 0 }, coords: [0,0] })) }
13
+ },
14
+ pluginState: { dispatch: jest.fn(), selectionBounds: null, selectedFeatures: [], closeOnAction: true, multiSelect: false },
15
+ buttonConfig: { selectDone: {}, selectAtTarget: {}, selectCancel: {} },
16
+ events: { MAP_CLICK: 'map:click' },
17
+ eventBus: { on: jest.fn(), off: jest.fn(), emit: jest.fn() },
18
+ handleInteraction: jest.fn(),
19
+ closeApp: jest.fn()
20
+ })
21
+ })
22
+
23
+ afterEach(() => cleanup?.())
24
+
25
+ it('keyboard Enter triggers only on viewport', () => {
26
+ const params = createParams()
27
+ cleanup = attachEvents(params)
28
+
29
+ const keydown = new KeyboardEvent('keydown', { key: 'Enter' })
30
+ Object.defineProperty(keydown, 'target', { value: document.body })
31
+ document.dispatchEvent(keydown)
32
+
33
+ const keyup = new KeyboardEvent('keyup', { key: 'Enter' })
34
+ Object.defineProperty(keyup, 'target', { value: document.body })
35
+ document.dispatchEvent(keyup)
36
+
37
+ expect(params.handleInteraction).toHaveBeenCalled()
38
+ })
39
+
40
+ it('ignores Enter outside viewport or other keys', () => {
41
+ const params = createParams()
42
+ cleanup = attachEvents(params)
43
+ const input = document.createElement('input')
44
+
45
+ // Enter outside viewport
46
+ let kd = new KeyboardEvent('keydown', { key: 'Enter' })
47
+ Object.defineProperty(kd, 'target', { value: input })
48
+ document.dispatchEvent(kd)
49
+ let ku = new KeyboardEvent('keyup', { key: 'Enter' })
50
+ Object.defineProperty(ku, 'target', { value: input })
51
+ document.dispatchEvent(ku)
52
+
53
+ // other key
54
+ kd = new KeyboardEvent('keydown', { key: 'Space' })
55
+ Object.defineProperty(kd, 'target', { value: document.body })
56
+ document.dispatchEvent(kd)
57
+ ku = new KeyboardEvent('keyup', { key: 'Space' })
58
+ Object.defineProperty(ku, 'target', { value: document.body })
59
+ document.dispatchEvent(ku)
60
+
61
+ expect(params.handleInteraction).not.toHaveBeenCalled()
62
+ })
63
+
64
+ it('map click triggers interaction', () => {
65
+ const params = createParams()
66
+ cleanup = attachEvents(params)
67
+
68
+ const handler = params.eventBus.on.mock.calls.find(c => c[0]==='map:click')[1]
69
+ const clickData = { point:{x:1,y:2}, coords:[3,4] }
70
+ handler(clickData)
71
+
72
+ expect(params.handleInteraction).toHaveBeenCalledWith(clickData)
73
+ })
74
+
75
+ it('selectAtTarget triggers crosshair interaction', () => {
76
+ const params = createParams()
77
+ cleanup = attachEvents(params)
78
+
79
+ const crossDetail = { point:{x:1,y:2}, coords:[3,4] }
80
+ params.mapState.crossHair.getDetail.mockReturnValue(crossDetail)
81
+
82
+ params.buttonConfig.selectAtTarget.onClick()
83
+ expect(params.handleInteraction).toHaveBeenCalledWith(crossDetail)
84
+ })
85
+
86
+ it('selectDone emits correct payload and respects closeOnAction', () => {
87
+ const params = createParams()
88
+ cleanup = attachEvents(params)
89
+
90
+ // closeOnAction = true (already covered)
91
+ params.mapState.markers.getMarker.mockReturnValue({ coords:[1,2] })
92
+ params.buttonConfig.selectDone.onClick()
93
+ expect(params.closeApp).toHaveBeenCalled()
94
+
95
+ // cover closeOnAction = false
96
+ params.closeApp.mockClear()
97
+ params.pluginState.closeOnAction = false
98
+ params.mapState.markers.getMarker.mockReturnValue({ coords:[3,4] })
99
+ params.buttonConfig.selectDone.onClick()
100
+ expect(params.closeApp).not.toHaveBeenCalled()
101
+ })
102
+
103
+ it('selectCancel emits cancel and respects closeOnAction', () => {
104
+ const params = createParams()
105
+ cleanup = attachEvents(params)
106
+
107
+ // closeOnAction = true
108
+ params.buttonConfig.selectCancel.onClick()
109
+ expect(params.closeApp).toHaveBeenCalled()
110
+
111
+ // cover closeOnAction = false
112
+ cleanup()
113
+ const params2 = createParams()
114
+ cleanup = attachEvents(params2)
115
+ params2.pluginState.closeOnAction = false
116
+ params2.buttonConfig.selectCancel.onClick()
117
+ expect(params2.closeApp).not.toHaveBeenCalled()
118
+ })
119
+
120
+ it('does not emit or closeApp if selectDone button is disabled', () => {
121
+ const params = createParams()
122
+ cleanup = attachEvents(params)
123
+
124
+ params.appState.disabledButtons.add('selectDone')
125
+ params.buttonConfig.selectDone.onClick()
126
+
127
+ expect(params.eventBus.emit).not.toHaveBeenCalled()
128
+ expect(params.closeApp).not.toHaveBeenCalled()
129
+ })
130
+
131
+ it('programmatic select/unselect dispatches and removes location', () => {
132
+ const params = createParams()
133
+ cleanup = attachEvents(params)
134
+
135
+ const selectHandler = params.eventBus.on.mock.calls.find(c => c[0]==='interact:selectFeature')[1]
136
+ const unselectHandler = params.eventBus.on.mock.calls.find(c => c[0]==='interact:unselectFeature')[1]
137
+
138
+ selectHandler({ featureId:'F1' })
139
+ unselectHandler({ featureId:'F2' })
140
+
141
+ expect(params.pluginState.dispatch).toHaveBeenCalledTimes(2)
142
+ expect(params.mapState.markers.remove).toHaveBeenCalledTimes(2)
143
+ })
144
+
145
+ it('cleanup removes all handlers', () => {
146
+ const params = createParams()
147
+ cleanup = attachEvents(params)
148
+ cleanup()
149
+ Object.values(params.buttonConfig).forEach(btn => expect(btn.onClick).toBeNull())
150
+ })
151
+ })
@@ -0,0 +1,212 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { useHighlightSync } from './useHighlightSync.js'
3
+ import { buildStylesMap } from '../utils/buildStylesMap.js'
4
+
5
+ jest.mock('../utils/buildStylesMap.js', () => ({
6
+ buildStylesMap: jest.fn(() => ({ layer1: { stroke: 'red', fill: 'blue' } }))
7
+ }))
8
+
9
+ describe('useHighlightSync', () => {
10
+ let mockDeps
11
+ let capturedEventHandler
12
+
13
+ const render = (overrides = {}) =>
14
+ renderHook(() => useHighlightSync({ ...mockDeps, ...overrides }))
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks()
18
+ capturedEventHandler = null
19
+
20
+ mockDeps = {
21
+ mapProvider: {
22
+ updateHighlightedFeatures: jest.fn(() => ({ sw: [0, 0], ne: [1, 1] }))
23
+ },
24
+ mapStyle: { id: 'default-style' },
25
+ pluginState: {
26
+ dataLayers: [{ layerId: 'layer1' }]
27
+ },
28
+ selectedFeatures: [],
29
+ dispatch: jest.fn(),
30
+ events: { MAP_DATA_CHANGE: 'map:datachange' },
31
+ eventBus: {
32
+ on: jest.fn((event, handler) => {
33
+ if (event === 'map:datachange') {
34
+ capturedEventHandler = handler
35
+ }
36
+ }),
37
+ off: jest.fn()
38
+ }
39
+ }
40
+ })
41
+
42
+ /* ------------------------------------------------------------------ */
43
+ /* Highlighting */
44
+ /* ------------------------------------------------------------------ */
45
+
46
+ it('updates map highlights and dispatches bounds', () => {
47
+ mockDeps.selectedFeatures = [{ featureId: 'F1', layerId: 'layer1' }]
48
+
49
+ render()
50
+
51
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).toHaveBeenCalledWith(
52
+ mockDeps.selectedFeatures,
53
+ expect.any(Object)
54
+ )
55
+
56
+ expect(mockDeps.dispatch).toHaveBeenCalledWith({
57
+ type: 'UPDATE_SELECTED_BOUNDS',
58
+ payload: { sw: [0, 0], ne: [1, 1] }
59
+ })
60
+ })
61
+
62
+ it('dispatches null bounds when provider returns null', () => {
63
+ mockDeps.selectedFeatures = [{ featureId: 'F1' }]
64
+ mockDeps.mapProvider.updateHighlightedFeatures.mockReturnValue(null)
65
+
66
+ render()
67
+
68
+ expect(mockDeps.dispatch).toHaveBeenCalledWith({
69
+ type: 'UPDATE_SELECTED_BOUNDS',
70
+ payload: null
71
+ })
72
+ })
73
+
74
+ /* ------------------------------------------------------------------ */
75
+ /* Styles memoization */
76
+ /* ------------------------------------------------------------------ */
77
+
78
+ it('rebuilds styles when mapStyle changes', () => {
79
+ mockDeps.selectedFeatures = [{ featureId: 'F1' }]
80
+
81
+ const { rerender } = renderHook(
82
+ ({ mapStyle }) => useHighlightSync({ ...mockDeps, mapStyle }),
83
+ { initialProps: { mapStyle: { id: 'light' } } }
84
+ )
85
+
86
+ buildStylesMap.mockClear()
87
+
88
+ rerender({ mapStyle: { id: 'satellite' } })
89
+
90
+ expect(buildStylesMap).toHaveBeenCalledWith(
91
+ expect.anything(),
92
+ { id: 'satellite' }
93
+ )
94
+ })
95
+
96
+ it('rebuilds styles when dataLayers change', () => {
97
+ mockDeps.selectedFeatures = [{ featureId: 'F1' }]
98
+
99
+ const { rerender } = renderHook(
100
+ ({ dataLayers }) =>
101
+ useHighlightSync({
102
+ ...mockDeps,
103
+ pluginState: { dataLayers }
104
+ }),
105
+ { initialProps: { dataLayers: [{ layerId: 'layer1' }] } }
106
+ )
107
+
108
+ buildStylesMap.mockClear()
109
+
110
+ rerender({ dataLayers: [{ layerId: 'layer1' }, { layerId: 'layer2' }] })
111
+
112
+ expect(buildStylesMap).toHaveBeenCalled()
113
+ })
114
+
115
+ /* ------------------------------------------------------------------ */
116
+ /* Map data change events */
117
+ /* ------------------------------------------------------------------ */
118
+
119
+ it('refreshes highlights on MAP_DATA_CHANGE', () => {
120
+ mockDeps.selectedFeatures = [{ featureId: 'F1', layerId: 'layer1' }]
121
+
122
+ render()
123
+
124
+ mockDeps.mapProvider.updateHighlightedFeatures.mockClear()
125
+
126
+ act(() => capturedEventHandler())
127
+
128
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).toHaveBeenCalled()
129
+ })
130
+
131
+ it('unsubscribes on unmount', () => {
132
+ mockDeps.selectedFeatures = [{ featureId: 'F1', layerId: 'layer1' }]
133
+
134
+ const { unmount } = render()
135
+
136
+ unmount()
137
+
138
+ expect(mockDeps.eventBus.off).toHaveBeenCalledWith(
139
+ 'map:datachange',
140
+ expect.any(Function)
141
+ )
142
+ })
143
+
144
+ /* ------------------------------------------------------------------ */
145
+ /* Guards */
146
+ /* ------------------------------------------------------------------ */
147
+
148
+ it('does nothing when mapProvider is null', () => {
149
+ mockDeps.mapProvider = null
150
+ mockDeps.selectedFeatures = [{ featureId: 'F1' }]
151
+
152
+ render()
153
+
154
+ expect(mockDeps.dispatch).not.toHaveBeenCalled()
155
+ })
156
+
157
+ it('does nothing when selectedFeatures is null', () => {
158
+ mockDeps.selectedFeatures = null
159
+
160
+ render()
161
+
162
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).not.toHaveBeenCalled()
163
+ })
164
+
165
+ it('does nothing when mapStyle is null', () => {
166
+ mockDeps.mapStyle = null
167
+ mockDeps.selectedFeatures = [{ featureId: 'F1' }]
168
+
169
+ render()
170
+
171
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).not.toHaveBeenCalled()
172
+ expect(buildStylesMap).not.toHaveBeenCalled()
173
+ })
174
+
175
+ /* ------------------------------------------------------------------ */
176
+ /* Selection updates */
177
+ /* ------------------------------------------------------------------ */
178
+
179
+ it('updates highlights when selection changes', () => {
180
+ const { rerender } = renderHook(
181
+ ({ selectedFeatures }) =>
182
+ useHighlightSync({ ...mockDeps, selectedFeatures }),
183
+ { initialProps: { selectedFeatures: [{ featureId: 'F1' }] } }
184
+ )
185
+
186
+ mockDeps.mapProvider.updateHighlightedFeatures.mockClear()
187
+
188
+ rerender({ selectedFeatures: [{ featureId: 'F1' }, { featureId: 'F2' }] })
189
+
190
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).toHaveBeenCalledWith(
191
+ [{ featureId: 'F1' }, { featureId: 'F2' }],
192
+ expect.anything()
193
+ )
194
+ })
195
+
196
+ it('clears highlights when selection becomes empty', () => {
197
+ const { rerender } = renderHook(
198
+ ({ selectedFeatures }) =>
199
+ useHighlightSync({ ...mockDeps, selectedFeatures }),
200
+ { initialProps: { selectedFeatures: [{ featureId: 'F1' }] } }
201
+ )
202
+
203
+ mockDeps.mapProvider.updateHighlightedFeatures.mockClear()
204
+
205
+ rerender({ selectedFeatures: [] })
206
+
207
+ expect(mockDeps.mapProvider.updateHighlightedFeatures).toHaveBeenCalledWith(
208
+ [],
209
+ expect.anything()
210
+ )
211
+ })
212
+ })
@@ -1,4 +1,6 @@
1
1
  import { useCallback, useEffect, useRef } from 'react'
2
+ import booleanDisjoint from '@turf/boolean-disjoint'
3
+ import { toTurfGeometry } from '../utils/turfHelpers.js'
2
4
  import { getFeaturesAtPoint, findMatchingFeature, buildLayerConfigMap } from '../utils/featureQueries.js'
3
5
 
4
6
  export const useInteractionHandlers = ({
@@ -8,7 +10,7 @@ export const useInteractionHandlers = ({
8
10
  mapProvider,
9
11
  }) => {
10
12
  const { markers } = mapState
11
- const { dispatch, dataLayers, interactionMode, multiSelect, markerColor, selectedFeatures, selectionBounds } = pluginState
13
+ const { dispatch, dataLayers, interactionMode, multiSelect, contiguous, markerColor, selectedFeatures, selectionBounds } = pluginState
12
14
  const { eventBus } = services
13
15
  const lastEmittedSelectionChange = useRef(null)
14
16
  const layerConfigMap = buildLayerConfigMap(dataLayers)
@@ -17,15 +19,19 @@ export const useInteractionHandlers = ({
17
19
  const allFeatures = getFeaturesAtPoint(mapProvider, point)
18
20
  const hasDataLayers = dataLayers.length > 0
19
21
 
20
- const match = hasDataLayers &&
21
- (interactionMode === 'select' || interactionMode === 'auto')
22
- ? findMatchingFeature(allFeatures, layerConfigMap)
23
- : null
22
+ const canMatchFeature = hasDataLayers && (interactionMode === 'select' || interactionMode === 'auto')
23
+ const match = canMatchFeature ? findMatchingFeature(allFeatures, layerConfigMap) : null
24
24
 
25
25
  if (match) {
26
26
  markers.remove('location')
27
-
28
27
  const { feature, config } = match
28
+
29
+ // Using Turf not booleanDisjoint to test if the new feature is contiguous
30
+ let isNewFeatureContiguous = false
31
+ if (contiguous) {
32
+ isNewFeatureContiguous = selectedFeatures.some(sf => !booleanDisjoint(toTurfGeometry(sf), toTurfGeometry(feature)))
33
+ }
34
+
29
35
  const featureId = feature.properties?.[config.idProperty]
30
36
 
31
37
  if (featureId) {
@@ -37,7 +43,8 @@ export const useInteractionHandlers = ({
37
43
  layerId: config.layerId,
38
44
  idProperty: config.idProperty,
39
45
  properties: feature.properties,
40
- geometry: feature.geometry
46
+ geometry: feature.geometry,
47
+ replaceAll: contiguous && !isNewFeatureContiguous
41
48
  },
42
49
  })
43
50
  }