@defra/interactive-map 0.0.4-alpha → 0.0.6-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 (203) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/index.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api.md +8 -0
  7. package/package.json +3 -2
  8. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -1
  9. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt +1 -0
  10. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -1
  11. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt +1 -0
  12. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  13. package/plugins/beta/datasets/src/datasets.js +55 -2
  14. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
  15. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
  16. package/plugins/beta/datasets/src/handleSetMapStyle.js +8 -1
  17. package/plugins/beta/datasets/src/mapLayers.js +41 -3
  18. package/plugins/beta/datasets/src/utils/bbox.js +113 -2
  19. package/plugins/beta/draw-ml/dist/css/index.css +1 -1
  20. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  21. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  22. package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
  23. package/plugins/beta/draw-ml/src/api/addFeature.js +15 -3
  24. package/plugins/beta/draw-ml/src/api/editFeature.js +16 -5
  25. package/plugins/beta/draw-ml/src/api/newLine.js +32 -7
  26. package/plugins/beta/draw-ml/src/api/newPolygon.js +33 -8
  27. package/plugins/beta/draw-ml/src/defaults.js +1 -0
  28. package/plugins/beta/draw-ml/src/draw.scss +23 -0
  29. package/plugins/beta/draw-ml/src/events.js +55 -113
  30. package/plugins/beta/draw-ml/src/manifest.js +64 -38
  31. package/plugins/beta/draw-ml/src/mapboxSnap.js +16 -12
  32. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +4 -3
  33. package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
  34. package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
  35. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
  36. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
  37. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
  38. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
  39. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +51 -406
  40. package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
  41. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  42. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  43. package/plugins/beta/frame/src/Frame.jsx +1 -1
  44. package/plugins/beta/map-styles/dist/css/index.css +1 -1
  45. package/plugins/beta/map-styles/dist/esm/index.js +1 -1
  46. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  47. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  48. package/plugins/beta/map-styles/src/mapStyles.scss +4 -1
  49. package/plugins/interact/dist/css/index.css +1 -1
  50. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  51. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  52. package/plugins/interact/dist/umd/index.js +1 -1
  53. package/plugins/interact/src/InteractInit.jsx +1 -1
  54. package/plugins/interact/src/defaults.js +1 -0
  55. package/plugins/interact/src/events.js +37 -52
  56. package/plugins/interact/src/events.test.js +35 -0
  57. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  58. package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -48
  59. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +64 -8
  60. package/plugins/interact/src/interact.scss +6 -0
  61. package/plugins/interact/src/reducer.js +25 -27
  62. package/plugins/interact/src/reducer.test.js +19 -6
  63. package/plugins/interact/src/utils/featureQueries.js +2 -2
  64. package/plugins/interact/src/utils/spatial.js +99 -0
  65. package/plugins/interact/src/utils/spatial.test.js +100 -0
  66. package/plugins/search/dist/css/index.css +1 -1
  67. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  68. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  69. package/plugins/search/dist/umd/index.js +1 -1
  70. package/plugins/search/src/Search.jsx +2 -2
  71. package/plugins/search/src/components/Form/Form.jsx +1 -1
  72. package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
  73. package/plugins/search/src/datasets.js +1 -1
  74. package/plugins/search/src/defaults.js +3 -0
  75. package/plugins/search/src/events/fetchSuggestions.js +48 -53
  76. package/plugins/search/src/events/formHandlers.js +3 -2
  77. package/plugins/search/src/events/index.js +1 -1
  78. package/plugins/search/src/events/inputHandlers.js +3 -1
  79. package/plugins/search/src/events/suggestionHandlers.js +12 -3
  80. package/plugins/search/src/search.scss +4 -1
  81. package/plugins/search/src/utils/parseOsNamesResults.js +12 -12
  82. package/providers/beta/esri/src/esriProvider.js +1 -1
  83. package/providers/beta/esri/src/index.js +1 -1
  84. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  85. package/providers/maplibre/dist/esm/index.js +1 -1
  86. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  87. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  88. package/providers/maplibre/dist/umd/index.js +1 -1
  89. package/providers/maplibre/src/defaults.js +3 -2
  90. package/providers/maplibre/src/index.js +22 -19
  91. package/providers/maplibre/src/maplibreProvider.js +13 -11
  92. package/providers/maplibre/src/utils/detectWebgl.js +1 -2
  93. package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
  94. package/providers/maplibre/src/utils/labels.js +174 -120
  95. package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
  96. package/providers/maplibre/src/utils/queryFeatures.js +146 -0
  97. package/providers/maplibre/src/utils/spatial.js +14 -17
  98. package/sonar-project.properties +39 -2
  99. package/src/App/components/Actions/Actions.jsx +2 -2
  100. package/src/App/components/Actions/Actions.module.scss +2 -2
  101. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
  102. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
  103. package/src/App/components/Logo/Logo.jsx +1 -1
  104. package/src/App/components/MapButton/MapButton.jsx +217 -41
  105. package/src/App/components/MapButton/MapButton.module.scss +32 -21
  106. package/src/App/components/MapButton/MapButton.test.jsx +128 -84
  107. package/src/App/components/Markers/Markers.jsx +1 -1
  108. package/src/App/components/Markers/Markers.module.scss +0 -5
  109. package/src/App/components/Panel/Panel.jsx +75 -45
  110. package/src/App/components/Panel/Panel.module.scss +18 -24
  111. package/src/App/components/Panel/Panel.test.jsx +18 -0
  112. package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
  113. package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
  114. package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
  115. package/src/App/components/Tooltip/Tooltip.jsx +4 -2
  116. package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
  117. package/src/App/components/Viewport/MapController.jsx +1 -1
  118. package/src/App/components/Viewport/MapStatus.jsx +1 -1
  119. package/src/App/components/Viewport/Viewport.jsx +2 -2
  120. package/src/App/components/Viewport/Viewport.module.scss +2 -2
  121. package/src/App/controls/keyboardActions.js +3 -2
  122. package/src/App/controls/keyboardShortcuts.js +4 -2
  123. package/src/App/hooks/useButtonStateEvaluator.js +58 -45
  124. package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
  125. package/src/App/hooks/useCrossHairAPI.js +49 -47
  126. package/src/App/hooks/useFocusVisible.js +2 -2
  127. package/src/App/hooks/useInterfaceAPI.js +16 -4
  128. package/src/App/hooks/useKeyboardHint.js +1 -1
  129. package/src/App/hooks/useKeyboardShortcuts.js +4 -2
  130. package/src/App/hooks/useMapAnnouncements.js +34 -32
  131. package/src/App/hooks/useMapEvents.js +12 -1
  132. package/src/App/hooks/useMapProviderOverrides.js +3 -3
  133. package/src/App/hooks/useMapStateSync.js +7 -1
  134. package/src/App/hooks/useMapURLSync.js +6 -1
  135. package/src/App/hooks/useMarkersAPI.js +88 -50
  136. package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
  137. package/src/App/hooks/useModalPanelBehaviour.js +46 -40
  138. package/src/App/hooks/useResizeObserver.js +2 -2
  139. package/src/App/initialiseApp.js +45 -38
  140. package/src/App/layout/Layout.jsx +6 -4
  141. package/src/App/layout/layout.module.scss +13 -17
  142. package/src/App/registry/keyboardShortcutRegistry.js +12 -2
  143. package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
  144. package/src/App/registry/mergeManifests.js +1 -1
  145. package/src/App/registry/panelRegistry.js +1 -1
  146. package/src/App/registry/panelRegistry.test.js +30 -1
  147. package/src/App/registry/pluginRegistry.js +14 -51
  148. package/src/App/registry/pluginRegistry.test.js +54 -6
  149. package/src/App/renderer/HtmlElementHost.jsx +186 -0
  150. package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
  151. package/src/App/renderer/mapButtons.js +11 -8
  152. package/src/App/renderer/mapButtons.test.js +6 -4
  153. package/src/App/renderer/mapControls.js +6 -0
  154. package/src/App/renderer/mapControls.test.js +10 -2
  155. package/src/App/renderer/mapPanels.js +39 -26
  156. package/src/App/renderer/mapPanels.test.js +29 -0
  157. package/src/App/renderer/pluginWrapper.test.js +28 -0
  158. package/src/App/renderer/slotHelpers.js +60 -0
  159. package/src/App/renderer/slotHelpers.test.js +82 -0
  160. package/src/App/store/AppProvider.jsx +21 -18
  161. package/src/App/store/AppProvider.test.jsx +40 -0
  162. package/src/App/store/PluginProvider.jsx +7 -5
  163. package/src/App/store/appActionsMap.js +28 -1
  164. package/src/App/store/appActionsMap.test.js +26 -0
  165. package/src/App/store/appReducer.js +1 -0
  166. package/src/App/store/mapActionsMap.js +12 -24
  167. package/src/App/store/mapReducer.js +1 -1
  168. package/src/InteractiveMap/InteractiveMap.js +79 -6
  169. package/src/InteractiveMap/InteractiveMap.test.js +164 -2
  170. package/src/InteractiveMap/behaviourController.js +12 -2
  171. package/src/InteractiveMap/behaviourController.test.js +82 -1
  172. package/src/InteractiveMap/deviceChecker.js +1 -1
  173. package/src/InteractiveMap/deviceChecker.test.js +2 -2
  174. package/src/InteractiveMap/historyManager.js +41 -5
  175. package/src/InteractiveMap/historyManager.test.js +37 -6
  176. package/src/InteractiveMap/polyfills.js +39 -0
  177. package/src/InteractiveMap/polyfills.test.js +127 -0
  178. package/src/config/appConfig.js +5 -5
  179. package/src/config/appConfig.test.js +107 -42
  180. package/src/config/defaults.js +1 -0
  181. package/src/index.umd.js +1 -1
  182. package/src/scss/main.scss +1 -0
  183. package/src/scss/settings/_colors.scss +1 -1
  184. package/src/scss/settings/_dimensions.scss +4 -0
  185. package/src/services/eventBus.test.js +24 -1
  186. package/src/test-utils.js +1 -0
  187. package/src/types.js +5 -0
  188. package/src/utils/detectBreakpoint.js +78 -77
  189. package/src/utils/detectBreakpoint.test.js +50 -4
  190. package/src/utils/detectInterfaceType.js +6 -5
  191. package/src/utils/getIsFullscreen.js +3 -1
  192. package/src/utils/getSafeZoneInset.js +23 -7
  193. package/src/utils/getSafeZoneInset.test.js +18 -0
  194. package/src/utils/mapStateSync.js +3 -3
  195. package/src/utils/queryString.js +1 -1
  196. package/src/utils/stringToKebab.js +1 -1
  197. package/src/utils/toggleInertElements.js +37 -12
  198. package/webpack.dev.mjs +3 -4
  199. package/plugins/interact/src/utils/turfHelpers.js +0 -30
  200. package/plugins/interact/src/utils/turfHelpers.test.js +0 -30
  201. package/plugins/search/src/utils/fetchDataset.js +0 -23
  202. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
  203. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see index.js.LICENSE.txt */
