vue3-router-tab 1.2.6 → 1.2.8

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.
@@ -1 +1 @@
1
- (function(T,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],t):(T=typeof globalThis<"u"?globalThis:T||self,t(T["vue3-router-tab"]={},T.Vue,T.VueRouter))})(this,(function(T,t,me){"use strict";function pe(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function $(e,o){const a=e.resolve(o);if(!a||!a.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return a}const ge={path:e=>e.path,fullpath:e=>e.fullPath,fullname:e=>e.fullPath,full:e=>e.fullPath,name:e=>e.name?String(e.name):e.fullPath};function x(e){const o=e.meta?.key;if(typeof o=="function"){const a=o(e);if(typeof a=="string"&&a.length)return a}else if(typeof o=="string"&&o.length){const a=ge[o.toLowerCase()];return a?a(e):o}return e.fullPath}function J(e,o){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:o}function q(e,o){const a=e.meta?.reuse;return typeof a=="boolean"?a:o}function oe(e){const o=e.meta??{},a={};return"title"in o&&(a.title=o.title),"tips"in o&&(a.tips=o.tips),"icon"in o&&(a.icon=o.icon),"closable"in o&&(a.closable=o.closable),"tabClass"in o&&(a.tabClass=o.tabClass),"target"in o&&(a.target=o.target),"href"in o&&(a.href=o.href),a}function _(e,o,a){const r=oe(e);return{id:x(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:J(e,a),reusable:q(e,!1),closable:r.closable??!0,...r,...o}}function G(e,o,a,r){e.find(p=>p.id===o.id)||e.push(o)}function ae(e,o,a){if(!o||o<=0)return;const r=e.filter(i=>i.alive);for(;r.length>o;){const i=r.shift();if(!i||i.id===a)continue;const p=e.findIndex(k=>k.id===i.id);p>-1&&(e[p].alive=!1)}}function he(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ye(e){const o={};return"title"in e&&(o.title=e.title),"tips"in e&&(o.tips=e.tips),"icon"in e&&(o.icon=e.icon),"tabClass"in e&&(o.tabClass=e.tabClass),"closable"in e&&(o.closable=e.closable),o}function Te(e,o={}){const a=pe(o),r=t.reactive([]),i=t.ref(null),p=t.shallowRef(),k=t.ref(null),c=t.computed(()=>r.filter(s=>s.alive).map(s=>s.id));let u=!1;function g(s){const f=typeof s.matched=="object"?s:$(e,s);return{key:x(f),fullPath:f.fullPath,alive:J(f,a.keepAlive),reusable:q(f,!1),matched:f}}function R(s){const f=x(s);let b=r.find(h=>h.id===f);return b?(b.fullPath=s.fullPath,b.to=s.fullPath,b.matched=s,b.alive=J(s,a.keepAlive),b.reusable=q(s,b.reusable),Object.assign(b,oe(s)),b):(b=_(s,{},a.keepAlive),G(r,b,a.appendPosition,i.value),ae(r,a.maxAlive,i.value),b)}async function I(s,f=!1,b=!0){const h=$(e,s),B=x(h),A=i.value===B;b==="sameTab"&&(b=A),b&&await M(B,!0),await e[f?"replace":"push"](h),A&&await z()}function K(s){const f=r.findIndex(D=>D.id===s);if(f===-1)return a.defaultRoute;const b=r[f+1],h=r[f-1],B=r.find(D=>D.id!==s),A=b||h||B;return A?A.to:a.defaultRoute}async function E(s=i.value,f={}){if(!s)return;if(!f.force&&a.keepLastTab&&r.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const h=i.value===s&&f.redirect!==null,B=h?f.redirect??K(s):null;await U(s,{force:f.force}),f.redirect!==null&&h&&B&&await e.replace(B)}async function U(s,f={}){const b=r.findIndex(h=>h.id===s);b!==-1&&(r.splice(b,1),k.value===s&&(k.value=null),i.value===s&&(i.value=null,p.value=void 0))}async function M(s=i.value??void 0,f=!1){s&&(k.value=s,await t.nextTick(),f||await t.nextTick(),k.value=null)}async function w(s=!1){for(const f of r)await M(f.id,s)}async function y(s=a.defaultRoute){r.splice(0,r.length),i.value=null,p.value=void 0;for(const f of a.initialTabs){const b=$(e,f.to),h=_(b,f,a.keepAlive);r.push(h)}await e.replace(s)}async function z(){const s=i.value;s&&await M(s,!0)}function V(s){return typeof s.matched=="object"?x(s):x($(e,s))}function F(){const s=r.find(f=>f.id===i.value);return{tabs:r.map(he),active:s?s.to:null}}async function Y(s){u=!0,r.splice(0,r.length),i.value=null,p.value=void 0;const f=s?.tabs??[];for(const h of f)try{const B=$(e,h.to),A=ye(h),D=_(B,A,a.keepAlive);G(r,D,"last",null)}catch{}u=!1;const b=s?.active??f[f.length-1]?.to??a.defaultRoute;if(b)try{await e.replace(b)}catch{}}return t.watch(()=>e.currentRoute.value,s=>{if(u)return;const f=R(s);i.value=f.id,p.value=f,ae(r,a.maxAlive,i.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(s=>{const f=$(e,s.to),b=_(f,s,a.keepAlive);G(r,b)}),{options:a,tabs:r,activeId:i,current:p,includeKeys:c,refreshingKey:k,openTab:I,closeTab:E,removeTab:U,refreshTab:M,refreshAll:w,reset:y,reload:z,getRouteKey:V,matchRoute:g,snapshot:F,hydrate:Y}}function ie(e){return e?typeof e=="string"?{name:e}:e:{}}const S=Symbol("RouterTabsContext"),N="router-tabs:snapshot";function W(e={}){const{optional:o=!1}=e,a=t.inject(S,null);if(a)return a;const r=t.inject("$tabs",null);if(r)return r;const p=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const ke=864e5;function ve(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const r of a)if(r.startsWith(o))return decodeURIComponent(r.slice(o.length));return null}function re(e,o,a){if(typeof document>"u")return;const{expiresInDays:r=7,path:i="/",domain:p,secure:k,sameSite:c="lax"}=a,u=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(r!==1/0){const g=new Date(Date.now()+r*ke).toUTCString();u.push(`Expires=${g}`)}i&&u.push(`Path=${i}`),p&&u.push(`Domain=${p}`),k&&u.push("Secure"),c&&u.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=u.join("; ")}function le(e,o){if(typeof document>"u")return;const{path:a="/",domain:r}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),a&&i.push(`Path=${a}`),r&&i.push(`Domain=${r}`),document.cookie=i.join("; ")}const Ce=e=>JSON.stringify(e??null),Re=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function H(e={}){const{cookieKey:o=N,serialize:a=Ce,deserialize:r=Re}=e,i=W({optional:!0}),p=t.ref(!0),k=c=>{t.onMounted(async()=>{const u=r(ve(o));if(u&&u.tabs?.length)try{if(p.value=!0,await c.hydrate(u),u.active){await t.nextTick();const R=c.tabs.find(I=>I.to===u.active);R&&(c.activeId.value=R.id,c.current.value=R)}}finally{p.value=!1}else try{p.value=!0;const R=e.fallbackRoute??c.options.defaultRoute;await c.reset(R)}finally{p.value=!1}const g=c.snapshot();g.tabs.length?re(o,a(g),e):le(o,e),p.value=!1}),t.watch(()=>({tabs:c.tabs.map(u=>({to:u.to,title:u.title,tips:u.tips,icon:u.icon,tabClass:u.tabClass,closable:u.closable})),active:c.activeId.value}),()=>{if(p.value)return;const u=c.snapshot();u.tabs.length?re(o,a(u),e):le(o,e)},{deep:!0})};i?k(i):t.onMounted(()=>{const c=W({optional:!0});c&&k(c)})}const we=t.defineComponent({name:"RouterTab",components:{RouterView:me.RouterView},props:{tabs:{type:Array,default:()=>[]},keepAlive:{type:Boolean,default:!0},maxAlive:{type:Number,default:0},keepLastTab:{type:Boolean,default:!0},append:{type:String,default:"last"},defaultPage:{type:[String,Object],default:"/"},tabTransition:{type:[String,Object],default:"router-tab-zoom"},pageTransition:{type:[String,Object],default:()=>({name:"router-tab-swap",mode:"out-in"})},contextmenu:{type:[Boolean,Array],default:!0},cookieKey:{type:String,default:N},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:o}){const a=t.getCurrentInstance();if(!a)throw new Error("[RouterTab] component must be used within a Vue application context.");const r=a.appContext.app.config.globalProperties.$router;if(!r)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Te(r,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(S,i),a.appContext.config.globalProperties.$tabs=i;const p=t.computed(()=>!!a?.slots?.default),k=t.ref(0),c=t.computed(()=>{k.value;const n={};return i.tabs.forEach(l=>{const m=typeof l.title=="string"?l.title:String(l.title||ee(l));n[l.id]=m}),n});function u(){k.value++}const g=new Map,R=new Map;function I(n,l){if(!l||g.has(n))return;g.set(n,l);const m=i.tabs.find(P=>i.getRouteKey(P.to)===n);if(!m)return;const v=[];if(l.routeTabTitle!==void 0){const P=t.watch(()=>{const d=l.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const fe=String(d);m.title=fe,u()}},{immediate:!0});v.push(P)}if(l.routeTabIcon!==void 0){const P=t.watch(()=>{const d=l.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.icon=String(d),u())},{immediate:!0});v.push(P)}if(l.routeTabClosable!==void 0){const P=t.watch(()=>{const d=l.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.closable=!!d,u())},{immediate:!0});v.push(P)}if(l.routeTabMeta!==void 0){const P=t.watch(()=>{const d=l.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(m,d),u())},{immediate:!0,deep:!0});v.push(P)}R.set(n,v)}function K(n){const l=R.get(n);l&&(l.forEach(m=>m()),R.delete(n)),g.delete(n)}function E(n,l){n?n.routeTabTitle!==void 0||n.routeTabIcon!==void 0||n.routeTabClosable!==void 0?I(l,n):n.$&&(n.$.routeTabTitle!==void 0||n.$.routeTabIcon!==void 0||n.$.routeTabClosable!==void 0)&&I(l,n.$):n===null&&K(l)}if(e.cookieKey!==null||e.persistence){const n={...e.persistence??{}};e.cookieKey!==null?n.cookieKey=e.cookieKey||N:n.cookieKey||(n.cookieKey=N),H(n)}const U=t.computed(()=>ie(e.tabTransition)),M=t.computed(()=>ie(e.pageTransition)),w=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),y=t.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),z=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function V(n){return i.tabs.findIndex(l=>l.id===n)}function F(n){const l=V(n.id);return l>0?i.tabs.slice(0,l):[]}function Y(n){const l=V(n.id);return l>-1?i.tabs.slice(l+1):[]}function s(n){return i.tabs.filter(l=>l.id!==n.id)}async function f(n,l){const m=n.filter(v=>v.closable!==!1);if(m.length){for(const v of m)i.activeId.value===v.id?await i.closeTab(v.id,{redirect:l.to,force:!0}):await i.removeTab(v.id,{force:!0});i.activeId.value!==l.id&&await i.openTab(l.to,!0,!1)}}const b={refresh:{label:"Refresh",handler:async({target:n})=>{await i.refreshTab(n.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:n})=>{await i.closeTab(n.id)},enable:({target:n})=>te(n)},closeLefts:{label:"Close to the Left",handler:async({target:n})=>{await f(F(n),n)},enable:({target:n})=>F(n).some(l=>l.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:n})=>{await f(Y(n),n)},enable:({target:n})=>Y(n).some(l=>l.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:n})=>{await f(s(n),n)},enable:({target:n})=>s(n).some(l=>l.closable!==!1)}};function h(){w.visible=!1,w.target=null}function B(n,l){e.contextmenu&&(w.visible=!0,w.target=n,w.position.x=l.clientX,w.position.y=l.clientY,document.addEventListener("click",h,{once:!0}))}function A(n,l){const m=typeof n=="string"?{id:n}:n,v=b[m.id],P=m.label??v?.label??String(m.id),d=m.visible??v?.visible??!0;if(!(typeof d=="function"?d(l):d!==!1))return null;const ne=m.enable??v?.enable??!0,it=typeof ne=="function"?ne(l):ne!==!1,be=m.handler??v?.handler;if(!be)return null;const rt=async()=>{await Promise.resolve(be(l))};return{id:String(m.id),label:P,disabled:!it,action:rt}}const D=t.computed(()=>{if(!w.visible||!w.target||e.contextmenu===!1)return[];const n=Array.isArray(e.contextmenu)?e.contextmenu:z,l={target:w.target,controller:i};return n.map(m=>A(m,l)).filter(m=>!!m)});async function qe(n){n.disabled||(h(),await n.action())}function ee(n){return typeof n.title=="string"&&n.title.trim()?n.title:Array.isArray(n.title)&&n.title.length&&String(n.title[0]).trim()?String(n.title[0]):"Untitled"}function Ge(n){return c.value[n.id]||ee(n)}function te(n){return!(n.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function We(n){await i.closeTab(n.id)}function He(n){i.activeId.value!==n.id&&i.openTab(n.to,!1)}function Qe(n){return["router-tab__item",{"is-active":i.activeId.value===n.id,"is-closable":te(n),"is-dragging":y.dragging&&y.dragTab?.id===n.id,"is-drag-over":y.dropIndex===V(n.id)},n.tabClass]}function Xe(n){return i.refreshingKey.value===i.getRouteKey(n)}function Ze(n,l,m){e.sortable&&(y.dragging=!0,y.dragIndex=l,y.dragTab=n,m.dataTransfer&&(m.dataTransfer.effectAllowed="move",m.dataTransfer.setData("text/plain",n.id)),o("tab-sort",{tab:n,index:l}))}function et(n,l){!e.sortable||!y.dragging||(l.preventDefault(),l.dataTransfer&&(l.dataTransfer.dropEffect="move"))}function tt(n){!e.sortable||!y.dragging||(y.dropIndex=n)}function nt(){!e.sortable||y.dragging}function ot(n,l){if(!(!e.sortable||!y.dragging)){if(l.preventDefault(),y.dragIndex!==-1&&y.dragIndex!==n){const m=i.tabs.splice(y.dragIndex,1)[0];i.tabs.splice(n,0,m),o("tab-sorted",{tab:m,fromIndex:y.dragIndex,toIndex:n})}de()}}function de(){y.dragging=!1,y.dragIndex=-1,y.dropIndex=-1,y.dragTab=null}t.onMounted(()=>{document.addEventListener("keydown",h)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",h),a.appContext.config.globalProperties.$tabs=null,R.forEach(n=>{n.forEach(l=>l())}),R.clear(),g.clear()}),t.watch(()=>e.keepAlive,n=>{i.options.keepAlive=n}),t.watch(()=>i.activeId.value,()=>h()),t.watch(()=>e.contextmenu,n=>{n||h()}),t.watch(()=>D.value.length,n=>{w.visible&&n===0&&h()});const at=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:at,tabTransitionProps:U,pageTransitionProps:M,buildTabClass:Qe,activate:He,close:We,context:w,menuItems:D,handleMenuAction:qe,showContextMenu:B,hideContextMenu:h,getTabTitle:ee,isClosable:te,isRefreshing:Xe,hasCustomSlot:p,onDragStart:Ze,onDragOver:et,onDragEnter:tt,onDragLeave:nt,onDrop:ot,onDragEnd:de,setupComponentWatching:I,cleanupComponentWatching:K,handleComponentRef:E,getReactiveTabTitle:Ge,triggerTabUpdate:u}}}),Be=(e,o)=>{const a=e.__vccOpts||e;for(const[r,i]of o)a[r]=i;return a},Ee={class:"router-tab"},Pe={class:"router-tab__header"},Ae={class:"router-tab__slot-start"},Ie={class:"router-tab__scroll"},De=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],$e=["title"],xe=["onClick"],Me={class:"router-tab__slot-end"},Se={class:"router-tab__container"},Le=["aria-disabled","onClick"];function Ke(e,o,a,r,i,p){const k=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",Ee,[t.createElementVNode("header",Pe,[t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",Ie,[t.createVNode(t.TransitionGroup,t.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:t.withCtx(()=>[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.tabs,(c,u)=>(t.openBlock(),t.createElementBlock("li",{key:c.id,class:t.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:g=>e.activate(c),onAuxclick:t.withModifiers(g=>e.close(c),["middle","prevent"]),onContextmenu:t.withModifiers(g=>e.showContextMenu(c,g),["prevent"]),onDragstart:g=>e.onDragStart(c,u,g),onDragover:g=>e.onDragOver(u,g),onDragenter:g=>e.onDragEnter(u),onDragleave:o[0]||(o[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(u,g),onDragend:o[1]||(o[1]=(...g)=>e.onDragEnd&&e.onDragEnd(...g))},[c.icon?(t.openBlock(),t.createElementBlock("i",{key:0,class:t.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):t.createCommentVNode("",!0),t.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},t.toDisplayString(e.getReactiveTabTitle(c)),9,$e),e.isClosable(c)?(t.openBlock(),t.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:t.withModifiers(g=>e.close(c),["stop"])},null,8,xe)):t.createCommentVNode("",!0)],42,De))),128))]),_:1},16)]),t.createElementVNode("div",Me,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Se,[t.createVNode(k,null,{default:t.withCtx(c=>[e.hasCustomSlot?t.renderSlot(e.$slots,"default",t.normalizeProps(t.mergeProps({key:0},{...c,controller:e.controller,pageRef:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route))}))):(t.openBlock(),t.createElementBlock(t.Fragment,{key:1},[t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[e.controller.options.keepAlive?(t.openBlock(),t.createBlock(t.KeepAlive,{key:0,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isRefreshing(c.route)?t.createCommentVNode("",!0):(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route)),class:"router-tab-page"}))],1032,["include","max"])):t.createCommentVNode("",!0)]),_:2},1040),t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[!e.controller.options.keepAlive||e.isRefreshing(c.route)?(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route)+(e.isRefreshing(c.route)?"-refresh":""),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):t.createCommentVNode("",!0)]),_:2},1040)],64))]),_:3})]),e.context.visible&&e.context.target?(t.openBlock(),t.createElementBlock("div",{key:0,class:"router-tab__contextmenu",style:t.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.menuItems,c=>(t.openBlock(),t.createElementBlock("a",{key:c.id,class:"router-tab__contextmenu-item","aria-disabled":c.disabled,onClick:t.withModifiers(u=>e.handleMenuAction(c),["prevent"])},t.toDisplayString(c.label),9,Le))),128))],4)):t.createCommentVNode("",!0)])}const Q=Be(we,[["render",Ke]]),Ve={class:"router-tabs","aria-hidden":"true"},j=t.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return H(e),(a,r)=>(t.openBlock(),t.createElementBlock("span",Ve))}}),se="tab-theme-style",_e="tab-theme-primary-color",Ne="system",je="(prefers-color-scheme: dark)";let L=null;const C={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Oe={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function X(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??C.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??C.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??C.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??C.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??C.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??C.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??C.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??C.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??C.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??C.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??C.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??C.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??C.iconColor))}function ce(e){if(typeof document>"u")return;const o=document.documentElement,a=window.matchMedia(je),r=()=>{o.dataset.theme=a.matches?"dark":"light"};L&&(a.removeEventListener("change",L),L=null),e==="system"?(r(),L=()=>r(),a.addEventListener("change",L)):o.dataset.theme=e}function ue(e={}){if(typeof window>"u")return;const{styleKey:o=se,defaultStyle:a=Ne}=e,r=window.localStorage.getItem(o)??a;ce(r),X(r==="dark"?Oe:C)}function Ue(e,o){if(typeof window>"u")return;const a=o?.styleKey??se;window.localStorage.setItem(a,e),ce(e)}function ze(e,o){if(typeof window>"u")return;const a=o?.primaryKey??_e;window.localStorage.setItem(a,JSON.stringify(e)),X(e)}function O(e={}){const o=t.ref(e.title||"Untitled"),a=t.ref(e.icon||"mdi-tab"),r=t.ref(e.closable!==!1),i=t.ref(e.meta||{}),p=typeof e.title=="function"?t.computed(e.title):o,k=typeof e.icon=="function"?t.computed(e.icon):a,c=typeof e.closable=="function"?t.computed(e.closable):r,u=typeof e.meta=="function"?t.computed(e.meta):i;return{routeTabTitle:p,routeTabIcon:k,routeTabClosable:c,routeTabMeta:u,updateTitle:E=>{"value"in o&&(o.value=E)},updateIcon:E=>{"value"in a&&(a.value=E)},updateClosable:E=>{"value"in r&&(r.value=E)},updateMeta:E=>{"value"in i&&(i.value=E)}}}function Fe(e,o="Page"){return O({title:t.computed(()=>e.value?"Loading...":o),icon:t.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:t.computed(()=>!e.value)})}function Ye(e,o="Page",a="mdi-page"){return O({title:t.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:t.computed(()=>e.value>0?"mdi-bell-badge":a)})}function Je(e,o="Page"){const a={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return O({title:t.computed(()=>o+a[e.value].suffix),icon:t.computed(()=>a[e.value].icon),closable:t.computed(()=>e.value!=="loading")})}const Z={install(e){if(Z._installed)return;Z._installed=!0,ue();const o=Q.name||"RouterTab",a=j.name||"RouterTabs";e.component(o,Q),e.component(a,j),a!=="router-tabs"&&e.component("router-tabs",j),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[S]},set(r){r&&e.provide(S,r)}})}};T.RouterTab=Q,T.RouterTabs=j,T.default=Z,T.initRouterTabsTheme=ue,T.routerTabsKey=S,T.setRouterTabsPrimary=ze,T.setRouterTabsTheme=Ue,T.useLoadingTab=Fe,T.useNotificationTab=Ye,T.useReactiveTab=O,T.useRouterTabs=W,T.useRouterTabsPersistence=H,T.useStatusTab=Je,Object.defineProperties(T,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(w,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],n):(w=typeof globalThis<"u"?globalThis:w||self,n(w["vue3-router-tab"]={},w.Vue,w.VueRouter))})(this,(function(w,n,Re){"use strict";function Ee(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function D(e,o){const a=e.resolve(o);if(!a||!a.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return a}const Pe={path:e=>e.path,fullpath:e=>e.fullPath,fullname:e=>e.fullPath,full:e=>e.fullPath,name:e=>e.name?String(e.name):e.fullPath};function $(e){const o=e.meta?.key;if(typeof o=="function"){const a=o(e);if(typeof a=="string"&&a.length)return a}else if(typeof o=="string"&&o.length){const a=Pe[o.toLowerCase()];return a?a(e):o}return e.fullPath}function G(e,o){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:o}function X(e,o){const a=e.meta?.reuse;return typeof a=="boolean"?a:o}function se(e){const o=e.meta??{},a={};return"title"in o&&(a.title=o.title),"tips"in o&&(a.tips=o.tips),"icon"in o&&(a.icon=o.icon),"closable"in o&&(a.closable=o.closable),"tabClass"in o&&(a.tabClass=o.tabClass),"target"in o&&(a.target=o.target),"href"in o&&(a.href=o.href),a}function _(e,o,a){const s=se(e);return{id:$(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:G(e,a),reusable:X(e,!1),closable:s.closable??!0,renderKey:typeof o.renderKey=="number"?o.renderKey:0,...s,...o}}function Q(e,o,a,s){if(!e.find(p=>p.id===o.id)){if(a==="next"&&s){const p=e.findIndex(v=>v.id===s);if(p!==-1){e.splice(p+1,0,o);return}}e.push(o)}}function ce(e,o,a){if(!o||o<=0)return;const s=e.filter(i=>i.alive);for(;s.length>o;){const i=s.shift();if(!i||i.id===a)continue;const p=e.findIndex(v=>v.id===i.id);p>-1&&(e[p].alive=!1)}}function Be(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Ae(e){const o={};return"title"in e&&(o.title=e.title),"tips"in e&&(o.tips=e.tips),"icon"in e&&(o.icon=e.icon),"tabClass"in e&&(o.tabClass=e.tabClass),"closable"in e&&(o.closable=e.closable),"renderKey"in e&&typeof e.renderKey=="number"&&(o.renderKey=e.renderKey),o}function Ke(e,o={}){const a=Ee(o),s=n.reactive([]),i=n.ref(null),p=n.shallowRef(),v=n.ref(null),c=n.computed(()=>s.filter(l=>l.alive).map(l=>`${l.id}::${l.renderKey}`));let d=!1;function g(l){const b=typeof l.matched=="object"?l:D(e,l);return{key:$(b),fullPath:b.fullPath,alive:G(b,a.keepAlive),reusable:X(b,!1),matched:b}}function E(l){const b=$(l);let m=s.find(k=>k.id===b);return m?(m.fullPath=l.fullPath,m.to=l.fullPath,m.matched=l,m.alive=G(l,a.keepAlive),m.reusable=X(l,m.reusable),Object.assign(m,se(l)),m):(m=_(l,{},a.keepAlive),Q(s,m,a.appendPosition,i.value),ce(s,a.maxAlive,i.value),m)}async function K(l,b=!1,m=!0){const k=D(e,l),C=$(k),P=i.value===C;m==="sameTab"&&(m=P),m&&await M(C,!0),await e[b?"replace":"push"](k),P&&await T()}function S(l){const b=s.findIndex(A=>A.id===l);if(b===-1)return a.defaultRoute;const m=s[b+1],k=s[b-1],C=s.find(A=>A.id!==l),P=m||k||C;return P?P.to:a.defaultRoute}async function j(l=i.value,b={}){if(!l)return;if(!b.force&&a.keepLastTab&&s.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const k=i.value===l&&b.redirect!==null,C=k?b.redirect??S(l):null;await O(l,{force:b.force}),b.redirect!==null&&k&&C&&await e.replace(C)}async function O(l,b={}){const m=s.findIndex(k=>k.id===l);m!==-1&&(s.splice(m,1),v.value===l&&(v.value=null),i.value===l&&(i.value=null,p.value=void 0))}async function M(l=i.value??void 0,b=!1){if(!l)return;const m=s.find(C=>C.id===l);if(!m)return;const k=a.keepAlive&&m.alive;k&&(m.alive=!1,await n.nextTick()),m.renderKey=(m.renderKey??0)+1,k&&(m.alive=!0,await n.nextTick()),v.value=l,await n.nextTick(),b||await n.nextTick(),v.value=null}async function oe(l=!1){for(const b of s)await M(b.id,l)}async function ae(l=a.defaultRoute){s.splice(0,s.length),i.value=null,p.value=void 0;for(const b of a.initialTabs){const m=D(e,b.to),k=_(m,b,a.keepAlive);s.push(k)}await e.replace(l)}async function T(){const l=i.value;l&&await M(l,!0)}function W(l){return typeof l.matched=="object"?$(l):$(D(e,l))}function x(){const l=s.find(b=>b.id===i.value);return{tabs:s.map(Be),active:l?l.to:null}}async function I(l){d=!0,s.splice(0,s.length),i.value=null,p.value=void 0;const b=l?.tabs??[];for(const k of b)try{const C=D(e,k.to),P=Ae(k),A=_(C,P,a.keepAlive);Q(s,A,"last",null)}catch{}d=!1;const m=l?.active??b[b.length-1]?.to??a.defaultRoute;if(m)try{await e.replace(m)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(d)return;const b=E(l);i.value=b.id,p.value=b,ce(s,a.maxAlive,i.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(l=>{const b=D(e,l.to),m=_(b,l,a.keepAlive);Q(s,m,"last",null)}),{options:a,tabs:s,activeId:i,current:p,includeKeys:c,refreshingKey:v,openTab:K,closeTab:j,removeTab:O,refreshTab:M,refreshAll:oe,reset:ae,reload:T,getRouteKey:W,matchRoute:g,snapshot:x,hydrate:I}}function ue(e){return e?typeof e=="string"?{name:e}:e:{}}const V=Symbol("RouterTabsContext"),Y="router-tabs:snapshot";function Z(e={}){const{optional:o=!1}=e,a=n.inject(V,null);if(a)return a;const s=n.inject("$tabs",null);if(s)return s;const p=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const Ie=864e5;function Se(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const s of a)if(s.startsWith(o))return decodeURIComponent(s.slice(o.length));return null}function de(e,o,a){if(typeof document>"u")return;const{expiresInDays:s=7,path:i="/",domain:p,secure:v,sameSite:c="lax"}=a,d=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(s!==1/0){const g=new Date(Date.now()+s*Ie).toUTCString();d.push(`Expires=${g}`)}i&&d.push(`Path=${i}`),p&&d.push(`Domain=${p}`),v&&d.push("Secure"),c&&d.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=d.join("; ")}function fe(e,o){if(typeof document>"u")return;const{path:a="/",domain:s}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),a&&i.push(`Path=${a}`),s&&i.push(`Domain=${s}`),document.cookie=i.join("; ")}const xe=e=>JSON.stringify(e??null),De=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ee(e={}){const{cookieKey:o=Y,serialize:a=xe,deserialize:s=De}=e,i=Z({optional:!0}),p=n.ref(!0),v=c=>{n.onMounted(async()=>{const d=s(Se(o));if(d&&d.tabs?.length)try{if(p.value=!0,await c.hydrate(d),d.active){await n.nextTick();const E=c.tabs.find(K=>K.to===d.active);E&&(c.activeId.value=E.id,c.current.value=E)}}finally{p.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{p.value=!0;const E=e.fallbackRoute??c.options.defaultRoute;await c.reset(E)}finally{p.value=!1}else p.value=!1;const g=c.snapshot();g.tabs.length?de(o,a(g),e):fe(o,e),p.value=!1}),n.watch(()=>({tabs:c.tabs.map(d=>({to:d.to,title:d.title,tips:d.tips,icon:d.icon,tabClass:d.tabClass,closable:d.closable,renderKey:d.renderKey})),active:c.activeId.value}),()=>{if(p.value)return;const d=c.snapshot();d.tabs.length?de(o,a(d),e):fe(o,e)},{deep:!0})};i?v(i):n.onMounted(()=>{const c=Z({optional:!0});c&&v(c)})}const $e=n.defineComponent({name:"RouterTab",components:{RouterView:Re.RouterView},props:{tabs:{type:Array,default:()=>[]},keepAlive:{type:Boolean,default:!0},maxAlive:{type:Number,default:0},keepLastTab:{type:Boolean,default:!0},append:{type:String,default:"last"},defaultPage:{type:[String,Object],default:"/"},tabTransition:{type:[String,Object],default:"router-tab-zoom"},pageTransition:{type:[String,Object],default:()=>({name:"router-tab-swap",mode:"out-in"})},contextmenu:{type:[Boolean,Array],default:!0},cookieKey:{type:String,default:Y},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:o}){const a=n.getCurrentInstance();if(!a)throw new Error("[RouterTab] component must be used within a Vue application context.");const s=a.appContext.app.config.globalProperties.$router;if(!s)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Ke(s,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(V,i),a.appContext.config.globalProperties.$tabs=i;const p=n.computed(()=>!!a?.slots?.default),v=n.computed(()=>!!a?.slots?.start),c=n.computed(()=>!!a?.slots?.end),d=n.ref(0),g=n.computed(()=>{d.value;const t={};return i.tabs.forEach(r=>{const f=typeof r.title=="string"?r.title:String(r.title||re(r));t[r.id]=f}),t});function E(){d.value++}const K=new Map,S=new Map;function j(t,r){if(!r||K.has(t))return;K.set(t,r);const f=i.tabs.find(y=>i.getRouteKey(y.to)===t);if(!f)return;const h=[];if(r.routeTabTitle!==void 0){const y=n.watch(()=>{const u=r.routeTabTitle;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{if(u!=null){const L=String(u);f.title=L,E()}},{immediate:!0});h.push(y)}if(r.routeTabIcon!==void 0){const y=n.watch(()=>{const u=r.routeTabIcon;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.icon=String(u),E())},{immediate:!0});h.push(y)}if(r.routeTabClosable!==void 0){const y=n.watch(()=>{const u=r.routeTabClosable;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.closable=!!u,E())},{immediate:!0});h.push(y)}if(r.routeTabMeta!==void 0){const y=n.watch(()=>{const u=r.routeTabMeta;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u&&typeof u=="object"&&(Object.assign(f,u),E())},{immediate:!0,deep:!0});h.push(y)}S.set(t,h)}function O(t){const r=S.get(t);r&&(r.forEach(f=>f()),S.delete(t)),K.delete(t)}function M(t,r){t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?j(r,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&j(r,t.$):t===null&&O(r)}if(e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||Y:t.cookieKey||(t.cookieKey=Y),ee(t)}const oe=n.computed(()=>ue(e.tabTransition)),ae=n.computed(()=>ue(e.pageTransition)),T=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),W=n.ref(null),x=n.ref([]),I=n.ref(-1),l=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),b=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function m(t){return i.tabs.findIndex(r=>r.id===t)}function k(t){const r=m(t.id);return r>0?i.tabs.slice(0,r):[]}function C(t){const r=m(t.id);return r>-1?i.tabs.slice(r+1):[]}function P(t){return i.tabs.filter(r=>r.id!==t.id)}async function A(t,r){const f=t.filter(h=>h.closable!==!1);if(f.length){for(const h of f)i.activeId.value===h.id?await i.closeTab(h.id,{redirect:r.to,force:!0}):await i.removeTab(h.id,{force:!0});i.activeId.value!==r.id&&await i.openTab(r.to,!0,!1)}}const tt={refresh:{label:"Refresh",handler:async({target:t})=>{await i.refreshTab(t.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:t})=>{await i.closeTab(t.id)},enable:({target:t})=>ie(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await A(k(t),t)},enable:({target:t})=>k(t).some(r=>r.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await A(C(t),t)},enable:({target:t})=>C(t).some(r=>r.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await A(P(t),t)},enable:({target:t})=>P(t).some(r=>r.closable!==!1)}};function B(){T.visible=!1,T.target=null,I.value=-1,x.value=[]}function nt(t,r){e.contextmenu&&(T.visible=!0,T.target=t,T.position.x=r.clientX,T.position.y=r.clientY,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{at()}))}function ot(t,r){const f=typeof t=="string"?{id:t}:t,h=tt[f.id],y=f.label??h?.label??String(f.id),u=f.visible??h?.visible??!0;if(!(typeof u=="function"?u(r):u!==!1))return null;const le=f.enable??h?.enable??!0,kt=typeof le=="function"?le(r):le!==!1,Ce=f.handler??h?.handler;if(!Ce)return null;const vt=async()=>{await Promise.resolve(Ce(r))};return{id:String(f.id),label:y,disabled:!kt,action:vt}}const U=n.computed(()=>{if(!T.visible||!T.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:b,r={target:T.target,controller:i};return t.map(f=>ot(f,r)).filter(f=>!!f)});function at(){const t=W.value;if(!t)return;const r=8,{innerWidth:f,innerHeight:h}=window,y=t.getBoundingClientRect();let u=T.position.x,L=T.position.y;y.right>f-r&&(u=Math.max(r,f-y.width-r)),y.bottom>h-r&&(L=Math.max(r,h-y.height-r)),(u!==T.position.x||L!==T.position.y)&&(T.position.x=u,T.position.y=L)}function rt(t,r){x.value[r]=t??null}function it(t){if(t<0)return;x.value[t]?.focus({preventScroll:!0})}function q(t,r,f=U.value){if(!f.length)return-1;const h=f.length;let y=t;for(let u=0;u<h;u++)if(y=(y+r+h)%h,!f[y].disabled)return y;return-1}function z(t){I.value=t,!(t<0)&&n.nextTick(()=>it(t))}function Te(t){const r=q(I.value,t);r!==-1&&z(r)}function lt(t){if(!T.visible)return;const r=t.key,f=U.value;if(!f.length)return;if(r==="Tab"){B();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(r))switch(t.preventDefault(),r){case"ArrowDown":case"ArrowRight":Te(1);break;case"ArrowUp":case"ArrowLeft":Te(-1);break;case"Home":z(q(-1,1));break;case"End":z(q(f.length,-1));break;case"Enter":case" ":case"Spacebar":{const y=I.value;if(y>-1){const u=f[y];u.disabled||ke(u)}break}case"Escape":B();break}}async function ke(t){t.disabled||(B(),await t.action())}function re(t){return typeof t.title=="string"&&t.title.trim()?t.title:Array.isArray(t.title)&&t.title.length&&String(t.title[0]).trim()?String(t.title[0]):"Untitled"}function st(t){return g.value[t.id]||re(t)}function ve(t){const r=i.getRouteKey(t),h=i.tabs.find(y=>y.id===r)?.renderKey??0;return`${r}::${h}`}function ct(t){return`${ve(t)}::refresh`}function ie(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function ut(t){await i.closeTab(t.id)}function dt(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}i.activeId.value!==t.id&&i.openTab(t.to,!1)}function ft(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":ie(t),"is-dragging":l.dragging&&l.dragTab?.id===t.id,"is-drag-over":l.dropIndex===m(t.id)},t.tabClass]}function bt(t){return i.refreshingKey.value===i.getRouteKey(t)}function mt(t,r,f){e.sortable&&(l.dragging=!0,l.dragIndex=r,l.dragTab=t,f.dataTransfer&&(f.dataTransfer.effectAllowed="move",f.dataTransfer.setData("text/plain",t.id)),o("tab-sort",{tab:t,index:r}))}function pt(t,r){!e.sortable||!l.dragging||(r.preventDefault(),r.dataTransfer&&(r.dataTransfer.dropEffect="move"))}function gt(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function ht(){!e.sortable||l.dragging}function yt(t,r){if(!(!e.sortable||!l.dragging)){if(r.preventDefault(),l.dragIndex!==-1&&l.dragIndex!==t){const f=i.tabs.splice(l.dragIndex,1)[0];i.tabs.splice(t,0,f),o("tab-sorted",{tab:f,fromIndex:l.dragIndex,toIndex:t})}we()}}function we(){l.dragging=!1,l.dragIndex=-1,l.dropIndex=-1,l.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",B)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",B),a.appContext.config.globalProperties.$tabs=null,S.forEach(t=>{t.forEach(r=>r())}),S.clear(),K.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,()=>B()),n.watch(()=>e.contextmenu,t=>{t||B()}),n.watch(()=>U.value.length,t=>{T.visible&&t===0&&B()}),n.watch(U,t=>{if(x.value=new Array(t.length).fill(null),!T.visible)return;const r=q(-1,1,t);z(r)}),n.watch(()=>T.visible,t=>{t||(I.value=-1,x.value=[])});const Tt=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:Tt,tabTransitionProps:oe,pageTransitionProps:ae,buildTabClass:ft,activate:dt,close:ut,context:T,menuItems:U,handleMenuAction:ke,showContextMenu:nt,hideContextMenu:B,getTabTitle:re,isClosable:ie,isRefreshing:bt,hasCustomSlot:p,hasStartSlot:v,hasEndSlot:c,onDragStart:mt,onDragOver:pt,onDragEnter:gt,onDragLeave:ht,onDrop:yt,onDragEnd:we,setupComponentWatching:j,cleanupComponentWatching:O,handleComponentRef:M,getReactiveTabTitle:st,getComponentCacheKey:ve,getRefreshComponentKey:ct,triggerTabUpdate:E,menuRef:W,highlightedIndex:I,setMenuItemRef:rt,onMenuKeydown:lt,highlightMenuIndex:z}}}),Me=(e,o)=>{const a=e.__vccOpts||e;for(const[s,i]of o)a[s]=i;return a},Le={class:"router-tab"},Ve={class:"router-tab__header"},Ne={class:"router-tab__scroll"},je=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Oe=["title"],Ue=["onClick"],ze={class:"router-tab__container"},_e=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Ye(e,o,a,s,i,p){const v=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Le,[n.createElementVNode("header",Ve,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",Ne,[n.createVNode(n.TransitionGroup,n.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:n.withCtx(()=>[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.tabs,(c,d)=>(n.openBlock(),n.createElementBlock("li",{key:c.id,class:n.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:g=>e.activate(c),onAuxclick:n.withModifiers(g=>e.close(c),["middle","prevent"]),onContextmenu:n.withModifiers(g=>e.showContextMenu(c,g),["prevent"]),onDragstart:g=>e.onDragStart(c,d,g),onDragover:g=>e.onDragOver(d,g),onDragenter:g=>e.onDragEnter(d),onDragleave:o[0]||(o[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(d,g),onDragend:o[1]||(o[1]=(...g)=>e.onDragEnd&&e.onDragEnd(...g))},[c.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},n.toDisplayString(e.getReactiveTabTitle(c)),9,Oe),e.isClosable(c)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(g=>e.close(c),["stop"])},null,8,Ue)):n.createCommentVNode("",!0)],42,je))),128))]),_:1},16)]),n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-end",{"has-content":e.hasEndSlot}])},[n.renderSlot(e.$slots,"end")],2)]),n.createElementVNode("div",ze,[n.createVNode(v,null,{default:n.withCtx(c=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{...c,controller:e.controller,pageRef:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route))}))):(n.openBlock(),n.createElementBlock(n.Fragment,{key:1},[n.createVNode(n.Transition,n.mergeProps(e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:0,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isRefreshing(c.route)?n.createCommentVNode("",!0):(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getComponentCacheKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"}))],1032,["include","max"])):n.createCommentVNode("",!0)]),_:2},1040),n.createVNode(n.Transition,n.mergeProps(e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[!e.controller.options.keepAlive||e.isRefreshing(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getRefreshComponentKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)]),_:2},1040)],64))]),_:3})]),e.context.visible&&e.context.target?(n.openBlock(),n.createElementBlock("div",{key:0,ref:"menuRef",class:"router-tab__contextmenu",role:"menu",onKeydown:o[2]||(o[2]=(...c)=>e.onMenuKeydown&&e.onMenuKeydown(...c)),style:n.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.menuItems,(c,d)=>(n.openBlock(),n.createElementBlock("a",{key:c.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":d===e.highlightedIndex}]),"aria-disabled":c.disabled,disabled:c.disabled,tabindex:c.disabled?-1:d===e.highlightedIndex?0:-1,ref_for:!0,ref:g=>e.setMenuItemRef(g,d),onMouseenter:g=>!c.disabled&&e.highlightMenuIndex(d),onClick:g=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,_e))),128))],36)):n.createCommentVNode("",!0)])}const te=Me($e,[["render",Ye]]),Fe={class:"router-tabs","aria-hidden":"true"},F=n.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return ee(e),(a,s)=>(n.openBlock(),n.createElementBlock("span",Fe))}}),be="tab-theme-style",me="tab-theme-primary-color",He="system",pe="(prefers-color-scheme: dark)";let N=null;const R={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Je={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function We(e){if(typeof window>"u")return null;const o=window.localStorage.getItem(e);if(!o)return null;try{const a=JSON.parse(o);return a&&typeof a=="object"?a:null}catch{return null}}function ne(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??R.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??R.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??R.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??R.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??R.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??R.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??R.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??R.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??R.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??R.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??R.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??R.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??R.iconColor))}function ge(e){if(typeof document>"u")return;const o=document.documentElement,a=window.matchMedia(pe),s=()=>{o.dataset.theme=a.matches?"dark":"light"};N&&(a.removeEventListener("change",N),N=null),e==="system"?(s(),N=()=>s(),a.addEventListener("change",N)):o.dataset.theme=e}function he(e={}){if(typeof window>"u")return;const{styleKey:o=be,primaryKey:a=me,defaultStyle:s=He,defaultPrimary:i}=e,p=window.localStorage.getItem(o)??s;ge(p);const c=p==="dark"||p==="system"&&window.matchMedia(pe).matches?{...Je}:{...R};i&&(c.primary=i);const d=We(a);ne(d?{...c,...d}:c)}function qe(e,o){if(typeof window>"u")return;const a=o?.styleKey??be;window.localStorage.setItem(a,e),ge(e)}function Ge(e,o){if(typeof window>"u")return;const a=o?.primaryKey??me;window.localStorage.setItem(a,JSON.stringify(e)),ne(e)}function H(e,o){if(n.isRef(e)){const s=!n.isReadonly(e);return{value:e,update:s?i=>{e.value=i}:()=>{}}}if(typeof e=="function"){const s=e;return{value:n.computed(s),update:()=>{}}}const a=n.ref(e===void 0?o:e);return{value:a,update:s=>{a.value=s}}}function J(e={}){const o=H(e.title,"Untitled"),a=H(e.icon,""),s=H(e.closable,!0),i=H(e.meta,{});return{routeTabTitle:o.value,routeTabIcon:a.value,routeTabClosable:s.value,routeTabMeta:i.value,updateTitle:o.update,updateIcon:a.update,updateClosable:s.update,updateMeta:i.update}}function Xe(e,o="Page"){return J({title:n.computed(()=>e.value?"Loading...":o),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function Qe(e,o="Page",a="mdi-page"){return J({title:n.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:n.computed(()=>e.value>0?"mdi-bell-badge":a)})}function Ze(e,o="Page"){const a={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return J({title:n.computed(()=>o+a[e.value].suffix),icon:n.computed(()=>a[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let ye=!1;const et={install(e,o){if(ye)return;ye=!0;const{initTheme:a=!0,themeOptions:s,componentName:i=te.name||"RouterTab",tabsComponentName:p=F.name||"RouterTabs"}=o??{};a&&he(s??{}),e.component(i,te),e.component(p,F),p.toLowerCase()!=="router-tabs"&&e.component("router-tabs",F),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[V]},set(v){v&&e.provide(V,v)}})}};w.RouterTab=te,w.RouterTabs=F,w.default=et,w.initRouterTabsTheme=he,w.routerTabsKey=V,w.setRouterTabsPrimary=Ge,w.setRouterTabsTheme=qe,w.useLoadingTab=Xe,w.useNotificationTab=Qe,w.useReactiveTab=J,w.useRouterTabs=Z,w.useRouterTabsPersistence=ee,w.useStatusTab=Ze,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -1,7 +1,10 @@
1
1
  <template>
2
2
  <div class="router-tab">
3
3
  <header class="router-tab__header">
4
- <div class="router-tab__slot-start">
4
+ <div
5
+ class="router-tab__slot-start"
6
+ :class="{ 'has-content': hasStartSlot }"
7
+ >
5
8
  <slot name="start" />
6
9
  </div>
7
10
 
@@ -40,7 +43,10 @@
40
43
  </transition-group>
41
44
  </div>
42
45
 
43
- <div class="router-tab__slot-end">
46
+ <div
47
+ class="router-tab__slot-end"
48
+ :class="{ 'has-content': hasEndSlot }"
49
+ >
44
50
  <slot name="end" />
45
51
  </div>
46
52
  </header>
@@ -70,7 +76,7 @@
70
76
  <component
71
77
  v-if="!isRefreshing(routerSlot.route)"
72
78
  :is="routerSlot.Component"
73
- :key="controller.getRouteKey(routerSlot.route)"
79
+ :key="getComponentCacheKey(routerSlot.route)"
74
80
  :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
75
81
  class="router-tab-page"
76
82
  />
@@ -84,7 +90,7 @@
84
90
  <component
85
91
  v-if="!controller.options.keepAlive || isRefreshing(routerSlot.route)"
86
92
  :is="routerSlot.Component"
87
- :key="controller.getRouteKey(routerSlot.route) + (isRefreshing(routerSlot.route) ? '-refresh' : '')"
93
+ :key="getRefreshComponentKey(routerSlot.route)"
88
94
  :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
89
95
  class="router-tab-page"
90
96
  />
@@ -95,18 +101,26 @@
95
101
 
96
102
  <div
97
103
  v-if="context.visible && context.target"
104
+ ref="menuRef"
98
105
  class="router-tab__contextmenu"
106
+ role="menu"
107
+ @keydown="onMenuKeydown"
99
108
  :style="{ left: context.position.x + 'px', top: context.position.y + 'px' }"
100
109
  >
101
110
  <a
102
- v-for="item in menuItems"
111
+ v-for="(item, index) in menuItems"
103
112
  :key="item.id"
104
- class="router-tab__contextmenu-item"
113
+ role="menuitem"
114
+ :class="['router-tab__contextmenu-item', { 'is-focused': index === highlightedIndex }]"
105
115
  :aria-disabled="item.disabled"
106
- @click.prevent="handleMenuAction(item)"
116
+ :disabled="item.disabled"
117
+ :tabindex="item.disabled ? -1 : index === highlightedIndex ? 0 : -1"
118
+ :ref="el => setMenuItemRef(el, index)"
119
+ @mouseenter="!item.disabled && highlightMenuIndex(index)"
120
+ @click="handleMenuAction(item)"
107
121
  >
108
122
  {{ item.label }}
109
- </a>
123
+ </a>
110
124
  </div>
111
125
  </div>
112
126
  </template>
@@ -116,6 +130,7 @@ import {
116
130
  computed,
117
131
  defineComponent,
118
132
  getCurrentInstance,
133
+ nextTick,
119
134
  onBeforeUnmount,
120
135
  onMounted,
121
136
  provide,
@@ -226,6 +241,8 @@ export default defineComponent({
226
241
  instance.appContext.config.globalProperties.$tabs = controller
227
242
 
228
243
  const hasCustomSlot = computed(() => Boolean(instance?.slots?.default))
244
+ const hasStartSlot = computed(() => Boolean(instance?.slots?.start))
245
+ const hasEndSlot = computed(() => Boolean(instance?.slots?.end))
229
246
 
230
247
  // Force reactivity by creating a trigger ref
231
248
  const tabUpdateTrigger = ref(0)
@@ -390,6 +407,10 @@ export default defineComponent({
390
407
  position: { x: 0, y: 0 }
391
408
  })
392
409
 
410
+ const menuRef = ref<HTMLElement | null>(null)
411
+ const menuItemRefs = ref<(HTMLElement | null)[]>([])
412
+ const highlightedIndex = ref(-1)
413
+
393
414
  // Drag and drop state
394
415
  const dragState = reactive({
395
416
  dragging: false,
@@ -500,6 +521,8 @@ export default defineComponent({
500
521
  function hideContextMenu() {
501
522
  context.visible = false
502
523
  context.target = null
524
+ highlightedIndex.value = -1
525
+ menuItemRefs.value = []
503
526
  }
504
527
 
505
528
  function showContextMenu(tab: TabRecord, event: MouseEvent) {
@@ -510,6 +533,9 @@ export default defineComponent({
510
533
  context.position.y = event.clientY
511
534
 
512
535
  document.addEventListener('click', hideContextMenu, { once: true })
536
+ nextTick(() => {
537
+ adjustMenuPosition()
538
+ })
513
539
  }
514
540
 
515
541
  function normalizeMenuItem(raw: MenuConfig, ctx: MenuActionContext): ResolvedMenuItem | null {
@@ -549,6 +575,128 @@ export default defineComponent({
549
575
  .filter((item): item is ResolvedMenuItem => !!item)
550
576
  })
551
577
 
578
+ function adjustMenuPosition() {
579
+ const menuEl = menuRef.value
580
+ if (!menuEl) return
581
+ const margin = 8
582
+ const { innerWidth, innerHeight } = window
583
+ const rect = menuEl.getBoundingClientRect()
584
+
585
+ let nextX = context.position.x
586
+ let nextY = context.position.y
587
+
588
+ if (rect.right > innerWidth - margin) {
589
+ nextX = Math.max(margin, innerWidth - rect.width - margin)
590
+ }
591
+
592
+ if (rect.bottom > innerHeight - margin) {
593
+ nextY = Math.max(margin, innerHeight - rect.height - margin)
594
+ }
595
+
596
+ if (nextX !== context.position.x || nextY !== context.position.y) {
597
+ context.position.x = nextX
598
+ context.position.y = nextY
599
+ }
600
+ }
601
+
602
+ function setMenuItemRef(el: Element | null, index: number) {
603
+ menuItemRefs.value[index] = (el as HTMLElement) ?? null
604
+ }
605
+
606
+ function focusMenuItem(index: number) {
607
+ if (index < 0) return
608
+ const el = menuItemRefs.value[index]
609
+ el?.focus({ preventScroll: true })
610
+ }
611
+
612
+ function findNextEnabledIndex(start: number, step: 1 | -1, items = menuItems.value): number {
613
+ if (!items.length) return -1
614
+ const total = items.length
615
+ let idx = start
616
+
617
+ for (let i = 0; i < total; i++) {
618
+ idx = (idx + step + total) % total
619
+ if (!items[idx].disabled) return idx
620
+ }
621
+
622
+ return -1
623
+ }
624
+
625
+ function highlightMenuIndex(index: number) {
626
+ highlightedIndex.value = index
627
+ if (index < 0) return
628
+ nextTick(() => focusMenuItem(index))
629
+ }
630
+
631
+ function moveHighlight(step: 1 | -1) {
632
+ const nextIndex = findNextEnabledIndex(highlightedIndex.value, step)
633
+ if (nextIndex !== -1) {
634
+ highlightMenuIndex(nextIndex)
635
+ }
636
+ }
637
+
638
+ function onMenuKeydown(event: KeyboardEvent) {
639
+ if (!context.visible) return
640
+
641
+ const key = event.key
642
+ const items = menuItems.value
643
+ if (!items.length) return
644
+
645
+ if (key === 'Tab') {
646
+ hideContextMenu()
647
+ return
648
+ }
649
+
650
+ const handledKeys = [
651
+ 'ArrowDown',
652
+ 'ArrowUp',
653
+ 'ArrowRight',
654
+ 'ArrowLeft',
655
+ 'Home',
656
+ 'End',
657
+ 'Enter',
658
+ ' ',
659
+ 'Spacebar',
660
+ 'Escape'
661
+ ]
662
+
663
+ if (!handledKeys.includes(key)) return
664
+
665
+ event.preventDefault()
666
+
667
+ switch (key) {
668
+ case 'ArrowDown':
669
+ case 'ArrowRight':
670
+ moveHighlight(1)
671
+ break
672
+ case 'ArrowUp':
673
+ case 'ArrowLeft':
674
+ moveHighlight(-1)
675
+ break
676
+ case 'Home':
677
+ highlightMenuIndex(findNextEnabledIndex(-1, 1))
678
+ break
679
+ case 'End':
680
+ highlightMenuIndex(findNextEnabledIndex(items.length, -1))
681
+ break
682
+ case 'Enter':
683
+ case ' ':
684
+ case 'Spacebar': {
685
+ const index = highlightedIndex.value
686
+ if (index > -1) {
687
+ const item = items[index]
688
+ if (!item.disabled) {
689
+ handleMenuAction(item)
690
+ }
691
+ }
692
+ break
693
+ }
694
+ case 'Escape':
695
+ hideContextMenu()
696
+ break
697
+ }
698
+ }
699
+
552
700
  async function handleMenuAction(item: ResolvedMenuItem) {
553
701
  if (item.disabled) return
554
702
  hideContextMenu()
@@ -568,6 +716,17 @@ export default defineComponent({
568
716
  return reactiveTitles[tab.id] || getTabTitle(tab)
569
717
  }
570
718
 
719
+ function getComponentCacheKey(route: RouteLocationNormalizedLoaded): string {
720
+ const routeKey = controller.getRouteKey(route)
721
+ const tab = controller.tabs.find(item => item.id === routeKey)
722
+ const renderKey = tab?.renderKey ?? 0
723
+ return `${routeKey}::${renderKey}`
724
+ }
725
+
726
+ function getRefreshComponentKey(route: RouteLocationNormalizedLoaded): string {
727
+ return `${getComponentCacheKey(route)}::refresh`
728
+ }
729
+
571
730
  function isClosable(tab: TabRecord) {
572
731
  if (tab.closable === false) return false
573
732
  if (controller.options.keepLastTab && controller.tabs.length <= 1) return false
@@ -579,6 +738,15 @@ export default defineComponent({
579
738
  }
580
739
 
581
740
  function activate(tab: TabRecord) {
741
+ if (tab.href && typeof window !== 'undefined') {
742
+ if (tab.target && tab.target !== '_self') {
743
+ window.open(tab.href as string, tab.target)
744
+ } else {
745
+ window.location.assign(tab.href as string)
746
+ }
747
+ return
748
+ }
749
+
582
750
  if (controller.activeId.value === tab.id) return
583
751
  controller.openTab(tab.to, false)
584
752
  }
@@ -704,6 +872,23 @@ export default defineComponent({
704
872
  }
705
873
  )
706
874
 
875
+ watch(menuItems, items => {
876
+ menuItemRefs.value = new Array(items.length).fill(null)
877
+ if (!context.visible) return
878
+ const first = findNextEnabledIndex(-1, 1, items)
879
+ highlightMenuIndex(first)
880
+ })
881
+
882
+ watch(
883
+ () => context.visible,
884
+ visible => {
885
+ if (!visible) {
886
+ highlightedIndex.value = -1
887
+ menuItemRefs.value = []
888
+ }
889
+ }
890
+ )
891
+
707
892
  const includeKeys = controller.includeKeys
708
893
 
709
894
  return {
@@ -724,6 +909,8 @@ export default defineComponent({
724
909
  isClosable,
725
910
  isRefreshing,
726
911
  hasCustomSlot,
912
+ hasStartSlot,
913
+ hasEndSlot,
727
914
  onDragStart,
728
915
  onDragOver,
729
916
  onDragEnter,
@@ -734,8 +921,15 @@ export default defineComponent({
734
921
  cleanupComponentWatching,
735
922
  handleComponentRef,
736
923
  getReactiveTabTitle,
737
- triggerTabUpdate
924
+ getComponentCacheKey,
925
+ getRefreshComponentKey,
926
+ triggerTabUpdate,
927
+ menuRef,
928
+ highlightedIndex,
929
+ setMenuItemRef,
930
+ onMenuKeydown,
931
+ highlightMenuIndex
738
932
  }
739
933
  }
740
934
  })
741
- </script>
935
+ </script>
@@ -96,6 +96,7 @@ function createTabFromRoute(
96
96
  alive: resolveAlive(route, keepAliveDefault),
97
97
  reusable: resolveReusable(route, false),
98
98
  closable: meta.closable ?? true,
99
+ renderKey: typeof base.renderKey === 'number' ? base.renderKey : 0,
99
100
  ...meta,
100
101
  ...base
101
102
  }
@@ -105,6 +106,14 @@ function insertTab(tabs: TabRecord[], tab: TabRecord, position: 'last' | 'next',
105
106
  const exists = tabs.find(item => item.id === tab.id)
106
107
  if (exists) return
107
108
 
109
+ if (position === 'next' && referenceId) {
110
+ const referenceIndex = tabs.findIndex(item => item.id === referenceId)
111
+ if (referenceIndex !== -1) {
112
+ tabs.splice(referenceIndex + 1, 0, tab)
113
+ return
114
+ }
115
+ }
116
+
108
117
  tabs.push(tab)
109
118
  }
110
119
 
@@ -126,7 +135,8 @@ function toSnapshotTab(tab: TabRecord): RouterTabsSnapshotTab {
126
135
  tips: tab.tips,
127
136
  icon: tab.icon,
128
137
  tabClass: tab.tabClass,
129
- closable: tab.closable
138
+ closable: tab.closable,
139
+ renderKey: tab.renderKey
130
140
  }
131
141
  }
132
142
 
@@ -137,6 +147,9 @@ function fromSnapshotTab(snapshot: RouterTabsSnapshotTab): Partial<TabRecord> {
137
147
  if ('icon' in snapshot) base.icon = snapshot.icon
138
148
  if ('tabClass' in snapshot) base.tabClass = snapshot.tabClass
139
149
  if ('closable' in snapshot) base.closable = snapshot.closable
150
+ if ('renderKey' in snapshot && typeof snapshot.renderKey === 'number') {
151
+ base.renderKey = snapshot.renderKey
152
+ }
140
153
  return base
141
154
  }
142
155
 
@@ -150,7 +163,11 @@ export function createRouterTabs(
150
163
  const activeId = ref<string | null>(null)
151
164
  const current = shallowRef<TabRecord>()
152
165
  const refreshingKey = ref<string | null>(null)
153
- const includeKeys = computed(() => tabs.filter(tab => tab.alive).map(tab => tab.id))
166
+ const includeKeys = computed(() =>
167
+ tabs
168
+ .filter(tab => tab.alive)
169
+ .map(tab => `${tab.id}::${tab.renderKey}`)
170
+ )
154
171
 
155
172
  let isHydrating = false
156
173
 
@@ -259,6 +276,23 @@ export function createRouterTabs(
259
276
 
260
277
  async function refreshTab(id: string | undefined = activeId.value ?? undefined, force = false) {
261
278
  if (!id) return
279
+ const tab = tabs.find(item => item.id === id)
280
+ if (!tab) return
281
+
282
+ const wasAlive = options.keepAlive && tab.alive
283
+
284
+ if (wasAlive) {
285
+ tab.alive = false
286
+ await nextTick()
287
+ }
288
+
289
+ tab.renderKey = (tab.renderKey ?? 0) + 1
290
+
291
+ if (wasAlive) {
292
+ tab.alive = true
293
+ await nextTick()
294
+ }
295
+
262
296
  refreshingKey.value = id
263
297
  await nextTick()
264
298
  if (!force) await nextTick()
package/lib/core/types.ts CHANGED
@@ -23,6 +23,7 @@ export interface TabMeta {
23
23
  export interface TabInput extends Partial<TabMeta> {
24
24
  to: RouteLocationRaw
25
25
  id?: string
26
+ renderKey?: number
26
27
  }
27
28
 
28
29
  export interface TabRecord extends TabMeta {
@@ -32,6 +33,7 @@ export interface TabRecord extends TabMeta {
32
33
  matched: RouteLocationNormalizedLoaded
33
34
  alive: boolean
34
35
  reusable: boolean
36
+ renderKey: number
35
37
  }
36
38
 
37
39
  export type RouterTabsMenuPreset =
@@ -64,6 +66,7 @@ export interface RouterTabsSnapshotTab {
64
66
  icon?: TabRecord['icon']
65
67
  tabClass?: TabRecord['tabClass']
66
68
  closable?: boolean
69
+ renderKey?: number
67
70
  }
68
71
 
69
72
  export interface RouterTabsSnapshot {
package/lib/index.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from './theme'
12
12
 
13
13
  import type { RouterTabsContext } from './core/types'
14
+ import type { RouterTabsThemeOptions } from './theme'
14
15
 
15
16
  export type {
16
17
  TabRecord,
@@ -22,6 +23,28 @@ export type {
22
23
 
23
24
  export type { RouterTabsThemeOptions } from './theme'
24
25
 
26
+ export interface RouterTabsPluginOptions {
27
+ /**
28
+ * Whether to initialise the theme system automatically during install.
29
+ * Defaults to `true`.
30
+ */
31
+ initTheme?: boolean
32
+ /**
33
+ * Theme options passed to `initRouterTabsTheme` when `initTheme` is enabled.
34
+ */
35
+ themeOptions?: RouterTabsThemeOptions
36
+ /**
37
+ * Global component name used when registering `RouterTab`.
38
+ * Defaults to the component's `name` option or `"RouterTab"`.
39
+ */
40
+ componentName?: string
41
+ /**
42
+ * Global component name used when registering `RouterTabs`.
43
+ * Defaults to the component's `name` option or `"RouterTabs"`.
44
+ */
45
+ tabsComponentName?: string
46
+ }
47
+
25
48
  export {
26
49
  routerTabsKey,
27
50
  useRouterTabs,
@@ -45,22 +68,30 @@ export type {
45
68
  ReactiveTabReturn
46
69
  } from './useReactiveTab'
47
70
 
48
- import "./scss/index.scss";
71
+ import './scss/index.scss'
72
+
73
+ let installed = false
49
74
 
50
75
  const plugin: Plugin = {
51
- install(app: App) {
52
- if ((plugin as any)._installed) return
53
- ; (plugin as any)._installed = true
76
+ install(app: App, options?: RouterTabsPluginOptions) {
77
+ if (installed) return
78
+ installed = true
54
79
 
55
- initRouterTabsTheme()
80
+ const {
81
+ initTheme = true,
82
+ themeOptions,
83
+ componentName = RouterTab.name || 'RouterTab',
84
+ tabsComponentName = RouterTabsComponent.name || 'RouterTabs'
85
+ } = options ?? {}
56
86
 
57
- const componentName = RouterTab.name || 'RouterTab'
58
- const persistenceComponentName = RouterTabsComponent.name || 'RouterTabs'
87
+ if (initTheme) {
88
+ initRouterTabsTheme(themeOptions ?? {})
89
+ }
59
90
 
60
91
  app.component(componentName, RouterTab)
61
- app.component(persistenceComponentName, RouterTabsComponent)
92
+ app.component(tabsComponentName, RouterTabsComponent)
62
93
 
63
- if (persistenceComponentName !== 'router-tabs') {
94
+ if (tabsComponentName.toLowerCase() !== 'router-tabs') {
64
95
  app.component('router-tabs', RouterTabsComponent)
65
96
  }
66
97
 
@@ -119,7 +119,7 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
119
119
  } finally {
120
120
  hydrating.value = false
121
121
  }
122
- } else {
122
+ } else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
123
123
  try {
124
124
  hydrating.value = true
125
125
  const fallback = options.fallbackRoute ?? ctrl.options.defaultRoute
@@ -127,6 +127,8 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
127
127
  } finally {
128
128
  hydrating.value = false
129
129
  }
130
+ } else {
131
+ hydrating.value = false
130
132
  }
131
133
 
132
134
  const snapshot = ctrl.snapshot()
@@ -147,7 +149,8 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
147
149
  tips: tab.tips,
148
150
  icon: tab.icon,
149
151
  tabClass: tab.tabClass,
150
- closable: tab.closable
152
+ closable: tab.closable,
153
+ renderKey: tab.renderKey
151
154
  })),
152
155
  active: ctrl.activeId.value
153
156
  }),