@defra/interactive-map 0.0.3-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 (407) hide show
  1. package/README.md +16 -57
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/button-definition.md +176 -0
  8. package/docs/api/context.md +124 -0
  9. package/docs/api/control-definition.md +72 -0
  10. package/docs/api/icon-definition.md +28 -0
  11. package/docs/api/map-style-config.md +76 -0
  12. package/docs/api/marker-config.md +51 -0
  13. package/docs/api/panel-definition.md +117 -0
  14. package/docs/api/slots.md +25 -0
  15. package/docs/api.md +721 -0
  16. package/docs/architecture.md +139 -0
  17. package/docs/building-a-plugin.md +149 -0
  18. package/docs/getting-started.md +46 -0
  19. package/docs/govuk-prototype.md +0 -0
  20. package/docs/plugins/interact.md +205 -0
  21. package/docs/plugins/map-styles.md +84 -0
  22. package/docs/plugins/plugin-context.md +67 -0
  23. package/docs/plugins/plugin-descriptor.md +71 -0
  24. package/docs/plugins/plugin-manifest.md +118 -0
  25. package/docs/plugins/scale-bar.md +62 -0
  26. package/docs/plugins/search.md +162 -0
  27. package/docs/plugins.md +116 -0
  28. package/govuk-prototype-kit.config.json +7 -7
  29. package/package.json +16 -2
  30. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -0
  31. package/plugins/beta/datasets/dist/esm/index.js +2 -0
  32. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
  33. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  34. package/plugins/beta/datasets/src/datasets.js +113 -0
  35. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
  36. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
  37. package/plugins/{datasets → beta/datasets}/src/handleSetMapStyle.js +8 -1
  38. package/plugins/{datasets → beta/datasets}/src/manifest.js +1 -1
  39. package/plugins/{datasets → beta/datasets}/src/mapLayers.js +42 -4
  40. package/plugins/{datasets → beta/datasets}/src/panels/Key.jsx +1 -1
  41. package/plugins/beta/datasets/src/utils/bbox.js +115 -0
  42. package/plugins/beta/draw-ml/dist/css/index.css +1 -0
  43. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -0
  44. package/plugins/beta/draw-ml/dist/esm/index.js +2 -0
  45. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -0
  46. package/plugins/beta/draw-ml/dist/umd/index.js +2 -0
  47. package/plugins/beta/draw-ml/src/api/addFeature.js +27 -0
  48. package/plugins/{draw-ml → beta/draw-ml}/src/api/editFeature.js +17 -6
  49. package/plugins/beta/draw-ml/src/api/merge.js +9 -0
  50. package/plugins/beta/draw-ml/src/api/newLine.js +68 -0
  51. package/plugins/beta/draw-ml/src/api/newPolygon.js +68 -0
  52. package/plugins/beta/draw-ml/src/api/split.js +81 -0
  53. package/plugins/{draw-ml → beta/draw-ml}/src/defaults.js +3 -0
  54. package/plugins/beta/draw-ml/src/draw.scss +32 -0
  55. package/plugins/beta/draw-ml/src/events.js +171 -0
  56. package/plugins/beta/draw-ml/src/manifest.js +132 -0
  57. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxDraw.js +51 -2
  58. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxSnap.js +18 -12
  59. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +497 -0
  60. package/plugins/beta/draw-ml/src/modes/drawLineMode.js +18 -0
  61. package/plugins/beta/draw-ml/src/modes/drawPolygonMode.js +16 -0
  62. package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
  63. package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
  64. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
  65. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
  66. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
  67. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
  68. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +437 -0
  69. package/plugins/{draw-ml → beta/draw-ml}/src/reducer.js +30 -2
  70. package/plugins/{draw-ml → beta/draw-ml}/src/styles.js +66 -36
  71. package/plugins/beta/draw-ml/src/undoStack.js +50 -0
  72. package/plugins/beta/draw-ml/src/utils/debounce.js +16 -0
  73. package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
  74. package/plugins/beta/draw-ml/src/utils/spatial.js +258 -0
  75. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -0
  76. package/plugins/beta/frame/dist/esm/index.js +2 -0
  77. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -0
  78. package/plugins/beta/frame/dist/umd/index.js +2 -0
  79. package/plugins/{frame → beta/frame}/src/Frame.jsx +1 -1
  80. package/plugins/{map-styles → beta/map-styles}/dist/css/index.css +1 -1
  81. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -0
  82. package/plugins/beta/map-styles/dist/esm/index.js +2 -0
  83. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -0
  84. package/plugins/beta/map-styles/dist/umd/index.js +2 -0
  85. package/plugins/{map-styles → beta/map-styles}/src/MapStyles.jsx +1 -1
  86. package/plugins/{map-styles → beta/map-styles}/src/manifest.js +8 -10
  87. package/plugins/{map-styles → beta/map-styles}/src/mapStyles.scss +4 -1
  88. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -0
  89. package/plugins/beta/scale-bar/dist/esm/index.js +2 -0
  90. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -0
  91. package/plugins/beta/scale-bar/dist/umd/index.js +2 -0
  92. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -0
  93. package/plugins/beta/use-location/dist/esm/index.js +2 -0
  94. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -0
  95. package/plugins/beta/use-location/dist/umd/index.js +2 -0
  96. package/plugins/interact/dist/css/index.css +1 -1
  97. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  98. package/plugins/interact/dist/esm/index.js +1 -1
  99. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  100. package/plugins/interact/dist/umd/index.js +1 -1
  101. package/plugins/interact/src/InteractInit.jsx +1 -1
  102. package/plugins/interact/src/InteractInit.test.js +97 -0
  103. package/plugins/interact/src/api/clear.test.js +18 -0
  104. package/plugins/interact/src/api/disable.test.js +12 -0
  105. package/plugins/interact/src/api/enable.test.js +48 -0
  106. package/plugins/interact/src/api/selectFeature.test.js +33 -0
  107. package/plugins/interact/src/api/unselectFeature.test.js +33 -0
  108. package/plugins/interact/src/defaults.js +2 -0
  109. package/plugins/interact/src/events.js +37 -52
  110. package/plugins/interact/src/events.test.js +186 -0
  111. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  112. package/plugins/interact/src/hooks/useHighlightSync.test.js +212 -0
  113. package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -41
  114. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +316 -0
  115. package/plugins/interact/src/index.test.js +22 -0
  116. package/plugins/interact/src/interact.scss +6 -0
  117. package/plugins/interact/src/manifest.js +1 -1
  118. package/plugins/interact/src/manifest.test.js +94 -0
  119. package/plugins/interact/src/reducer.js +28 -21
  120. package/plugins/interact/src/reducer.test.js +155 -0
  121. package/plugins/interact/src/utils/buildStylesMap.test.js +57 -0
  122. package/plugins/interact/src/utils/featureQueries.js +2 -2
  123. package/plugins/interact/src/utils/featureQueries.test.js +56 -0
  124. package/plugins/interact/src/utils/spatial.js +95 -0
  125. package/plugins/interact/src/utils/spatial.test.js +93 -0
  126. package/plugins/search/dist/css/index.css +1 -1
  127. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  128. package/plugins/search/dist/esm/index.js +1 -1
  129. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  130. package/plugins/search/dist/umd/index.js +1 -1
  131. package/plugins/search/src/Search.jsx +2 -2
  132. package/plugins/search/src/components/Form/Form.jsx +1 -1
  133. package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
  134. package/plugins/search/src/datasets.js +1 -1
  135. package/plugins/search/src/defaults.js +3 -0
  136. package/plugins/search/src/events/fetchSuggestions.js +48 -53
  137. package/plugins/search/src/events/formHandlers.js +3 -2
  138. package/plugins/search/src/events/index.js +1 -1
  139. package/plugins/search/src/events/inputHandlers.js +3 -1
  140. package/plugins/search/src/events/suggestionHandlers.js +12 -3
  141. package/plugins/search/src/search.scss +4 -1
  142. package/plugins/search/src/utils/parseOsNamesResults.js +15 -19
  143. package/providers/{esri → beta/esri}/src/esriProvider.js +1 -1
  144. package/providers/{esri → beta/esri}/src/index.js +1 -1
  145. package/providers/{esri → beta/esri}/src/mapEvents.js +2 -2
  146. package/providers/beta/open-names/dist/esm/im-reverse-geocode.js +2 -0
  147. package/providers/beta/open-names/dist/esm/index.js +2 -0
  148. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js +2 -0
  149. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt +1 -0
  150. package/providers/beta/open-names/dist/umd/index.js +2 -0
  151. package/providers/beta/open-names/dist/umd/index.js.LICENSE.txt +1 -0
  152. package/providers/maplibre/dist/esm/im-maplibre-framework.js +1 -1
  153. package/providers/maplibre/dist/esm/im-maplibre-framework.js.LICENSE.txt +1 -1
  154. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  155. package/providers/maplibre/dist/esm/index.js +1 -1
  156. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  157. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  158. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  159. package/providers/maplibre/dist/umd/index.js +1 -1
  160. package/providers/maplibre/src/defaults.js +3 -2
  161. package/providers/maplibre/src/index.js +36 -20
  162. package/providers/maplibre/src/maplibreProvider.js +140 -11
  163. package/providers/maplibre/src/utils/detectWebgl.js +1 -2
  164. package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
  165. package/providers/maplibre/src/utils/labels.js +174 -120
  166. package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
  167. package/providers/maplibre/src/utils/queryFeatures.js +146 -0
  168. package/providers/maplibre/src/utils/spatial.js +14 -17
  169. package/sonar-project.properties +40 -3
  170. package/src/App/components/Actions/Actions.jsx +2 -2
  171. package/src/App/components/Actions/Actions.module.scss +2 -2
  172. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
  173. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
  174. package/src/App/components/Logo/Logo.jsx +1 -1
  175. package/src/App/components/MapButton/MapButton.jsx +217 -41
  176. package/src/App/components/MapButton/MapButton.module.scss +32 -21
  177. package/src/App/components/MapButton/MapButton.test.jsx +128 -84
  178. package/src/App/components/Markers/Markers.jsx +1 -1
  179. package/src/App/components/Markers/Markers.module.scss +0 -5
  180. package/src/App/components/Panel/Panel.jsx +75 -45
  181. package/src/App/components/Panel/Panel.module.scss +18 -24
  182. package/src/App/components/Panel/Panel.test.jsx +18 -0
  183. package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
  184. package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
  185. package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
  186. package/src/App/components/Tooltip/Tooltip.jsx +4 -2
  187. package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
  188. package/src/App/components/Viewport/MapController.jsx +1 -1
  189. package/src/App/components/Viewport/MapStatus.jsx +1 -1
  190. package/src/App/components/Viewport/Viewport.jsx +2 -2
  191. package/src/App/components/Viewport/Viewport.module.scss +2 -2
  192. package/src/App/controls/keyboardActions.js +3 -2
  193. package/src/App/controls/keyboardShortcuts.js +4 -2
  194. package/src/App/hooks/useButtonStateEvaluator.js +58 -45
  195. package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
  196. package/src/App/hooks/useCrossHairAPI.js +49 -47
  197. package/src/App/hooks/useFocusVisible.js +2 -2
  198. package/src/App/hooks/useInterfaceAPI.js +36 -1
  199. package/src/App/hooks/useKeyboardHint.js +1 -1
  200. package/src/App/hooks/useKeyboardShortcuts.js +4 -2
  201. package/src/App/hooks/useMapAnnouncements.js +34 -32
  202. package/src/App/hooks/useMapEvents.js +12 -1
  203. package/src/App/hooks/useMapProviderOverrides.js +3 -3
  204. package/src/App/hooks/useMapStateSync.js +7 -1
  205. package/src/App/hooks/useMapURLSync.js +6 -1
  206. package/src/App/hooks/useMarkersAPI.js +88 -50
  207. package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
  208. package/src/App/hooks/useModalPanelBehaviour.js +46 -40
  209. package/src/App/hooks/useResizeObserver.js +2 -2
  210. package/src/App/initialiseApp.js +45 -38
  211. package/src/App/layout/Layout.jsx +6 -4
  212. package/src/App/layout/layout.module.scss +13 -17
  213. package/src/App/registry/keyboardShortcutRegistry.js +12 -2
  214. package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
  215. package/src/App/registry/mergeManifests.js +1 -1
  216. package/src/App/registry/panelRegistry.js +1 -1
  217. package/src/App/registry/panelRegistry.test.js +30 -1
  218. package/src/App/registry/pluginRegistry.js +14 -51
  219. package/src/App/registry/pluginRegistry.test.js +54 -6
  220. package/src/App/renderer/HtmlElementHost.jsx +186 -0
  221. package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
  222. package/src/App/renderer/mapButtons.js +12 -9
  223. package/src/App/renderer/mapButtons.test.js +6 -4
  224. package/src/App/renderer/mapControls.js +6 -0
  225. package/src/App/renderer/mapControls.test.js +10 -2
  226. package/src/App/renderer/mapPanels.js +39 -26
  227. package/src/App/renderer/mapPanels.test.js +29 -0
  228. package/src/App/renderer/pluginWrapper.test.js +28 -0
  229. package/src/App/renderer/slotHelpers.js +60 -0
  230. package/src/App/renderer/slotHelpers.test.js +82 -0
  231. package/src/App/store/AppProvider.jsx +3 -0
  232. package/src/App/store/AppProvider.test.jsx +40 -0
  233. package/src/App/store/PluginProvider.jsx +7 -5
  234. package/src/App/store/appActionsMap.js +46 -1
  235. package/src/App/store/appActionsMap.test.js +21 -0
  236. package/src/App/store/appReducer.js +1 -0
  237. package/src/App/store/mapActionsMap.js +12 -24
  238. package/src/App/store/mapReducer.js +1 -1
  239. package/src/InteractiveMap/InteractiveMap.js +194 -13
  240. package/src/InteractiveMap/InteractiveMap.test.js +164 -2
  241. package/src/InteractiveMap/behaviourController.js +12 -2
  242. package/src/InteractiveMap/behaviourController.test.js +82 -1
  243. package/src/InteractiveMap/deviceChecker.js +1 -1
  244. package/src/InteractiveMap/deviceChecker.test.js +2 -2
  245. package/src/InteractiveMap/historyManager.js +41 -5
  246. package/src/InteractiveMap/historyManager.test.js +37 -6
  247. package/src/InteractiveMap/polyfills.js +39 -0
  248. package/src/InteractiveMap/polyfills.test.js +127 -0
  249. package/src/config/appConfig.js +5 -5
  250. package/src/config/appConfig.test.js +107 -42
  251. package/src/config/defaults.js +30 -20
  252. package/src/config/events.js +110 -4
  253. package/src/index.js +5 -2
  254. package/src/index.umd.js +1 -1
  255. package/src/scss/main.scss +1 -0
  256. package/src/scss/settings/_dimensions.scss +6 -2
  257. package/src/scss/tools/_border-focus.scss +20 -4
  258. package/src/services/eventBus.test.js +24 -1
  259. package/src/test-utils.js +1 -0
  260. package/src/types.js +590 -0
  261. package/src/utils/detectBreakpoint.js +78 -77
  262. package/src/utils/detectBreakpoint.test.js +50 -4
  263. package/src/utils/detectInterfaceType.js +4 -4
  264. package/src/utils/getIsFullscreen.js +3 -1
  265. package/src/utils/getSafeZoneInset.js +23 -7
  266. package/src/utils/getSafeZoneInset.test.js +18 -0
  267. package/src/utils/mapStateSync.js +3 -3
  268. package/src/utils/queryString.js +1 -1
  269. package/src/utils/stringToKebab.js +1 -1
  270. package/src/utils/toggleInertElements.js +37 -12
  271. package/webpack.dev.mjs +3 -4
  272. package/webpack.esm.mjs +8 -8
  273. package/webpack.umd.mjs +8 -8
  274. package/plugins/datasets/dist/esm/im-datasets-plugin.js +0 -1
  275. package/plugins/datasets/dist/esm/index.js +0 -2
  276. package/plugins/datasets/dist/umd/im-datasets-plugin.js +0 -1
  277. package/plugins/datasets/dist/umd/index.js +0 -2
  278. package/plugins/datasets/src/datasets.js +0 -60
  279. package/plugins/datasets/src/utils/bbox.js +0 -4
  280. package/plugins/draw-ml/dist/css/index.css +0 -1
  281. package/plugins/draw-ml/dist/esm/im-draw-ml-plugin.js +0 -1
  282. package/plugins/draw-ml/dist/esm/index.js +0 -2
  283. package/plugins/draw-ml/dist/umd/im-draw-ml-plugin.js +0 -1
  284. package/plugins/draw-ml/dist/umd/index.js +0 -2
  285. package/plugins/draw-ml/src/api/addFeature.js +0 -15
  286. package/plugins/draw-ml/src/api/newPolygon.js +0 -43
  287. package/plugins/draw-ml/src/draw.scss +0 -9
  288. package/plugins/draw-ml/src/events.js +0 -127
  289. package/plugins/draw-ml/src/manifest.js +0 -100
  290. package/plugins/draw-ml/src/modes/drawVertexMode.js +0 -275
  291. package/plugins/draw-ml/src/modes/editVertexMode.js +0 -426
  292. package/plugins/draw-ml/src/utils.js +0 -102
  293. package/plugins/frame/dist/esm/im-frame-plugin.js +0 -1
  294. package/plugins/frame/dist/esm/index.js +0 -2
  295. package/plugins/frame/dist/umd/im-frame-plugin.js +0 -1
  296. package/plugins/frame/dist/umd/index.js +0 -2
  297. package/plugins/map-styles/dist/esm/im-map-styles-plugin.js +0 -1
  298. package/plugins/map-styles/dist/esm/index.js +0 -2
  299. package/plugins/map-styles/dist/umd/im-map-styles-plugin.js +0 -1
  300. package/plugins/map-styles/dist/umd/index.js +0 -2
  301. package/plugins/scale-bar/dist/esm/im-scale-bar-plugin.js +0 -1
  302. package/plugins/scale-bar/dist/esm/index.js +0 -2
  303. package/plugins/scale-bar/dist/umd/im-scale-bar-plugin.js +0 -1
  304. package/plugins/scale-bar/dist/umd/index.js +0 -2
  305. package/plugins/search/src/utils/fetchDataset.js +0 -23
  306. package/plugins/use-location/dist/esm/im-use-location-plugin.js +0 -1
  307. package/plugins/use-location/dist/esm/index.js +0 -2
  308. package/plugins/use-location/dist/umd/im-use-location-plugin.js +0 -1
  309. package/plugins/use-location/dist/umd/index.js +0 -2
  310. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
  311. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
  312. package/providers/open-names/dist/esm/im-reverse-geocode.js +0 -2
  313. package/providers/open-names/dist/esm/index.js +0 -2
  314. package/providers/open-names/dist/umd/im-reverse-geocode.js +0 -2
  315. package/providers/open-names/dist/umd/index.js +0 -2
  316. /package/docs/architecture/{ARCHITECTURE_DIAGRAMS.md → architecture-diagrams.md} +0 -0
  317. /package/plugins/{datasets → beta/datasets}/dist/css/index.css +0 -0
  318. /package/plugins/{datasets/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt} +0 -0
  319. /package/plugins/{datasets/dist/umd → beta/datasets/dist/esm}/index.js.LICENSE.txt +0 -0
  320. /package/plugins/{draw-ml/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt} +0 -0
  321. /package/plugins/{draw-ml → beta/datasets}/dist/umd/index.js.LICENSE.txt +0 -0
  322. /package/plugins/{datasets → beta/datasets}/src/DatasetsInit.jsx +0 -0
  323. /package/plugins/{datasets → beta/datasets}/src/api/addDataset.js +0 -0
  324. /package/plugins/{datasets → beta/datasets}/src/api/hideDataset.js +0 -0
  325. /package/plugins/{datasets → beta/datasets}/src/api/hideFeatures.js +0 -0
  326. /package/plugins/{datasets → beta/datasets}/src/api/removeDataset.js +0 -0
  327. /package/plugins/{datasets → beta/datasets}/src/api/showDataset.js +0 -0
  328. /package/plugins/{datasets → beta/datasets}/src/api/showFeatures.js +0 -0
  329. /package/plugins/{datasets → beta/datasets}/src/datasets.scss +0 -0
  330. /package/plugins/{datasets → beta/datasets}/src/defaults.js +0 -0
  331. /package/plugins/{datasets → beta/datasets}/src/index.js +0 -0
  332. /package/plugins/{datasets → beta/datasets}/src/panels/Key.module.scss +0 -0
  333. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.jsx +0 -0
  334. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.module.scss +0 -0
  335. /package/plugins/{datasets → beta/datasets}/src/reducer.js +0 -0
  336. /package/plugins/{datasets → beta/datasets}/src/utils/debounce.js +0 -0
  337. /package/plugins/{datasets → beta/datasets}/src/utils/filters.js +0 -0
  338. /package/plugins/{draw-es → beta/draw-es}/src/DrawInit.jsx +0 -0
  339. /package/plugins/{draw-es → beta/draw-es}/src/api/addFeature.js +0 -0
  340. /package/plugins/{draw-es → beta/draw-es}/src/api/deleteFeature.js +0 -0
  341. /package/plugins/{draw-es → beta/draw-es}/src/api/editFeature.js +0 -0
  342. /package/plugins/{draw-es → beta/draw-es}/src/api/newPolygon.js +0 -0
  343. /package/plugins/{draw-es → beta/draw-es}/src/events.js +0 -0
  344. /package/plugins/{draw-es → beta/draw-es}/src/graphic.js +0 -0
  345. /package/plugins/{draw-es → beta/draw-es}/src/index.js +0 -0
  346. /package/plugins/{draw-es → beta/draw-es}/src/manifest.js +0 -0
  347. /package/plugins/{draw-es → beta/draw-es}/src/reducer.js +0 -0
  348. /package/plugins/{draw-es → beta/draw-es}/src/sketchViewModel.js +0 -0
  349. /package/plugins/{frame → beta/draw-ml}/dist/esm/index.js.LICENSE.txt +0 -0
  350. /package/plugins/{frame → beta/draw-ml}/dist/umd/index.js.LICENSE.txt +0 -0
  351. /package/plugins/{draw-ml → beta/draw-ml}/src/DrawInit.jsx +0 -0
  352. /package/plugins/{draw-ml → beta/draw-ml}/src/api/deleteFeature.js +0 -0
  353. /package/plugins/{draw-ml → beta/draw-ml}/src/index.js +0 -0
  354. /package/plugins/{draw-ml → beta/draw-ml}/src/modes/disabledMode.js +0 -0
  355. /package/plugins/{draw-ml/src → beta/draw-ml/src/utils}/snapHelpers.js +0 -0
  356. /package/plugins/{frame → beta/frame}/dist/css/index.css +0 -0
  357. /package/plugins/{map-styles → beta/frame}/dist/esm/index.js.LICENSE.txt +0 -0
  358. /package/plugins/{map-styles → beta/frame}/dist/umd/index.js.LICENSE.txt +0 -0
  359. /package/plugins/{frame → beta/frame}/package.json +0 -0
  360. /package/plugins/{frame → beta/frame}/src/FrameInit.jsx +0 -0
  361. /package/plugins/{frame → beta/frame}/src/api/addFrame.js +0 -0
  362. /package/plugins/{frame → beta/frame}/src/api/editFeature.js +0 -0
  363. /package/plugins/{frame → beta/frame}/src/config.js +0 -0
  364. /package/plugins/{frame → beta/frame}/src/frame.scss +0 -0
  365. /package/plugins/{frame → beta/frame}/src/index.js +0 -0
  366. /package/plugins/{frame → beta/frame}/src/manifest.js +0 -0
  367. /package/plugins/{frame → beta/frame}/src/reducer.js +0 -0
  368. /package/plugins/{frame → beta/frame}/src/utils.js +0 -0
  369. /package/plugins/{scale-bar → beta/map-styles}/dist/esm/index.js.LICENSE.txt +0 -0
  370. /package/plugins/{scale-bar → beta/map-styles}/dist/umd/index.js.LICENSE.txt +0 -0
  371. /package/plugins/{map-styles → beta/map-styles}/src/MapStylesInit.jsx +0 -0
  372. /package/plugins/{map-styles → beta/map-styles}/src/config.js +0 -0
  373. /package/plugins/{map-styles → beta/map-styles}/src/index.js +0 -0
  374. /package/plugins/{scale-bar → beta/scale-bar}/dist/css/index.css +0 -0
  375. /package/plugins/{use-location → beta/scale-bar}/dist/esm/index.js.LICENSE.txt +0 -0
  376. /package/plugins/{use-location → beta/scale-bar}/dist/umd/index.js.LICENSE.txt +0 -0
  377. /package/plugins/{scale-bar → beta/scale-bar}/package.json +0 -0
  378. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.jsx +0 -0
  379. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.test.jsx +0 -0
  380. /package/plugins/{scale-bar → beta/scale-bar}/src/index.js +0 -0
  381. /package/plugins/{scale-bar → beta/scale-bar}/src/index.test.js +0 -0
  382. /package/plugins/{scale-bar → beta/scale-bar}/src/manifest.js +0 -0
  383. /package/plugins/{scale-bar → beta/scale-bar}/src/scaleBar.scss +0 -0
  384. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.js +0 -0
  385. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.test.js +0 -0
  386. /package/{providers/open-names → plugins/beta/use-location}/dist/esm/index.js.LICENSE.txt +0 -0
  387. /package/{providers/open-names → plugins/beta/use-location}/dist/umd/index.js.LICENSE.txt +0 -0
  388. /package/plugins/{use-location → beta/use-location}/src/UseLocation.jsx +0 -0
  389. /package/plugins/{use-location → beta/use-location}/src/UseLocationInit.jsx +0 -0
  390. /package/plugins/{use-location → beta/use-location}/src/defaults.js +0 -0
  391. /package/plugins/{use-location → beta/use-location}/src/events.js +0 -0
  392. /package/plugins/{use-location → beta/use-location}/src/index.js +0 -0
  393. /package/plugins/{use-location → beta/use-location}/src/manifest.js +0 -0
  394. /package/plugins/{use-location → beta/use-location}/src/reducer.js +0 -0
  395. /package/providers/{esri → beta/esri}/src/appEvents.js +0 -0
  396. /package/providers/{esri → beta/esri}/src/defaults.js +0 -0
  397. /package/providers/{esri → beta/esri}/src/esriProvider.scss +0 -0
  398. /package/providers/{esri → beta/esri}/src/utils/coords.js +0 -0
  399. /package/providers/{esri → beta/esri}/src/utils/detectWebGL.js +0 -0
  400. /package/providers/{esri → beta/esri}/src/utils/esriFixes.js +0 -0
  401. /package/providers/{esri → beta/esri}/src/utils/query.js +0 -0
  402. /package/providers/{esri → beta/esri}/src/utils/spatial.js +0 -0
  403. /package/providers/{open-names → beta/open-names}/dist/esm/im-reverse-geocode.js.LICENSE.txt +0 -0
  404. /package/providers/{open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt → beta/open-names/dist/esm/index.js.LICENSE.txt} +0 -0
  405. /package/providers/{open-names → beta/open-names}/src/index.js +0 -0
  406. /package/providers/{open-names → beta/open-names}/src/reverseGeocode.js +0 -0
  407. /package/providers/{open-names → beta/open-names}/src/utils/mapToLocationModel.js +0 -0