2
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("defra",[],t):"object"==typeof exports?exports.defra=t():(e.defra=e.defra||{},e.defra.drawMLPlugin=t())}(this,()=>(()=>{"use strict";var e,t,r={738(e){e.exports=preactCompat}},n={};function o(e){var t=n[e];if(void 0!==t)return t.exports;var i=n[e]={exports:{}};return r[e].call(i.exports,i,i.exports,o),i.exports}o.m=r,o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce((t,r)=>(o.f[r](e,t),t),[])),o.u=e=>"im-draw-ml-plugin.js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e={},t="defra.drawMLPlugin:",o.l=(r,n,i,a)=>{if(e[r])e[r].push(n);else{var u,c;if(void 0!==i)for(var f=document.getElementsByTagName("script"),p=0;p<f.length;p++){var l=f[p];if(l.getAttribute("src")==r||l.getAttribute("data-webpack")==t+i){u=l;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",o.nc&&u.setAttribute("nonce",o.nc),u.setAttribute("data-webpack",t+i),u.src=r),e[r]=[n];var s=(t,n)=>{u.onerror=u.onload=null,clearTimeout(d);var o=e[r];if(delete e[r],u.parentNode&&u.parentNode.removeChild(u),o&&o.forEach(e=>e(n)),t)return t(n)},d=setTimeout(s.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=s.bind(null,u.onerror),u.onload=s.bind(null,u.onload),c&&document.head.appendChild(u)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;o.g.importScripts&&(e=o.g.location+"");var t=o.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var n=r.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=r[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o.p=e})(),(()=>{var e={57:0};o.f.j=(t,r)=>{var n=o.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else{var i=new Promise((r,o)=>n=e[t]=[r,o]);r.push(n[2]=i);var a=o.p+o.u(t),u=new Error;o.l(a,r=>{if(o.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var i=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;u.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",u.name="ChunkLoadError",u.type=i,u.request=a,n[1](u)}},"chunk-"+t,t)}};var t=(t,r)=>{var n,i,[a,u,c]=r,f=0;if(a.some(t=>0!==e[t])){for(n in u)o.o(u,n)&&(o.m[n]=u[n]);c&&c(o)}for(t&&t(r);f<a.length;f++)i=a[f],o.o(e,i)&&e[i]&&e[i][0](),e[i]=0},r=this.webpackChunkdefra_DefraMap=this.webpackChunkdefra_DefraMap||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})();var i={};function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}function u(){var e,t,r="function"==typeof Symbol?Symbol:{},n=r.iterator||"@@iterator",o=r.toStringTag||"@@toStringTag";function i(r,n,o,i){var u=n&&n.prototype instanceof f?n:f,p=Object.create(u.prototype);return c(p,"_invoke",function(r,n,o){var i,u,c,f=0,p=o||[],l=!1,s={p:0,n:0,v:e,a:d,f:d.bind(e,4),d:function(t,r){return i=t,u=0,c=e,s.n=r,a}};function d(r,n){for(u=r,c=n,t=0;!l&&f&&!o&&t<p.length;t++){var o,i=p[t],d=s.p,b=i[2];r>3?(o=b===n)&&(c=i[(u=i[4])?5:(u=3,3)],i[4]=i[5]=e):i[0]<=d&&((o=r<2&&d<i[1])?(u=0,s.v=n,s.n=i[1]):d<b&&(o=r<3||i[0]>n||n>b)&&(i[4]=r,i[5]=n,s.n=b,u=0))}if(o||r>1)return a;throw l=!0,n}return function(o,p,b){if(f>1)throw TypeError("Generator is already running");for(l&&1===p&&d(p,b),u=p,c=b;(t=u<2?e:c)||!l;){i||(u?u<3?(u>1&&(s.n=-1),d(u,c)):s.n=c:s.v=c);try{if(f=2,i){if(u||(o="next"),t=i[o]){if(!(t=t.call(i,c)))throw TypeError("iterator result is not an object");if(!t.done)return t;c=t.value,u<2&&(u=0)}else 1===u&&(t=i.return)&&t.call(i),u<2&&(c=TypeError("The iterator does not provide a '"+o+"' method"),u=1);i=e}else if((t=(l=s.n<0)?c:r.call(n,s))!==a)break}catch(t){i=e,u=1,c=t}finally{f=1}}return{value:t,done:l}}}(r,o,i),!0),p}var a={};function f(){}function p(){}function l(){}t=Object.getPrototypeOf;var s=[][n]?t(t([][n]())):(c(t={},n,function(){return this}),t),d=l.prototype=f.prototype=Object.create(s);function b(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,l):(e.__proto__=l,c(e,o,"GeneratorFunction")),e.prototype=Object.create(d),e}return p.prototype=l,c(d,"constructor",l),c(l,"constructor",p),p.displayName="GeneratorFunction",c(l,o,"GeneratorFunction"),c(d),c(d,o,"Generator"),c(d,n,function(){return this}),c(d,"toString",function(){return"[object Generator]"}),(u=function(){return{w:i,m:b}})()}function c(e,t,r,n){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}c=function(e,t,r,n){function i(t,r){c(e,t,function(e){return this._invoke(t,r,e)})}t?o?o(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(i("next",0),i("throw",1),i("return",2))},c(e,t,r,n)}function f(e,t,r,n,o,i,a){try{var u=e[i](a),c=u.value}catch(e){return void r(e)}u.done?t(c):Promise.resolve(c).then(n,o)}function p(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function l(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?p(Object(r),!0).forEach(function(t){s(e,t,r[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):p(Object(r)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))})}return e}function s(e,t,r){return(t=function(e){var t=function(e){if("object"!=a(e)||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=a(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==a(t)?t:t+""}(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function d(){var e,t;return l(l({},arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),{},{id:"draw",load:(e=u().m(function e(){var t;return u().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,o.e(966).then(o.bind(o,300));case 1:return t=e.v.manifest,e.a(2,t)}},e)}),t=function(){var t=this,r=arguments;return new Promise(function(n,o){var i=e.apply(t,r);function a(e){f(i,n,o,a,u,"next",e)}function u(e){f(i,n,o,a,u,"throw",e)}a(void 0)})},function(){return t.apply(this,arguments)})})}return o.d(i,{default:()=>d}),i.default})());
2
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("defra",[],t):"object"==typeof exports?exports.defra=t():(e.defra=e.defra||{},e.defra.drawMLPlugin=t())}(this,()=>(()=>{"use strict";var e,t,r={738(e){e.exports=preactCompat}},n={};function o(e){var t=n[e];if(void 0!==t)return t.exports;var i=n[e]={exports:{}};return r[e].call(i.exports,i,i.exports,o),i.exports}o.m=r,o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce((t,r)=>(o.f[r](e,t),t),[])),o.u=e=>"im-draw-ml-plugin.js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e={},t="defra.drawMLPlugin:",o.l=(r,n,i,a)=>{if(e[r])e[r].push(n);else{var u,c;if(void 0!==i)for(var f=document.getElementsByTagName("script"),p=0;p<f.length;p++){var l=f[p];if(l.getAttribute("src")==r||l.getAttribute("data-webpack")==t+i){u=l;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",o.nc&&u.setAttribute("nonce",o.nc),u.setAttribute("data-webpack",t+i),u.src=r),e[r]=[n];var s=(t,n)=>{u.onerror=u.onload=null,clearTimeout(d);var o=e[r];if(delete e[r],u.parentNode&&u.parentNode.removeChild(u),o&&o.forEach(e=>e(n)),t)return t(n)},d=setTimeout(s.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=s.bind(null,u.onerror),u.onload=s.bind(null,u.onload),c&&document.head.appendChild(u)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;o.g.importScripts&&(e=o.g.location+"");var t=o.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var n=r.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=r[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o.p=e})(),(()=>{var e={57:0};o.f.j=(t,r)=>{var n=o.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else{var i=new Promise((r,o)=>n=e[t]=[r,o]);r.push(n[2]=i);var a=o.p+o.u(t),u=new Error;o.l(a,r=>{if(o.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var i=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;u.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",u.name="ChunkLoadError",u.type=i,u.request=a,n[1](u)}},"chunk-"+t,t)}};var t=(t,r)=>{var n,i,[a,u,c]=r,f=0;if(a.some(t=>0!==e[t])){for(n in u)o.o(u,n)&&(o.m[n]=u[n]);c&&c(o)}for(t&&t(r);f<a.length;f++)i=a[f],o.o(e,i)&&e[i]&&e[i][0](),e[i]=0},r=this.webpackChunkdefra_DefraMap=this.webpackChunkdefra_DefraMap||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})();var i={};function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}function u(){var e,t,r="function"==typeof Symbol?Symbol:{},n=r.iterator||"@@iterator",o=r.toStringTag||"@@toStringTag";function i(r,n,o,i){var u=n&&n.prototype instanceof f?n:f,p=Object.create(u.prototype);return c(p,"_invoke",function(r,n,o){var i,u,c,f=0,p=o||[],l=!1,s={p:0,n:0,v:e,a:d,f:d.bind(e,4),d:function(t,r){return i=t,u=0,c=e,s.n=r,a}};function d(r,n){for(u=r,c=n,t=0;!l&&f&&!o&&t<p.length;t++){var o,i=p[t],d=s.p,b=i[2];r>3?(o=b===n)&&(c=i[(u=i[4])?5:(u=3,3)],i[4]=i[5]=e):i[0]<=d&&((o=r<2&&d<i[1])?(u=0,s.v=n,s.n=i[1]):d<b&&(o=r<3||i[0]>n||n>b)&&(i[4]=r,i[5]=n,s.n=b,u=0))}if(o||r>1)return a;throw l=!0,n}return function(o,p,b){if(f>1)throw TypeError("Generator is already running");for(l&&1===p&&d(p,b),u=p,c=b;(t=u<2?e:c)||!l;){i||(u?u<3?(u>1&&(s.n=-1),d(u,c)):s.n=c:s.v=c);try{if(f=2,i){if(u||(o="next"),t=i[o]){if(!(t=t.call(i,c)))throw TypeError("iterator result is not an object");if(!t.done)return t;c=t.value,u<2&&(u=0)}else 1===u&&(t=i.return)&&t.call(i),u<2&&(c=TypeError("The iterator does not provide a '"+o+"' method"),u=1);i=e}else if((t=(l=s.n<0)?c:r.call(n,s))!==a)break}catch(t){i=e,u=1,c=t}finally{f=1}}return{value:t,done:l}}}(r,o,i),!0),p}var a={};function f(){}function p(){}function l(){}t=Object.getPrototypeOf;var s=[][n]?t(t([][n]())):(c(t={},n,function(){return this}),t),d=l.prototype=f.prototype=Object.create(s);function b(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,l):(e.__proto__=l,c(e,o,"GeneratorFunction")),e.prototype=Object.create(d),e}return p.prototype=l,c(d,"constructor",l),c(l,"constructor",p),p.displayName="GeneratorFunction",c(l,o,"GeneratorFunction"),c(d),c(d,o,"Generator"),c(d,n,function(){return this}),c(d,"toString",function(){return"[object Generator]"}),(u=function(){return{w:i,m:b}})()}function c(e,t,r,n){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}c=function(e,t,r,n){function i(t,r){c(e,t,function(e){return this._invoke(t,r,e)})}t?o?o(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(i("next",0),i("throw",1),i("return",2))},c(e,t,r,n)}function f(e,t,r,n,o,i,a){try{var u=e[i](a),c=u.value}catch(e){return void r(e)}u.done?t(c):Promise.resolve(c).then(n,o)}function p(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function l(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?p(Object(r),!0).forEach(function(t){s(e,t,r[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):p(Object(r)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))})}return e}function s(e,t,r){return(t=function(e){var t=function(e){if("object"!=a(e)||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=a(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==a(t)?t:t+""}(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function d(){var e,t;return l(l({},arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),{},{id:"draw",load:(e=u().m(function e(){var t;return u().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,o.e(966).then(o.bind(o,133));case 1:return t=e.v.manifest,e.a(2,t)}},e)}),t=function(){var t=this,r=arguments;return new Promise(function(n,o){var i=e.apply(t,r);function a(e){f(i,n,o,a,u,"next",e)}function u(e){f(i,n,o,a,u,"throw",e)}a(void 0)})},function(){return t.apply(this,arguments)})})}return o.d(i,{default:()=>d}),i.default})());
@@ -1,3 +1,5 @@
1
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
2
+
1
3
  export const addFeature = ({ mapProvider, services }, feature) => {
2
4
  const { draw } = mapProvider
3
5
  const { eventBus } = services
@@ -6,10 +8,20 @@ export const addFeature = ({ mapProvider, services }, feature) => {
6
8
  return
7
9
  }
8
10
 
11
+ // Extract style props from top level, flatten variants, merge with custom properties
12
+ const { stroke, fill, strokeWidth, properties, ...rest } = feature
13
+ const flatFeature = {
14
+ ...rest,
15
+ properties: {
16
+ ...properties,
17
+ ...flattenStyleProperties({ stroke, fill, strokeWidth })
18
+ }
19
+ }
20
+
9
21
  // --- Add feature to draw instance
10
- draw.add(feature, {
22
+ draw.add(flatFeature, {
11
23
  userProperties: true
12
24
  })
13
25
 
14
- eventBus.emit('draw:add', feature)
15
- }
26
+ eventBus.emit('draw:add', flatFeature)
27
+ }
@@ -6,7 +6,7 @@ import { getSnapInstance } from '../utils/snapHelpers.js'
6
6
  * @param {string} featureId - ID of the feature to edit
7
7
  * @param {object} options - Options including snapLayers
8
8
  */
9
- export const editFeature = ({ appState, appConfig, mapState, pluginState, mapProvider }, featureId, options = {}) => {
9
+ export const editFeature = ({ appState, appConfig, mapState, pluginConfig, pluginState, mapProvider }, featureId, options = {}) => {
10
10
  const { dispatch } = pluginState
11
11
  const { draw, map } = mapProvider
12
12
 
@@ -14,24 +14,35 @@ export const editFeature = ({ appState, appConfig, mapState, pluginState, mapPro
14
14
  return
15
15
  }
16
16
 
17
+ // Determin snapLayers from pluginConfig or runtime config
18
+ let snapLayers = null
19
+ if (options.snapLayers !== undefined) {
20
+ snapLayers = options.snapLayers
21
+ } else if (pluginConfig.snapLayers !== undefined) {
22
+ snapLayers = pluginConfig.snapLayers
23
+ } else {
24
+ snapLayers = null
25
+ }
26
+
17
27
  // Set per-call snap layers if provided
18
28
  const snap = getSnapInstance(map)
19
29
  if (snap?.setSnapLayers) {
20
- snap.setSnapLayers(options.snapLayers || null)
21
- } else if (options.snapLayers) {
30
+ snap.setSnapLayers(snapLayers)
31
+ } else if (snapLayers) {
22
32
  // Snap instance not ready yet - store for later
23
- map._pendingSnapLayers = options.snapLayers
33
+ map._pendingSnapLayers = snapLayers
24
34
  } else {
25
35
  // No action
26
36
  }
27
37
 
28
38
  // Update state so UI can react to snap layer availability
29
- dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: options.snapLayers?.length > 0 })
39
+ dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
30
40
 
31
41
  // Change mode to edit_vertex
32
42
  draw.changeMode('edit_vertex', {
33
43
  container: appState.layoutRefs.viewportRef.current,
34
44
  deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
45
+ undoButtonId: `${appConfig.id}-draw-undo`,
35
46
  isPanEnabled: appState.interfaceType !== 'keyboard',
36
47
  interfaceType: appState.interfaceType,
37
48
  scale: { small: 1, medium: 1.5, large: 2 }[mapState.mapSize],
@@ -1,32 +1,55 @@
1
1
  import { getSnapInstance } from '../utils/snapHelpers.js'
2
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
3
+ import { DEFAULTS } from '../defaults.js'
2
4
 
3
5
  /**
4
6
  * Programmatically create a new line
5
7
  * @param {object} context - plugin context
6
8
  * @param {string} featureId - ID for the new feature
7
- * @param {object} options - Options including snapLayers.
9
+ * @param {object} options - Options including snapLayers, stroke, fill, strokeWidth, properties.
8
10
  */
9
- export const newLine = ({ appState, appConfig, pluginState, mapProvider }, featureId, options = {}) => {
11
+ export const newLine = ({ appState, appConfig, pluginConfig, pluginState, mapProvider, services }, featureId, options = {}) => {
10
12
  const { dispatch } = pluginState
11
13
  const { draw, map } = mapProvider
14
+ const { eventBus } = services
12
15
 
13
16
  if (!draw) {
14
17
  return
15
18
  }
16
19
 
20
+ // Emit draw:start
21
+ eventBus.emit('draw:start', { mode: 'draw_line' })
22
+
23
+ // Determin snapLayers from pluginConfig or runtime config
24
+ let snapLayers = null
25
+ if (options.snapLayers !== undefined) {
26
+ snapLayers = options.snapLayers
27
+ } else if (pluginConfig.snapLayers !== undefined) {
28
+ snapLayers = pluginConfig.snapLayers
29
+ } else {
30
+ snapLayers = null
31
+ }
32
+
17
33
  // Set per-call snap layers if provided
18
34
  const snap = getSnapInstance(map)
19
35
  if (snap?.setSnapLayers) {
20
- snap.setSnapLayers(options.snapLayers || null)
21
- } else if (options.snapLayers) {
36
+ snap.setSnapLayers(snapLayers)
37
+ } else if (snapLayers) {
22
38
  // Snap instance not ready yet - store for later
23
- map._pendingSnapLayers = options.snapLayers
39
+ map._pendingSnapLayers = snapLayers
24
40
  } else {
25
41
  // No action
26
42
  }
27
43
 
28
44
  // Update state so UI can react to snap layer availability
29
- dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: options.snapLayers?.length > 0 })
45
+ dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
46
+
47
+ // Extract style props and flatten variants into properties
48
+ const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
49
+ const properties = {
50
+ ...customProperties,
51
+ ...flattenStyleProperties({ stroke, fill, strokeWidth })
52
+ }
30
53
 
31
54
  // Change mode to draw_line
32
55
  draw.changeMode('draw_line', {
@@ -35,7 +58,9 @@ export const newLine = ({ appState, appConfig, pluginState, mapProvider }, featu
35
58
  addVertexButtonId: `${appConfig.id}-draw-add-point`,
36
59
  interfaceType: appState.interfaceType,
37
60
  getSnapEnabled: () => mapProvider.snapEnabled === true,
38
- featureId
61
+ featureId,
62
+ ...modeOptions,
63
+ properties
39
64
  })
40
65
 
41
66
  // Set mode to draw_line
@@ -1,32 +1,55 @@
1
1
  import { getSnapInstance } from '../utils/snapHelpers.js'
2
+ import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
3
+ import { DEFAULTS } from '../defaults.js'
2
4
 
3
5
  /**
4
6
  * Programmatically create a new polygon
5
7
  * @param {object} context - plugin context
6
8
  * @param {string} featureId - ID for the new feature
7
- * @param {object} options - Options including snapLayers.
9
+ * @param {object} options - Options including snapLayers, stroke, fill, strokeWidth, properties.
8
10
  */
9
- export const newPolygon = ({ appState, appConfig, pluginState, mapProvider }, featureId, options = {}) => {
11
+ export const newPolygon = ({ appState, appConfig, pluginConfig, pluginState, mapProvider, services }, featureId, options = {}) => {
10
12
  const { dispatch } = pluginState
11
13
  const { draw, map } = mapProvider
14
+ const { eventBus } = services
12
15
 
13
16
  if (!draw) {
14
17
  return
15
18
  }
16
19
 
20
+ // Emit draw:start
21
+ eventBus.emit('draw:start', { mode: 'draw_polygon' })
22
+
23
+ // Determin snapLayers from pluginConfig or runtime config
24
+ let snapLayers = null
25
+ if (options.snapLayers !== undefined) {
26
+ snapLayers = options.snapLayers
27
+ } else if (pluginConfig.snapLayers !== undefined) {
28
+ snapLayers = pluginConfig.snapLayers
29
+ } else {
30
+ snapLayers = null
31
+ }
32
+
17
33
  // Set per-call snap layers if provided
18
34
  const snap = getSnapInstance(map)
19
35
  if (snap?.setSnapLayers) {
20
- snap.setSnapLayers(options.snapLayers || null)
21
- } else if (options.snapLayers) {
36
+ snap.setSnapLayers(snapLayers)
37
+ } else if (snapLayers) {
22
38
  // Snap instance not ready yet - store for later
23
- map._pendingSnapLayers = options.snapLayers
39
+ map._pendingSnapLayers = snapLayers
24
40
  } else {
25
41
  // No action
26
42
  }
27
43
 
28
44
  // Update state so UI can react to snap layer availability
29
- dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: options.snapLayers?.length > 0 })
45
+ dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
46
+
47
+ // Extract style props and flatten variants into properties
48
+ const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
49
+ const properties = {
50
+ ...customProperties,
51
+ ...flattenStyleProperties({ stroke, fill, strokeWidth })
52
+ }
30
53
 
31
54
  // Change mode to draw_polygon
32
55
  draw.changeMode('draw_polygon', {
@@ -35,9 +58,11 @@ export const newPolygon = ({ appState, appConfig, pluginState, mapProvider }, fe
35
58
  addVertexButtonId: `${appConfig.id}-draw-add-point`,
36
59
  interfaceType: appState.interfaceType,
37
60
  getSnapEnabled: () => mapProvider.snapEnabled === true,
38
- featureId
61
+ featureId,
62
+ ...modeOptions,
63
+ properties
39
64
  })
40
65
 
41
66
  // Set mode to draw_polygon
42
67
  dispatch({ type: 'SET_MODE', payload: 'draw_polygon' })
43
- }
68
+ }
@@ -7,6 +7,7 @@ export const DEFAULTS = {
7
7
  stroke: 'rgba(212,53,28,1)',
8
8
  strokeWidth: 2,
9
9
  fill: 'rgba(212,53,28,0.1)',
10
+ snapLayers: [],
10
11
  snapColors: {
11
12
  vertex: 'rgba(212,53,28,1)',
12
13
  midpoint: 'rgba(40,161,151,1)',
@@ -6,4 +6,27 @@
6
6
  path {
7
7
  fill: var(--map-overlay-halo-color);
8
8
  }
9
+ }
10
+
11
+ // Ensure action buttons take up same width
12
+ .im-c-actions {
13
+ .im-c-button-wrapper--draw-done,
14
+ .im-c-button-wrapper--draw-menu,
15
+ .im-c-button-wrapper--draw-cancel {
16
+ width: 33.33%;
17
+ }
18
+ }
19
+ .im-o-app--tablet .im-c-actions,
20
+ .im-o-app--desktop .im-c-actions {
21
+ .im-c-button-wrapper--draw-done,
22
+ .im-c-button-wrapper--draw-menu,
23
+ .im-c-button-wrapper--draw-cancel {
24
+ width: 100px;
25
+ }
26
+ }
27
+
28
+ // Position touch add point button
29
+ .im-c-button-wrapper--draw-add-point {
30
+ position: absolute;
31
+ bottom: calc(100% + var(--primary-gap));
9
32
  }
@@ -1,20 +1,11 @@
1
1
  import { clearSnapState, getSnapInstance } from './utils/snapHelpers.js'
2
2
 
3
- export function attachEvents ({ appState, appConfig, mapState, pluginState, mapProvider, buttonConfig, eventBus }) {
4
- const {
5
- drawDone,
6
- drawAddPoint,
7
- drawUndo,
8
- drawFinish,
9
- drawDeletePoint,
10
- drawSnap,
11
- drawCancel
12
- } = buttonConfig
13
-
3
+ export function attachEvents({ pluginState, mapProvider, buttonConfig, eventBus }) {
4
+ const { drawDone, drawAddPoint, drawUndo, drawDeletePoint, drawSnap, drawCancel } = buttonConfig
14
5
  const { map, draw } = mapProvider
15
6
  const { dispatch, feature, tempFeature } = pluginState
16
7
 
17
- // Helper to disable snap and hide indicators
8
+ // --- Helper to disable snap
18
9
  const disableSnap = () => {
19
10
  mapProvider.snapEnabled = false
20
11
  dispatch({ type: 'SET_SNAP', payload: false })
@@ -28,100 +19,72 @@ export function attachEvents ({ appState, appConfig, mapState, pluginState, mapP
28
19
  }
29
20
  }
30
21
 
31
- // --- Done
32
- const handleDone = () => {
33
- disableSnap()
22
+ // --- Helper to reset draw mode and feature (without touching snap)
23
+ const resetDrawModeAndFeature = () => {
34
24
  mapProvider.undoStack?.clear()
35
- draw.changeMode('disabled')
36
25
  dispatch({ type: 'SET_MODE', payload: null })
37
26
  dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
38
- eventBus.emit('draw:done', { newFeature: tempFeature })
39
27
  }
40
- drawDone.onClick = handleDone
41
28
 
42
- // --- Cancel
43
- const handleCancel = () => {
44
- if (tempFeature?.id) {
45
- draw.delete(tempFeature.id)
46
- }
29
+ // --- Button handlers
30
+ const handleDone = () => {
31
+ const mode = draw.getMode()
32
+ const features = draw.getAll().features
33
+ const feature = features?.[0]
47
34
 
48
- // Reinstate initial feature
49
- if (feature) {
50
- draw.add(feature)
51
- }
52
35
  disableSnap()
53
36
  mapProvider.undoStack?.clear()
54
- draw.changeMode('disabled')
55
37
 
56
- dispatch({ type: 'SET_MODE', payload: null })
57
- dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
58
- eventBus.emit('draw:cancel', { originalFeature: feature })
59
- }
60
- drawCancel.onClick = handleCancel
61
-
62
- // --- Finish shape (programmatically complete the drawing)
63
- const handleFinish = () => {
64
- const mode = draw.getMode()
65
- if (!['draw_polygon', 'draw_line'].includes(mode)) {
38
+ if (mode === 'edit_vertex') {
39
+ map.fire('draw.editfinish', { features: [feature] })
66
40
  return
67
41
  }
68
42
 
69
- const features = draw.getAll().features
70
- if (features.length === 0) {
43
+ if (!['draw_polygon', 'draw_line'].includes(mode) || features.length === 0) {
71
44
  return
72
45
  }
73
46
 
74
- const feature = features[0]
75
47
  const geom = feature.geometry
76
-
77
- // Remove the rubber band point (last coordinate that follows cursor)
78
48
  if (geom.type === 'Polygon') {
79
- // Polygon coords: [v1, v2, v3, ..., rubberBand, v1(closing)]
80
- // Remove rubber band (second to last) and keep the ring closed
81
49
  const ring = geom.coordinates[0]
82
50
  geom.coordinates[0] = [...ring.slice(0, -2), ring[0]]
83
51
  } else {
84
- // Line coords: [v1, v2, v3, ..., rubberBand]
85
- // Remove the rubber band (last point)
86
52
  geom.coordinates = geom.coordinates.slice(0, -1)
87
53
  }
88
54
 
89
- // Fire draw.create to trigger the standard completion flow
90
55
  map.fire('draw.create', { features: [feature] })
56
+ }
91
57
 
92
- // Change mode to trigger cleanup (this calls onStop on the draw mode)
93
- draw.changeMode('simple_select', { featureIds: [feature.id] })
58
+ const handleCancel = () => {
59
+ if (tempFeature?.id) {
60
+ draw.delete(tempFeature.id)
61
+ }
62
+ if (feature) {
63
+ draw.add(feature)
64
+ }
65
+ disableSnap()
66
+ draw.changeMode('disabled')
67
+ resetDrawModeAndFeature()
68
+ eventBus.emit('draw:cancel', { originalFeature: feature })
94
69
  }
95
- drawFinish.onClick = handleFinish
96
70
 
97
- // --- Undo last action (only for draw modes - edit_vertex handles its own undo)
98
71
  const handleUndo = () => {
99
- // edit_vertex mode handles its own undo via button click listener
100
72
  if (draw.getMode() === 'edit_vertex') {
101
73
  return
102
74
  }
103
-
104
75
  const undoStack = mapProvider.undoStack
105
- if (!undoStack || undoStack.length === 0) {
76
+ if (!undoStack?.length) {
106
77
  return
107
78
  }
108
-
109
79
  const operation = undoStack.pop()
110
-
111
- // Set flag to prevent button click from also adding a vertex
112
80
  map._undoInProgress = true
113
81
  setTimeout(() => { map._undoInProgress = false }, 100)
114
-
115
- // Fire event for the current mode to handle
116
82
  map.fire('draw.undo', { operation })
117
83
  }
118
- drawUndo.onClick = handleUndo
119
84
 
120
- // --- Snap toggle with throttle to prevent Safari slowdown
121
85
  let lastToggleTime = 0
122
86
  const THROTTLE_MS = 300
123
87
  const handleSnap = () => {
124
- // Strict throttle - ignore clicks within throttle window
125
88
  const now = Date.now()
126
89
  if (now - lastToggleTime < THROTTLE_MS) {
127
90
  return
@@ -137,90 +100,69 @@ export function attachEvents ({ appState, appConfig, mapState, pluginState, mapP
137
100
  snap.setSnapStatus(newSnapState)
138
101
  }
139
102
 
140
- // Clear all snap state when disabling - prevents accumulation
141
103
  if (!newSnapState && snap) {
142
104
  clearSnapState(snap)
143
- // Hide indicator immediately when disabling (single call, not in RAF)
144
105
  if (map.getLayer('snap-helper-circle')) {
145
106
  map.setLayoutProperty('snap-helper-circle', 'visibility', 'none')
146
107
  }
147
108
  }
148
- // When enabling, the indicator will show automatically via setMapData patch
149
109
  }
150
- drawSnap.onClick = handleSnap
151
110
 
152
- // --- Map style change
111
+ // --- Map style update
153
112
  const handleStyleData = () => {
154
113
  const layers = map.getStyle().layers || []
155
- if (layers.length === 0 || layers[layers.length - 1].source?.startsWith('mapbox-gl-draw')) {
114
+ if (!layers.length || layers[layers.length - 1].source?.startsWith('mapbox-gl-draw')) {
156
115
  return
157
116
  }
158
117
  layers.filter(l => l.source?.startsWith('mapbox-gl-draw')).forEach(l => map.moveLayer(l.id))
159
118
  }
160
- map.on('styledata', () => handleStyleData())
161
119
 
162
- // --- A new shape is created
163
- const onCreate = (e) => {
120
+ // --- Draw completion handlers (create / edit)
121
+ const handleDrawCompletion = (eventName) => (e) => {
164
122
  const newFeature = e.features[0]
165
- dispatch({ type: 'SET_FEATURE', payload: { tempFeature: newFeature }})
166
- eventBus.emit('draw:create', e)
167
-
168
- // Clear draw mode undo stack - editing starts fresh
169
- mapProvider.undoStack?.clear()
170
-
171
- // Switch straight to edit vertex mode
172
- dispatch({ type: 'SET_MODE', payload: 'edit_vertex'})
173
-
174
- setTimeout(() => {
175
- draw.changeMode('edit_vertex', {
176
- container: appState.layoutRefs.viewportRef.current,
177
- deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
178
- undoButtonId: `${appConfig.id}-draw-undo`,
179
- isPanEnabled: appState.interfaceType !== 'keyboard',
180
- interfaceType: appState.interfaceType,
181
- scale: { small: 1, medium: 1.5, large: 2 }[mapState.mapSize],
182
- featureId: newFeature.id,
183
- getSnapEnabled: () => mapProvider.snapEnabled === true
184
- })
185
- }, 0)
123
+ resetDrawModeAndFeature()
124
+ setTimeout(() => draw.changeMode('disabled'), 0)
125
+ eventBus.emit(eventName, { newFeature })
186
126
  }
187
- map.on('draw.create', onCreate)
188
127
 
189
- // --- A shape is updated
190
- const onUpdate = (e) => {
191
- eventBus.emit('draw:update', e)
192
- }
193
- map.on('draw.update', onUpdate)
128
+ const onCreate = handleDrawCompletion('draw:create')
129
+ const onEditFinish = handleDrawCompletion('draw:edit')
194
130
 
195
- // --- A vertex is selected
131
+ // --- Other map events
132
+ const onUpdate = (e) => eventBus.emit('draw:update', e)
196
133
  const onVertexSelection = (e) => {
197
134
  dispatch({ type: 'SET_SELECTED_VERTEX_INDEX', payload: e })
198
135
  eventBus.emit('draw:vertexselection', e)
199
136
  }
200
- map.on('draw.vertexselection', onVertexSelection)
201
-
202
- // --- Vertex count changes during drawing
203
137
  const onVertexChange = (e) => {
204
138
  dispatch({ type: 'SET_SELECTED_VERTEX_INDEX', payload: { index: -1, numVertecies: e.numVertecies } })
205
139
  }
206
- map.on('draw.vertexchange', onVertexChange)
207
-
208
- // --- Undo stack changes
209
140
  const onUndoChange = (e) => {
210
141
  dispatch({ type: 'SET_UNDO_STACK_LENGTH', payload: e.length })
211
142
  }
143
+
144
+ // --- Register events
145
+ drawDone.onClick = handleDone
146
+ drawCancel.onClick = handleCancel
147
+ drawUndo.onClick = handleUndo
148
+ drawSnap.onClick = handleSnap
149
+
150
+ map.on('styledata', handleStyleData)
151
+ map.on('draw.create', onCreate)
152
+ map.on('draw.editfinish', onEditFinish)
153
+ map.on('draw.update', onUpdate)
154
+ map.on('draw.vertexselection', onVertexSelection)
155
+ map.on('draw.vertexchange', onVertexChange)
212
156
  map.on('draw.undochange', onUndoChange)
213
157
 
158
+ // --- Cleanup
214
159
  return () => {
215
- drawDone.onClick = null
216
- drawAddPoint.onClick = null
217
- drawUndo.onClick = null
218
- drawFinish.onClick = null
219
- drawDeletePoint.onClick = null
220
- drawSnap.onClick = null
221
- drawCancel.onClick = null
160
+ [drawDone, drawAddPoint, drawUndo, drawDeletePoint, drawSnap, drawCancel].forEach(btn => { if (btn) {
161
+ btn.onClick = null
162
+ }})
222
163
  map.off('styledata', handleStyleData)
223
164
  map.off('draw.create', onCreate)
165
+ map.off('draw.editfinish', onEditFinish)
224
166
  map.off('draw.update', onUpdate)
225
167
  map.off('draw.vertexselection', onVertexSelection)
226
168
  map.off('draw.vertexchange', onVertexChange)