@defra/interactive-map 0.0.15-alpha → 0.0.17-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/assets/css/docusaurus.css +104 -0
  2. package/assets/images/favicon.svg +1 -0
  3. package/assets/images/hero.png +0 -0
  4. package/assets/images/slot-map.svg +264 -0
  5. package/dist/css/index.css +1 -1
  6. package/dist/esm/im-core.js +1 -1
  7. package/dist/esm/im-shell.js +1 -1
  8. package/dist/umd/im-core.js +1 -1
  9. package/dist/umd/index.js +1 -1
  10. package/docs/api/slots.md +90 -6
  11. package/docs/api.md +4 -4
  12. package/docs/architecture.md +3 -1
  13. package/docs/{demo.mdx → examples.mdx} +1 -1
  14. package/docs/getting-started.md +5 -4
  15. package/docs/index.mdx +42 -0
  16. package/docs/plugins/datasets.md +561 -0
  17. package/docs/plugins/interact.md +176 -55
  18. package/docs/plugins/map-styles.md +64 -7
  19. package/docs/plugins/search.md +207 -63
  20. package/docs/plugins.md +8 -16
  21. package/docusaurus.config.cjs +34 -34
  22. package/jest.setup.js +1 -1
  23. package/package.json +6 -5
  24. package/plugins/beta/datasets/dist/css/index.css +85 -15
  25. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  26. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  27. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  28. package/plugins/beta/datasets/src/DatasetsInit.jsx +24 -9
  29. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  30. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
  31. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
  32. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
  33. package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
  34. package/plugins/beta/datasets/src/api/addDataset.js +3 -9
  35. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  36. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  37. package/plugins/beta/datasets/src/api/removeDataset.js +3 -45
  38. package/plugins/beta/datasets/src/api/setData.js +8 -0
  39. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  40. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  41. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  42. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  43. package/plugins/beta/datasets/src/datasets.js +33 -59
  44. package/plugins/beta/datasets/src/defaults.js +43 -9
  45. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +39 -30
  46. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  47. package/plugins/beta/datasets/src/manifest.js +27 -19
  48. package/plugins/beta/datasets/src/panels/Key.jsx +129 -49
  49. package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
  50. package/plugins/beta/datasets/src/panels/Layers.jsx +131 -29
  51. package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
  52. package/plugins/beta/datasets/src/reducer.js +128 -9
  53. package/plugins/beta/datasets/src/styles/patterns.js +157 -0
  54. package/plugins/beta/datasets/src/utils/bbox.js +8 -6
  55. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  56. package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
  57. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  58. package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
  59. package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
  60. package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
  61. package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
  62. package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
  63. package/plugins/beta/draw-es/src/events.js +52 -20
  64. package/plugins/beta/draw-es/src/events.test.js +301 -0
  65. package/plugins/beta/draw-es/src/graphic.js +1 -1
  66. package/plugins/beta/draw-es/src/manifest.js +4 -4
  67. package/plugins/beta/draw-es/src/reducer.js +1 -1
  68. package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
  69. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  70. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  71. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  72. package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
  73. package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
  74. package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
  75. package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
  76. package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
  77. package/plugins/beta/draw-ml/src/api/split.js +4 -4
  78. package/plugins/beta/draw-ml/src/defaults.js +1 -1
  79. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  80. package/plugins/beta/draw-ml/src/events.js +8 -6
  81. package/plugins/beta/draw-ml/src/manifest.js +29 -29
  82. package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
  83. package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
  84. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
  85. package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
  86. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
  87. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
  88. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
  89. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
  90. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
  91. package/plugins/beta/draw-ml/src/reducer.js +1 -1
  92. package/plugins/beta/draw-ml/src/undoStack.js +4 -4
  93. package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
  94. package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
  95. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  96. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  97. package/plugins/beta/frame/src/Frame.jsx +9 -9
  98. package/plugins/beta/frame/src/FrameInit.jsx +4 -4
  99. package/plugins/beta/frame/src/api/addFrame.js +1 -1
  100. package/plugins/beta/frame/src/api/editFeature.js +1 -1
  101. package/plugins/beta/frame/src/config.js +1 -1
  102. package/plugins/beta/frame/src/manifest.js +3 -3
  103. package/plugins/beta/frame/src/reducer.js +1 -1
  104. package/plugins/beta/frame/src/utils.js +1 -1
  105. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  106. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  107. package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
  108. package/plugins/beta/map-styles/src/manifest.js +1 -1
  109. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  110. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  111. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  112. package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
  113. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  114. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  115. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  116. package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
  117. package/plugins/beta/use-location/src/defaults.js +1 -1
  118. package/plugins/beta/use-location/src/events.js +3 -3
  119. package/plugins/interact/dist/css/index.css +1 -1
  120. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  121. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  122. package/plugins/interact/src/InteractInit.jsx +1 -2
  123. package/plugins/interact/src/api/enable.js +8 -5
  124. package/plugins/interact/src/api/enable.test.js +2 -2
  125. package/plugins/interact/src/api/selectFeature.js +4 -4
  126. package/plugins/interact/src/api/unselectFeature.js +5 -5
  127. package/plugins/interact/src/defaults.js +0 -1
  128. package/plugins/interact/src/events.test.js +15 -15
  129. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  130. package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
  131. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
  132. package/plugins/interact/src/interact.scss +0 -7
  133. package/plugins/interact/src/manifest.js +15 -19
  134. package/plugins/interact/src/manifest.test.js +6 -5
  135. package/plugins/interact/src/reducer.js +3 -3
  136. package/plugins/interact/src/reducer.test.js +0 -1
  137. package/plugins/interact/src/utils/spatial.js +10 -10
  138. package/plugins/interact/src/utils/spatial.test.js +14 -14
  139. package/plugins/search/dist/css/index.css +1 -1
  140. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  141. package/plugins/search/dist/esm/index.js +1 -1
  142. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  143. package/plugins/search/dist/umd/index.js +1 -1
  144. package/plugins/search/src/Search.jsx +7 -6
  145. package/plugins/search/src/Search.test.jsx +23 -23
  146. package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
  147. package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
  148. package/plugins/search/src/components/Form/Form.jsx +14 -14
  149. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  150. package/plugins/search/src/components/Form/Form.test.jsx +11 -11
  151. package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
  152. package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
  153. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
  154. package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
  155. package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
  156. package/plugins/search/src/datasets.js +12 -13
  157. package/plugins/search/src/datasets.test.js +1 -1
  158. package/plugins/search/src/defaults.js +1 -1
  159. package/plugins/search/src/events/fetchSuggestions.js +3 -3
  160. package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
  161. package/plugins/search/src/events/formHandlers.js +3 -3
  162. package/plugins/search/src/events/formHandlers.test.js +1 -1
  163. package/plugins/search/src/events/index.js +2 -2
  164. package/plugins/search/src/events/index.test.js +2 -2
  165. package/plugins/search/src/events/inputHandlers.js +4 -4
  166. package/plugins/search/src/events/inputHandlers.test.js +1 -1
  167. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  168. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  169. package/plugins/search/src/index.js +2 -1
  170. package/plugins/search/src/index.test.js +3 -3
  171. package/plugins/search/src/manifest.js +6 -4
  172. package/plugins/search/src/reducer.js +1 -2
  173. package/plugins/search/src/reducer.test.js +2 -2
  174. package/plugins/search/src/search.scss +10 -3
  175. package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
  176. package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
  177. package/plugins/search/src/utils/updateMap.js +1 -1
  178. package/plugins/search/src/utils/updateMap.test.js +5 -5
  179. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  180. package/providers/beta/esri/src/esriProvider.js +5 -5
  181. package/providers/beta/esri/src/utils/coords.js +1 -1
  182. package/providers/beta/esri/src/utils/esriFixes.js +1 -1
  183. package/providers/beta/esri/src/utils/query.js +4 -4
  184. package/providers/beta/esri/src/utils/spatial.js +1 -2
  185. package/providers/beta/esri/src/utils/spatial.test.js +4 -1
  186. package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
  187. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  188. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  189. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  190. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  191. package/providers/maplibre/src/appEvents.test.js +1 -1
  192. package/providers/maplibre/src/index.js +1 -1
  193. package/providers/maplibre/src/index.test.js +3 -5
  194. package/providers/maplibre/src/mapEvents.test.js +15 -5
  195. package/providers/maplibre/src/maplibreProvider.test.js +6 -2
  196. package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
  197. package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
  198. package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
  199. package/providers/maplibre/src/utils/highlightFeatures.js +3 -2
  200. package/providers/maplibre/src/utils/highlightFeatures.test.js +13 -6
  201. package/providers/maplibre/src/utils/labels.js +19 -20
  202. package/providers/maplibre/src/utils/labels.test.js +15 -13
  203. package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
  204. package/providers/maplibre/src/utils/queryFeatures.js +6 -6
  205. package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
  206. package/providers/maplibre/src/utils/spatial.js +0 -1
  207. package/providers/maplibre/src/utils/spatial.test.js +26 -27
  208. package/src/App/components/Actions/Actions.jsx +2 -2
  209. package/src/App/components/Actions/Actions.module.scss +0 -7
  210. package/src/App/components/Actions/Actions.test.jsx +1 -1
  211. package/src/App/components/Icon/Icon.jsx +3 -2
  212. package/src/App/components/Icon/Icon.module.scss +4 -0
  213. package/src/App/components/Icon/Icon.test.jsx +43 -4
  214. package/src/App/components/MapButton/MapButton.jsx +42 -17
  215. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  216. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  217. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  218. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  219. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  220. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  221. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  222. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  223. package/src/App/hooks/useInterfaceAPI.js +6 -0
  224. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  225. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  226. package/src/App/layout/Layout.jsx +12 -7
  227. package/src/App/layout/Layout.test.jsx +2 -2
  228. package/src/App/layout/layout.module.scss +67 -29
  229. package/src/App/registry/pluginRegistry.js +17 -0
  230. package/src/App/registry/pluginRegistry.test.js +33 -0
  231. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  232. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  233. package/src/App/renderer/mapButtons.js +3 -2
  234. package/src/App/renderer/mapPanels.test.js +2 -2
  235. package/src/App/renderer/slotHelpers.js +2 -2
  236. package/src/App/renderer/slotHelpers.test.js +5 -5
  237. package/src/App/renderer/slots.js +9 -5
  238. package/src/App/store/AppProvider.jsx +3 -1
  239. package/src/App/store/AppProvider.test.jsx +1 -1
  240. package/src/App/store/ServiceProvider.jsx +3 -1
  241. package/src/App/store/appActionsMap.js +16 -0
  242. package/src/App/store/appActionsMap.test.js +27 -0
  243. package/src/App/store/appDispatchMiddleware.js +33 -1
  244. package/src/App/store/appDispatchMiddleware.test.js +250 -222
  245. package/src/App/store/appReducer.js +2 -0
  246. package/src/InteractiveMap/InteractiveMap.js +4 -0
  247. package/src/config/appConfig.js +7 -4
  248. package/src/config/events.js +28 -0
  249. package/src/scss/main.scss +1 -0
  250. package/src/scss/settings/_dimensions.scss +0 -1
  251. package/src/services/logger.js +6 -0
  252. package/src/services/logger.test.js +32 -0
  253. package/src/utils/getSafeZoneInset.js +9 -7
  254. package/src/utils/getSafeZoneInset.test.js +10 -10
  255. package/webpack.dev.mjs +23 -19
  256. package/docs/govuk-prototype.md +0 -23
  257. package/docs/index.md +0 -19
  258. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  259. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  260. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  261. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  262. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  263. package/plugins/beta/datasets/src/mapLayers.js +0 -165