@@ -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,8 @@
1
1
  export const DEFAULTS = {
2
+ tolerance: 10, // Used for cross hair and click events
2
3
  interactionMode: 'marker',
3
4
  multiSelect: false,
5
+ contiguous: false,
4
6
  markerColor: 'rgba(212,53,28,1)',
5
7
  selectedStroke: 'rgba(212,53,28,1)',
6
8
  selectedFill: 'rgba(255, 0, 0, 0.1)',
@@ -1,3 +1,16 @@
1
+ // Helper for feature toggling logic
2
+ const createFeatureHandler = (mapState, pluginState) => (args, addToExisting) => {
3
+ mapState.markers.remove('location')
4
+ pluginState.dispatch({
5
+ type: 'TOGGLE_SELECTED_FEATURES',
6
+ payload: {
7
+ multiSelect: pluginState.multiSelect,
8
+ addToExisting,
9
+ ...args
10
+ }
11
+ })
12
+ }
13
+
1
14
  export function attachEvents ({
2
15
  appState,
3
16
  mapState,
@@ -8,41 +21,24 @@ export function attachEvents ({
8
21
  handleInteraction,
9
22
  closeApp
10
23
  }) {
11
- const {
12
- selectDone,
13
- selectAtTarget,
14
- selectCancel
15
- } = buttonConfig
16
-
24
+ const { selectDone, selectAtTarget, selectCancel } = buttonConfig
17
25
  const { viewportRef } = appState.layoutRefs
18
26
 
27
+ // Keyboard Logic
19
28
  let enterOnViewport = false
20
- const handleKeydown = (e) => {
21
- enterOnViewport = e.key === 'Enter' && viewportRef.current === e.target
22
- }
23
- document.addEventListener('keydown', handleKeydown)
24
-
29
+ const handleKeydown = (e) => { enterOnViewport = e.key === 'Enter' && viewportRef.current === e.target }
25
30
  const handleKeyup = (e) => {
26
- if (e.key !== 'Enter' || !enterOnViewport) {
27
- return
31
+ if (e.key === 'Enter' && enterOnViewport) {
32
+ e.preventDefault()
33
+ handleSelectAtTarget()
28
34
  }
29
- e.preventDefault()
30
- handleSelectAtTarget()
31
35
  }
32
- document.addEventListener('keyup', handleKeyup)
33
36
 
34
- // Allow tapping on touch devices as well as accurate placement
35
- const handleMapClick = (e) => {
36
- handleInteraction(e)
37
- }
38
- eventBus.on(events.MAP_CLICK, handleMapClick)
37
+ // Interaction Handlers
38
+ const handleMapClick = (e) => handleInteraction(e)
39
+ const handleSelectAtTarget = () => handleInteraction(mapState.crossHair.getDetail())
39
40
 
40
- const handleSelectAtTarget = () => {
41
- handleInteraction(mapState.crossHair.getDetail())
42
- }
43
- selectAtTarget.onClick = handleSelectAtTarget
44
-
45
- const handleSelectDone = (e) => {
41
+ const handleSelectDone = () => {
46
42
  const marker = mapState.markers.getMarker('location')
47
43
  const { coords } = marker || {}
48
44
  const { selectionBounds, selectedFeatures } = pluginState
@@ -57,43 +53,32 @@ export function attachEvents ({
57
53
  ...(!coords && selectionBounds && { selectionBounds })
58
54
  })
59
55
 
60
- if (!(pluginState.closeOnAction ?? true)) {
61
- return
56
+ if (pluginState.closeOnAction ?? true) {
57
+ closeApp()
62
58
  }
63
-
64
- closeApp()
65
59
  }
66
- selectDone.onClick = handleSelectDone
67
60
 
68
61
  const handleSelectCancel = () => {
69
62
  eventBus.emit('interact:cancel')
70
-
71
- if (!(pluginState.closeOnAction ?? true)) {
72
- return
63
+ if (pluginState.closeOnAction ?? true) {
64
+ closeApp()
73
65
  }
74
-
75
- closeApp()
76
66
  }
77
- selectCancel.onClick = handleSelectCancel
78
67
 
79
- const handleToggleFeature = (args, addToExisting) => {
80
- mapState.markers.remove('location')
68
+ const toggleFeature = createFeatureHandler(mapState, pluginState)
69
+ const handleSelect = (args) => toggleFeature(args, true)
70
+ const handleUnselect = (args) => toggleFeature(args, false)
81
71
 
82
- pluginState.dispatch({
83
- type: 'TOGGLE_SELECTED_FEATURES',
84
- payload: {
85
- multiSelect: pluginState.multiSelect,
86
- addToExisting,
87
- ...args
88
- }
89
- })
90
- }
91
- const handleSelect = (args) => handleToggleFeature(args, true)
92
- const handleUnselect = (args) => handleToggleFeature(args, false)
72
+ // Attach Listeners
73
+ document.addEventListener('keydown', handleKeydown)
74
+ document.addEventListener('keyup', handleKeyup)
75
+ eventBus.on(events.MAP_CLICK, handleMapClick)
93
76
  eventBus.on('interact:selectFeature', handleSelect)
94
77
  eventBus.on('interact:unselectFeature', handleUnselect)
78
+ selectAtTarget.onClick = handleSelectAtTarget
79
+ selectDone.onClick = handleSelectDone
80
+ selectCancel.onClick = handleSelectCancel
95
81
 
96
- // Return cleanup function
97
82
  return () => {
98
83
  selectDone.onClick = null
99
84
  selectAtTarget.onClick = null
@@ -0,0 +1,186 @@
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
+
152
+ it('selectDone handles emission when no marker/coords exist', () => {
153
+ const params = createParams()
154
+ cleanup = attachEvents(params)
155
+
156
+ // Ensure marker returns null (no coords)
157
+ params.mapState.markers.getMarker.mockReturnValue(null)
158
+
159
+ // Set up features and bounds
160
+ params.pluginState.selectedFeatures = [{ id: 'f1' }]
161
+ params.pluginState.selectionBounds = { sw: [0, 0], ne: [1, 1] }
162
+
163
+ params.buttonConfig.selectDone.onClick()
164
+
165
+ expect(params.eventBus.emit).toHaveBeenCalledWith('interact:done', {
166
+ selectedFeatures: [{ id: 'f1' }],
167
+ selectionBounds: { sw: [0, 0], ne: [1, 1] }
168
+ })
169
+ })
170
+
171
+ it('respects default closeOnAction when value is undefined (fallback to true)', () => {
172
+ const params = createParams()
173
+ // Explicitly set to undefined to trigger the ?? fallback
174
+ params.pluginState.closeOnAction = undefined
175
+ cleanup = attachEvents(params)
176
+
177
+ // Test for selectDone
178
+ params.buttonConfig.selectDone.onClick()
179
+ expect(params.closeApp).toHaveBeenCalledTimes(1)
180
+
181
+ // Test for selectCancel
182
+ params.closeApp.mockClear()
183
+ params.buttonConfig.selectCancel.onClick()
184
+ expect(params.closeApp).toHaveBeenCalledTimes(1)
185
+ })
186
+ })
@@ -32,7 +32,7 @@ export const useHighlightSync = ({
32
32
 
33
33
  useEffect(() => {
34
34
  if (!mapProvider || !selectedFeatures || !stylesMap) {
35
- return
35
+ return undefined // Explicit return to match the cleanup function return below
36
36
  }
37
37
 
38
38
  // Update updateHighlightedFeatures on interaction
@@ -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
+ })