@defra/interactive-map 0.0.10-alpha → 0.0.12-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 (121) hide show
  1. package/README.md +1 -1
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/button-definition.md +21 -3
  8. package/docs/api/panel-definition.md +10 -12
  9. package/docs/api.md +80 -7
  10. package/docs/demo.mdx +70 -0
  11. package/docs/index.md +0 -4
  12. package/docs/plugins/plugin-context.md +3 -3
  13. package/docs/plugins/plugin-descriptor.md +37 -0
  14. package/docs/plugins/plugin-manifest.md +1 -1
  15. package/docusaurus.config.cjs +55 -25
  16. package/package.json +18 -9
  17. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  18. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  19. package/plugins/beta/datasets/src/manifest.js +3 -3
  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/src/events.js +4 -14
  23. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +1 -3
  24. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  25. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  26. package/plugins/beta/map-styles/src/manifest.js +3 -3
  27. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
  28. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
  29. package/plugins/beta/use-location/src/manifest.js +7 -7
  30. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  31. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  32. package/plugins/interact/src/InteractInit.jsx +28 -6
  33. package/plugins/interact/src/InteractInit.test.js +19 -5
  34. package/plugins/interact/src/events.js +17 -15
  35. package/plugins/interact/src/events.test.js +25 -16
  36. package/plugins/search/dist/css/index.css +1 -1
  37. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  38. package/plugins/search/dist/esm/index.js +1 -1
  39. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  40. package/plugins/search/dist/umd/index.js +1 -1
  41. package/plugins/search/src/Search.jsx +9 -3
  42. package/plugins/search/src/Search.test.jsx +26 -6
  43. package/plugins/search/src/components/Form/Form.jsx +35 -7
  44. package/plugins/search/src/components/Form/Form.module.scss +27 -0
  45. package/plugins/search/src/components/Form/Form.test.jsx +99 -2
  46. package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +28 -0
  47. package/plugins/search/src/components/SubmitButton/SubmitButton.module.scss +8 -0
  48. package/plugins/search/src/components/SubmitButton/SubmitButton.test.jsx +33 -0
  49. package/plugins/search/src/datasets.js +15 -11
  50. package/plugins/search/src/datasets.test.js +17 -2
  51. package/plugins/search/src/events/fetchSuggestions.js +1 -1
  52. package/plugins/search/src/index.js +1 -1
  53. package/plugins/search/src/index.test.js +4 -4
  54. package/plugins/search/src/reducer.js +9 -4
  55. package/plugins/search/src/reducer.test.js +12 -7
  56. package/plugins/search/src/search.scss +5 -1
  57. package/plugins/search/src/utils/parseOsNamesResults.js +18 -2
  58. package/plugins/search/src/utils/parseOsNamesResults.test.js +33 -15
  59. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  60. package/providers/beta/esri/src/appEvents.js +8 -2
  61. package/providers/beta/esri/src/esriProvider.js +25 -17
  62. package/providers/beta/esri/src/mapEvents.js +41 -4
  63. package/providers/beta/esri/src/utils/coords.js +34 -1
  64. package/providers/beta/esri/src/utils/coords.test.js +126 -0
  65. package/providers/beta/esri/src/utils/spatial.js +47 -1
  66. package/providers/beta/esri/src/utils/spatial.test.js +55 -0
  67. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  68. package/providers/maplibre/dist/esm/index.js +1 -1
  69. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  70. package/providers/maplibre/dist/umd/index.js +1 -1
  71. package/providers/maplibre/src/appEvents.js +10 -1
  72. package/providers/maplibre/src/appEvents.test.js +13 -4
  73. package/providers/maplibre/src/index.js +5 -13
  74. package/providers/maplibre/src/index.test.js +34 -15
  75. package/providers/maplibre/src/mapEvents.js +9 -1
  76. package/providers/maplibre/src/maplibreProvider.js +25 -15
  77. package/providers/maplibre/src/maplibreProvider.test.js +28 -2
  78. package/providers/maplibre/src/utils/spatial.js +51 -0
  79. package/providers/maplibre/src/utils/spatial.test.js +47 -0
  80. package/src/App/components/Actions/Actions.module.scss +5 -4
  81. package/src/App/components/MapButton/MapButton.jsx +4 -16
  82. package/src/App/components/MapButton/MapButton.module.scss +12 -12
  83. package/src/App/components/MapButton/MapButton.test.jsx +0 -9
  84. package/src/App/components/Panel/Panel.jsx +6 -6
  85. package/src/App/components/Panel/Panel.test.jsx +14 -15
  86. package/src/App/components/Viewport/MapController.jsx +6 -1
  87. package/src/App/hooks/useLayoutMeasurements.js +1 -1
  88. package/src/App/hooks/useLayoutMeasurements.test.js +1 -1
  89. package/src/App/hooks/useMapProviderOverrides.js +21 -1
  90. package/src/App/hooks/useMapProviderOverrides.test.js +51 -2
  91. package/src/App/hooks/useMarkersAPI.js +5 -3
  92. package/src/App/hooks/useModalPanelBehaviour.js +19 -2
  93. package/src/App/hooks/useModalPanelBehaviour.test.js +84 -60
  94. package/src/App/hooks/useVisibleGeometry.js +100 -0
  95. package/src/App/hooks/useVisibleGeometry.test.js +331 -0
  96. package/src/App/layout/Layout.jsx +5 -5
  97. package/src/App/layout/layout.module.scss +2 -4
  98. package/src/App/registry/panelRegistry.js +1 -10
  99. package/src/App/registry/panelRegistry.test.js +6 -11
  100. package/src/App/renderer/HtmlElementHost.jsx +12 -3
  101. package/src/App/renderer/HtmlElementHost.test.jsx +89 -0
  102. package/src/App/renderer/mapButtons.js +128 -28
  103. package/src/App/renderer/mapButtons.test.js +119 -19
  104. package/src/App/renderer/pluginWrapper.js +3 -2
  105. package/src/App/renderer/slots.js +1 -1
  106. package/src/App/store/AppProvider.jsx +1 -0
  107. package/src/App/store/MapProvider.jsx +18 -5
  108. package/src/App/store/MapProvider.test.jsx +56 -1
  109. package/src/App/store/appActionsMap.js +17 -9
  110. package/src/App/store/appActionsMap.test.js +33 -7
  111. package/src/App/store/appDispatchMiddleware.js +19 -0
  112. package/src/App/store/appDispatchMiddleware.test.js +56 -0
  113. package/src/App/store/mapActionsMap.js +4 -7
  114. package/src/InteractiveMap/InteractiveMap.js +18 -0
  115. package/src/InteractiveMap/InteractiveMap.test.js +12 -0
  116. package/src/config/appConfig.js +17 -15
  117. package/src/config/events.js +41 -4
  118. package/src/config/getInitialOpenPanels.js +2 -2
  119. package/src/config/getInitialOpenPanels.test.js +7 -7
  120. package/src/types.js +22 -11
  121. package/src/utils/getValueForStyle.js +1 -1