@@ -0,0 +1,157 @@
1
+ import { getValueForStyle } from '../../../../../src/utils/getValueForStyle.js'
2
+
3
+ // ─── Built-in pattern library ────────────────────────────────────────────────
4
+ // Each value is the inner SVG content (paths only, no wrapper).
5
+ // Paths are authored in a 16×16 coordinate space (power-of-two, tiles seamlessly).
6
+ // Use {{foreground}} and {{background}} tokens for colours.
7
+
8
+ const BUILT_IN_PATTERNS = {
9
+ 'cross-hatch': '<path d="M0 4.486V3.485h3.5V.001h1v3.484h7.002V.001h1v3.484h3.5v1.001h-3.5v7h3.5v.999h-3.5v3.516h-1v-3.516H4.499v3.516h-1v-3.516H0v-.999h3.5v-7H0zm11.501 0H4.499v7h7.002v-7z" fill="{{foreground}}"/>',
10
+ 'diagonal-cross-hatch': '<path d="M0 8.707V7.293L7.293 0h1.414L16 7.293v1.414L8.707 16H7.293L0 8.707zM.707 8L8 15.293 15.293 8 8 .707.707 8z" fill="{{foreground}}"/>',
11
+ 'forward-diagonal-hatch': '<path d="M16 8.707V7.293L7.293 16h1.414L16 8.707zm-16 0L8.707 0H7.293L0 7.293v1.414z" fill="{{foreground}}"/>',
12
+ 'backward-diagonal-hatch': '<path d="M0 8.707V7.293L8.707 16H7.293L0 8.707zm16 0L7.293 0h1.414L16 7.293v1.414z" fill="{{foreground}}"/>',
13
+ 'horizontal-hatch': '<path d="M0 4.5V3.499h15.999V4.5H0zm0 7h15.999V12.5H0v-1.001z" fill="{{foreground}}"/>',
14
+ 'vertical-hatch': '<path d="M3.501 16.001V0h1v16.001h-1zm7.998 0V0h1v16.001h-1z" fill="{{foreground}}"/>',
15
+ dot: '<path d="M3.999 2A2 2 0 0 1 6 3.999C6 5.103 5.103 6 3.999 6a2 2 0 0 1-1.999-2.001A2 2 0 0 1 3.999 2zm0 7.999C5.103 10 6 10.897 6 12.001A2 2 0 0 1 3.999 14a2 2 0 0 1-1.999-1.999A2 2 0 0 1 3.999 10zM11.999 2A2 2 0 0 1 14 3.999C14 5.103 13.103 6 11.999 6S10 5.103 10 3.999A2 2 0 0 1 11.999 2zm0 7.999c1.104 0 2.001.897 2.001 2.001A2 2 0 0 1 11.999 14 2 2 0 0 1 10 12.001c0-1.104.897-2.001 1.999-2.001z" fill="{{foreground}}"/>',
16
+ diamond: '<path d="M4 .465L7.535 4 4 7.535.465 4 4 .465zm0 7.999l3.535 3.535L4 15.535.465 11.999 4 8.464zm8-8l3.535 3.535-3.536 3.536L8.464 4 12 .464zm0 8.001L15.536 12 12 15.536 8.465 12 12 8.465z" fill="{{foreground}}"/>'
17
+ }
18
+
19
+ // Plugin-controlled border path used in the key symbol (20×20 coordinate space).
20
+ // This is always rendered as the first element, before the user-supplied content.
21
+ const KEY_BORDER_PATH = '<path d="M19 2.862v14.275c0 1.028-.835 1.862-1.862 1.862H2.863c-1.028 0-1.862-.835-1.862-1.862V2.862C1.001 1.834 1.836 1 2.863 1h14.275C18.166 1 19 1.835 19 2.862z" fill="{{background}}" stroke="{{foreground}}" stroke-width="2"/>'
22
+
23
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
24
+
25
+ export const hashString = (str) => {
26
+ let hash = 0
27
+ for (const ch of str) {
28
+ hash = ((hash << 5) - hash) + ch.codePointAt(0)
29
+ hash = hash & hash
30
+ }
31
+ return Math.abs(hash).toString(36) // NOSONAR: base36 encoding for compact alphanumeric hash string
32
+ }
33
+
34
+ export const injectColors = (content, foreground, background) =>
35
+ content
36
+ .replace(/\{\{foreground\}\}/g, foreground || 'black')
37
+ .replace(/\{\{background\}\}/g, background || 'transparent')
38
+
39
+ // ─── Public API ───────────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Returns true if a dataset has a fill pattern configured.
43
+ * @param {Object} dataset
44
+ * @returns {boolean}
45
+ */
46
+ export const hasPattern = (dataset) => !!(dataset.fillPattern || dataset.fillPatternSvgContent)
47
+
48
+ /**
49
+ * Returns the raw (un-coloured) inner SVG content for a dataset's pattern.
50
+ * Custom fillPatternSvgContent takes precedence over built-in fillPattern ids.
51
+ * @param {Object} dataset
52
+ * @returns {string|null}
53
+ */
54
+ export const getPatternInnerContent = (dataset) => {
55
+ if (dataset.fillPatternSvgContent) {
56
+ return dataset.fillPatternSvgContent
57
+ }
58
+ if (dataset.fillPattern && BUILT_IN_PATTERNS[dataset.fillPattern]) {
59
+ return BUILT_IN_PATTERNS[dataset.fillPattern]
60
+ }
61
+ return null
62
+ }
63
+
64
+ /**
65
+ * Returns a deterministic image ID for a pattern + resolved colour combination.
66
+ * @param {Object} dataset
67
+ * @param {string} mapStyleId
68
+ * @returns {string|null}
69
+ */
70
+ export const getPatternImageId = (dataset, mapStyleId) => {
71
+ const innerContent = getPatternInnerContent(dataset)
72
+ if (!innerContent) {
73
+ return null
74
+ }
75
+ const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
76
+ const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
77
+ return `pattern-${hashString(innerContent + fg + bg)}`
78
+ }
79
+
80
+ /**
81
+ * Returns colour-injected inner SVG path content for use in the Key symbol.
82
+ * The caller is responsible for wrapping this in the SVG element and border path.
83
+ * @param {Object} dataset
84
+ * @param {string} mapStyleId
85
+ * @returns {{ border: string, content: string }|null}
86
+ */
87
+ export const getKeyPatternPaths = (dataset, mapStyleId) => {
88
+ const innerContent = getPatternInnerContent(dataset)
89
+ if (!innerContent) {
90
+ return null
91
+ }
92
+ const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
93
+ const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
94
+ const borderStroke = getValueForStyle(dataset.stroke, mapStyleId) || fg
95
+ return {
96
+ border: injectColors(KEY_BORDER_PATH, borderStroke, bg),
97
+ content: injectColors(innerContent, fg, bg)
98
+ }
99
+ }
100
+
101
+ // ─── Rasterisation ────────────────────────────────────────────────────────────
102
+
103
+ // Module-level cache: imageId → ImageData. Avoids re-rasterising identical patterns.
104
+ const imageDataCache = new Map()
105
+
106
+ const rasteriseToImageData = (svgString, width, height) =>
107
+ new Promise((resolve, reject) => {
108
+ const blob = new Blob([svgString], { type: 'image/svg+xml' })
109
+ const url = URL.createObjectURL(blob)
110
+ const img = new Image(width, height)
111
+ img.onload = () => {
112
+ const canvas = document.createElement('canvas')
113
+ canvas.width = width
114
+ canvas.height = height
115
+ const ctx = canvas.getContext('2d')
116
+ ctx.drawImage(img, 0, 0, width, height)
117
+ URL.revokeObjectURL(url)
118
+ resolve(ctx.getImageData(0, 0, width, height))
119
+ }
120
+ img.onerror = () => {
121
+ URL.revokeObjectURL(url)
122
+ reject(new Error(`Failed to rasterise pattern SVG: ${svgString.slice(0, 80)}`))
123
+ }
124
+ img.src = url
125
+ })
126
+
127
+ /**
128
+ * Rasterises a dataset's pattern SVG to ImageData, using an in-memory cache
129
+ * to avoid re-rasterising identical patterns. Framework-agnostic — callers
130
+ * are responsible for registering the result with their map framework.
131
+ *
132
+ * @param {Object} dataset
133
+ * @param {string} mapStyleId
134
+ * @returns {Promise<{ imageId: string, imageData: ImageData }|null>}
135
+ */
136
+ export const rasterisePattern = async (dataset, mapStyleId) => {
137
+ const innerContent = getPatternInnerContent(dataset)
138
+ if (!innerContent) {
139
+ return null
140
+ }
141
+
142
+ const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
143
+ const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
144
+ const imageId = `pattern-${hashString(innerContent + fg + bg)}`
145
+
146
+ let imageData = imageDataCache.get(imageId)
147
+ if (!imageData) {
148
+ const colored = injectColors(innerContent, fg, bg)
149
+ const bgRect = `<rect width="16" height="16" fill="${bg}"/>`
150
+ // pixelRatio: 2 means the map treats this as an 8×8 logical tile — crisp on retina screens.
151
+ const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">${bgRect}${colored}</svg>`
152
+ imageData = await rasteriseToImageData(svgString, 16, 16)
153
+ imageDataCache.set(imageId, imageData)
154
+ }
155
+
156
+ return { imageId, imageData }
157
+ }
@@ -22,7 +22,7 @@ export const bboxContains = (outer, inner) => {
22
22
  inner[0] >= outer[0] && // west
23
23
  inner[1] >= outer[1] && // south
24
24
  inner[2] <= outer[2] && // east
25
- inner[3] <= outer[3] // north
25
+ inner[3] <= outer[3] // NOSONAR, north
26
26
  )
