vue3-tabor
Version:
Vue 3 routing tabs with keepAlive support, browser-like tab navigation for Vue applications
3 lines (2 loc) • 16.6 kB
JavaScript
;Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");class t{constructor(e,t){this.vnode=e,this.lastAccessed=t}}const o=o=>{const{max:n=10}=o,a=e.reactive({keySet:new Set,keyToWrapper:new Map,componentMap:new WeakMap,refreshing:!1,activeKey:void 0}),r=e.computed((()=>Array.from(a.keySet))),l=e=>{a.keySet.add(e)},i=e=>{e&&(a.keySet.delete(e),a.keyToWrapper.delete(e))};return{state:a,keys:r,setActiveKey:e=>{if(a.activeKey=e,e){const t=a.keyToWrapper.get(e);t&&(t.lastAccessed=Date.now())}},add:l,has:e=>a.keySet.has(e),hasComponent:e=>{const t=a.keyToWrapper.get(e);return Boolean(t&&a.componentMap.has(t))},getComponent:e=>{const t=a.keyToWrapper.get(e);return t?a.componentMap.get(t):void 0},addComponent:(o,r)=>{l(o),a.keySet.size>n&&(()=>{if(0===a.keyToWrapper.size)return;const e=Array.from(a.keyToWrapper.entries());if(0===e.length)return;const t=e.reduce(((e,t)=>e[1].lastAccessed<t[1].lastAccessed?e:t),e[0]),o=null==t?void 0:t[0];o&&i(o)})();const s=new t(e.markRaw(r),Date.now());a.keyToWrapper.set(o,s),a.componentMap.set(s,r)},remove:i,reset:()=>{a.keySet.clear(),a.keyToWrapper.clear()},refresh:async o=>{if(!o)return;const n=a.keyToWrapper.get(o),r=n?a.componentMap.get(n):void 0;if(!n||!r)return;a.keyToWrapper.delete(o);const l=a.activeKey===o;l&&(a.refreshing=!0,a.keySet.delete(o)),await e.nextTick();const i=new t(e.markRaw(r),Date.now());a.keyToWrapper.set(o,i),a.componentMap.set(i,r),l&&(a.refreshing=!1,a.keySet.add(o))},cleanup:()=>{if(a.keySet.size<=n)return;const e=Array.from(a.keyToWrapper.entries()).sort((([,e],[,t])=>e.lastAccessed-t.lastAccessed)),t=e.slice(0,e.length-n);for(const[o]of t)i(o)}}},n=e=>t=>Object.prototype.toString.call(t)===`[object ${e}]`,a=e=>n("String")(e),r=e=>{console.error(`[vue3-tabor]: ${e}`)};const l={key:"fullPath",keepAlive:!0,hideClose:!1},i="line",s=(e,t)=>{const o=e??l.key,i=(s=o,n("Function")(s)?o(t):t[o]);var s;return(e=>a(e)&&""!==e)(i)?i:r("tabKey is not 'path','fullPath' or a function, or the return value of the function is not a non-empty string")},c=e=>{const{key:t=l.key,name:o,keepAlive:n=l.keepAlive,iframeAttributes:a,hideClose:i=l.hideClose}=e.meta.tabConfig??l;if(!t)return r("tabKey is required");const c=s(t,e);if(!c)return r(`TabId is not found, please check the tab key: ${t}`);const d={name:o??e.name??e.path,id:c,keepAlive:n??l.keepAlive,fullPath:e.fullPath,hideClose:i??!1};return a&&(d.iframeAttributes=a),e.meta.routeName&&(d.routeName=e.meta.routeName),d},d=Symbol("tabor-store"),u="tabor-iframe-route",p=(t,n={})=>{const{maxCache:i=10}=n,d=e.reactive({tabs:[],activeTab:void 0,shouldClose:!0}),p=e.computed((()=>d.activeTab)),h=e.computed((()=>{var e;return null==(e=d.activeTab)?void 0:e.id})),f=e.computed((()=>d.tabs.filter((e=>e.iframeAttributes)))),m=o({max:i}),b=e=>{const t=d.tabs.findIndex((({id:t})=>t===e));return t<0&&r(`Tab not found, please check the tab id: ${e}`),t},y=e=>d.tabs.some((({id:t})=>t===e)),g=e=>d.tabs.find((({id:t})=>t===e)),C=e=>d.tabs.find((t=>t.fullPath===e)),k=e=>(d.activeTab=e,e&&m.setActiveKey(e.id),e),T=(e,t)=>{const o=d.tabs.push(e);return m.add(e.id),(null==t?void 0:t.setActive)&&k(e),o},A=e=>e<0?r(`Index is less than 0, please check the index: ${e}`):d.tabs.splice(e,1)[0],S=e=>{const o=b(e);if(o<0)return;const n=A(o);return m.remove(e),(null==n?void 0:n.iframeAttributes)&&n.routeName&&t.removeRoute(n.routeName),n?{...n,index:o}:void 0},P=e=>t.push(e),w=e=>t.replace(e),N=e=>{if(!e)return;if(!g(e))return r(`Tab not found, please check the tab id: ${e}`);m.refresh(e)},x=e=>t.resolve(e).matched.length>0;let O=!1;t.beforeEach(((e,o,n)=>{if(!O){(()=>{try{const e=localStorage.getItem(u);if(!e)return;const o=JSON.parse(e);x({path:o.path})||t.addRoute({path:o.path,name:o.name,meta:o.meta,component:v})}catch(e){console.error("[vue3-tabor]: Failed to restore iframe route from localStorage:",e)}})(),O=!0;const o=localStorage.getItem(u);if(o)try{const t=JSON.parse(o);if(e.path===t.path)return void n({path:e.fullPath,replace:!0})}catch(a){console.error("[vue3-tabor]: Error parsing stored route:",a)}}n()}));const V=async(e,o={replace:!1,refresh:!1})=>{const{replace:n,tabConfig:a}=o;if(!x(e)&&(null==a?void 0:a.iframeAttributes)){const o="string"==typeof e?e:e.path;if(!o)return r(`Path not found, please check the path: ${e}`);const n=`rt-iframe-${o.replace(/\//g,"-")}`,l={path:o,name:n,meta:{tabConfig:a,routeName:n},component:v};t.addRoute(l),(e=>{try{localStorage.setItem(u,JSON.stringify(e))}catch(t){console.error("[vue3-tabor]: Failed to save iframe route to localStorage:",t)}})({path:o,name:n,meta:{tabConfig:a,routeName:n}})}if(n)return w(e);const l=await P(e);if(o.refresh&&l){const e=q(l.to);e&&N(e)}return l},B=async e=>{const{index:t}=e,o=d.tabs[t]??d.tabs[t-1]??void 0;o&&await V(o.fullPath)},q=e=>{var t,o;const n=(null==(o=null==(t=e.meta)?void 0:t.tabConfig)?void 0:o.key)??l.key;return s(n,e)},$=e=>{const t=(e=>{var t;let o;const n="string"==typeof e?{fullPath:e}:e;return"id"in n&&(o=n.id),"fullPath"in n&&(o=n.fullPath?null==(t=C(n.fullPath))?void 0:t.id:void 0),o||r(`Tab not found, please check the param: ${e}`)})(e);if(t)return 1===d.tabs.length&&d.tabs[0].id===t?r(`The last tab cannot be closed:${e}`):S(t)},j=(E=()=>{d.tabs=[]},I=()=>{m.reset()},function(...e){const t=E.apply(this,e);return I.apply(this,e),t});var E,I;const W=e=>{var t,o;if(!e&&!(null==(t=d.activeTab)?void 0:t.id))return;const n=a(e)?{fullPath:e}:e;return n||{id:null==(o=d.activeTab)?void 0:o.id}};return t.afterEach((e=>{const t=q(e);if(!t||!y(t)){const t=c(e);return void(t&&T(t,{setActive:!0}))}const o=g(t);if(!o)return r(`Tab not found, please check the tab id: ${t}`);const n=e.fullPath===o.fullPath,a=n?o:((e,t)=>{const o=b(e);return o<0?r(`Tab not found, please check the tab id: ${e}`):(d.tabs[o]=t,t)})(t,{...o,fullPath:e.fullPath})??o;k(a),n||N(t)})),{$router:t,state:d,createTab:c,indexOf:b,find:g,getTabByFullpath:C,getTabIdByRoute:q,setActive:k,addTab:T,removeTabByIndex:A,removeTabById:S,routerPush:P,routerReplace:w,openTabById:e=>{const t=g(e);return t?P(t.fullPath):r(`Tab not found, please check the tab id: ${e}`)},openNearTab:B,remove:$,refresh:N,clear:j,getRemoveItem:W,setShouldClose:e=>{d.shouldClose=e},currentTab:p,currentTabId:h,open:V,close:async(e,t)=>{var o,n,a;if(!d.shouldClose)return;const l=W(e);if(!l)return;const i=l.id?l.id===(null==(o=d.activeTab)?void 0:o.id):l.fullPath===(null==(n=d.activeTab)?void 0:n.fullPath);i&&k(void 0);const s=$(l);if(s&&i&&await B(s),t&&(t.id||t.fullPath)){const{id:o,fullPath:n}=t;if(o===e)return r("The id of the tab to be closed cannot be the same as the id of the tab to be opened,if you want to open the tab, please use the fullPath parameter.");const l=o?null==(a=g(o))?void 0:a.fullPath:n;return l?(await P(l),s):r(`The fullPath of the tab to be opened is not found, please check ${o||n}.`)}return s},closeOthers:e=>{var t,o;if(!e&&!(null==(t=d.activeTab)?void 0:t.id))return;const n=e??(null==(o=d.activeTab)?void 0:o.id);if(!y(n))return;for(const r of[...d.tabs])if(r.id!==n){const e=g(r.id);if(e){("function"==typeof e.hideClose?e.hideClose(e):e.hideClose)||S(r.id)}}if(!n)return;const a=g(n);a&&k(a)},has:y,retrieveOrCacheComponent:e=>{var t,o;const n=h.value;if(!e||!n)return e;if(m.hasComponent(n))return m.getComponent(n);if(!(null==(t=p.value)?void 0:t.iframeAttributes)){if(null==(o=p.value)?void 0:o.keepAlive){const t=(a=e,r=n,{...a,type:{...a.type,name:r}});return m.addComponent(n,t),m.add(n),m.getComponent(n)}var a,r;return e}},cache:m,iframeTabs:f}},h=e.defineComponent({name:"Iframe",setup(){const t=e.inject(d),o=e.computed((()=>null==t?void 0:t.iframeTabs.value));return()=>{var n;return e.createVNode("div",{class:"tabor-iframe-container"},[null==(n=o.value)?void 0:n.map((o=>{var n;const a=null==(n=null==t?void 0:t.state.activeTab)?void 0:n.id,r=o.id===a||o.keepAlive,l=o.id===a;return r?e.withDirectives(e.createVNode("iframe",e.mergeProps({key:o.id,width:"100%",height:"100%"},o.iframeAttributes),null),[[e.vShow,l]]):null}))])}}}),f=(e,t)=>{const o=e.__vccOpts||e;for(const[n,a]of t)o[n]=a;return o};const v=f(e.defineComponent({name:"RtPages",components:{Iframe:h},setup(){const t=e.inject(d),o=e.inject("pageClass");e.onMounted((()=>{t||console.error("[vue3-tabor]: taborStore not provided. Did you install the plugin correctly?")}));const n=e.computed((()=>null==t?void 0:t.state.activeTab)),a=e.computed((()=>{var e;return null==(e=n.value)?void 0:e.id})),r=e.computed((()=>null==t?void 0:t.cache.state.refreshing)),l=e.computed((()=>{var e;const o=null==t?void 0:t.cache.keys.value;return(null==(e=n.value)?void 0:e.keepAlive)?o:null==o?void 0:o.filter((e=>e!==a.value))}));return{activeTabKey:a,cachedKeys:l,refreshing:r,pageClass:o,retrieveOrCacheComponent:null==t?void 0:t.retrieveOrCacheComponent}}}),[["render",function(t,o,n,a,r,l){const i=e.resolveComponent("router-view"),s=e.resolveComponent("Iframe");return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["tabor-pages",t.pageClass])},[e.createVNode(i,null,{default:e.withCtx((({Component:o})=>{var n;return[(e.openBlock(),e.createBlock(e.KeepAlive,{include:t.cachedKeys},[t.refreshing?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(e.resolveDynamicComponent(null==(n=t.retrieveOrCacheComponent)?void 0:n.call(t,o)),{key:t.activeTabKey}))],1032,["include"]))]})),_:1}),e.createVNode(s)],2)}]]),m={zh:{refresh:"刷新",close:"关闭",closeOthers:"关闭其他"},en:{refresh:"Refresh",close:"Close",closeOthers:"Close Others"}};let b="zh";function y(e){b=e}function g(e){return m[b][e]}const C=e.defineComponent({name:"DropdownMenu",props:{visible:{type:Boolean,required:!0},position:{type:Object,required:!0},disabledActions:{type:Array,default:()=>[]},hideActions:{type:Array,default:()=>[]},language:{type:String,default:"zh"}},emits:["action"],setup(e,{emit:t}){e.language&&e.language!==b&&y(e.language);return{translations:{refresh:g("refresh"),close:g("close"),closeOthers:g("closeOthers")},handleAction:o=>{e.disabledActions.includes(o)||t("action",o)},handleRightClick:e=>{e.preventDefault(),e.stopPropagation()},hideActions:e.hideActions}}}),k={key:1,class:"tabor-dropdown-divider"};const T=f(C,[["render",function(t,o,n,a,r,l){return t.visible?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tabor-dropdown-menu",style:e.normalizeStyle({top:t.position.y+"px",left:t.position.x+"px"}),onContextmenu:o[3]||(o[3]=(...e)=>t.handleRightClick&&t.handleRightClick(...e))},[e.createElementVNode("ul",null,[e.createElementVNode("li",{onClick:o[0]||(o[0]=e=>t.handleAction("refresh"))},[e.createElementVNode("span",null,e.toDisplayString(t.translations.refresh),1)]),t.hideActions.includes("close")?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("li",{key:0,onClick:o[1]||(o[1]=e=>!t.disabledActions.includes("close")&&t.handleAction("close")),class:e.normalizeClass({"tabor-dropdown-item-disabled":t.disabledActions.includes("close")})},[e.createElementVNode("span",null,e.toDisplayString(t.translations.close),1)],2)),t.hideActions.includes("closeOthers")?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("li",k)),t.hideActions.includes("closeOthers")?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("li",{key:2,onClick:o[2]||(o[2]=e=>!t.disabledActions.includes("closeOthers")&&t.handleAction("closeOthers")),class:e.normalizeClass({"tabor-dropdown-item-disabled":t.disabledActions.includes("closeOthers")})},[e.createElementVNode("span",null,e.toDisplayString(t.translations.closeOthers),1)],2))])],36)):e.createCommentVNode("",!0)}],["__scopeId","data-v-54f47c12"]]),A=e.defineComponent({name:"ElementClose",props:{style:{type:Object,required:!1}},setup:t=>()=>e.createVNode("div",{style:{width:"1em",height:"1em",...t.style}},[e.createVNode("svg",{viewBox:"0 0 24 24"},[e.createVNode("path",{fill:"currentColor",d:"m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275q-.275-.275-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7q.275-.275.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275q.275.275.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275L12 13.4Z"},null)])])}),S=e.defineComponent({name:"TaborTabClose",props:{id:{type:String,required:!0}},setup(t){const o=e.inject(d),n=e=>{null==o||o.close({id:t.id}),e.stopPropagation()};return()=>e.createVNode("div",{class:"tabor-remove-icon",onClick:n},[e.createVNode(A,null,null)])}}),P=e.defineComponent({name:"TaborTabLabel",props:{name:{type:[String,Symbol],required:!1,default:void 0}},setup:t=>()=>e.createVNode("div",{class:"tabor-tab-label"},[e.createVNode("span",null,[t.name])])}),w={beforeMount(e,t){const o=o=>{const n=o.target;e===n||e.contains(n)||t.value(o)};e._clickOutsideHandler=o,document.addEventListener("click",o,!0)},unmounted(e){e._clickOutsideHandler&&document.removeEventListener("click",e._clickOutsideHandler,!0)}},N=e.ref(null),x=e.defineComponent({name:"TaborTab",directives:{clickOutside:w},props:{name:{type:[String,Symbol,Function],required:!0},id:{type:String,required:!0},hideClose:{type:Boolean,default:!1},fullPath:{type:String,required:!0},prefix:{type:[Object,Function]}},setup(t){const o=e.inject(d),n=e.inject("tabClass"),a=e.inject("tabType"),r=e.inject("language"),l=e.computed((()=>(null==o?void 0:o.state.tabs.length)??0)),i=e.computed((()=>{var e;return(null==(e=null==o?void 0:o.state.activeTab)?void 0:e.id)===t.id})),s=e.computed((()=>null==o?void 0:o.find(t.id))),c=e.computed((()=>{var e;return l.value>1&&!(null==(e=s.value)?void 0:e.hideClose)&&!t.hideClose})),u=e.computed((()=>{if("function"==typeof t.name){const e=null==o?void 0:o.$router.resolve(t.fullPath);return e?t.name(e):(console.warn(`Route not found for fullPath: ${t.fullPath}`),String(t.name))}return t.name})),p=e.ref(!1),h=e.ref({x:0,y:0});e.computed((()=>{N.value!==t.id&&(p.value=!1)}));const f=e.computed((()=>["tabor-tab",`tabor-tab--${a}`,i.value&&"tabor-tab-active"])),v=()=>{if(N.value=null,i.value)return;const e=null==o?void 0:o.find(t.id);e&&(null==o||o.open(e.fullPath))},m=()=>{p.value=!1,N.value=null},b=e=>{e.preventDefault(),e.stopPropagation();let o=e.clientX,n=e.clientY;o+120>window.innerWidth&&(o=window.innerWidth-120-5);n+110>window.innerHeight&&(n=window.innerHeight-110-5),h.value={x:o,y:n},p.value=!0,N.value=t.id},y=e=>{p.value=!1,N.value=null;if(null==o?void 0:o.find(t.id))switch(e){case"refresh":null==o||o.refresh(t.id);break;case"close":null==o||o.close(t.id);break;case"closeOthers":null==o||o.closeOthers(t.id)}},g=()=>{if(!t.prefix)return null;const n=null==o?void 0:o.find(t.id);return n?e.h(t.prefix,{tab:n}):null};return()=>{let o=null;if(p.value&&N.value===t.id){const n=[],a=[];l.value<=1&&n.push("close"),l.value<=1&&n.push("closeOthers"),t.hideClose&&(a.push("close"),a.push("closeOthers")),o=e.withDirectives(e.h(T,{visible:p.value,position:h.value,disabledActions:n,hideActions:a,language:r,onAction:y}),[[w,m]])}return e.createVNode("div",{class:[...f.value,n],onClick:v,onContextmenu:b},[t.prefix&&e.createVNode("div",{class:"tabor-tab--prefix"},[g()]),e.createVNode(P,{name:u.value},null),c.value&&e.createVNode(S,{id:t.id},null),o])}}}),O=e.defineComponent({name:"Tabs",props:{tabPrefix:{type:Object},hideClose:{type:Boolean,default:!1}},setup(t){const o=e.inject(d),n=e.inject("tabType"),a=e.computed((()=>(null==o?void 0:o.state.tabs)??[])),r=e.computed((()=>["tabor-tabs",`tabor-tabs--${n}`]));return()=>e.createVNode("div",{class:r.value},[a.value.map((o=>e.createVNode(x,e.mergeProps({prefix:t.tabPrefix},o,{key:o.id,hideClose:t.hideClose}),null)))])}}),V=e.defineComponent({name:"vue-tabor",components:{Tabs:O,Page:v},props:{maxAlive:{type:Number,required:!1,default:10},hideClose:{type:Boolean,required:!1,default:!1},tabClass:{type:String},pageClass:{type:String},dropdownClass:{type:String},tabType:{type:String,default:i},tabPrefix:{type:Object},language:{type:String,default:"zh"},showLanguageSwitch:{type:Boolean,default:!0}},setup:t=>(t.language&&y(t.language),e.provide("dropdownClass",t.dropdownClass),e.provide("tabClass",t.tabClass),e.provide("pageClass",t.pageClass),e.provide("tabType",t.tabType??i),e.provide("language",t.language??"zh"),()=>e.createVNode("div",{class:"rt-container"},[e.createVNode(O,{tabPrefix:t.tabPrefix,hideClose:t.hideClose},null),e.createVNode(v,null,null)]))}),B=(e,t)=>{const{router:o}=t,n=((e,t={})=>p(e,t))(o,t);e.provide(d,n),e.config.globalProperties.$taborStore=n},q={install(e,t){B(e,t),e.component("vue-tabor",V)}};exports.Tabor=V,exports.default=q,exports.useTabor=()=>{const t=e.inject(d);return t||r("Tabor store not found"),t};
//# sourceMappingURL=index.cjs.js.map