@defra/interactive-map 0.0.22-alpha → 0.0.24-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.
- package/assets/images/light-raster-thumb.jpg +0 -0
- package/assets/images/outdoor-raster-thumb.jpg +0 -0
- package/assets/images/road-raster-thumb.jpg +0 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +39 -0
- package/docs/api/context.md +4 -4
- package/docs/api/map-style-config.md +11 -2
- package/docs/api/marker-config.md +38 -7
- package/docs/api/panel-definition.md +7 -3
- package/docs/api/symbol-config.md +10 -26
- package/docs/api/symbol-registry.md +28 -13
- package/docs/api.md +24 -2
- package/docs/assets/css/{docusaurus.css → docusaurus.scss} +18 -12
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/examples/index.mdx +12 -0
- package/docs/examples/toggle-marker-label.mdx +89 -0
- package/docs/plugins/interact.md +4 -11
- package/docs/plugins/search.md +70 -0
- package/docusaurus.config.cjs +2 -1
- package/package.json +23 -9
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/beta/datasets/dist/css/index.css +2 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +5 -5
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +24 -29
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +2 -2
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +3 -3
- package/plugins/beta/datasets/src/components/KeySvg.jsx +2 -2
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +1 -2
- package/plugins/beta/datasets/src/components/svgProperties.js +2 -2
- package/plugins/beta/datasets/src/panels/Key.module.scss +2 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/css/index.css +1 -21
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/api/editFeature.js +9 -4
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +11 -3
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +1 -0
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +23 -55
- package/plugins/interact/src/InteractInit.test.js +58 -80
- package/plugins/interact/src/defaults.js +1 -3
- package/plugins/interact/src/hooks/useAttachEvents.js +46 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +68 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +47 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +54 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +30 -8
- package/plugins/interact/src/hooks/useHighlightSync.test.js +127 -80
- package/plugins/interact/src/hooks/useInteractionHandlers.js +18 -17
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +22 -61
- package/plugins/interact/src/hooks/useMapItemList.js +187 -0
- package/plugins/interact/src/hooks/useMapItemList.test.js +584 -0
- package/plugins/interact/src/index.js +0 -2
- package/plugins/interact/src/index.test.js +0 -2
- package/plugins/interact/src/manifest.js +37 -6
- package/plugins/interact/src/reducer.js +8 -4
- package/plugins/interact/src/reducer.test.js +18 -3
- package/plugins/interact/src/utils/buildStylesMap.js +21 -10
- package/plugins/interact/src/utils/buildStylesMap.test.js +29 -29
- package/plugins/interact/src/utils/spatial.js +0 -15
- package/plugins/interact/src/utils/spatial.test.js +1 -13
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +2 -1
- package/plugins/search/src/components/CloseButton/CloseButton.module.scss +2 -0
- package/plugins/search/src/components/Form/Form.jsx +11 -7
- package/plugins/search/src/components/Form/Form.test.jsx +23 -1
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +1 -1
- package/plugins/search/src/components/OpenButton/OpenButton.module.scss +3 -0
- package/plugins/search/src/datasets.js +2 -2
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +7 -1
- package/plugins/search/src/events/fetchSuggestions.js +13 -10
- package/plugins/search/src/events/fetchSuggestions.test.js +14 -1
- package/plugins/search/src/events/formHandlers.js +2 -4
- package/plugins/search/src/events/formHandlers.test.js +3 -3
- package/plugins/search/src/events/inputHandlers.js +1 -1
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -1
- package/plugins/search/src/events/suggestionHandlers.test.js +7 -1
- package/plugins/search/src/index.js +3 -7
- package/plugins/search/src/reducer.js +10 -5
- package/plugins/search/src/reducer.test.js +2 -1
- package/plugins/search/src/search.scss +1 -0
- package/plugins/search/src/utils/parseOsNamesResults.js +3 -3
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -0
- package/providers/beta/openlayers/dist/esm/index.js +1 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +2 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/dist/umd/index.js +2 -0
- package/providers/beta/openlayers/dist/umd/index.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/src/appEvents.js +50 -0
- package/providers/beta/openlayers/src/appEvents.test.js +140 -0
- package/providers/beta/openlayers/src/defaults.js +36 -0
- package/providers/beta/openlayers/src/index.js +21 -0
- package/providers/beta/openlayers/src/mapEvents.js +132 -0
- package/providers/beta/openlayers/src/mapEvents.test.js +197 -0
- package/providers/beta/openlayers/src/openlayersProvider.js +234 -0
- package/providers/beta/openlayers/src/openlayersProvider.test.js +331 -0
- package/providers/beta/openlayers/src/utils/spatial.js +142 -0
- package/providers/beta/openlayers/src/utils/spatial.test.js +117 -0
- package/providers/beta/openlayers/src/utils/zoom.js +26 -0
- package/providers/beta/openlayers/src/utils/zoom.test.js +23 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +9 -14
- package/providers/maplibre/src/mapEvents.js +11 -5
- package/providers/maplibre/src/mapEvents.test.js +14 -3
- package/providers/maplibre/src/maplibreProvider.js +18 -11
- package/providers/maplibre/src/maplibreProvider.test.js +43 -36
- package/providers/maplibre/src/utils/highlightFeatures.js +112 -57
- package/providers/maplibre/src/utils/highlightFeatures.test.js +179 -51
- package/providers/maplibre/src/utils/maplibreFixes.js +10 -0
- package/providers/maplibre/src/utils/maplibreFixes.test.js +31 -0
- package/providers/maplibre/src/utils/patternImages.js +22 -19
- package/providers/maplibre/src/utils/patternImages.test.js +146 -167
- package/providers/maplibre/src/utils/rasteriseToImageData.js +1 -4
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +23 -24
- package/providers/maplibre/src/utils/symbolImages.js +23 -86
- package/providers/maplibre/src/utils/symbolImages.test.js +60 -105
- package/rollup.esm.mjs +14 -3
- package/src/App/components/Actions/Actions.module.scss +1 -0
- package/src/App/components/Actions/Actions.test.jsx +17 -4
- package/src/App/components/Hints/Hints.jsx +52 -0
- package/src/App/components/Hints/Hints.module.scss +37 -0
- package/src/App/components/Hints/Hints.test.jsx +104 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +57 -19
- package/src/App/components/KeyboardHelp/KeyboardHelp.module.scss +10 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +140 -31
- package/src/App/components/Markers/LabelMarker.jsx +14 -0
- package/src/App/components/Markers/LabelMarker.test.jsx +36 -0
- package/src/App/components/Markers/Markers.jsx +44 -75
- package/src/App/components/Markers/Markers.module.scss +58 -0
- package/src/App/components/Markers/Markers.test.jsx +90 -113
- package/src/App/components/Markers/SymbolLabelMarker.jsx +43 -0
- package/src/App/components/Markers/SymbolLabelMarker.test.jsx +56 -0
- package/src/App/components/Markers/SymbolMarker.jsx +31 -0
- package/src/App/components/Markers/SymbolMarker.test.jsx +72 -0
- package/src/App/components/Panel/Panel.jsx +1 -1
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +117 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +132 -85
- package/src/App/components/Tabs/Tabs.jsx +68 -0
- package/src/App/components/Tabs/Tabs.module.scss +57 -0
- package/src/App/components/Tabs/Tabs.test.jsx +121 -0
- package/src/App/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/App/components/Viewport/Features.jsx +35 -0
- package/src/App/components/Viewport/Features.module.scss +31 -0
- package/src/App/components/Viewport/Features.test.jsx +124 -0
- package/src/App/components/Viewport/MapStatus.jsx +0 -4
- package/src/App/components/Viewport/Viewport.jsx +35 -29
- package/src/App/components/Viewport/Viewport.module.scss +7 -21
- package/src/App/components/Viewport/Viewport.test.jsx +108 -65
- package/src/App/controls/keyboardActions.js +9 -3
- package/src/App/controls/keyboardActions.test.js +122 -87
- package/src/App/controls/keyboardShortcuts.js +1 -14
- package/src/App/hooks/useFeatureFocus.js +199 -0
- package/src/App/hooks/useFeatureFocus.test.js +635 -0
- package/src/App/hooks/useFeatureItems.js +30 -0
- package/src/App/hooks/useFeatureItems.test.js +98 -0
- package/src/App/hooks/useFocusVisible.js +4 -7
- package/src/App/hooks/useFocusVisible.test.js +6 -6
- package/src/App/hooks/useKeyboardHint.js +9 -34
- package/src/App/hooks/useKeyboardHint.test.js +34 -84
- package/src/App/hooks/useKeyboardShortcuts.js +8 -5
- package/src/App/hooks/useKeyboardShortcuts.test.js +16 -12
- package/src/App/hooks/useLayoutMeasurements.js +10 -4
- package/src/App/hooks/useLayoutMeasurements.test.js +30 -1
- package/src/App/hooks/useMarkerCursor.js +33 -0
- package/src/App/hooks/useMarkerCursor.test.js +87 -0
- package/src/App/hooks/useMarkersAPI.js +32 -9
- package/src/App/hooks/useMarkersAPI.test.js +163 -94
- package/src/App/hooks/useModalPanelBehaviour.test.js +2 -1
- package/src/App/layout/Layout.jsx +2 -0
- package/src/App/layout/Layout.test.jsx +3 -0
- package/src/App/registry/keyboardShortcutRegistry.js +3 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +6 -8
- package/src/App/renderer/mapButtons.js +5 -1
- package/src/App/renderer/mapButtons.test.js +37 -1
- package/src/App/store/AppProvider.test.jsx +5 -0
- package/src/App/store/MapProvider.test.jsx +8 -1
- package/src/App/store/ServiceProvider.jsx +4 -1
- package/src/App/store/ServiceProvider.test.jsx +11 -0
- package/src/App/store/appActionsMap.js +5 -1
- package/src/App/store/appActionsMap.test.js +13 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +12 -0
- package/src/InteractiveMap/InteractiveMap.test.js +2 -0
- package/src/config/appConfig.js +3 -1
- package/src/config/appConfig.test.js +4 -0
- package/src/config/defaults.js +1 -1
- package/src/config/events.js +8 -0
- package/src/config/mapTheme.js +8 -4
- package/src/config/symbolConfig.js +23 -16
- package/src/scss/main.scss +3 -0
- package/src/scss/tools/_border-focus.scss +0 -1
- package/src/services/announcer.test.js +5 -7
- package/src/services/eventBus.js +15 -0
- package/src/services/eventBus.test.js +11 -0
- package/src/services/hints.js +47 -0
- package/src/services/hints.test.js +110 -0
- package/src/services/patternRegistry.js +69 -1
- package/src/services/patternRegistry.test.js +112 -1
- package/src/services/reverseGeocode.js +4 -0
- package/src/services/reverseGeocode.test.js +11 -1
- package/src/services/symbolRegistry.js +152 -17
- package/src/services/symbolRegistry.test.js +141 -31
- package/src/types.js +71 -14
- package/src/utils/constrainKeyboardFocus.js +5 -5
- package/src/utils/detectInterfaceType.js +27 -5
- package/src/utils/detectInterfaceType.test.js +29 -3
- package/src/utils/patternUtils.js +4 -67
- package/src/utils/patternUtils.test.js +1 -123
- package/src/utils/symbolUtils.js +11 -13
- package/src/utils/symbolUtils.test.js +18 -26
- package/webpack.dev.mjs +6 -1
- package/webpack.umd.mjs +1 -0
- package/plugins/interact/dist/css/index.css +0 -1
- package/plugins/interact/src/interact.scss +0 -24
|
@@ -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.interactPlugin=t())}(this,()=>(()=>{"use strict";var e,t,r={738(e){e.exports=preactCompat},287(e){e.exports=preactJsxRuntime}},n={};function o(e){var t=n[e];if(void 0!==t)return t.exports;var i=n[e]={exports:{}};return r[e](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-interact-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.interactPlugin:",o.l=(r,n,i,a)=>{if(e[r])e[r].push(n);else{var c,u;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){c=l;break}}c||(u=!0,(c=document.createElement("script")).charset="utf-8",o.nc&&c.setAttribute("nonce",o.nc),c.setAttribute("data-webpack",t+i),c.src=r),e[r]=[n];var s=(t,n)=>{c.onerror=c.onload=null,clearTimeout(b);var o=e[r];if(delete e[r],c.parentNode&&c.parentNode.removeChild(c),o&&o.forEach(e=>e(n)),t)return t(n)},b=setTimeout(s.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=s.bind(null,c.onerror),c.onload=s.bind(null,c.onload),u&&document.head.appendChild(c)}},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),c=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;c.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",c.name="ChunkLoadError",c.type=i,c.request=a,n[1](c)}},"chunk-"+t,t)}};var t=(t,r)=>{var n,i,[a,c,u]=r,f=0;if(a.some(t=>0!==e[t])){for(n in c)o.o(c,n)&&(o.m[n]=c[n]);u&&u(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 c(){var e,t,r="function"==typeof Symbol?Symbol:{},n=r.iterator||"@@iterator",o=r.toStringTag||"@@toStringTag";function i(r,n,o,i){var c=n&&n.prototype instanceof f?n:f,p=Object.create(c.prototype);return u(p,"_invoke",function(r,n,o){var i,c,u,f=0,p=o||[],l=!1,s={p:0,n:0,v:e,a:b,f:b.bind(e,4),d:function(t,r){return i=t,c=0,u=e,s.n=r,a}};function b(r,n){for(c=r,u=n,t=0;!l&&f&&!o&&t<p.length;t++){var o,i=p[t],b=s.p,d=i[2];r>3?(o=d===n)&&(u=i[(c=i[4])?5:(c=3,3)],i[4]=i[5]=e):i[0]<=b&&((o=r<2&&b<i[1])?(c=0,s.v=n,s.n=i[1]):b<d&&(o=r<3||i[0]>n||n>d)&&(i[4]=r,i[5]=n,s.n=d,c=0))}if(o||r>1)return a;throw l=!0,n}return function(o,p,d){if(f>1)throw TypeError("Generator is already running");for(l&&1===p&&b(p,d),c=p,u=d;(t=c<2?e:u)||!l;){i||(c?c<3?(c>1&&(s.n=-1),b(c,u)):s.n=u:s.v=u);try{if(f=2,i){if(c||(o="next"),t=i[o]){if(!(t=t.call(i,u)))throw TypeError("iterator result is not an object");if(!t.done)return t;u=t.value,c<2&&(c=0)}else 1===c&&(t=i.return)&&t.call(i),c<2&&(u=TypeError("The iterator does not provide a '"+o+"' method"),c=1);i=e}else if((t=(l=s.n<0)?u:r.call(n,s))!==a)break}catch(t){i=e,c=1,u=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]())):(u(t={},n,function(){return this}),t),b=l.prototype=f.prototype=Object.create(s);function d(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,l):(e.__proto__=l,u(e,o,"GeneratorFunction")),e.prototype=Object.create(b),e}return p.prototype=l,u(b,"constructor",l),u(l,"constructor",p),p.displayName="GeneratorFunction",u(l,o,"GeneratorFunction"),u(b),u(b,o,"Generator"),u(b,n,function(){return this}),u(b,"toString",function(){return"[object Generator]"}),(c=function(){return{w:i,m:d}})()}function u(e,t,r,n){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}u=function(e,t,r,n){function i(t,r){u(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))},u(e,t,r,n)}function f(e,t,r,n,o,i,a){try{var c=e[i](a),u=c.value}catch(e){return void r(e)}c.done?t(u):Promise.resolve(u).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 b(){var e,t;return l(l({},arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),{},{id:"interact",load:(e=c().m(function e(){var t;return c().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,o.e(936).then(o.bind(o,
|
|
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.interactPlugin=t())}(this,()=>(()=>{"use strict";var e,t,r={738(e){e.exports=preactCompat},287(e){e.exports=preactJsxRuntime}},n={};function o(e){var t=n[e];if(void 0!==t)return t.exports;var i=n[e]={exports:{}};return r[e](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-interact-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.interactPlugin:",o.l=(r,n,i,a)=>{if(e[r])e[r].push(n);else{var c,u;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){c=l;break}}c||(u=!0,(c=document.createElement("script")).charset="utf-8",o.nc&&c.setAttribute("nonce",o.nc),c.setAttribute("data-webpack",t+i),c.src=r),e[r]=[n];var s=(t,n)=>{c.onerror=c.onload=null,clearTimeout(b);var o=e[r];if(delete e[r],c.parentNode&&c.parentNode.removeChild(c),o&&o.forEach(e=>e(n)),t)return t(n)},b=setTimeout(s.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=s.bind(null,c.onerror),c.onload=s.bind(null,c.onload),u&&document.head.appendChild(c)}},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),c=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;c.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",c.name="ChunkLoadError",c.type=i,c.request=a,n[1](c)}},"chunk-"+t,t)}};var t=(t,r)=>{var n,i,[a,c,u]=r,f=0;if(a.some(t=>0!==e[t])){for(n in c)o.o(c,n)&&(o.m[n]=c[n]);u&&u(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 c(){var e,t,r="function"==typeof Symbol?Symbol:{},n=r.iterator||"@@iterator",o=r.toStringTag||"@@toStringTag";function i(r,n,o,i){var c=n&&n.prototype instanceof f?n:f,p=Object.create(c.prototype);return u(p,"_invoke",function(r,n,o){var i,c,u,f=0,p=o||[],l=!1,s={p:0,n:0,v:e,a:b,f:b.bind(e,4),d:function(t,r){return i=t,c=0,u=e,s.n=r,a}};function b(r,n){for(c=r,u=n,t=0;!l&&f&&!o&&t<p.length;t++){var o,i=p[t],b=s.p,d=i[2];r>3?(o=d===n)&&(u=i[(c=i[4])?5:(c=3,3)],i[4]=i[5]=e):i[0]<=b&&((o=r<2&&b<i[1])?(c=0,s.v=n,s.n=i[1]):b<d&&(o=r<3||i[0]>n||n>d)&&(i[4]=r,i[5]=n,s.n=d,c=0))}if(o||r>1)return a;throw l=!0,n}return function(o,p,d){if(f>1)throw TypeError("Generator is already running");for(l&&1===p&&b(p,d),c=p,u=d;(t=c<2?e:u)||!l;){i||(c?c<3?(c>1&&(s.n=-1),b(c,u)):s.n=u:s.v=u);try{if(f=2,i){if(c||(o="next"),t=i[o]){if(!(t=t.call(i,u)))throw TypeError("iterator result is not an object");if(!t.done)return t;u=t.value,c<2&&(c=0)}else 1===c&&(t=i.return)&&t.call(i),c<2&&(u=TypeError("The iterator does not provide a '"+o+"' method"),c=1);i=e}else if((t=(l=s.n<0)?u:r.call(n,s))!==a)break}catch(t){i=e,c=1,u=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]())):(u(t={},n,function(){return this}),t),b=l.prototype=f.prototype=Object.create(s);function d(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,l):(e.__proto__=l,u(e,o,"GeneratorFunction")),e.prototype=Object.create(b),e}return p.prototype=l,u(b,"constructor",l),u(l,"constructor",p),p.displayName="GeneratorFunction",u(l,o,"GeneratorFunction"),u(b),u(b,o,"Generator"),u(b,n,function(){return this}),u(b,"toString",function(){return"[object Generator]"}),(c=function(){return{w:i,m:d}})()}function u(e,t,r,n){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}u=function(e,t,r,n){function i(t,r){u(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))},u(e,t,r,n)}function f(e,t,r,n,o,i,a){try{var c=e[i](a),u=c.value}catch(e){return void r(e)}c.done?t(u):Promise.resolve(u).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 b(){var e,t;return l(l({},arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),{},{id:"interact",load:(e=c().m(function e(){var t;return c().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,o.e(936).then(o.bind(o,559));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,c,"next",e)}function c(e){f(i,n,o,a,c,"throw",e)}a(void 0)})},function(){return t.apply(this,arguments)})})}return o.d(i,{default:()=>b}),i.default})());
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
2
|
import { EVENTS } from '../../../src/config/events.js'
|
|
3
3
|
import { useInteractionHandlers } from './hooks/useInteractionHandlers.js'
|
|
4
|
+
import { useMapItemList } from './hooks/useMapItemList.js'
|
|
4
5
|
import { useHighlightSync } from './hooks/useHighlightSync.js'
|
|
5
6
|
import { useHoverCursor } from './hooks/useHoverCursor.js'
|
|
6
|
-
import {
|
|
7
|
+
import { useCrossHairVisibility } from './hooks/useCrossHairVisibility.js'
|
|
8
|
+
import { useAttachEvents } from './hooks/useAttachEvents.js'
|
|
7
9
|
import { isSelectMarkerOnly } from './utils/interactionModes.js'
|
|
8
10
|
|
|
11
|
+
function useListboxCapable ({ enabled, interactionModes, markers, layers, eventBus }) {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!enabled) { return }
|
|
14
|
+
const hasLabeledMarkers = interactionModes?.includes('selectMarker') && markers.items.some(m => m.label)
|
|
15
|
+
const hasFeatureLayers = interactionModes?.includes('selectFeature') && layers.some(l => l.labelProperty)
|
|
16
|
+
if (hasLabeledMarkers || hasFeatureLayers) {
|
|
17
|
+
eventBus.emit('interact:listboxcapable')
|
|
18
|
+
}
|
|
19
|
+
}, [enabled, interactionModes, markers, layers, eventBus])
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
export const InteractInit = ({
|
|
10
23
|
appState,
|
|
11
24
|
mapState,
|
|
@@ -14,14 +27,14 @@ export const InteractInit = ({
|
|
|
14
27
|
mapProvider,
|
|
15
28
|
pluginState
|
|
16
29
|
}) => {
|
|
17
|
-
const {
|
|
18
|
-
const { dispatch, enabled, selectedFeatures, selectionBounds, interactionModes, layers } = pluginState
|
|
30
|
+
const { dispatch, enabled, selectedFeatures, interactionModes, layers } = pluginState
|
|
19
31
|
const { eventBus, closeApp } = services
|
|
20
|
-
const { crossHair, mapStyle } = mapState
|
|
32
|
+
const { crossHair, mapStyle, markers } = mapState
|
|
21
33
|
|
|
22
|
-
const isTouchOrKeyboard = ['touch', 'keyboard'].includes(interfaceType)
|
|
23
34
|
const selectMarkerOnly = isSelectMarkerOnly(interactionModes)
|
|
24
35
|
|
|
36
|
+
useMapItemList({ mapState, pluginState, services, mapProvider })
|
|
37
|
+
|
|
25
38
|
// Core interaction logic (click > select/marker)
|
|
26
39
|
const { handleInteraction } = useInteractionHandlers({
|
|
27
40
|
appState,
|
|
@@ -31,34 +44,12 @@ export const InteractInit = ({
|
|
|
31
44
|
mapProvider
|
|
32
45
|
})
|
|
33
46
|
|
|
34
|
-
// Refs updated synchronously each render — keeps callbacks fresh without re-attaching events
|
|
35
|
-
const handleInteractionRef = useRef(handleInteraction)
|
|
36
|
-
handleInteractionRef.current = handleInteraction
|
|
37
|
-
|
|
38
|
-
const pluginStateRef = useRef(pluginState)
|
|
39
|
-
pluginStateRef.current = pluginState
|
|
40
|
-
|
|
41
|
-
const appStateRef = useRef(appState)
|
|
42
|
-
appStateRef.current = appState
|
|
43
|
-
|
|
44
|
-
// Defer click handling by one macrotask so any click that triggered the enable
|
|
45
|
-
// (e.g. finishing a draw gesture) fires before this handler is live.
|
|
46
|
-
// Managed separately from attachEvents so re-runs of that effect don't reset it —
|
|
47
|
-
// only resets when enabled actually changes.
|
|
48
|
-
const clickReadyRef = useRef(false)
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
clickReadyRef.current = false
|
|
51
|
-
const timer = setTimeout(() => { clickReadyRef.current = true }, 0)
|
|
52
|
-
return () => clearTimeout(timer)
|
|
53
|
-
}, [pluginState.enabled])
|
|
54
|
-
|
|
55
47
|
// Highlight features and sync state selectedBounds from mapProvider
|
|
56
48
|
useHighlightSync({
|
|
57
49
|
mapProvider,
|
|
58
50
|
mapStyle,
|
|
59
51
|
pluginState,
|
|
60
52
|
selectedFeatures,
|
|
61
|
-
selectionBounds,
|
|
62
53
|
dispatch,
|
|
63
54
|
events: EVENTS,
|
|
64
55
|
eventBus
|
|
@@ -69,36 +60,13 @@ export const InteractInit = ({
|
|
|
69
60
|
eventBus.emit('interact:active', { active: enabled, interactionModes })
|
|
70
61
|
}, [enabled, interactionModes])
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Toggle target marker visibility
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (enabled && isTouchOrKeyboard && !(interfaceType === 'touch' && selectMarkerOnly)) {
|
|
77
|
-
crossHair.fixAtCenter()
|
|
78
|
-
} else {
|
|
79
|
-
crossHair.hide()
|
|
80
|
-
}
|
|
81
|
-
}, [enabled, interfaceType, interactionModes])
|
|
63
|
+
useListboxCapable({ enabled, interactionModes, markers, layers, eventBus })
|
|
82
64
|
|
|
83
|
-
|
|
84
|
-
if (!pluginState.enabled) {
|
|
85
|
-
return undefined // Explicit return
|
|
86
|
-
}
|
|
65
|
+
useHoverCursor(mapProvider, enabled, interactionModes, layers)
|
|
87
66
|
|
|
88
|
-
|
|
89
|
-
getAppState: () => appStateRef.current,
|
|
90
|
-
mapState,
|
|
91
|
-
getPluginState: () => pluginStateRef.current,
|
|
92
|
-
buttonConfig,
|
|
93
|
-
events: EVENTS,
|
|
94
|
-
eventBus,
|
|
95
|
-
handleInteraction: (event) => handleInteractionRef.current(event),
|
|
96
|
-
clickReadyRef,
|
|
97
|
-
closeApp
|
|
98
|
-
})
|
|
67
|
+
useCrossHairVisibility({ crossHair, enabled, selectMarkerOnly, appState })
|
|
99
68
|
|
|
100
|
-
|
|
101
|
-
}, [pluginState.enabled, buttonConfig, eventBus, closeApp])
|
|
69
|
+
useAttachEvents({ pluginState, appState, mapState, buttonConfig, eventBus, handleInteraction, closeApp })
|
|
102
70
|
|
|
103
71
|
return null
|
|
104
72
|
}
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
2
|
import { EVENTS } from '../../../src/config/events.js'
|
|
3
3
|
import { InteractInit } from './InteractInit.jsx'
|
|
4
4
|
import { useInteractionHandlers } from './hooks/useInteractionHandlers.js'
|
|
5
5
|
import { useHighlightSync } from './hooks/useHighlightSync.js'
|
|
6
6
|
import { useHoverCursor } from './hooks/useHoverCursor.js'
|
|
7
|
-
import {
|
|
7
|
+
import { useMapItemList } from './hooks/useMapItemList.js'
|
|
8
|
+
|
|
9
|
+
const LISTBOX_CAPABLE = 'interact:listboxcapable'
|
|
8
10
|
|
|
9
11
|
jest.mock('./hooks/useInteractionHandlers.js')
|
|
10
12
|
jest.mock('./hooks/useHighlightSync.js')
|
|
11
13
|
jest.mock('./hooks/useHoverCursor.js')
|
|
12
|
-
jest.mock('./
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
layers: []
|
|
42
|
-
}
|
|
14
|
+
jest.mock('./hooks/useMapItemList.js')
|
|
15
|
+
jest.mock('./hooks/useCrossHairVisibility.js')
|
|
16
|
+
jest.mock('./hooks/useAttachEvents.js')
|
|
17
|
+
|
|
18
|
+
let props
|
|
19
|
+
let handleInteractionMock
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
handleInteractionMock = jest.fn()
|
|
23
|
+
|
|
24
|
+
useInteractionHandlers.mockReturnValue({ handleInteraction: handleInteractionMock })
|
|
25
|
+
useHighlightSync.mockReturnValue(undefined)
|
|
26
|
+
useHoverCursor.mockReturnValue(undefined)
|
|
27
|
+
useMapItemList.mockReturnValue(undefined)
|
|
28
|
+
|
|
29
|
+
props = {
|
|
30
|
+
appState: { interfaceType: 'mouse', layoutRefs: { viewportRef: { current: document.createElement('div') }, appContainerRef: { current: document.createElement('div') } } },
|
|
31
|
+
mapState: { crossHair: { fixAtCenter: jest.fn(), hide: jest.fn() }, mapStyle: {} },
|
|
32
|
+
services: { eventBus: { emit: jest.fn() }, closeApp: jest.fn() },
|
|
33
|
+
buttonConfig: {},
|
|
34
|
+
mapProvider: { setHoverCursor: jest.fn() },
|
|
35
|
+
pluginState: {
|
|
36
|
+
dispatch: jest.fn(),
|
|
37
|
+
enabled: true,
|
|
38
|
+
selectedFeatures: [],
|
|
39
|
+
selectedMarkers: [],
|
|
40
|
+
selectionBounds: {},
|
|
41
|
+
interactionModes: ['selectFeature'],
|
|
42
|
+
layers: []
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
}
|
|
45
|
+
})
|
|
45
46
|
|
|
47
|
+
describe('InteractInit — hook delegation', () => {
|
|
46
48
|
it('calls useInteractionHandlers with correct arguments', () => {
|
|
47
49
|
render(<InteractInit {...props} />)
|
|
48
50
|
expect(useInteractionHandlers).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -66,44 +68,9 @@ describe('InteractInit', () => {
|
|
|
66
68
|
eventBus: props.services.eventBus
|
|
67
69
|
}))
|
|
68
70
|
})
|
|
71
|
+
})
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
// enabled true + non-touch = hide
|
|
72
|
-
render(<InteractInit {...props} />)
|
|
73
|
-
expect(props.mapState.crossHair.hide).toHaveBeenCalled()
|
|
74
|
-
expect(props.mapState.crossHair.fixAtCenter).not.toHaveBeenCalled()
|
|
75
|
-
|
|
76
|
-
// touch interface
|
|
77
|
-
props.appState.interfaceType = 'touch'
|
|
78
|
-
render(<InteractInit {...props} />)
|
|
79
|
-
expect(props.mapState.crossHair.fixAtCenter).toHaveBeenCalled()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('attaches events and returns cleanup', () => {
|
|
83
|
-
const { unmount } = render(<InteractInit {...props} />)
|
|
84
|
-
expect(attachEvents).toHaveBeenCalledWith(expect.objectContaining({
|
|
85
|
-
getAppState: expect.any(Function),
|
|
86
|
-
getPluginState: expect.any(Function),
|
|
87
|
-
handleInteraction: expect.any(Function),
|
|
88
|
-
mapState: props.mapState,
|
|
89
|
-
buttonConfig: props.buttonConfig,
|
|
90
|
-
events: EVENTS,
|
|
91
|
-
eventBus: props.services.eventBus,
|
|
92
|
-
closeApp: props.services.closeApp
|
|
93
|
-
}))
|
|
94
|
-
|
|
95
|
-
const { getAppState, getPluginState, handleInteraction } = attachEvents.mock.calls.at(-1)[0]
|
|
96
|
-
expect(getAppState()).toMatchObject(props.appState)
|
|
97
|
-
expect(getPluginState()).toMatchObject({ enabled: props.pluginState.enabled })
|
|
98
|
-
|
|
99
|
-
const event = { point: {}, coords: [] }
|
|
100
|
-
handleInteraction(event)
|
|
101
|
-
expect(handleInteractionMock).toHaveBeenCalledWith(event)
|
|
102
|
-
|
|
103
|
-
unmount()
|
|
104
|
-
expect(cleanupMock).toHaveBeenCalled()
|
|
105
|
-
})
|
|
106
|
-
|
|
73
|
+
describe('InteractInit — event bus emissions', () => {
|
|
107
74
|
it('emits interact:active with active state and interactionModes on enable', () => {
|
|
108
75
|
render(<InteractInit {...props} />)
|
|
109
76
|
expect(props.services.eventBus.emit).toHaveBeenCalledWith('interact:active', {
|
|
@@ -112,20 +79,31 @@ describe('InteractInit', () => {
|
|
|
112
79
|
})
|
|
113
80
|
})
|
|
114
81
|
|
|
115
|
-
it('
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
82
|
+
it('emits interact:listboxcapable when enabled with a feature layer that has a labelProperty', () => {
|
|
83
|
+
const capableProps = {
|
|
84
|
+
...props,
|
|
85
|
+
pluginState: { ...props.pluginState, interactionModes: ['selectFeature'], layers: [{ layerId: 'myLayer', labelProperty: 'name' }] }
|
|
86
|
+
}
|
|
87
|
+
render(<InteractInit {...capableProps} />)
|
|
88
|
+
expect(capableProps.services.eventBus.emit).toHaveBeenCalledWith(LISTBOX_CAPABLE)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('emits interact:listboxcapable when enabled with a labeled marker', () => {
|
|
92
|
+
const capableProps = {
|
|
93
|
+
...props,
|
|
94
|
+
mapState: { ...props.mapState, markers: { items: [{ id: 'm1', label: 'My marker' }] } },
|
|
95
|
+
pluginState: { ...props.pluginState, interactionModes: ['selectMarker'] }
|
|
96
|
+
}
|
|
97
|
+
render(<InteractInit {...capableProps} />)
|
|
98
|
+
expect(capableProps.services.eventBus.emit).toHaveBeenCalledWith(LISTBOX_CAPABLE)
|
|
120
99
|
})
|
|
121
100
|
|
|
122
|
-
it('does not
|
|
101
|
+
it('does not emit interact:listboxcapable when disabled', () => {
|
|
123
102
|
const disabledProps = {
|
|
124
103
|
...props,
|
|
125
|
-
pluginState: { ...props.pluginState, enabled: false
|
|
104
|
+
pluginState: { ...props.pluginState, enabled: false, interactionModes: ['selectFeature'], layers: [{ layerId: 'myLayer', labelProperty: 'name' }] }
|
|
126
105
|
}
|
|
127
|
-
attachEvents.mockClear() // ensure previous calls don't interfere
|
|
128
106
|
render(<InteractInit {...disabledProps} />)
|
|
129
|
-
expect(
|
|
107
|
+
expect(disabledProps.services.eventBus.emit).not.toHaveBeenCalledWith(LISTBOX_CAPABLE)
|
|
130
108
|
})
|
|
131
109
|
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { EVENTS } from '../../../../src/config/events.js'
|
|
3
|
+
import { attachEvents } from '../events.js'
|
|
4
|
+
|
|
5
|
+
export function useAttachEvents ({ pluginState, appState, mapState, buttonConfig, eventBus, handleInteraction, closeApp }) {
|
|
6
|
+
// Refs updated synchronously each render — keeps callbacks fresh without re-attaching events
|
|
7
|
+
const handleInteractionRef = useRef(handleInteraction)
|
|
8
|
+
handleInteractionRef.current = handleInteraction
|
|
9
|
+
|
|
10
|
+
const pluginStateRef = useRef(pluginState)
|
|
11
|
+
pluginStateRef.current = pluginState
|
|
12
|
+
|
|
13
|
+
const appStateRef = useRef(appState)
|
|
14
|
+
appStateRef.current = appState
|
|
15
|
+
|
|
16
|
+
// Defer click handling by one macrotask so any click that triggered the enable
|
|
17
|
+
// (e.g. finishing a draw gesture) fires before this handler is live.
|
|
18
|
+
// Managed separately from attachEvents so re-runs of that effect don't reset it —
|
|
19
|
+
// only resets when enabled actually changes.
|
|
20
|
+
const clickReadyRef = useRef(false)
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
clickReadyRef.current = false
|
|
23
|
+
const timer = setTimeout(() => { clickReadyRef.current = true }, 0)
|
|
24
|
+
return () => clearTimeout(timer)
|
|
25
|
+
}, [pluginState.enabled])
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!pluginState.enabled) {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cleanupEvents = attachEvents({
|
|
33
|
+
getAppState: () => appStateRef.current,
|
|
34
|
+
mapState,
|
|
35
|
+
getPluginState: () => pluginStateRef.current,
|
|
36
|
+
buttonConfig,
|
|
37
|
+
events: EVENTS,
|
|
38
|
+
eventBus,
|
|
39
|
+
handleInteraction: (event) => handleInteractionRef.current(event),
|
|
40
|
+
clickReadyRef,
|
|
41
|
+
closeApp
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return cleanupEvents
|
|
45
|
+
}, [pluginState.enabled, buttonConfig, eventBus, closeApp])
|
|
46
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react'
|
|
2
|
+
import { useAttachEvents } from './useAttachEvents.js'
|
|
3
|
+
import { attachEvents } from '../events.js'
|
|
4
|
+
import { EVENTS } from '../../../../src/config/events.js'
|
|
5
|
+
|
|
6
|
+
jest.mock('../events.js')
|
|
7
|
+
|
|
8
|
+
let props
|
|
9
|
+
let cleanupMock
|
|
10
|
+
let handleInteractionMock
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks()
|
|
14
|
+
cleanupMock = jest.fn()
|
|
15
|
+
handleInteractionMock = jest.fn()
|
|
16
|
+
attachEvents.mockReturnValue(cleanupMock)
|
|
17
|
+
|
|
18
|
+
props = {
|
|
19
|
+
pluginState: { enabled: true },
|
|
20
|
+
appState: { interfaceType: 'mouse' },
|
|
21
|
+
mapState: { mapStyle: {} },
|
|
22
|
+
buttonConfig: {},
|
|
23
|
+
eventBus: { emit: jest.fn() },
|
|
24
|
+
closeApp: jest.fn(),
|
|
25
|
+
handleInteraction: handleInteractionMock
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('useAttachEvents', () => {
|
|
30
|
+
it('attaches events and returns cleanup', () => {
|
|
31
|
+
const { unmount } = renderHook(() => useAttachEvents(props))
|
|
32
|
+
expect(attachEvents).toHaveBeenCalledWith(expect.objectContaining({
|
|
33
|
+
getAppState: expect.any(Function),
|
|
34
|
+
getPluginState: expect.any(Function),
|
|
35
|
+
handleInteraction: expect.any(Function),
|
|
36
|
+
mapState: props.mapState,
|
|
37
|
+
buttonConfig: props.buttonConfig,
|
|
38
|
+
events: EVENTS,
|
|
39
|
+
eventBus: props.eventBus,
|
|
40
|
+
closeApp: props.closeApp
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const { getAppState, getPluginState, handleInteraction } = attachEvents.mock.calls.at(-1)[0]
|
|
44
|
+
expect(getAppState()).toBe(props.appState)
|
|
45
|
+
expect(getPluginState()).toMatchObject({ enabled: props.pluginState.enabled })
|
|
46
|
+
|
|
47
|
+
handleInteraction({ point: {}, coords: [] })
|
|
48
|
+
expect(handleInteractionMock).toHaveBeenCalled()
|
|
49
|
+
|
|
50
|
+
unmount()
|
|
51
|
+
expect(cleanupMock).toHaveBeenCalled()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('enables click handling after a macrotask', () => {
|
|
55
|
+
jest.useFakeTimers()
|
|
56
|
+
renderHook(() => useAttachEvents(props))
|
|
57
|
+
const { clickReadyRef } = attachEvents.mock.calls[0][0]
|
|
58
|
+
expect(clickReadyRef.current).toBe(false)
|
|
59
|
+
act(() => jest.runAllTimers())
|
|
60
|
+
expect(clickReadyRef.current).toBe(true)
|
|
61
|
+
jest.useRealTimers()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('does not attach events if plugin not enabled', () => {
|
|
65
|
+
renderHook(() => useAttachEvents({ ...props, pluginState: { enabled: false } }))
|
|
66
|
+
expect(attachEvents).not.toHaveBeenCalled()
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import { getInterfaceType, subscribeToInterfaceChangesImmediate } from '../../../../src/utils/detectInterfaceType.js'
|
|
3
|
+
|
|
4
|
+
export function useCrossHairVisibility ({ crossHair, enabled, selectMarkerOnly, appState }) {
|
|
5
|
+
const enabledRef = useRef(enabled)
|
|
6
|
+
enabledRef.current = enabled
|
|
7
|
+
const selectMarkerOnlyRef = useRef(selectMarkerOnly)
|
|
8
|
+
selectMarkerOnlyRef.current = selectMarkerOnly
|
|
9
|
+
const crossHairRef = useRef(crossHair)
|
|
10
|
+
crossHairRef.current = crossHair
|
|
11
|
+
const listboxFocusRef = useRef(false)
|
|
12
|
+
|
|
13
|
+
const updateCrossHair = useCallback(() => {
|
|
14
|
+
const type = getInterfaceType()
|
|
15
|
+
const isToK = ['touch', 'keyboard'].includes(type)
|
|
16
|
+
if (enabledRef.current && !listboxFocusRef.current && isToK && !(type === 'touch' && selectMarkerOnlyRef.current)) {
|
|
17
|
+
crossHairRef.current.fixAtCenter()
|
|
18
|
+
} else {
|
|
19
|
+
crossHairRef.current.hide()
|
|
20
|
+
}
|
|
21
|
+
}, [])
|
|
22
|
+
|
|
23
|
+
// Toggle target marker visibility on enabled/interactionModes changes
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
updateCrossHair()
|
|
26
|
+
}, [enabled, selectMarkerOnly, updateCrossHair])
|
|
27
|
+
|
|
28
|
+
// Toggle target marker visibility immediately on interface type change (no 150ms React delay)
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
return subscribeToInterfaceChangesImmediate(updateCrossHair)
|
|
31
|
+
}, [updateCrossHair])
|
|
32
|
+
|
|
33
|
+
// Hide crosshair when listbox has focus
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const container = appState.layoutRefs?.appContainerRef?.current
|
|
36
|
+
if (!container) { return undefined }
|
|
37
|
+
const handleFocusIn = (e) => {
|
|
38
|
+
const inListbox = !!e.target.closest('[role="listbox"], [role="option"]')
|
|
39
|
+
if (listboxFocusRef.current !== inListbox) {
|
|
40
|
+
listboxFocusRef.current = inListbox
|
|
41
|
+
updateCrossHair()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
container.addEventListener('focusin', handleFocusIn)
|
|
45
|
+
return () => { container.removeEventListener('focusin', handleFocusIn) }
|
|
46
|
+
}, [appState.layoutRefs, updateCrossHair])
|
|
47
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react'
|
|
2
|
+
import { useCrossHairVisibility } from './useCrossHairVisibility.js'
|
|
3
|
+
import { getInterfaceType } from '../../../../src/utils/detectInterfaceType.js'
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../../src/utils/detectInterfaceType.js', () => ({
|
|
6
|
+
getInterfaceType: jest.fn(() => 'mouse'),
|
|
7
|
+
subscribeToInterfaceChangesImmediate: jest.fn(() => () => {})
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
let crossHair
|
|
11
|
+
let appState
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks()
|
|
15
|
+
crossHair = { fixAtCenter: jest.fn(), hide: jest.fn() }
|
|
16
|
+
appState = { layoutRefs: { appContainerRef: { current: document.createElement('div') } } }
|
|
17
|
+
getInterfaceType.mockReturnValue('mouse')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('useCrossHairVisibility', () => {
|
|
21
|
+
it('skips listbox focus listeners when appContainerRef is null', () => {
|
|
22
|
+
appState.layoutRefs.appContainerRef = { current: null }
|
|
23
|
+
renderHook(() => useCrossHairVisibility({ crossHair, enabled: true, selectMarkerOnly: false, appState }))
|
|
24
|
+
expect(crossHair.hide).toHaveBeenCalled()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('shows crosshair on touch/keyboard and hides when listbox has focus', () => {
|
|
28
|
+
const container = appState.layoutRefs.appContainerRef.current
|
|
29
|
+
getInterfaceType.mockReturnValue('touch')
|
|
30
|
+
renderHook(() => useCrossHairVisibility({ crossHair, enabled: true, selectMarkerOnly: false, appState }))
|
|
31
|
+
|
|
32
|
+
expect(crossHair.fixAtCenter).toHaveBeenCalled()
|
|
33
|
+
|
|
34
|
+
// Focus moves into listbox — hide
|
|
35
|
+
const listboxEl = document.createElement('div')
|
|
36
|
+
listboxEl.setAttribute('role', 'listbox')
|
|
37
|
+
container.appendChild(listboxEl)
|
|
38
|
+
act(() => listboxEl.dispatchEvent(new FocusEvent('focusin', { bubbles: true })))
|
|
39
|
+
expect(crossHair.hide).toHaveBeenCalled()
|
|
40
|
+
|
|
41
|
+
// Second focusin still inside listbox — no-op (state unchanged)
|
|
42
|
+
const listboxEl2 = document.createElement('div')
|
|
43
|
+
listboxEl2.setAttribute('role', 'listbox')
|
|
44
|
+
container.appendChild(listboxEl2)
|
|
45
|
+
act(() => listboxEl2.dispatchEvent(new FocusEvent('focusin', { bubbles: true })))
|
|
46
|
+
expect(crossHair.hide).toHaveBeenCalledTimes(1)
|
|
47
|
+
|
|
48
|
+
// Focus moves back out of listbox — show again
|
|
49
|
+
const otherEl = document.createElement('div')
|
|
50
|
+
container.appendChild(otherEl)
|
|
51
|
+
act(() => otherEl.dispatchEvent(new FocusEvent('focusin', { bubbles: true })))
|
|
52
|
+
expect(crossHair.fixAtCenter).toHaveBeenCalledTimes(2)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'react'
|
|
2
2
|
import { buildStylesMap } from '../utils/buildStylesMap.js'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Keeps map highlight rendering in sync with the current selection and keyboard-active item.
|
|
6
|
+
*
|
|
7
|
+
* Calls mapProvider.updateHighlightedFeatures whenever selectedFeatures or listboxActiveItem
|
|
8
|
+
* changes, passing selected features (shown with the selection ring) and the active item
|
|
9
|
+
* (shown with the keyboard cursor ring) as separate arguments so the provider can style them
|
|
10
|
+
* differently. Also re-applies highlights after a map style reload, since highlight layers
|
|
11
|
+
* are removed when the base style changes.
|
|
12
|
+
*
|
|
13
|
+
* Dispatches UPDATE_SELECTED_BOUNDS with the bounding box returned by the provider so
|
|
14
|
+
* downstream consumers (e.g. interact:done) receive up-to-date bounds.
|
|
15
|
+
*/
|
|
4
16
|
export const useHighlightSync = ({
|
|
5
17
|
mapProvider,
|
|
6
18
|
mapStyle,
|
|
@@ -10,7 +22,7 @@ export const useHighlightSync = ({
|
|
|
10
22
|
events,
|
|
11
23
|
eventBus
|
|
12
24
|
}) => {
|
|
13
|
-
const { layers } = pluginState
|
|
25
|
+
const { layers, listboxActiveItem } = pluginState
|
|
14
26
|
|
|
15
27
|
// Memoize stylesMap so it only recalculates when style or layers change
|
|
16
28
|
const stylesMap = useMemo(() => {
|
|
@@ -22,7 +34,10 @@ export const useHighlightSync = ({
|
|
|
22
34
|
|
|
23
35
|
// Force re-application of all selected features
|
|
24
36
|
const updateHighlightedFeatures = () => {
|
|
25
|
-
const
|
|
37
|
+
const activeFeatures = listboxActiveItem
|
|
38
|
+
? [{ featureId: listboxActiveItem.featureId, layerId: listboxActiveItem.layerId, idProperty: listboxActiveItem.idProperty, geometry: listboxActiveItem.geometry }]
|
|
39
|
+
: []
|
|
40
|
+
const bounds = mapProvider.updateHighlightedFeatures?.(selectedFeatures, activeFeatures, stylesMap)
|
|
26
41
|
|
|
27
42
|
dispatch({
|
|
28
43
|
type: 'UPDATE_SELECTED_BOUNDS',
|
|
@@ -31,18 +46,25 @@ export const useHighlightSync = ({
|
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
useEffect(() => {
|
|
34
|
-
if (!mapProvider || !
|
|
49
|
+
if (!mapProvider || !stylesMap) {
|
|
35
50
|
return undefined // Explicit return to match the cleanup function return below
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
// Update updateHighlightedFeatures on interaction
|
|
39
53
|
updateHighlightedFeatures()
|
|
40
54
|
|
|
41
|
-
//
|
|
42
|
-
|
|
55
|
+
// Re-apply after style reload — highlight layers are removed when style reloads.
|
|
56
|
+
// MAP_STYLE_CHANGE fires as soon as the new style is ready, before any plugin has
|
|
57
|
+
// re-added its layers, so we register a one-time MAP_DATA_CHANGE listener instead.
|
|
58
|
+
// MAP_DATA_CHANGE is debounced and fires once all layer additions from every plugin
|
|
59
|
+
// have settled, giving us a single well-timed trigger. Using once() avoids the
|
|
60
|
+
// infinite loop a permanent listener would cause (layer moves re-trigger MAP_DATA_CHANGE).
|
|
61
|
+
const handleStyleChange = () => {
|
|
62
|
+
eventBus.once(events.MAP_DATA_CHANGE, updateHighlightedFeatures)
|
|
63
|
+
}
|
|
64
|
+
eventBus.on(events.MAP_STYLE_CHANGE, handleStyleChange)
|
|
43
65
|
|
|
44
66
|
return () => {
|
|
45
|
-
eventBus.off(events.
|
|
67
|
+
eventBus.off(events.MAP_STYLE_CHANGE, handleStyleChange)
|
|
46
68
|
}
|
|
47
|
-
}, [selectedFeatures, mapProvider, stylesMap])
|
|
69
|
+
}, [selectedFeatures, listboxActiveItem, mapProvider, stylesMap, eventBus])
|
|
48
70
|
}
|