27
27
  }
28
28
 
@@ -40,7 +40,7 @@ export const expandBbox = (existing, addition) => {
40
40
  Math.min(existing[0], addition[0]), // west
41
41
  Math.min(existing[1], addition[1]), // south
42
42
  Math.max(existing[2], addition[2]), // east
43
- Math.max(existing[3], addition[3]) // north
43
+ Math.max(existing[3], addition[3]) // NOSONAR, north
44
44
  ]
45
45
  }
46
46
 
@@ -57,8 +57,8 @@ export const bboxIntersects = (a, b) => {
57
57
  return !(
58
58
  a[2] < b[0] || // a is left of b
59
59
  a[0] > b[2] || // a is right of b
60
- a[3] < b[1] || // a is below b
61
- a[1] > b[3] // a is above b
60
+ a[3] < b[1] || // NOSONAR a is below b
61
+ a[1] > b[3] // NOSONAR a is above b
62
62
  )
63
63
  }
64
64
 
@@ -68,7 +68,7 @@ export const bboxIntersects = (a, b) => {
68
68
  * @returns {number[]} bbox as [west, south, east, north]
69
69
  */
70
70
  export const getGeometryBbox = (geometry) => {
71
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
71
+ let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity
72
72
 
73
73
  const processCoord = (coord) => {
74
74
  minX = Math.min(minX, coord[0])
@@ -98,7 +98,7 @@ export const getGeometryBbox = (geometry) => {
98
98
  processCoords(geometry.coordinates, 2)
99
99
  break
100
100
  case 'MultiPolygon':
101
- processCoords(geometry.coordinates, 3)
101
+ processCoords(geometry.coordinates, 3) // NOSONAR: 3 = coordinate nesting depth for MultiPolygon ([polygons][rings][points])
102
102
  break
103
103
  case 'GeometryCollection':
104
104
  geometry.geometries.forEach(g => {
@@ -109,6 +109,8 @@ export const getGeometryBbox = (geometry) => {
109
109
  maxY = Math.max(maxY, b[3])
110
110
  })
111
111
  break
112
+ default:
113
+ throw new Error(`Unsupported geometry type: ${geometry.type}`)
112
114
  }
113
115
 
114
116
  return [minX, minY, maxX, maxY]
@@ -8,8 +8,11 @@ export const buildExclusionFilter = (originalFilter, idProperty, excludeIds) =>
8
8
  }
9
9
 
10
10
  // Coerce both sides to strings to handle number/string type mismatches
11
- const stringIds = excludeIds.map(id => String(id))
12
- const exclusionFilter = ['!', ['in', ['to-string', ['get', idProperty]], ['literal', stringIds]]]
11
+ // When no idProperty, use feature-level id via ['id'] (GeoJSON feature.id)
12
+ const idExpr = idProperty ? ['to-string', ['get', idProperty]] : ['to-string', ['id']]
13
+ // Convert all IDs to strings; map passes each element as the first argument to String
14
+ const stringIds = excludeIds.map(String)
15
+ const exclusionFilter = ['!', ['in', idExpr, ['literal', stringIds]]]
13
16
 
14
17
  if (!originalFilter) {
15
18
  return exclusionFilter
@@ -0,0 +1,78 @@
1
+ import { hasCustomVisualStyle } from '../defaults.js'
2
+
3
+ const getFillProps = (dataset, sublayerStyle) => {
4
+ if (sublayerStyle.fillPattern || sublayerStyle.fillPatternSvgContent) {
5
+ return {
6
+ fillPattern: sublayerStyle.fillPattern,
7
+ fillPatternSvgContent: sublayerStyle.fillPatternSvgContent,
8
+ fillPatternForegroundColor: sublayerStyle.fillPatternForegroundColor ?? dataset.fillPatternForegroundColor,
9
+ fillPatternBackgroundColor: sublayerStyle.fillPatternBackgroundColor ?? dataset.fillPatternBackgroundColor
10
+ }
11
+ }
12
+ if ('fill' in sublayerStyle) {
13
+ // Sublayer explicitly sets a plain fill — do not inherit any parent pattern
14
+ return { fill: sublayerStyle.fill }
15
+ }
16
+ return {
17
+ fill: dataset.fill,
18
+ fillPattern: dataset.fillPattern,
19
+ fillPatternSvgContent: dataset.fillPatternSvgContent,
20
+ fillPatternForegroundColor: dataset.fillPatternForegroundColor,
21
+ fillPatternBackgroundColor: dataset.fillPatternBackgroundColor
22
+ }
23
+ }
24
+
25
+ const getCombinedFilter = (datasetFilter, sublayerFilter) => {
26
+ if (datasetFilter && sublayerFilter) {
27
+ return ['all', datasetFilter, sublayerFilter]
28
+ }
29
+ return sublayerFilter || datasetFilter || null
30
+ }
31
+
32
+ const getSymbolDescription = (dataset, sublayerStyle) => {
33
+ if ('symbolDescription' in sublayerStyle) {
34
+ return sublayerStyle.symbolDescription
35
+ }
36
+ if (hasCustomVisualStyle(sublayerStyle)) {
37
+ return undefined
38
+ }
39
+ return dataset.symbolDescription
40
+ }
41
+
42
+ /**
43
+ * Merge a sublayer with its parent dataset, producing a flat style
44
+ * object suitable for layer creation and key symbol rendering.
45
+ *
46
+ * The sublayer's nested `style` object is flattened before merging.
47
+ *
48
+ * Fill precedence (highest to lowest):
49
+ * 1. Sublayer's own fillPattern
50
+ * 2. Sublayer's own fill (explicit, even if transparent — clears any parent pattern)
51
+ * 3. Parent's fillPattern
52
+ * 4. Parent's fill
53
+ *
54
+ * symbolDescription is only inherited from the parent when the sublayer has no
55
+ * custom visual styles of its own. If the sublayer overrides stroke/fill/pattern
56
+ * without setting symbolDescription explicitly, no description is shown.
57
+ */
58
+ export const mergeSublayer = (dataset, sublayer) => {
59
+ const sublayerStyle = sublayer.style || {}
60
+ const combinedFilter = getCombinedFilter(dataset.filter, sublayer.filter)
61
+
62
+ return {
63
+ id: sublayer.id,
64
+ label: sublayer.label,
65
+ stroke: sublayerStyle.stroke ?? dataset.stroke,
66
+ strokeWidth: sublayerStyle.strokeWidth ?? dataset.strokeWidth,
67
+ strokeDashArray: sublayerStyle.strokeDashArray ?? dataset.strokeDashArray,
68
+ opacity: sublayerStyle.opacity ?? dataset.opacity,
69
+ keySymbolShape: sublayerStyle.keySymbolShape ?? dataset.keySymbolShape,
70
+ symbolDescription: getSymbolDescription(dataset, sublayerStyle),
71
+ showInKey: sublayer.showInKey ?? dataset.showInKey,
72
+ toggleVisibility: sublayer.toggleVisibility ?? false,
73
+ filter: combinedFilter,
74
+ minZoom: dataset.minZoom,
75
+ maxZoom: dataset.maxZoom,
76
+ ...getFillProps(dataset, sublayerStyle)
77
+ }
78
+ }
@@ -1 +1 @@
1
- import e from"@babel/runtime/helpers/defineProperty";import{useEffect as t}from"preact/compat";import r from"@arcgis/core/widgets/Sketch/SketchViewModel.js";import o from"@arcgis/core/layers/GraphicsLayer.js";import a from"@babel/runtime/helpers/asyncToGenerator";import*as n from"@arcgis/core/geometry/operators/simplifyOperator.js";import i from"@arcgis/core/Graphic.js";function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function p(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?l(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):l(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function s(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?c(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):c(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function u(e){return{type:"simple-fill",color:[0,120,255,.2],outline:{color:"dark"===e?"#ffffff":"#d4351c",width:2}}}function d(e,t,r){return new i({geometry:{type:"polygon",rings:t,spatialReference:27700},attributes:{id:e},symbol:u(r)})}function y(e){if(null==e||!e.geometry)throw new Error("Invalid graphic");var{geometry:t,attributes:r={}}=e;switch(t.type){case"point":return{type:"Feature",geometry:{type:"Point",coordinates:[t.x,t.y]},properties:s({},r)};case"polyline":return{type:"Feature",geometry:{type:"LineString",coordinates:t.paths[0]},properties:s({},r)};case"polygon":return{type:"Feature",geometry:{type:"Polygon",coordinates:t.rings},properties:s({},r)};default:throw new Error("Unsupported geometry type: ".concat(t.type))}}function m(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function f(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?m(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):m(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}var h={mobile:{slot:"actions",showLabel:!0},tablet:{slot:"actions",showLabel:!0},desktop:{slot:"actions",showLabel:!0}},v={reducer:{initialState:{mode:null,feature:null,tempFeature:null},actions:{SET_MODE:(e,t)=>p(p({},e),{},{mode:t}),SET_FEATURE:(e,t)=>p(p({},e),{},{feature:void 0===t.feature?e.feature:t.feature,tempFeature:void 0===t.tempFeature?e.tempFeature:t.tempFeature})}},InitComponent:e=>{var i,l,p,c,{appState:s,mapState:m,pluginConfig:f,pluginState:h,services:v,mapProvider:g,buttonConfig:b}=e,{events:O,eventBus:w}=v,{mapColorScheme:S}=m.mapStyle||{},E=null===(i=null===(l=f.includeModes)||void 0===l?void 0:l.includes(s.mode))||void 0===i||i,k=null!==(p=null===(c=f.excludeModes)||void 0===c?void 0:c.includes(s.mode))&&void 0!==p&&p,P=m.isMapReady&&E&&!k;t(()=>{if(P&&!g.sketchViewModel){var{sketchViewModel:e,sketchLayer:t,emptySketchLayer:a}=(e=>{var{mapProvider:t}=e,{view:a}=t,n=new o({id:"sketchLayer"});a.map.add(n);var i=new o({id:"emptySketchLayer"});return a.map.add(i),{sketchViewModel:new r({view:a,layer:i,defaultUpdateOptions:{tool:"reshape",updateOnGraphicClick:!1,multipleSelectionEnabled:!1,toggleToolOnClick:!1,highlightOptions:{enabled:!1}}}),emptySketchLayer:i,sketchLayer:n}})({mapProvider:g});return g.sketchViewModel=e,g.sketchLayer=t,g.emptySketchLayer=a,w.emit("draw:ready"),()=>{g.sketchViewModel=null,g.sketchLayer=null,g.emptySketchLayer=null}}},[m.isMapReady,s.mode]),t(()=>{if(P&&g.sketchViewModel){var e=function(e){var{pluginState:t,mapProvider:r,events:o,eventBus:i,buttonConfig:l,mapColorScheme:p}=e,{view:c,sketchViewModel:s,sketchLayer:m,emptySketchLayer:f}=r;if(!s)return null;var{drawDone:h,drawCancel:v}=l,{dispatch:g,mode:b,feature:O}=t,w=function(){var e=a(function*(){var e,r=null===(e=t.feature)||void 0===e||null===(e=e.properties)||void 0===e?void 0:e.id,o=null,a="active"===s.state&&!r;if("active"===s.state&&r&&(s.cancel(),yield new Promise(e=>setTimeout(e,50))),s.polygonSymbol=u(p),null==m||m.graphics.items.forEach(e=>{var t=d(e.attributes.id,e.geometry.rings,p);e.symbol=t.symbol,r===e.attributes.id&&(o=e)}),o&&!a&&s.layer===m)try{yield s.update(o,{tool:"reshape",toggleToolOnClick:!1})}catch(e){"AbortError"!==e.name&&console.error("Error updating sketch:",e)}});return function(){return e.apply(this,arguments)}}(),S=()=>{var e,t,r=null!==(e=null===(t=m.graphics)||void 0===t||null===(t=t.items)||void 0===t?void 0:t[0])&&void 0!==e?e:null;r&&setTimeout(()=>s.update(r),0)},E=()=>w(),k=function(){var e=a(function*(){b&&((s.updateGraphics||[]).length||S())});return function(){return e.apply(this,arguments)}}();i.on(o.MAP_STYLE_CHANGE,E);var P=s.on("update",e=>{var t,r=null===(t=e.toolEventInfo)||void 0===t?void 0:t.type,o=e.graphics[0];if("move-start"===r&&(s.cancel(),S()),"reshape"===r&&(n.isSimple(o.geometry)||s.undo()),"reshape-stop"===r){var a=y(o);i.emit("draw:updated",a),g({type:"SET_FEATURE",payload:{tempFeature:a}})}}),j=c.on("click",k),T=h.onClick,C=v.onClick;return h.onClick=()=>{s.cancel(),s.layer=f,g({type:"SET_MODE",payload:null}),g({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),i.emit("draw:done",{newFeature:t.tempFeature})},v.onClick=()=>{if(s.cancel(),m.removeAll(),O){var e=d(O.id||O.properties.id,O.geometry.coordinates,p);m.add(e)}s.layer=f,g({type:"SET_MODE",payload:null}),i.emit("draw:cancelled")},()=>{i.off(o.MAP_STYLE_CHANGE,E),P.remove(),j.remove(),h.onClick=T,v.onClick=C}}({pluginState:h,mapProvider:g,events:O,eventBus:w,buttonConfig:b,mapColorScheme:S});return()=>{e()}}},[P,S,h])},buttons:[f({id:"drawDone",label:"Done",variant:"primary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode},enableWhen:e=>{var{pluginState:t}=e;return!!t.tempFeature}},h),f({id:"drawCancel",label:"Cancel",variant:"tertiary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode}},h)],api:{newPolygon:(e,t)=>{var{mapState:r,pluginState:o,mapProvider:a,services:n}=e,{dispatch:i}=o,{sketchViewModel:l,sketchLayer:p}=a,{eventBus:c}=n;l.layer=p;var s=l.on("create",e=>{if("complete"===e.state){e.graphic.attributes={id:t},requestAnimationFrame(()=>{l.update(e.graphic,{tool:"reshape",toggleToolOnClick:!1})});var r=y(e.graphic);c.emit("draw:created",r),i({type:"SET_FEATURE",payload:{tempFeature:r}}),s.remove()}});l.polygonSymbol=u(r.mapStyle.mapColorScheme),l.create("polygon"),i({type:"SET_MODE",payload:"new-polygon"})},editFeature:(e,t)=>{var{pluginState:r,mapProvider:o}=e,{dispatch:a}=r,{sketchViewModel:n,sketchLayer:i}=o,l=i.graphics.items.find(e=>e.attributes.id===t),p=l.geometry.extent,c=[p.xmin,p.ymin,p.xmax,p.ymax];o.fitToBounds(c),n.layer=i,n.update(l,{tool:"reshape",toggleToolOnClick:!1,enableRotation:!1,enableScaling:!1}),a({type:"SET_FEATURE",payload:{feature:y(l)}}),a({type:"SET_MODE",payload:"edit-feature"})},addFeature:(e,t)=>{var{pluginState:r,mapState:o,mapProvider:a,services:n}=e,{dispatch:i}=r,{mapStyle:l}=o,{sketchViewModel:p,sketchLayer:c,emptySketchLayer:s}=a,{eventBus:u}=n,y=d(t.id,t.geometry.coordinates,l.mapColorScheme);c.add(y),p.layer=s,i({type:"SET_FEATURE",payload:{feature:t}}),u.emit("draw:add",t)},deleteFeature:(e,t)=>{var{pluginState:r,mapProvider:o,services:a}=e,{dispatch:n}=r,{sketchViewModel:i,sketchLayer:l,emptySketchLayer:p}=o,{eventBus:c}=a,s=l.graphics.items.find(e=>e.attributes.id===t);i.cancel(),l.remove(s),i.layer=p,n({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),c.emit("draw:delete",{featureId:t}),n({type:"SET_MODE",payload:null})}}};export{v as manifest};
1
+ import e from"@babel/runtime/helpers/defineProperty";import{useEffect as t}from"preact/compat";import r from"@arcgis/core/widgets/Sketch/SketchViewModel.js";import o from"@arcgis/core/layers/GraphicsLayer.js";import a from"@babel/runtime/helpers/asyncToGenerator";import*as n from"@arcgis/core/geometry/operators/simplifyOperator.js";import i from"@arcgis/core/Graphic.js";function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function p(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?l(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):l(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function u(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?c(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):c(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function s(e){return{type:"simple-fill",color:[0,120,255,.2],outline:{color:"dark"===e?"#ffffff":"#d4351c",width:2}}}function d(e,t,r){return new i({geometry:{type:"polygon",rings:t,spatialReference:27700},attributes:{id:e},symbol:s(r)})}function y(e){if(null==e||!e.geometry)throw new Error("Invalid graphic");var{geometry:t,attributes:r={}}=e;switch(t.type){case"point":return{type:"Feature",geometry:{type:"Point",coordinates:[t.x,t.y]},properties:u({},r)};case"polyline":return{type:"Feature",geometry:{type:"LineString",coordinates:t.paths[0]},properties:u({},r)};case"polygon":return{type:"Feature",geometry:{type:"Polygon",coordinates:t.rings},properties:u({},r)};default:throw new Error("Unsupported geometry type: ".concat(t.type))}}function m(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function v(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?m(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):m(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}var f={mobile:{slot:"actions",showLabel:!0},tablet:{slot:"actions",showLabel:!0},desktop:{slot:"actions",showLabel:!0}},h={reducer:{initialState:{mode:null,feature:null,tempFeature:null},actions:{SET_MODE:(e,t)=>p(p({},e),{},{mode:t}),SET_FEATURE:(e,t)=>p(p({},e),{},{feature:void 0===t.feature?e.feature:t.feature,tempFeature:void 0===t.tempFeature?e.tempFeature:t.tempFeature})}},InitComponent:e=>{var i,l,p,c,{appState:u,mapState:m,pluginConfig:v,pluginState:f,services:h,mapProvider:g,buttonConfig:b}=e,{events:O,eventBus:w}=h,{mapColorScheme:S}=m.mapStyle||{},E=null===(i=null===(l=v.includeModes)||void 0===l?void 0:l.includes(u.mode))||void 0===i||i,k=null!==(p=null===(c=v.excludeModes)||void 0===c?void 0:c.includes(u.mode))&&void 0!==p&&p,P=m.isMapReady&&E&&!k;t(()=>{if(P&&!g.sketchViewModel){var{sketchViewModel:e,sketchLayer:t,emptySketchLayer:a}=(e=>{var{mapProvider:t}=e,{view:a}=t,n=new o({id:"sketchLayer"});a.map.add(n);var i=new o({id:"emptySketchLayer"});return a.map.add(i),{sketchViewModel:new r({view:a,layer:i,defaultUpdateOptions:{tool:"reshape",updateOnGraphicClick:!1,multipleSelectionEnabled:!1,toggleToolOnClick:!1,highlightOptions:{enabled:!1}}}),emptySketchLayer:i,sketchLayer:n}})({mapProvider:g});return g.sketchViewModel=e,g.sketchLayer=t,g.emptySketchLayer=a,w.emit("draw:ready"),()=>{g.sketchViewModel=null,g.sketchLayer=null,g.emptySketchLayer=null}}},[m.isMapReady,u.mode]),t(()=>{if(P&&g.sketchViewModel){var e=function(e){var{pluginState:t,mapProvider:r,events:o,eventBus:i,buttonConfig:l,mapColorScheme:p}=e,{view:c,sketchViewModel:u,sketchLayer:m,emptySketchLayer:v}=r;if(!u)return null;var{drawDone:f,drawCancel:h}=l,{dispatch:g,mode:b,feature:O}=t,w=function(){var e=a(function*(){var e,r=null===(e=t.feature)||void 0===e||null===(e=e.properties)||void 0===e?void 0:e.id,o=null,a="active"===u.state&&!r;if("active"===u.state&&r&&(u.cancel(),yield new Promise(e=>setTimeout(e,50))),u.polygonSymbol=s(p),null==m||m.graphics.items.forEach(e=>{var t=d(e.attributes.id,e.geometry.rings,p);e.symbol=t.symbol,r===e.attributes.id&&(o=e)}),o&&!a&&u.layer===m)try{yield u.update(o,{tool:"reshape",toggleToolOnClick:!1})}catch(e){"AbortError"!==e.name&&console.error("Error updating sketch:",e)}});return function(){return e.apply(this,arguments)}}(),S=()=>{var e,t,r=null!==(e=null===(t=m.graphics)||void 0===t||null===(t=t.items)||void 0===t?void 0:t[0])&&void 0!==e?e:null;r&&setTimeout(()=>u.update(r),0)},E=()=>w(),k=e=>{if(e){var t=y(e);i.emit("draw:updated",t),g({type:"SET_FEATURE",payload:{tempFeature:t}})}},P=function(){var e=a(function*(){b&&((u.updateGraphics||[]).length||S())});return function(){return e.apply(this,arguments)}}(),j=function(){var e=a(function*(e){var{toolEventInfo:t}=e,r=null==e?void 0:e.graphic;if(r&&"vertex-add"===(null==t?void 0:t.type)){var o,a,n=null===(o=r.geometry)||void 0===o?void 0:o.rings;(null==n?void 0:n.length)>1?setTimeout(()=>u.undo(),0):(null==n||null===(a=n[0])||void 0===a?void 0:a.length)>3&&k(r)}});return function(t){return e.apply(this,arguments)}}(),T=function(){var e=a(function*(e){var t,r=null==e||null===(t=e.graphics)||void 0===t?void 0:t[0];k(r)});return function(t){return e.apply(this,arguments)}}();i.on(o.MAP_STYLE_CHANGE,E);var C=u.on("update",e=>{var t,r=null===(t=e.toolEventInfo)||void 0===t?void 0:t.type,o=e.graphics[0];"move-start"===r&&(u.cancel(),S()),"reshape"===r&&(n.isSimple(o.geometry)||u.undo()),"reshape-stop"!==r&&"vertex-remove"!==r||k(o)}),F=c.on("click",P),L=u.on("create",j),M=u.on("undo",T),D=f.onClick,_=h.onClick;return f.onClick=()=>{u.cancel(),u.layer=v,g({type:"SET_MODE",payload:null}),g({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),i.emit("draw:done",{newFeature:t.tempFeature})},h.onClick=()=>{if(u.cancel(),m.removeAll(),O){var e=d(O.id||O.properties.id,O.geometry.coordinates,p);m.add(e)}u.layer=v,g({type:"SET_MODE",payload:null}),i.emit("draw:cancelled")},()=>{i.off(o.MAP_STYLE_CHANGE,E),C.remove(),F.remove(),L.remove(),M.remove(),f.onClick=D,h.onClick=_}}({pluginState:f,mapProvider:g,events:O,eventBus:w,buttonConfig:b,mapColorScheme:S});return()=>{e()}}},[P,S,f])},buttons:[v({id:"drawDone",label:"Done",variant:"primary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode},enableWhen:e=>{var{pluginState:t}=e;return!!t.tempFeature}},f),v({id:"drawCancel",label:"Cancel",variant:"tertiary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode}},f)],api:{newPolygon:(e,t)=>{var{mapState:r,pluginState:o,mapProvider:a,services:n}=e,{dispatch:i}=o,{sketchViewModel:l,sketchLayer:p}=a,{eventBus:c}=n;l.layer=p;var u=l.on("create",e=>{if("complete"===e.state){e.graphic.attributes={id:t},requestAnimationFrame(()=>{l.update(e.graphic,{tool:"reshape",toggleToolOnClick:!1})});var r=y(e.graphic);c.emit("draw:created",r),i({type:"SET_FEATURE",payload:{tempFeature:r}}),u.remove()}});l.polygonSymbol=s(r.mapStyle.mapColorScheme),l.create("polygon"),i({type:"SET_MODE",payload:"new-polygon"})},editFeature:(e,t)=>{var{pluginState:r,mapProvider:o}=e,{dispatch:a}=r,{sketchViewModel:n,sketchLayer:i}=o,l=i.graphics.items.find(e=>e.attributes.id===t),p=l.geometry.extent,c=[p.xmin,p.ymin,p.xmax,p.ymax];o.fitToBounds(c),n.layer=i,n.update(l,{tool:"reshape",toggleToolOnClick:!1,enableRotation:!1,enableScaling:!1}),a({type:"SET_FEATURE",payload:{feature:y(l)}}),a({type:"SET_MODE",payload:"edit-feature"})},addFeature:(e,t)=>{var{pluginState:r,mapState:o,mapProvider:a,services:n}=e,{dispatch:i}=r,{mapStyle:l}=o,{sketchViewModel:p,sketchLayer:c,emptySketchLayer:u}=a,{eventBus:s}=n,y=d(t.id,t.geometry.coordinates,l.mapColorScheme);c.add(y),p.layer=u,i({type:"SET_FEATURE",payload:{feature:t}}),s.emit("draw:add",t)},deleteFeature:(e,t)=>{var{pluginState:r,mapProvider:o,services:a}=e,{dispatch:n}=r,{sketchViewModel:i,sketchLayer:l,emptySketchLayer:p}=o,{eventBus:c}=a,u=l.graphics.items.find(e=>e.attributes.id===t);i.cancel(),l.remove(u),i.layer=p,n({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),c.emit("draw:delete",{featureId:t}),n({type:"SET_MODE",payload:null})}}};export{h as manifest};
@@ -2,14 +2,14 @@ import { useEffect } from 'react'
2
2
  import { createSketchViewModel } from './sketchViewModel.js'
3
3
  import { attachEvents } from './events.js'
4
4
 
5
- export const DrawInit = ({
6
- appState,
7
- mapState,
8
- pluginConfig,
9
- pluginState,
10
- services,
11
- mapProvider,
12
- buttonConfig
5
+ export const DrawInit = ({
6
+ appState,
7
+ mapState,
8
+ pluginConfig,
9
+ pluginState,
10
+ services,
11
+ mapProvider,
12
+ buttonConfig
13
13
  }) => {
14
14
  const { events, eventBus } = services
15
15
  const { mapColorScheme } = mapState.mapStyle || {}
@@ -22,9 +22,9 @@ export const DrawInit = ({
22
22
  // Initialize sketch components once
23
23
  useEffect(() => {
24
24
  if (!isActive || mapProvider.sketchViewModel) {
25
- return
26
- }
27
-
25
+ return
26
+ }
27
+
28
28
  const { sketchViewModel, sketchLayer, emptySketchLayer } = createSketchViewModel({
29
29
  pluginState,
30
30
  mapProvider,
@@ -33,21 +33,21 @@ export const DrawInit = ({
33
33
 
34
34
  mapProvider.sketchViewModel = sketchViewModel
35
35
  mapProvider.sketchLayer = sketchLayer
36
- mapProvider.emptySketchLayer = emptySketchLayer
36
+ mapProvider.emptySketchLayer = emptySketchLayer
37
37
  eventBus.emit('draw:ready')
38
38
 
39
39
  return () => {
40
40
  mapProvider.sketchViewModel = null
41
41
  mapProvider.sketchLayer = null
42
- mapProvider.emptySketchLayer = null
42
+ mapProvider.emptySketchLayer = null
43
43
  }
44
44
  }, [mapState.isMapReady, appState.mode])
45
45
 
46
46
  // Attach/detach events
47
47
  useEffect(() => {
48
48
  if (!isActive || !mapProvider.sketchViewModel) {
49
- return
50
- }
49
+ return
50
+ }
51
51
 
52
52
  const cleanup = attachEvents({
53
53
  pluginState,
@@ -62,4 +62,4 @@ export const DrawInit = ({
62
62
  cleanup()
63
63
  }
64
64
  }, [isActive, mapColorScheme, pluginState])
65
- }
65
+ }
@@ -7,7 +7,7 @@ export const addFeature = ({ pluginState, mapState, mapProvider, services }, fea
7
7
  const { eventBus } = services
8
8
 
9
9
  const graphic = createGraphic(feature.id, feature.geometry.coordinates, mapStyle.mapColorScheme)
10
-
10
+
11
11
  // Add the graphic to the layer
12
12
  sketchLayer.add(graphic)
13
13
 
@@ -15,7 +15,7 @@ export const addFeature = ({ pluginState, mapState, mapProvider, services }, fea
15
15
  sketchViewModel.layer = emptySketchLayer
16
16
 
17
17
  // Store initial feature in plugin state
18
- dispatch({ type: 'SET_FEATURE', payload: { feature }})
18
+ dispatch({ type: 'SET_FEATURE', payload: { feature } })
19
19
 
20
20
  eventBus.emit('draw:add', feature)
21
- }
21
+ }
@@ -10,13 +10,13 @@ export const deleteFeature = ({ pluginState, mapProvider, services }, featureId)
10
10
  sketchViewModel.cancel()
11
11
  sketchLayer.remove(graphic)
12
12
  sketchViewModel.layer = emptySketchLayer
13
-
13
+
14
14
  // Reset state
15
- dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
15
+ dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null } })
16
16
 
17
17
  // Emit event
18
18
  eventBus.emit('draw:delete', { featureId })
19
19
 
20
20
  // Clear mode
21
21
  dispatch({ type: 'SET_MODE', payload: null })
22
- }
22
+ }
@@ -18,12 +18,12 @@ export const editFeature = ({ pluginState, mapProvider }, featureId) => {
18
18
  tool: 'reshape',
19
19
  toggleToolOnClick: false,
20
20
  enableRotation: false,
21
- enableScaling: false
21
+ enableScaling: false
22
22
  })
23
23
 
24
24
  // Set original feature
25
25
  const feature = graphicToGeoJSON(graphic)
26
- dispatch({ type: 'SET_FEATURE', payload: { feature }})
26
+ dispatch({ type: 'SET_FEATURE', payload: { feature } })
27
27
 
28
28
  dispatch({ type: 'SET_MODE', payload: 'edit-feature' })
29
- }
29
+ }
@@ -12,7 +12,7 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
12
12
  const handleCreateComplete = sketchViewModel.on('create', (e) => {
13
13
  if (e.state === 'complete') {
14
14
  e.graphic.attributes = { id: featureId }
15
-
15
+
16
16
  // Fix: to address calling some sketchViewModel methods syncronously
17
17
  requestAnimationFrame(() => {
18
18
  sketchViewModel.update(e.graphic, {
@@ -24,7 +24,7 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
24
24
  // Store temp feature in state and emit create
25
25
  const tempFeature = graphicToGeoJSON(e.graphic)
26
26
  eventBus.emit('draw:created', tempFeature)
27
- dispatch({ type: 'SET_FEATURE', payload: { tempFeature }})
27
+ dispatch({ type: 'SET_FEATURE', payload: { tempFeature } })
28
28
 
29
29
  handleCreateComplete.remove()
30
30
  }
@@ -34,4 +34,4 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
34
34
  sketchViewModel.create('polygon')
35
35
 
36
36
  dispatch({ type: 'SET_MODE', payload: 'new-polygon' })
37
- }
37
+ }
@@ -1,9 +1,9 @@
1
- import * as simplifyOperator from "@arcgis/core/geometry/operators/simplifyOperator.js"
1
+ import * as simplifyOperator from '@arcgis/core/geometry/operators/simplifyOperator.js'
2
2
  import { createGraphic, createSymbol, graphicToGeoJSON } from './graphic.js'
3
3
 
4
4
  const MODE_CHANGE_DELAY = 50
5
5
 
6
- export function attachEvents({ pluginState, mapProvider, events, eventBus, buttonConfig, mapColorScheme }) {
6
+ export function attachEvents ({ pluginState, mapProvider, events, eventBus, buttonConfig, mapColorScheme }) {
7
7
  const { view, sketchViewModel, sketchLayer, emptySketchLayer } = mapProvider
8
8
 
9
9
  if (!sketchViewModel) {
@@ -12,36 +12,36 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
12
12
 
13
13
  const { drawDone, drawCancel } = buttonConfig
14
14
  const { dispatch, mode, feature } = pluginState
15
-
15
+
16
16
  // Re-colour graphics when map style changes
17
17
  const reColour = async () => {
18
18
  const activeGraphicId = pluginState.feature?.properties?.id
19
19
  let activeGraphic = null
20
20
  const isCreating = sketchViewModel.state === 'active' && !activeGraphicId
21
-
21
+
22
22
  // Cancel and wait, but only if we're in update mode (not create mode)
23
23
  if (sketchViewModel.state === 'active' && activeGraphicId) {
24
24
  sketchViewModel.cancel()
25
25
  await new Promise(resolve => setTimeout(resolve, MODE_CHANGE_DELAY))
26
26
  }
27
-
27
+
28
28
  // Update the default symbol for new polygons
29
29
  sketchViewModel.polygonSymbol = createSymbol(mapColorScheme)
30
-
30
+
31
31
  // Update existing graphics
32
32
  sketchLayer?.graphics.items.forEach(graphic => {
33
33
  const newGraphic = createGraphic(
34
- graphic.attributes.id,
35
- graphic.geometry.rings,
34
+ graphic.attributes.id,
35
+ graphic.geometry.rings,
36
36
  mapColorScheme
37
37
  )
38
38
  graphic.symbol = newGraphic.symbol
39
-
39
+
40
40
  if (activeGraphicId === graphic.attributes.id) {
41
41
  activeGraphic = graphic
42
42
  }
43
43
  })
44
-
44
+
45
45
  // Re-enter update mode only if we were editing (not creating)
46
46
  if (activeGraphic && !isCreating && sketchViewModel.layer === sketchLayer) {
47
47
  try {
@@ -68,10 +68,19 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
68
68
  // Event handlers
69
69
  const handleMapStyleChange = () => reColour()
70
70
 
71
+ const onGraphicChanged = (graphic) => {
72
+ if (!graphic) {
73
+ return
74
+ }
75
+ const tempFeature = graphicToGeoJSON(graphic)
76
+ eventBus.emit('draw:updated', tempFeature)
77
+ dispatch({ type: 'SET_FEATURE', payload: { tempFeature } })
78
+ }
79
+
71
80
  const handleSketchUpdate = (e) => {
72
81
  const toolInfoType = e.toolEventInfo?.type
73
82
  const graphic = e.graphics[0]
74
-
83
+
75
84
  // Prevent polygon move
76
85
  if (toolInfoType === 'move-start') {
77
86
  sketchViewModel.cancel()
@@ -87,10 +96,8 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
87
96
  }
88
97
 
89
98
  // Emit event on update
90
- if (toolInfoType === 'reshape-stop') {
91
- const tempFeature = graphicToGeoJSON(graphic)
92
- eventBus.emit('draw:updated', tempFeature)
93
- dispatch({ type: 'SET_FEATURE', payload: { tempFeature }})
99
+ if (toolInfoType === 'reshape-stop' || toolInfoType === 'vertex-remove') {
100
+ onGraphicChanged(graphic)
94
101
  }
95
102
  }
96
103
 
@@ -108,17 +115,38 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
108
115
  updateGraphic()
109
116
  }
110
117
 
118
+ const handleCreate = async (event) => {
119
+ const { toolEventInfo } = event
120
+ const graphic = event?.graphic
121
+ if (graphic && toolEventInfo?.type === 'vertex-add') {
122
+ const rings = graphic.geometry?.rings
123
+ // rings.length is > 1 occurs when the shape becomes complex (ie self intersects)
124
+ // setTimeout is required to cause the undo to be called after handleCreate completes
125
+ // otherwise the previous change, rather than this one, is undone
126
+ if (rings?.length > 1) {
127
+ setTimeout(() => sketchViewModel.undo(), 0)
128
+ } else if (rings?.[0]?.length > 3) {
129
+ onGraphicChanged(graphic) // emit a graphic update on draw, once the graphic is 2D
130
+ }
131
+ }
132
+ }
133
+
134
+ const handleUndo = async (event) => {
135
+ const graphic = event?.graphics?.[0]
136
+ onGraphicChanged(graphic)
137
+ }
138
+
111
139
  const handleDone = () => {
112
140
  sketchViewModel.cancel()
113
141
  sketchViewModel.layer = emptySketchLayer
114
142
  dispatch({ type: 'SET_MODE', payload: null })
115
- dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
143
+ dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null } })
116
144
  eventBus.emit('draw:done', { newFeature: pluginState.tempFeature })
117
145
  }
118
146
 
119
147
  const handleCancel = () => {
120
148
  sketchViewModel.cancel()
121
-
149
+
122
150
  // Clear all graphics
123
151
  sketchLayer.removeAll()
124
152
 
@@ -143,10 +171,12 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
143
171
  eventBus.on(events.MAP_STYLE_CHANGE, handleMapStyleChange)
144
172
  const sketchUpdateHandler = sketchViewModel.on('update', handleSketchUpdate)
145
173
  const viewClickHandler = view.on('click', handleViewClick)
146
-
174
+ const createHandler = sketchViewModel.on('create', handleCreate)
175
+ const undoHandler = sketchViewModel.on('undo', handleUndo)
176
+
147
177
  const prevDoneClick = drawDone.onClick
148
178
  const prevCancelClick = drawCancel.onClick
149
-
179
+
150
180
  drawDone.onClick = handleDone
151
181
  drawCancel.onClick = handleCancel
152
182
 
@@ -155,7 +185,9 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
155
185
  eventBus.off(events.MAP_STYLE_CHANGE, handleMapStyleChange)
156
186
  sketchUpdateHandler.remove()
157
187
  viewClickHandler.remove()
188
+ createHandler.remove()
189
+ undoHandler.remove()
158
190
  drawDone.onClick = prevDoneClick
159
191
  drawCancel.onClick = prevCancelClick
160
192
  }
161
- }
193
+ }