@@ -1 +1 @@
1
- import e from"@babel/runtime/helpers/objectWithoutProperties";import t from"@babel/runtime/helpers/asyncToGenerator";import i from"@arcgis/core/config.js";import n from"@arcgis/core/Map.js";import o from"@arcgis/core/views/MapView.js";import r from"@arcgis/core/layers/VectorTileLayer.js";import{when as a,once as s,watch as m}from"@arcgis/core/core/reactiveUtils.js";import u from"@arcgis/core/geometry/Extent.js";import l from"@arcgis/core/geometry/Point.js";var v={animationDuration:200},c=["showKeyboardHelp","selectControl","moveLarge","nudgeMap","zoomLarge","nudgeZoom"];var p=(e,t)=>{var i=null,n=function(){for(var n=arguments.length,o=new Array(n),r=0;r<n;r++)o[r]=arguments[r];clearTimeout(i),i=setTimeout(()=>{e(...o)},t)};return n.cancel=()=>{i&&(clearTimeout(i),i=null)},n};function h(e){var{mapProvider:t,view:i,baseTileLayer:n,events:o,eventBus:r,getZoom:u,getCenter:l,getBounds:v,getResolution:c}=e,h=!1,d=[],y=[],g=e=>{var t=(()=>{if(h||!i||i.destroyed||!i.extent)return null;var{maxZoom:e,minZoom:t}=i.constraints;return{center:l(),bounds:v(),resolution:c(),zoom:u(),isAtMaxZoom:i.zoom+.01>=e,isAtMinZoom:i.zoom-.01<=t}})();t&&r.emit(e,t)};a(()=>n.loaded&&i.resolution>0,()=>g(o.MAP_LOADED)),s(()=>i.ready).then(()=>{h||r.emit(o.MAP_READY,t.getMapAPI())}),s(()=>i.stationary).then(()=>g(o.MAP_FIRST_IDLE));var w=p(()=>g(o.MAP_MOVE_END),500);y.push(w),d.push(m(()=>[i.interacting,i.animation],e=>{var[t,i]=e;(t||i)&&r.emit(o.MAP_MOVE_START),t||i||w()}));var f,x,M,b=(f=()=>g(o.MAP_MOVE),x=10,M=0,function(){var e=Date.now();e-M>=x&&(M=e,f(...arguments))});y.push(b),d.push(m(()=>i.zoom,b)),d.push(m(()=>i.extent,()=>r.emit(o.MAP_RENDER),{initial:!1}));var E=p(()=>g(o.MAP_DATA_CHANGE),500);return y.push(E),d.push(m(()=>i.updating,e=>!e&&E())),d.push(i.on("click",e=>{var t=e.mapPoint,i={x:e.x,y:e.y};r.emit(o.MAP_CLICK,{point:i,coords:[t.x,t.y]})})),{remove(){h=!0,y.forEach(e=>e.cancel()),d.forEach(e=>e.remove())}}}var d=e=>{var t=1609.344,i=e/t;if(i<.5/t)return"".concat(Math.round(e),"m");if(i<10){var n=Number.parseFloat(i.toFixed(1)),o=1===n?"mile":"miles";return"".concat(n," ").concat(o)}var r=Math.round(i),a=1===r?"mile":"miles";return"".concat(r," ").concat(a)};var y={top:0,right:0,bottom:0,left:0};function g(){return(g=t(function*(e,t){if(!e||!t)return[];var i=e.map.layers.filter(e=>e instanceof r);return i.length?(yield e.hitTest(t,{include:i.toArray()})).results.map(e=>({layerId:e.layer.id,layerTitle:e.layer.title||e.layer.id,type:e.layer.type,geometry:e.graphic.geometry,symbol:e.graphic.symbol})):[]})).apply(this,arguments)}var w=e=>e?new u({xmin:e[0],ymin:e[1],xmax:e[2],ymax:e[3],spatialReference:{wkid:27700}}):void 0,f=e=>e?new l({x:e[0],y:e[1],spatialReference:{wkid:27700}}):void 0,x=["container","padding","mapStyle","maxExtent"];class M{constructor(e){var{mapProviderConfig:t={},events:i,eventBus:n}=e;this.events=i,this.eventBus=n,this.capabilities={supportedShortcuts:c,supportsMapSizes:!1},Object.assign(this,t),this.mapEventHandles=[],this.appEventHandles=[]}initMap(a){var s=this;return t(function*(){var{container:t,padding:m,mapStyle:u,maxExtent:l}=a;e(a,x);var{events:v,eventBus:c}=s;s.setupConfig&&(yield s.setupConfig(i));var p=new r({id:"baselayer",url:u.url,visible:!0}),d=new n({layers:[p]}),y=l?w(l):null,g=new o({spatialReference:27700,container:t,map:d,zoom:a.zoom,center:f(a.center),maxExtent:l,constraints:{snapToZoom:!1,minZoom:a.minZoom,maxZoom:a.maxZoom,maxScale:0,geometry:y,rotationEnabled:!1},ui:{components:[]},popupEnabled:!1});(e=>{if(e){var t=e.querySelector(".esri-view-surface");t.removeAttribute("role"),t.tabIndex=-1,t.style["outline-color"]="transparent",t.style.touchAction="none"}})(g.container),g.padding=m,a.bounds&&g.when(()=>g.goTo(w(a.bounds),{duration:0})),s.mapEventHandles=h({mapProvider:s,view:g,baseTileLayer:p,events:v,eventBus:c,getZoom:s.getZoom.bind(s),getCenter:s.getCenter.bind(s),getBounds:s.getBounds.bind(s),getResolution:s.getResolution.bind(s)}),s.appEventHandles=function(e){var{baseTileLayer:t,events:i,eventBus:n}=e,o=e=>{t.loadStyle(e.url).then(()=>{n.emit(i.MAP_STYLE_CHANGE,e)})};return n.on(i.MAP_SET_STYLE,o),{remove(){n.off(i.MAP_SET_STYLE,o)}}}({baseTileLayer:p,events:v,eventBus:c})||[],s.map=d,s.view=g,s.baseTileLayer=p})()}destroyMap(){var e,t;null===(e=this.mapEvents)||void 0===e||e.remove(),null===(t=this.appEvents)||void 0===t||t.remove(),this.mapEvents=null,this.appEvents=null,this.view&&(this.view.container=null,this.view.destroy(),this.view=null),this.map&&(this.map.removeAll(),this.map=null)}getMapAPI(){return{map:this.map,view:this.view,crs:this.crs,fitToBounds:this.fitToBounds.bind(this),setView:this.setView.bind(this)}}setView(e){var t,{center:i,zoom:n}=e;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({center:i,zoom:n,duration:v.animationDuration})}zoomIn(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom+e,duration:v.animationDuration})}zoomOut(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom-e,duration:v.animationDuration})}panBy(e){var{x:t,y:i}=this.view.toScreen(this.view.center),n={x:t+e[0],y:i+e[1]},o=this.view.toMap(n);this.view.goTo({center:o,duration:v.animationDuration})}fitToBounds(e){this.view.goTo(w(e),{duration:v.DELAY})}setPadding(e){this.view.padding=e}getCenter(){var e=this.view.center;return[e.x,e.y].map(e=>Math.round(100*e)/100)}getZoom(){return this.view.zoom}getBounds(){var{xmin:e,ymin:t,xmax:i,ymax:n}=this.view.extent;return[e,t,i,n].map(e=>Math.round(100*e)/100)}getFeaturesAtPoint(e,t){return function(e,t){return g.apply(this,arguments)}(this.view,e)}getAreaDimensions(){return(e=>{if(!(e&&e instanceof u))return"";var t=e.xmin,i=e.ymin,n=e.xmax,o=e.ymax-i,r=d(n-t),a=d(o);return"".concat(a," by ").concat(r)})(function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:y;if(!e.container)return null;var{width:i,height:n}=e.container.getBoundingClientRect(),o={x:t.left,y:n-t.bottom},r={x:i-t.right,y:t.top},a=e.toMap(o),s=e.toMap(r);return new u({xmin:a.x,ymin:a.y,xmax:s.x,ymax:s.y,spatialReference:a.spatialReference})}(this.view))}getCardinalMove(e,t){return function(e,t){var[i,n]=e,[o,r]=t,a=o-i,s=r-n,m=[];return Math.abs(s)>.1&&m.push("".concat(s>0?"north":"south"," ").concat(d(Math.abs(s)))),Math.abs(a)>.1&&m.push("".concat(a>0?"east":"west"," ").concat(d(Math.abs(a)))),m.join(", ")}(e,t)}getResolution(){return this.view.resolution}mapToScreen(e){var t=f(e),i=this.view.toScreen(t);return{x:i.x,y:i.y}}screenToMap(e){var t=this.view.toMap(e);return[t.x,t.y]}}export{M as default};
1
+ import e from"@babel/runtime/helpers/defineProperty";import t from"@babel/runtime/helpers/objectWithoutProperties";import r from"@babel/runtime/helpers/asyncToGenerator";import n from"@arcgis/core/config.js";import o from"@arcgis/core/layers/support/TileInfo.js";import i from"@arcgis/core/Map.js";import a from"@arcgis/core/views/MapView.js";import s from"@arcgis/core/layers/VectorTileLayer.js";import m from"@arcgis/core/geometry/Point.js";import{when as l,once as u,watch as c}from"@arcgis/core/core/reactiveUtils.js";import p from"@arcgis/core/geometry/Extent.js";var v={animationDuration:200},y=["showKeyboardHelp","selectControl","moveLarge","nudgeMap","zoomLarge","nudgeZoom"];var h=(e,t)=>{var r=null,n=function(){for(var n=arguments.length,o=new Array(n),i=0;i<n;i++)o[i]=arguments[i];clearTimeout(r),r=setTimeout(()=>{e(...o)},t)};return n.cancel=()=>{r&&(clearTimeout(r),r=null)},n};function d(e){var{mapProvider:t,view:r,baseTileLayer:n,events:o,eventBus:i,getZoom:a,getCenter:s,getBounds:m,getResolution:p}=e,v=!1,y=[],d=[],f=e=>{var t=(()=>{if(v||!r||r.destroyed||!r.extent)return null;var{maxZoom:e,minZoom:t}=r.constraints;return{center:s(),bounds:m(),resolution:p(),zoom:a(),isAtMaxZoom:r.zoom+.01>=e,isAtMinZoom:r.zoom-.01<=t}})();t&&i.emit(e,t)};l(()=>n.loaded&&r.resolution>0,()=>f(o.MAP_LOADED)),u(()=>r.ready).then(()=>{v||i.emit(o.MAP_READY,{map:t.map,view:t.view,mapStyleId:t.mapStyleId,mapSize:t.mapSize,crs:t.crs})}),u(()=>r.stationary).then(()=>f(o.MAP_FIRST_IDLE));var g=h(()=>f(o.MAP_MOVE_END),500);d.push(g),y.push(c(()=>[r.interacting,r.animation],e=>{var[t,r]=e;(t||r)&&i.emit(o.MAP_MOVE_START),t||r||g()}));var x,w,b,M=(x=()=>f(o.MAP_MOVE),w=10,b=0,function(){var e=Date.now();e-b>=w&&(b=e,x(...arguments))});d.push(M),y.push(c(()=>r.zoom,M)),y.push(c(()=>r.extent,()=>i.emit(o.MAP_RENDER),{initial:!1}));var E=h(()=>f(o.MAP_DATA_CHANGE),500);d.push(E),y.push(c(()=>r.updating,e=>!e&&E()));var A=null,S=null;return y.push(r.on("pointer-down",e=>{A=e.x,S=e.y})),y.push(r.on("pointer-up",e=>{var n;if(0===e.button&&!(Math.hypot(e.x-A,e.y-S)>6)&&"active"!==(null===(n=t.sketchViewModel)||void 0===n?void 0:n.state)){var a={x:e.x,y:e.y},s=r.toMap(a);s&&i.emit(o.MAP_CLICK,{point:a,coords:[s.x,s.y]})}})),{remove(){v=!0,d.forEach(e=>e.cancel()),y.forEach(e=>e.remove())}}}var f=e=>e?new p({xmin:e[0],ymin:e[1],xmax:e[2],ymax:e[3],spatialReference:{wkid:27700}}):void 0,g=e=>e?new m({x:e[0],y:e[1],spatialReference:{wkid:27700}}):void 0,x=(e,t)=>{if(e)if("FeatureCollection"===e.type)e.features.forEach(e=>x(e,t));else if("Feature"===e.type)x(e.geometry,t);else if("GeometryCollection"===e.type)e.geometries.forEach(e=>x(e,t));else{var r=e=>{Array.isArray(e)&&("number"==typeof e[0]?t.push(e):e.forEach(r))};r(e.coordinates)}},w=e=>{var t=[];x(e,t);var r=t.map(e=>e[0]),n=t.map(e=>e[1]);return f([Math.min(...r),Math.min(...n),Math.max(...r),Math.max(...n)])},b=e=>{var t=1609.344,r=e/t;if(r<.5/t)return"".concat(Math.round(e),"m");if(r<10){var n=Number.parseFloat(r.toFixed(1)),o=1===n?"mile":"miles";return"".concat(n," ").concat(o)}var i=Math.round(r),a=1===i?"mile":"miles";return"".concat(i," ").concat(a)};var M={top:0,right:0,bottom:0,left:0};function E(){return(E=r(function*(e,t){if(!e||!t)return[];var r=e.map.layers.filter(e=>e instanceof s);return r.length?(yield e.hitTest(t,{include:r.toArray()})).results.map(e=>({layerId:e.layer.id,layerTitle:e.layer.title||e.layer.id,type:e.layer.type,geometry:e.graphic.geometry,symbol:e.graphic.symbol})):[]})).apply(this,arguments)}var A=["container","padding","mapStyle","mapSize","maxExtent"];function S(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 T(t){for(var r=1;r<arguments.length;r++){var n=null!=arguments[r]?arguments[r]:{};r%2?S(Object(n),!0).forEach(function(r){e(t,r,n[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):S(Object(n)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))})}return t}class P{constructor(e){var{mapProviderConfig:t={},events:r,eventBus:n}=e;this.events=r,this.eventBus=n,this.capabilities={supportedShortcuts:y,supportsMapSizes:!1},Object.assign(this,t),this.mapEventHandles=[],this.appEventHandles=[]}initMap(e){var m=this;return r(function*(){var{container:r,padding:l,mapStyle:u,mapSize:c,maxExtent:p}=e;t(e,A),m.mapStyleId=null==u?void 0:u.id,m.mapSize=c;var{events:v,eventBus:y}=m;m.setupConfig&&(yield m.setupConfig(n));var h=new s({id:"baselayer",url:u.url,visible:!0}),x=new i({layers:[h]}),w=p?f(p):null,b=new a({spatialReference:27700,container:r,map:x,zoom:e.zoom,center:g(e.center),maxExtent:p,constraints:{lods:o.create({spatialReference:{wkid:27700}}).lods,snapToZoom:!1,minZoom:e.minZoom,maxZoom:e.maxZoom,maxScale:0,geometry:w,rotationEnabled:!1},ui:{components:[]},popupEnabled:!1});(e=>{if(e){var t=e.querySelector(".esri-view-surface");t.removeAttribute("role"),t.tabIndex=-1,t.style["outline-color"]="transparent",t.style.touchAction="none"}})(b.container),b.padding=l,e.bounds&&b.when(()=>b.goTo(f(e.bounds),{duration:0})),m.mapEventHandles=d({mapProvider:m,view:b,baseTileLayer:h,events:v,eventBus:y,getZoom:m.getZoom.bind(m),getCenter:m.getCenter.bind(m),getBounds:m.getBounds.bind(m),getResolution:m.getResolution.bind(m)}),m.appEventHandles=function(e){var{baseTileLayer:t,events:r,eventBus:n}=e,o=e=>{t.loadStyle(e.url).then(()=>{n.emit(r.MAP_STYLE_CHANGE,{mapStyleId:e.id})})};return n.on(r.MAP_SET_STYLE,o),{remove(){n.off(r.MAP_SET_STYLE,o)}}}({baseTileLayer:h,events:v,eventBus:y})||[],m.map=x,m.view=b,m.baseTileLayer=h})()}destroyMap(){var e,t;null===(e=this.mapEvents)||void 0===e||e.remove(),null===(t=this.appEvents)||void 0===t||t.remove(),this.mapEvents=null,this.appEvents=null,this.view&&(this.view.container=null,this.view.destroy(),this.view=null),this.map&&(this.map.removeAll(),this.map=null)}setView(e){var t,{center:r,zoom:n}=e;null===(t=this.view.animation)||void 0===t||t.destroy();var o={center:r?new m({x:r[0],y:r[1],spatialReference:{wkid:27700}}):this.view.center,zoom:null!=n?n:this.view.zoom};this.view.goTo(T(T({},o),{},{duration:v.animationDuration}))}zoomIn(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom+e,duration:v.animationDuration})}zoomOut(e){var t;null===(t=this.view.animation)||void 0===t||t.destroy(),this.view.goTo({zoom:this.view.zoom-e,duration:v.animationDuration})}panBy(e){var{x:t,y:r}=this.view.toScreen(this.view.center),n={x:t+e[0],y:r+e[1]},o=this.view.toMap(n);this.view.goTo({center:o,duration:v.animationDuration})}fitToBounds(e){var t=Array.isArray(e)?f(e):w(e);this.view.goTo(t,{duration:v.DELAY})}setPadding(e){this.view.padding=e}getCenter(){var e=this.view.center;return[e.x,e.y].map(e=>Math.round(100*e)/100)}getZoom(){return this.view.zoom}getBounds(){var{xmin:e,ymin:t,xmax:r,ymax:n}=this.view.extent;return[e,t,r,n].map(e=>Math.round(100*e)/100)}getFeaturesAtPoint(e,t){return function(e,t){return E.apply(this,arguments)}(this.view,e)}getAreaDimensions(){return(e=>{if(!(e&&e instanceof p))return"";var t=e.xmin,r=e.ymin,n=e.xmax,o=e.ymax-r,i=b(n-t),a=b(o);return"".concat(a," by ").concat(i)})(function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:M;if(!e.container)return null;var{width:r,height:n}=e.container.getBoundingClientRect(),o={x:t.left,y:n-t.bottom},i={x:r-t.right,y:t.top},a=e.toMap(o),s=e.toMap(i);return new p({xmin:a.x,ymin:a.y,xmax:s.x,ymax:s.y,spatialReference:a.spatialReference})}(this.view))}getCardinalMove(e,t){return function(e,t){var[r,n]=e,[o,i]=t,a=o-r,s=i-n,m=[];return Math.abs(s)>.1&&m.push("".concat(s>0?"north":"south"," ").concat(b(Math.abs(s)))),Math.abs(a)>.1&&m.push("".concat(a>0?"east":"west"," ").concat(b(Math.abs(a)))),m.join(", ")}(e,t)}getResolution(){return this.view.resolution}mapToScreen(e){var t=g(e),r=this.view.toScreen(t);return{x:r.x,y:r.y}}screenToMap(e){var t=this.view.toMap(e);return[t.x,t.y]}isGeometryObscured(e,t){return((e,t,r)=>{if(null==r||!r.container)return!1;var n=r.container.getBoundingClientRect(),o=w(e),i=[r.toScreen(new m({x:o.xmin,y:o.ymin,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmin,y:o.ymax,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmax,y:o.ymin,spatialReference:o.spatialReference})),r.toScreen(new m({x:o.xmax,y:o.ymax,spatialReference:o.spatialReference}))],a=Math.min(...i.map(e=>e.x)),s=Math.max(...i.map(e=>e.x)),l=Math.min(...i.map(e=>e.y)),u=Math.max(...i.map(e=>e.y)),c=t.left-n.left,p=t.top-n.top,v=t.right-n.left,y=t.bottom-n.top;return a<v&&s>c&&l<y&&u>p})(e,t,this.view)}}export{P as default};
@@ -1,7 +1,13 @@
1
- export function attachAppEvents ({ baseTileLayer, events, eventBus }) {
1
+ export function attachAppEvents ({
2
+ baseTileLayer,
3
+ events,
4
+ eventBus
5
+ }) {
2
6
  const handleSetMapStyle = mapStyle => {
3
7
  baseTileLayer.loadStyle(mapStyle.url).then(() => {
4
- eventBus.emit(events.MAP_STYLE_CHANGE, mapStyle)
8
+ eventBus.emit(events.MAP_STYLE_CHANGE, {
9
+ mapStyleId: mapStyle.id
10
+ })
5
11
  })
6
12
  }
7
13
 
@@ -1,15 +1,17 @@
1
1
  // src/plugins/mapStyles/EsriProvider.jsx
2
2
  import './esriProvider.scss'
3
3
  import esriConfig from '@arcgis/core/config.js'
4
+ import TileInfo from '@arcgis/core/layers/support/TileInfo.js'
4
5
  import EsriMap from '@arcgis/core/Map.js'
5
6
  import MapView from '@arcgis/core/views/MapView.js'
6
7
  import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js'
8
+ import Point from '@arcgis/core/geometry/Point.js'
7
9
  import { defaults, supportedShortcuts } from './defaults.js'
8
10
  import { attachAppEvents } from './appEvents.js'
9
11
  import { attachMapEvents } from './mapEvents.js'
10
- import { getAreaDimensions, getCardinalMove, getPaddedExtent } from './utils/spatial.js'
12
+ import { getAreaDimensions, getCardinalMove, getPaddedExtent, isGeometryObscured } from './utils/spatial.js'
11
13
  import { queryVectorTileFeatures } from './utils/query.js'
12
- import { getExtentFromFlatCoords, getPointFromFlatCoords } from './utils/coords.js'
14
+ import { getExtentFromFlatCoords, getPointFromFlatCoords, getBboxFromGeoJSON } from './utils/coords.js'
13
15
  import { cleanDOM } from './utils/esriFixes.js'
14
16
 
15
17
  export default class EsriProvider {
@@ -28,7 +30,9 @@ export default class EsriProvider {
28
30
  }
29
31
 
30
32
  async initMap (config) {
31
- const { container, padding, mapStyle, maxExtent, ...initConfig } = config
33
+ const { container, padding, mapStyle, mapSize, maxExtent, ...initConfig } = config
34
+ this.mapStyleId = mapStyle?.id
35
+ this.mapSize = mapSize
32
36
  const { events, eventBus } = this
33
37
 
34
38
  if (this.setupConfig) {
@@ -47,6 +51,7 @@ export default class EsriProvider {
47
51
  center: getPointFromFlatCoords(config.center),
48
52
  maxExtent: maxExtent,
49
53
  constraints: {
54
+ lods: TileInfo.create({ spatialReference: { wkid: 27700 } }).lods,
50
55
  snapToZoom: false,
51
56
  minZoom: config.minZoom,
52
57
  maxZoom: config.maxZoom,
@@ -112,24 +117,15 @@ export default class EsriProvider {
112
117
  }
113
118
  }
114
119
 
115
- /** Returns the public API exposed via the map:ready event. */
116
- getMapAPI () {
117
- return {
118
- map: this.map,
119
- view: this.view,
120
- crs: this.crs,
121
- fitToBounds: this.fitToBounds.bind(this),
122
- setView: this.setView.bind(this)
123
- }
124
- }
125
-
126
120
  // ==========================
127
121
  // Side-effects
128
122
  // ==========================
129
123
 
130
- setView ({ center, zoom }) {
124
+ setView({ center, zoom }) {
131
125
  this.view.animation?.destroy()
132
- this.view.goTo({ center, zoom, duration: defaults.animationDuration })
126
+ const point = center ? new Point({ x: center[0], y: center[1], spatialReference: { wkid: 27700 }}) : this.view.center
127
+ const target = { center: point, zoom: zoom ?? this.view.zoom }
128
+ this.view.goTo({ ...target, duration: defaults.animationDuration })
133
129
  }
134
130
 
135
131
  zoomIn (zoomDelta) {
@@ -150,7 +146,8 @@ export default class EsriProvider {
150
146
  }
151
147
 
152
148
  fitToBounds (bounds) {
153
- this.view.goTo(getExtentFromFlatCoords(bounds), { duration: defaults.DELAY })
149
+ const extent = Array.isArray(bounds) ? getExtentFromFlatCoords(bounds) : getBboxFromGeoJSON(bounds)
150
+ this.view.goTo(extent, { duration: defaults.DELAY })
154
151
  }
155
152
 
156
153
  setPadding (padding) {
@@ -211,4 +208,15 @@ export default class EsriProvider {
211
208
  const mapPoint = this.view.toMap(point)
212
209
  return [mapPoint.x, mapPoint.y]
213
210
  }
211
+
212
+ /**
213
+ * Returns true if the geometry's screen bounding box overlaps the given panel rectangle.
214
+ *
215
+ * @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry.
216
+ * @param {DOMRect} panelRect - Bounding rect of the panel element (viewport coordinates).
217
+ * @returns {boolean}
218
+ */
219
+ isGeometryObscured (geojson, panelRect) {
220
+ return isGeometryObscured(geojson, panelRect, this.view)
221
+ }
214
222
  }
@@ -54,7 +54,13 @@ export function attachMapEvents ({
54
54
  // ready
55
55
  once(() => view.ready).then(() => {
56
56
  if (!destroyed) {
57
- eventBus.emit(events.MAP_READY, mapProvider.getMapAPI())
57
+ eventBus.emit(events.MAP_READY, {
58
+ map: mapProvider.map,
59
+ view: mapProvider.view,
60
+ mapStyleId: mapProvider.mapStyleId,
61
+ mapSize: mapProvider.mapSize,
62
+ crs: mapProvider.crs
63
+ })
58
64
  }
59
65
  })
60
66
 
@@ -99,10 +105,41 @@ export function attachMapEvents ({
99
105
  updating => !updating && emitDataChange()
100
106
  ))
101
107
 
102
- // click
103
- handlers.push(view.on('click', e => {
104
- const mapPoint = e.mapPoint
108
+ // Click
109
+ // handlers.push(view.on('click', e => {
110
+ // const mapPoint = e.mapPoint
111
+ // const screenPoint = { x: e.x, y: e.y }
112
+ // eventBus.emit(events.MAP_CLICK, { point: screenPoint, coords: [mapPoint.x, mapPoint.y] })
113
+ // }))
114
+
115
+ // Using pointer-up instead of click/immediate-click — significantly more responsive as it
116
+ // bypasses ArcGIS's internal hit-testing pipeline. Track pointer-down position to suppress
117
+ // pointer-up after a drag/pan. Also suppress when SketchViewModel is active so draw/edit
118
+ // vertex clicks don't leak through to the map click handler.
119
+ const DRAG_TOLERANCE = 6
120
+ let pointerDownX = null
121
+ let pointerDownY = null
122
+
123
+ handlers.push(view.on('pointer-down', e => {
124
+ pointerDownX = e.x
125
+ pointerDownY = e.y
126
+ }))
127
+
128
+ handlers.push(view.on('pointer-up', e => {
129
+ if (e.button !== 0) {
130
+ return
131
+ }
132
+ if (Math.hypot(e.x - pointerDownX, e.y - pointerDownY) > DRAG_TOLERANCE) {
133
+ return
134
+ }
135
+ if (mapProvider.sketchViewModel?.state === 'active') {
136
+ return
137
+ }
105
138
  const screenPoint = { x: e.x, y: e.y }
139
+ const mapPoint = view.toMap(screenPoint)
140
+ if (!mapPoint) {
141
+ return
142
+ }
106
143
  eventBus.emit(events.MAP_CLICK, { point: screenPoint, coords: [mapPoint.x, mapPoint.y] })
107
144
  }))
108
145
 
@@ -23,7 +23,40 @@ const getPointFromFlatCoords = (coords) => {
23
23
  : undefined
24
24
  }
25
25
 
26
+ const collectCoords = (obj, acc) => {
27
+ if (!obj) return
28
+ if (obj.type === 'FeatureCollection') {
29
+ obj.features.forEach(f => collectCoords(f, acc))
30
+ } else if (obj.type === 'Feature') {
31
+ collectCoords(obj.geometry, acc)
32
+ } else if (obj.type === 'GeometryCollection') {
33
+ obj.geometries.forEach(g => collectCoords(g, acc))
34
+ } else {
35
+ const flatten = (coords) => {
36
+ if (!Array.isArray(coords)) { return }
37
+ if (typeof coords[0] === 'number') acc.push(coords)
38
+ else coords.forEach(flatten)
39
+ }
40
+ flatten(obj.coordinates)
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get an ESRI Extent from any GeoJSON object (Feature, FeatureCollection, or geometry).
46
+ *
47
+ * @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry
48
+ * @returns {import('@arcgis/core/geometry/Extent.js').default}
49
+ */
50
+ const getBboxFromGeoJSON = (geojson) => {
51
+ const points = []
52
+ collectCoords(geojson, points)
53
+ const xs = points.map(p => p[0])
54
+ const ys = points.map(p => p[1])
55
+ return getExtentFromFlatCoords([Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)])
56
+ }
57
+
26
58
  export {
27
59
  getExtentFromFlatCoords,
28
- getPointFromFlatCoords
60
+ getPointFromFlatCoords,
61
+ getBboxFromGeoJSON
29
62
  }
@@ -0,0 +1,126 @@
1
+ import { getExtentFromFlatCoords, getPointFromFlatCoords, getBboxFromGeoJSON } from './coords.js'
2
+
3
+ jest.mock('@arcgis/core/geometry/Extent.js', () =>
4
+ jest.fn().mockImplementation((opts) => ({ ...opts, type: 'extent' }))
5
+ )
6
+ jest.mock('@arcgis/core/geometry/Point.js', () =>
7
+ jest.fn().mockImplementation((opts) => ({ ...opts, type: 'point' }))
8
+ )
9
+
10
+ describe('coords utils', () => {
11
+ describe('getExtentFromFlatCoords', () => {
12
+ test('returns an Extent with correct xmin/ymin/xmax/ymax', () => {
13
+ const result = getExtentFromFlatCoords([1, 2, 3, 4])
14
+ expect(result.xmin).toBe(1)
15
+ expect(result.ymin).toBe(2)
16
+ expect(result.xmax).toBe(3)
17
+ expect(result.ymax).toBe(4)
18
+ expect(result.spatialReference).toEqual({ wkid: 27700 })
19
+ })
20
+
21
+ test('returns undefined for falsy input', () => {
22
+ expect(getExtentFromFlatCoords(null)).toBeUndefined()
23
+ expect(getExtentFromFlatCoords(undefined)).toBeUndefined()
24
+ })
25
+ })
26
+
27
+ describe('getPointFromFlatCoords', () => {
28
+ test('returns a Point with correct x/y', () => {
29
+ const result = getPointFromFlatCoords([100, 200])
30
+ expect(result.x).toBe(100)
31
+ expect(result.y).toBe(200)
32
+ expect(result.spatialReference).toEqual({ wkid: 27700 })
33
+ })
34
+
35
+ test('returns undefined for falsy input', () => {
36
+ expect(getPointFromFlatCoords(null)).toBeUndefined()
37
+ })
38
+ })
39
+
40
+ describe('getBboxFromGeoJSON', () => {
41
+ test('Point feature → extent wrapping that single point', () => {
42
+ const feature = {
43
+ type: 'Feature',
44
+ geometry: { type: 'Point', coordinates: [300000, 100000] },
45
+ properties: {}
46
+ }
47
+ const result = getBboxFromGeoJSON(feature)
48
+ expect(result.xmin).toBe(300000)
49
+ expect(result.ymin).toBe(100000)
50
+ expect(result.xmax).toBe(300000)
51
+ expect(result.ymax).toBe(100000)
52
+ })
53
+
54
+ test('LineString feature → tight bbox around all coords', () => {
55
+ const feature = {
56
+ type: 'Feature',
57
+ geometry: {
58
+ type: 'LineString',
59
+ coordinates: [[100, 200], [300, 50], [500, 400]]
60
+ },
61
+ properties: {}
62
+ }
63
+ const result = getBboxFromGeoJSON(feature)
64
+ expect(result.xmin).toBe(100)
65
+ expect(result.ymin).toBe(50)
66
+ expect(result.xmax).toBe(500)
67
+ expect(result.ymax).toBe(400)
68
+ })
69
+
70
+ test('Polygon feature → bbox around all rings', () => {
71
+ const feature = {
72
+ type: 'Feature',
73
+ geometry: {
74
+ type: 'Polygon',
75
+ coordinates: [[[0, 0], [10, 0], [10, 5], [0, 5], [0, 0]]]
76
+ },
77
+ properties: {}
78
+ }
79
+ const result = getBboxFromGeoJSON(feature)
80
+ expect(result.xmin).toBe(0)
81
+ expect(result.ymin).toBe(0)
82
+ expect(result.xmax).toBe(10)
83
+ expect(result.ymax).toBe(5)
84
+ })
85
+
86
+ test('FeatureCollection → bbox spanning all features', () => {
87
+ const fc = {
88
+ type: 'FeatureCollection',
89
+ features: [
90
+ { type: 'Feature', geometry: { type: 'Point', coordinates: [100, 200] }, properties: {} },
91
+ { type: 'Feature', geometry: { type: 'Point', coordinates: [500, 800] }, properties: {} }
92
+ ]
93
+ }
94
+ const result = getBboxFromGeoJSON(fc)
95
+ expect(result.xmin).toBe(100)
96
+ expect(result.ymin).toBe(200)
97
+ expect(result.xmax).toBe(500)
98
+ expect(result.ymax).toBe(800)
99
+ })
100
+
101
+ test('GeometryCollection → bbox spanning all geometries', () => {
102
+ const gc = {
103
+ type: 'GeometryCollection',
104
+ geometries: [
105
+ { type: 'Point', coordinates: [10, 20] },
106
+ { type: 'LineString', coordinates: [[50, 5], [100, 90]] }
107
+ ]
108
+ }
109
+ const result = getBboxFromGeoJSON(gc)
110
+ expect(result.xmin).toBe(10)
111
+ expect(result.ymin).toBe(5)
112
+ expect(result.xmax).toBe(100)
113
+ expect(result.ymax).toBe(90)
114
+ })
115
+
116
+ test('result has correct spatialReference from getExtentFromFlatCoords', () => {
117
+ const feature = {
118
+ type: 'Feature',
119
+ geometry: { type: 'Point', coordinates: [1, 2] },
120
+ properties: {}
121
+ }
122
+ const result = getBboxFromGeoJSON(feature)
123
+ expect(result.spatialReference).toEqual({ wkid: 27700 })
124
+ })
125
+ })
126
+ })
@@ -1,4 +1,6 @@
1
1
  import Extent from '@arcgis/core/geometry/Extent.js'
2
+ import Point from '@arcgis/core/geometry/Point.js'
3
+ import { getBboxFromGeoJSON } from './coords.js'
2
4
 
3
5
  // -----------------------------------------------------------------------------
4
6
  // Internal (not exported)
@@ -124,8 +126,52 @@ const getPaddedExtent = (view, padding = DEFAULT_PADDING) => {
124
126
  }
125
127
 
126
128
 
129
+ /**
130
+ * Returns true if the geometry's screen bounding box overlaps the given panel rectangle.
131
+ * Used to decide whether to pan/zoom when a panel opens over a visibleGeometry target.
132
+ *
133
+ * @param {object} geojson - GeoJSON Feature, FeatureCollection, or geometry
134
+ * @param {DOMRect} panelRect - Bounding rect of the panel element (viewport coordinates)
135
+ * @param {import('@arcgis/core/views/MapView.js').default} view - ESRI MapView instance
136
+ * @returns {boolean}
137
+ */
138
+ const isGeometryObscured = (geojson, panelRect, view) => {
139
+ if (!view?.container) {
140
+ return false
141
+ }
142
+
143
+ const containerRect = view.container.getBoundingClientRect()
144
+ const extent = getBboxFromGeoJSON(geojson)
145
+
146
+ const corners = [
147
+ view.toScreen(new Point({ x: extent.xmin, y: extent.ymin, spatialReference: extent.spatialReference })),
148
+ view.toScreen(new Point({ x: extent.xmin, y: extent.ymax, spatialReference: extent.spatialReference })),
149
+ view.toScreen(new Point({ x: extent.xmax, y: extent.ymin, spatialReference: extent.spatialReference })),
150
+ view.toScreen(new Point({ x: extent.xmax, y: extent.ymax, spatialReference: extent.spatialReference }))
151
+ ]
152
+
153
+ const screenMinX = Math.min(...corners.map(c => c.x))
154
+ const screenMaxX = Math.max(...corners.map(c => c.x))
155
+ const screenMinY = Math.min(...corners.map(c => c.y))
156
+ const screenMaxY = Math.max(...corners.map(c => c.y))
157
+
158
+ // Convert panelRect from viewport coords to view-container-relative coords
159
+ const panelLeft = panelRect.left - containerRect.left
160
+ const panelTop = panelRect.top - containerRect.top
161
+ const panelRight = panelRect.right - containerRect.left
162
+ const panelBottom = panelRect.bottom - containerRect.top
163
+
164
+ return (
165
+ screenMinX < panelRight &&
166
+ screenMaxX > panelLeft &&
167
+ screenMinY < panelBottom &&
168
+ screenMaxY > panelTop
169
+ )
170
+ }
171
+
127
172
  export {
128
173
  getAreaDimensions,
129
174
  getCardinalMove,
130
- getPaddedExtent
175
+ getPaddedExtent,
176
+ isGeometryObscured
131
177
  }
@@ -0,0 +1,55 @@
1
+ import { isGeometryObscured } from './spatial.js'
2
+
3
+ jest.mock('@arcgis/core/geometry/Extent.js', () =>
4
+ jest.fn().mockImplementation((opts) => ({ ...opts, type: 'extent' }))
5
+ )
6
+
7
+ jest.mock('@arcgis/core/geometry/Point.js', () =>
8
+ jest.fn().mockImplementation((opts) => ({ ...opts, type: 'point' }))
9
+ )
10
+
11
+ jest.mock('./coords.js', () => ({
12
+ getBboxFromGeoJSON: jest.fn(() => ({
13
+ xmin: 100, ymin: 200, xmax: 500, ymax: 600,
14
+ spatialReference: { wkid: 27700 },
15
+ type: 'extent'
16
+ }))
17
+ }))
18
+
19
+ describe('isGeometryObscured', () => {
20
+ const geojson = { type: 'Feature', geometry: { type: 'Point', coordinates: [300, 400] }, properties: {} }
21
+
22
+ // Container sits at viewport origin so container-relative coords equal viewport coords
23
+ const makeView = (toScreenFn) => ({
24
+ container: {
25
+ getBoundingClientRect: jest.fn(() => ({ left: 0, top: 0, right: 1000, bottom: 800 }))
26
+ },
27
+ toScreen: jest.fn(toScreenFn)
28
+ })
29
+
30
+ // Panel occupies the right 400px of the viewport
31
+ const panelRect = { left: 600, top: 0, right: 1000, bottom: 800, width: 400, height: 800 }
32
+
33
+ test('returns false when view has no container', () => {
34
+ expect(isGeometryObscured(geojson, panelRect, null)).toBe(false)
35
+ expect(isGeometryObscured(geojson, panelRect, {})).toBe(false)
36
+ })
37
+
38
+ test('returns true when geometry screen bbox overlaps the panel rect', () => {
39
+ // Corners project into the panel (x: 650 is between panelLeft 600 and panelRight 1000)
40
+ const view = makeView(() => ({ x: 650, y: 400 }))
41
+ expect(isGeometryObscured(geojson, panelRect, view)).toBe(true)
42
+ })
43
+
44
+ test('returns false when geometry screen bbox does not overlap the panel rect', () => {
45
+ // Corners project to x: 300, entirely left of panelLeft (600)
46
+ const view = makeView(() => ({ x: 300, y: 400 }))
47
+ expect(isGeometryObscured(geojson, panelRect, view)).toBe(false)
48
+ })
49
+
50
+ test('projects all four bbox corners', () => {
51
+ const view = makeView(() => ({ x: 300, y: 400 }))
52
+ isGeometryObscured(geojson, panelRect, view)
53
+ expect(view.toScreen).toHaveBeenCalledTimes(4)
54
+ })
55
+ })