vue3-router-tab 1.2.5 → 1.2.7

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(y,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):(y=typeof globalThis<"u"?globalThis:y||self,t(y["vue3-router-tab"]={},y.Vue,y.VueRouter))})(this,(function(y,t,ce){"use strict";function ue(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 P(e,a){const n=e.resolve(a);if(!n||!n.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return n}const de={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(e){const a=e.meta?.key;if(typeof a=="function"){const n=a(e);if(typeof n=="string"&&n.length)return n}else if(typeof a=="string"&&a.length){const n=de[a.toLowerCase()];return n?n(e):a}return e.fullPath}function N(e,a){const n=e.meta?.keepAlive;return typeof n=="boolean"?n:a}function j(e,a){const n=e.meta?.reuse;return typeof n=="boolean"?n:a}function Z(e){const a=e.meta??{},n={};return"title"in a&&(n.title=a.title),"tips"in a&&(n.tips=a.tips),"icon"in a&&(n.icon=a.icon),"closable"in a&&(n.closable=a.closable),"tabClass"in a&&(n.tabClass=a.tabClass),"target"in a&&(n.target=a.target),"href"in a&&(n.href=a.href),n}function S(e,a,n){const i=Z(e);return{id:E(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:N(e,n),reusable:j(e,!1),closable:i.closable??!0,...i,...a}}function O(e,a,n,i){if(!e.find(m=>m.id===a.id)){if(n==="next"&&i){const m=e.findIndex(g=>g.id===i);if(m>-1){e.splice(m+1,0,a);return}}e.push(a)}}function ee(e,a,n){if(!a||a<=0)return;const i=e.filter(r=>r.alive);for(;i.length>a;){const r=i.shift();if(!r||r.id===n)continue;const m=e.findIndex(g=>g.id===r.id);m>-1&&(e[m].alive=!1)}}function fe(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function be(e){const a={};return"title"in e&&(a.title=e.title),"tips"in e&&(a.tips=e.tips),"icon"in e&&(a.icon=e.icon),"tabClass"in e&&(a.tabClass=e.tabClass),"closable"in e&&(a.closable=e.closable),a}function me(e,a={}){const n=ue(a),i=t.reactive([]),r=t.ref(null),m=t.shallowRef(),g=t.ref(null),s=t.computed(()=>i.filter(l=>l.alive).map(l=>l.id));let c=!1;function u(l){const d=typeof l.matched=="object"?l:P(e,l);return{key:E(d),fullPath:d.fullPath,alive:N(d,n.keepAlive),reusable:j(d,!1),matched:d}}function C(l){const d=E(l);let b=i.find(h=>h.id===d);return b?(b.fullPath=l.fullPath,b.to=l.fullPath,b.matched=l,b.alive=N(l,n.keepAlive),b.reusable=j(l,b.reusable),Object.assign(b,Z(l)),b):(b=S(l,{},n.keepAlive),O(i,b,n.appendPosition,r.value),ee(i,n.maxAlive,r.value),b)}async function R(l,d=!1,b=!0){const h=P(e,l),B=E(h),A=r.value===B;b==="sameTab"&&(b=A),b&&await w(B,!0),await e[d?"replace":"push"](h),A&&await V()}function $(l){const d=i.findIndex(h=>h.id===l),b=i[d]||i[d-1]||i[0];return b?b.to:n.defaultRoute}async function L(l=r.value,d={}){if(l){if(!d.force&&n.keepLastTab&&i.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");if(await D(l,{force:d.force}),d.redirect!==null)if(r.value===l){const b=d.redirect??$(l);b&&await e.replace(b)}else d.redirect&&await e.replace(d.redirect)}}async function D(l,d={}){const b=i.findIndex(h=>h.id===l);b!==-1&&(i.splice(b,1),g.value===l&&(g.value=null),r.value===l&&(r.value=null,m.value=void 0))}async function w(l=r.value??void 0,d=!1){l&&(g.value=l,await t.nextTick(),d||await t.nextTick(),g.value=null)}async function q(l=!1){for(const d of i)await w(d.id,l)}async function T(l=n.defaultRoute){i.splice(0,i.length),r.value=null,m.value=void 0;for(const d of n.initialTabs){const b=P(e,d.to),h=S(b,d,n.keepAlive);i.push(h)}await e.replace(l)}async function V(){const l=r.value;l&&await w(l,!0)}function G(l){return typeof l.matched=="object"?E(l):E(P(e,l))}function M(){const l=i.find(d=>d.id===r.value);return{tabs:i.map(fe),active:l?l.to:null}}async function H(l){c=!0,i.splice(0,i.length),r.value=null,m.value=void 0;const d=l?.tabs??[];for(const h of d)try{const B=P(e,h.to),A=be(h),Q=S(B,A,n.keepAlive);O(i,Q,"last",null)}catch{}c=!1;const b=l?.active??d[d.length-1]?.to??n.defaultRoute;if(b)try{await e.replace(b)}catch{}}return t.watch(()=>e.currentRoute.value,l=>{if(c)return;const d=C(l);r.value=d.id,m.value=d,ee(i,n.maxAlive,r.value)},{immediate:!0}),n.initialTabs.length&&n.initialTabs.forEach(l=>{const d=P(e,l.to),b=S(d,l,n.keepAlive);O(i,b,"last",null)}),{options:n,tabs:i,activeId:r,current:m,includeKeys:s,refreshingKey:g,openTab:R,closeTab:L,removeTab:D,refreshTab:w,refreshAll:q,reset:T,reload:V,getRouteKey:G,matchRoute:u,snapshot:M,hydrate:H}}function te(e){return e?typeof e=="string"?{name:e}:e:{}}const I=Symbol("RouterTabsContext"),K="router-tabs:snapshot";function z(e={}){const{optional:a=!1}=e,n=t.inject(I,null);if(n)return n;const i=t.inject("$tabs",null);if(i)return i;const m=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(m)return m;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const pe=864e5;function ge(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,n=document.cookie?document.cookie.split("; "):[];for(const i of n)if(i.startsWith(a))return decodeURIComponent(i.slice(a.length));return null}function ne(e,a,n){if(typeof document>"u")return;const{expiresInDays:i=7,path:r="/",domain:m,secure:g,sameSite:s="lax"}=n,c=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(i!==1/0){const u=new Date(Date.now()+i*pe).toUTCString();c.push(`Expires=${u}`)}r&&c.push(`Path=${r}`),m&&c.push(`Domain=${m}`),g&&c.push("Secure"),s&&c.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=c.join("; ")}function oe(e,a){if(typeof document>"u")return;const{path:n="/",domain:i}=a,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),n&&r.push(`Path=${n}`),i&&r.push(`Domain=${i}`),document.cookie=r.join("; ")}const ye=e=>JSON.stringify(e??null),he=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function U(e={}){const{cookieKey:a=K,serialize:n=ye,deserialize:i=he}=e,r=z({optional:!0}),m=t.ref(!0),g=s=>{t.onMounted(async()=>{const c=i(ge(a));if(c&&c.tabs?.length)try{if(m.value=!0,await s.hydrate(c),c.active){await t.nextTick();const C=s.tabs.find(R=>R.to===c.active);C&&(s.activeId.value=C.id,s.current.value=C)}}finally{m.value=!1}else try{m.value=!0;const C=e.fallbackRoute??s.options.defaultRoute;await s.reset(C)}finally{m.value=!1}const u=s.snapshot();u.tabs.length?ne(a,n(u),e):oe(a,e),m.value=!1}),t.watch(()=>({tabs:s.tabs.map(c=>({to:c.to,title:c.title,tips:c.tips,icon:c.icon,tabClass:c.tabClass,closable:c.closable})),active:s.activeId.value}),()=>{if(m.value)return;const c=s.snapshot();c.tabs.length?ne(a,n(c),e):oe(a,e)},{deep:!0})};r?g(r):t.onMounted(()=>{const s=z({optional:!0});s&&g(s)})}const ke=t.defineComponent({name:"RouterTab",components:{RouterView:ce.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:K},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0},titleResolver:{type:Function,default:null}},emits:["tab-sort","tab-sorted"],setup(e,{emit:a}){const n=t.getCurrentInstance();if(!n)throw new Error("[RouterTab] component must be used within a Vue application context.");const i=n.appContext.app.config.globalProperties.$router;if(!i)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const r=me(i,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(I,r),n.appContext.config.globalProperties.$tabs=r;const m=t.computed(()=>!!n?.slots?.default);if(e.cookieKey!==null||e.persistence){const o={...e.persistence??{}};e.cookieKey!==null?o.cookieKey=e.cookieKey||K:o.cookieKey||(o.cookieKey=K),U(o)}const g=t.computed(()=>te(e.tabTransition)),s=t.computed(()=>te(e.pageTransition)),c=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),u=t.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),C=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function R(o){return r.tabs.findIndex(f=>f.id===o)}function $(o){const f=R(o.id);return f>0?r.tabs.slice(0,f):[]}function L(o){const f=R(o.id);return f>-1?r.tabs.slice(f+1):[]}function D(o){return r.tabs.filter(f=>f.id!==o.id)}async function w(o,f){const p=o.filter(v=>v.closable!==!1);if(p.length){for(const v of p)r.activeId.value===v.id?await r.closeTab(v.id,{redirect:f.to,force:!0}):await r.removeTab(v.id,{force:!0});r.activeId.value!==f.id&&await r.openTab(f.to,!0,!1)}}const q={refresh:{label:"Refresh",handler:async({target:o})=>{await r.refreshTab(o.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await r.refreshAll(!0)}},close:{label:"Close",handler:async({target:o})=>{await r.closeTab(o.id)},enable:({target:o})=>d(o)},closeLefts:{label:"Close to the Left",handler:async({target:o})=>{await w($(o),o)},enable:({target:o})=>$(o).some(f=>f.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:o})=>{await w(L(o),o)},enable:({target:o})=>L(o).some(f=>f.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:o})=>{await w(D(o),o)},enable:({target:o})=>D(o).some(f=>f.closable!==!1)}};function T(){c.visible=!1,c.target=null}function V(o,f){e.contextmenu&&(c.visible=!0,c.target=o,c.position.x=f.clientX,c.position.y=f.clientY,document.addEventListener("click",T,{once:!0}))}function G(o,f){const p=typeof o=="string"?{id:o}:o,v=q[p.id],Fe=p.label??v?.label??String(p.id),W=p.visible??v?.visible??!0;if(!(typeof W=="function"?W(f):W!==!1))return null;const X=p.enable??v?.enable??!0,Ye=typeof X=="function"?X(f):X!==!1,se=p.handler??v?.handler;if(!se)return null;const Je=async()=>{await Promise.resolve(se(f))};return{id:String(p.id),label:Fe,disabled:!Ye,action:Je}}const M=t.computed(()=>{if(!c.visible||!c.target||e.contextmenu===!1)return[];const o=Array.isArray(e.contextmenu)?e.contextmenu:C,f={target:c.target,controller:r};return o.map(p=>G(p,f)).filter(p=>!!p)});async function H(o){o.disabled||(T(),await o.action())}function l(o){return e.titleResolver?e.titleResolver(o):typeof o.title=="string"?o.title:Array.isArray(o.title)&&o.title.length?String(o.title[0]):o.fullPath}function d(o){return!(o.closable===!1||r.options.keepLastTab&&r.tabs.length<=1)}async function b(o){await r.closeTab(o.id)}function h(o){r.activeId.value!==o.id&&r.openTab(o.to,!1)}function B(o){return["router-tab__item",{"is-active":r.activeId.value===o.id,"is-closable":d(o),"is-dragging":u.dragging&&u.dragTab?.id===o.id,"is-drag-over":u.dropIndex===R(o.id)},o.tabClass]}function A(o){return r.refreshingKey.value===r.getRouteKey(o)}function Q(o,f,p){e.sortable&&(u.dragging=!0,u.dragIndex=f,u.dragTab=o,p.dataTransfer&&(p.dataTransfer.effectAllowed="move",p.dataTransfer.setData("text/plain",o.id)),a("tab-sort",{tab:o,index:f}))}function Ne(o,f){!e.sortable||!u.dragging||(f.preventDefault(),f.dataTransfer&&(f.dataTransfer.dropEffect="move"))}function je(o){!e.sortable||!u.dragging||(u.dropIndex=o)}function Oe(){!e.sortable||u.dragging}function ze(o,f){if(!(!e.sortable||!u.dragging)){if(f.preventDefault(),u.dragIndex!==-1&&u.dragIndex!==o){const p=r.tabs.splice(u.dragIndex,1)[0];r.tabs.splice(o,0,p),a("tab-sorted",{tab:p,fromIndex:u.dragIndex,toIndex:o})}le()}}function le(){u.dragging=!1,u.dragIndex=-1,u.dropIndex=-1,u.dragTab=null}t.onMounted(()=>{document.addEventListener("keydown",T)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",T),n.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,o=>{r.options.keepAlive=o}),t.watch(()=>r.activeId.value,()=>T()),t.watch(()=>e.contextmenu,o=>{o||T()}),t.watch(()=>M.value.length,o=>{c.visible&&o===0&&T()});const Ue=r.includeKeys;return{controller:r,tabs:r.tabs,includeKeys:Ue,tabTransitionProps:g,pageTransitionProps:s,buildTabClass:B,activate:h,close:b,context:c,menuItems:M,handleMenuAction:H,showContextMenu:V,hideContextMenu:T,getTabTitle:l,isClosable:d,isRefreshing:A,hasCustomSlot:m,onDragStart:Q,onDragOver:Ne,onDragEnter:je,onDragLeave:Oe,onDrop:ze,onDragEnd:le}}}),Te=(e,a)=>{const n=e.__vccOpts||e;for(const[i,r]of a)n[i]=r;return n},ve={class:"router-tab"},Ce={class:"router-tab__header"},we={class:"router-tab__slot-start"},Re={class:"router-tab__scroll"},Be=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Pe=["title"],Ee=["onClick"],Ae={class:"router-tab__slot-end"},Ie={class:"router-tab__container"},xe=["aria-disabled","onClick"];function De(e,a,n,i,r,m){const g=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",ve,[t.createElementVNode("header",Ce,[t.createElementVNode("div",we,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",Re,[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,(s,c)=>(t.openBlock(),t.createElementBlock("li",{key:s.id,class:t.normalizeClass(e.buildTabClass(s)),"data-title":e.getTabTitle(s),draggable:e.sortable,onClick:u=>e.activate(s),onAuxclick:t.withModifiers(u=>e.close(s),["middle","prevent"]),onContextmenu:t.withModifiers(u=>e.showContextMenu(s,u),["prevent"]),onDragstart:u=>e.onDragStart(s,c,u),onDragover:u=>e.onDragOver(c,u),onDragenter:u=>e.onDragEnter(c),onDragleave:a[0]||(a[0]=(...u)=>e.onDragLeave&&e.onDragLeave(...u)),onDrop:u=>e.onDrop(c,u),onDragend:a[1]||(a[1]=(...u)=>e.onDragEnd&&e.onDragEnd(...u))},[t.createElementVNode("span",{class:"router-tab__item-title",title:e.getTabTitle(s)},[s.icon?(t.openBlock(),t.createElementBlock("i",{key:0,class:t.normalizeClass(["router-tab__item-icon",s.icon])},null,2)):t.createCommentVNode("",!0),t.createTextVNode(" "+t.toDisplayString(e.getTabTitle(s)),1)],8,Pe),e.isClosable(s)?(t.openBlock(),t.createElementBlock("a",{key:0,class:"router-tab__item-close",onClick:t.withModifiers(u=>e.close(s),["stop"])},null,8,Ee)):t.createCommentVNode("",!0)],42,Be))),128))]),_:1},16)]),t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Ie,[t.createVNode(g,null,{default:t.withCtx(s=>[e.hasCustomSlot?t.renderSlot(e.$slots,"default",t.normalizeProps(t.mergeProps({key:0},{...s,controller:e.controller}))):(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(s.route)?t.createCommentVNode("",!0):(t.openBlock(),t.createBlock(t.resolveDynamicComponent(s.Component),{key:e.controller.getRouteKey(s.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(s.route)?(t.openBlock(),t.createBlock(t.resolveDynamicComponent(s.Component),{key:e.controller.getRouteKey(s.route)+(e.isRefreshing(s.route)?"-refresh":""),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,s=>(t.openBlock(),t.createElementBlock("a",{key:s.id,class:"router-tab__contextmenu-item","aria-disabled":s.disabled,onClick:t.withModifiers(c=>e.handleMenuAction(s),["prevent"])},t.toDisplayString(s.label),9,xe))),128))],4)):t.createCommentVNode("",!0)])}const F=Te(ke,[["render",De]]),Se={class:"router-tabs","aria-hidden":"true"},_=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 U(e),(n,i)=>(t.openBlock(),t.createElementBlock("span",Se))}}),ae="tab-theme-style",Ke="tab-theme-primary-color",_e="system",$e="(prefers-color-scheme: dark)";let x=null;const k={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Le={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 Y(e){typeof document>"u"||(console.log("applyPrimary",e),document.documentElement.style.setProperty("--router-tab-primary",e.primary??k.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??k.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??k.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??k.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??k.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??k.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??k.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??k.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??k.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??k.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??k.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??k.activeButtonBackground))}function re(e){if(typeof document>"u")return;const a=document.documentElement,n=window.matchMedia($e),i=()=>{a.dataset.theme=n.matches?"dark":"light"};x&&(n.removeEventListener("change",x),x=null),e==="system"?(i(),x=()=>i(),n.addEventListener("change",x)):a.dataset.theme=e}function ie(e={}){if(typeof window>"u")return;const{styleKey:a=ae,defaultStyle:n=_e}=e,i=window.localStorage.getItem(a)??n;re(i),Y(i==="dark"?Le:k)}function Ve(e,a){if(typeof window>"u")return;const n=a?.styleKey??ae;window.localStorage.setItem(n,e),re(e)}function Me(e,a){if(typeof window>"u")return;const n=a?.primaryKey??Ke;window.localStorage.setItem(n,JSON.stringify(e)),Y(e)}const J={install(e){if(J._installed)return;J._installed=!0,ie();const a=F.name||"RouterTab",n=_.name||"RouterTabs";e.component(a,F),e.component(n,_),n!=="router-tabs"&&e.component("router-tabs",_),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[I]},set(i){i&&e.provide(I,i)}})}};y.RouterTab=F,y.RouterTabs=_,y.default=J,y.initRouterTabsTheme=ie,y.routerTabsKey=I,y.setRouterTabsPrimary=Me,y.setRouterTabsTheme=Ve,y.useRouterTabs=z,y.useRouterTabsPersistence=U,Object.defineProperties(y,{__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,Ce){"use strict";function Re(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 M(e,a){const o=e.resolve(a);if(!o||!o.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return o}const Ee={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 a=e.meta?.key;if(typeof a=="function"){const o=a(e);if(typeof o=="string"&&o.length)return o}else if(typeof a=="string"&&a.length){const o=Ee[a.toLowerCase()];return o?o(e):a}return e.fullPath}function G(e,a){const o=e.meta?.keepAlive;return typeof o=="boolean"?o:a}function X(e,a){const o=e.meta?.reuse;return typeof o=="boolean"?o:a}function se(e){const a=e.meta??{},o={};return"title"in a&&(o.title=a.title),"tips"in a&&(o.tips=a.tips),"icon"in a&&(o.icon=a.icon),"closable"in a&&(o.closable=a.closable),"tabClass"in a&&(o.tabClass=a.tabClass),"target"in a&&(o.target=a.target),"href"in a&&(o.href=a.href),o}function _(e,a,o){const s=se(e);return{id:$(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:G(e,o),reusable:X(e,!1),closable:s.closable??!0,...s,...a}}function Q(e,a,o,s){if(!e.find(p=>p.id===a.id)){if(o==="next"&&s){const p=e.findIndex(k=>k.id===s);if(p!==-1){e.splice(p+1,0,a);return}}e.push(a)}}function ce(e,a,o){if(!a||a<=0)return;const s=e.filter(r=>r.alive);for(;s.length>a;){const r=s.shift();if(!r||r.id===o)continue;const p=e.findIndex(k=>k.id===r.id);p>-1&&(e[p].alive=!1)}}function Pe(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function Be(e){const a={};return"title"in e&&(a.title=e.title),"tips"in e&&(a.tips=e.tips),"icon"in e&&(a.icon=e.icon),"tabClass"in e&&(a.tabClass=e.tabClass),"closable"in e&&(a.closable=e.closable),a}function Ae(e,a={}){const o=Re(a),s=n.reactive([]),r=n.ref(null),p=n.shallowRef(),k=n.ref(null),c=n.computed(()=>s.filter(l=>l.alive).map(l=>l.id));let d=!1;function g(l){const b=typeof l.matched=="object"?l:M(e,l);return{key:$(b),fullPath:b.fullPath,alive:G(b,o.keepAlive),reusable:X(b,!1),matched:b}}function R(l){const b=$(l);let m=s.find(v=>v.id===b);return m?(m.fullPath=l.fullPath,m.to=l.fullPath,m.matched=l,m.alive=G(l,o.keepAlive),m.reusable=X(l,m.reusable),Object.assign(m,se(l)),m):(m=_(l,{},o.keepAlive),Q(s,m,o.appendPosition,r.value),ce(s,o.maxAlive,r.value),m)}async function I(l,b=!1,m=!0){const v=M(e,l),E=$(v),P=r.value===E;m==="sameTab"&&(m=P),m&&await K(E,!0),await e[b?"replace":"push"](v),P&&await T()}function x(l){const b=s.findIndex(A=>A.id===l);if(b===-1)return o.defaultRoute;const m=s[b+1],v=s[b-1],E=s.find(A=>A.id!==l),P=m||v||E;return P?P.to:o.defaultRoute}async function j(l=r.value,b={}){if(!l)return;if(!b.force&&o.keepLastTab&&s.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const v=r.value===l&&b.redirect!==null,E=v?b.redirect??x(l):null;await O(l,{force:b.force}),b.redirect!==null&&v&&E&&await e.replace(E)}async function O(l,b={}){const m=s.findIndex(v=>v.id===l);m!==-1&&(s.splice(m,1),k.value===l&&(k.value=null),r.value===l&&(r.value=null,p.value=void 0))}async function K(l=r.value??void 0,b=!1){if(!l)return;const m=s.find(v=>v.id===l);m&&(o.keepAlive&&m.alive&&(m.alive=!1,await n.nextTick(),m.alive=!0,await n.nextTick()),k.value=l,await n.nextTick(),b||await n.nextTick(),k.value=null)}async function oe(l=!1){for(const b of s)await K(b.id,l)}async function ae(l=o.defaultRoute){s.splice(0,s.length),r.value=null,p.value=void 0;for(const b of o.initialTabs){const m=M(e,b.to),v=_(m,b,o.keepAlive);s.push(v)}await e.replace(l)}async function T(){const l=r.value;l&&await K(l,!0)}function W(l){return typeof l.matched=="object"?$(l):$(M(e,l))}function D(){const l=s.find(b=>b.id===r.value);return{tabs:s.map(Pe),active:l?l.to:null}}async function S(l){d=!0,s.splice(0,s.length),r.value=null,p.value=void 0;const b=l?.tabs??[];for(const v of b)try{const E=M(e,v.to),P=Be(v),A=_(E,P,o.keepAlive);Q(s,A,"last",null)}catch{}d=!1;const m=l?.active??b[b.length-1]?.to??o.defaultRoute;if(m)try{await e.replace(m)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(d)return;const b=R(l);r.value=b.id,p.value=b,ce(s,o.maxAlive,r.value)},{immediate:!0}),o.initialTabs.length&&o.initialTabs.forEach(l=>{const b=M(e,l.to),m=_(b,l,o.keepAlive);Q(s,m,"last",null)}),{options:o,tabs:s,activeId:r,current:p,includeKeys:c,refreshingKey:k,openTab:I,closeTab:j,removeTab:O,refreshTab:K,refreshAll:oe,reset:ae,reload:T,getRouteKey:W,matchRoute:g,snapshot:D,hydrate:S}}function ue(e){return e?typeof e=="string"?{name:e}:e:{}}const V=Symbol("RouterTabsContext"),Y="router-tabs:snapshot";function Z(e={}){const{optional:a=!1}=e,o=n.inject(V,null);if(o)return o;const s=n.inject("$tabs",null);if(s)return s;const p=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!a)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 a=`${encodeURIComponent(e)}=`,o=document.cookie?document.cookie.split("; "):[];for(const s of o)if(s.startsWith(a))return decodeURIComponent(s.slice(a.length));return null}function de(e,a,o){if(typeof document>"u")return;const{expiresInDays:s=7,path:r="/",domain:p,secure:k,sameSite:c="lax"}=o,d=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(s!==1/0){const g=new Date(Date.now()+s*Ie).toUTCString();d.push(`Expires=${g}`)}r&&d.push(`Path=${r}`),p&&d.push(`Domain=${p}`),k&&d.push("Secure"),c&&d.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=d.join("; ")}function fe(e,a){if(typeof document>"u")return;const{path:o="/",domain:s}=a,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),o&&r.push(`Path=${o}`),s&&r.push(`Domain=${s}`),document.cookie=r.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:a=Y,serialize:o=xe,deserialize:s=De}=e,r=Z({optional:!0}),p=n.ref(!0),k=c=>{n.onMounted(async()=>{const d=s(Se(a));if(d&&d.tabs?.length)try{if(p.value=!0,await c.hydrate(d),d.active){await n.nextTick();const R=c.tabs.find(I=>I.to===d.active);R&&(c.activeId.value=R.id,c.current.value=R)}}finally{p.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{p.value=!0;const R=e.fallbackRoute??c.options.defaultRoute;await c.reset(R)}finally{p.value=!1}else p.value=!1;const g=c.snapshot();g.tabs.length?de(a,o(g),e):fe(a,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})),active:c.activeId.value}),()=>{if(p.value)return;const d=c.snapshot();d.tabs.length?de(a,o(d),e):fe(a,e)},{deep:!0})};r?k(r):n.onMounted(()=>{const c=Z({optional:!0});c&&k(c)})}const Me=n.defineComponent({name:"RouterTab",components:{RouterView:Ce.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:a}){const o=n.getCurrentInstance();if(!o)throw new Error("[RouterTab] component must be used within a Vue application context.");const s=o.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 r=Ae(s,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(V,r),o.appContext.config.globalProperties.$tabs=r;const p=n.computed(()=>!!o?.slots?.default),k=n.computed(()=>!!o?.slots?.start),c=n.computed(()=>!!o?.slots?.end),d=n.ref(0),g=n.computed(()=>{d.value;const t={};return r.tabs.forEach(i=>{const f=typeof i.title=="string"?i.title:String(i.title||ie(i));t[i.id]=f}),t});function R(){d.value++}const I=new Map,x=new Map;function j(t,i){if(!i||I.has(t))return;I.set(t,i);const f=r.tabs.find(y=>r.getRouteKey(y.to)===t);if(!f)return;const h=[];if(i.routeTabTitle!==void 0){const y=n.watch(()=>{const u=i.routeTabTitle;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{if(u!=null){const L=String(u);f.title=L,R()}},{immediate:!0});h.push(y)}if(i.routeTabIcon!==void 0){const y=n.watch(()=>{const u=i.routeTabIcon;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.icon=String(u),R())},{immediate:!0});h.push(y)}if(i.routeTabClosable!==void 0){const y=n.watch(()=>{const u=i.routeTabClosable;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.closable=!!u,R())},{immediate:!0});h.push(y)}if(i.routeTabMeta!==void 0){const y=n.watch(()=>{const u=i.routeTabMeta;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u&&typeof u=="object"&&(Object.assign(f,u),R())},{immediate:!0,deep:!0});h.push(y)}x.set(t,h)}function O(t){const i=x.get(t);i&&(i.forEach(f=>f()),x.delete(t)),I.delete(t)}function K(t,i){t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?j(i,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&j(i,t.$):t===null&&O(i)}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),D=n.ref([]),S=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 r.tabs.findIndex(i=>i.id===t)}function v(t){const i=m(t.id);return i>0?r.tabs.slice(0,i):[]}function E(t){const i=m(t.id);return i>-1?r.tabs.slice(i+1):[]}function P(t){return r.tabs.filter(i=>i.id!==t.id)}async function A(t,i){const f=t.filter(h=>h.closable!==!1);if(f.length){for(const h of f)r.activeId.value===h.id?await r.closeTab(h.id,{redirect:i.to,force:!0}):await r.removeTab(h.id,{force:!0});r.activeId.value!==i.id&&await r.openTab(i.to,!0,!1)}}const et={refresh:{label:"Refresh",handler:async({target:t})=>{await r.refreshTab(t.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await r.refreshAll(!0)}},close:{label:"Close",handler:async({target:t})=>{await r.closeTab(t.id)},enable:({target:t})=>re(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await A(v(t),t)},enable:({target:t})=>v(t).some(i=>i.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await A(E(t),t)},enable:({target:t})=>E(t).some(i=>i.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await A(P(t),t)},enable:({target:t})=>P(t).some(i=>i.closable!==!1)}};function B(){T.visible=!1,T.target=null,S.value=-1,D.value=[]}function tt(t,i){e.contextmenu&&(T.visible=!0,T.target=t,T.position.x=i.clientX,T.position.y=i.clientY,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{ot()}))}function nt(t,i){const f=typeof t=="string"?{id:t}:t,h=et[f.id],y=f.label??h?.label??String(f.id),u=f.visible??h?.visible??!0;if(!(typeof u=="function"?u(i):u!==!1))return null;const le=f.enable??h?.enable??!0,yt=typeof le=="function"?le(i):le!==!1,we=f.handler??h?.handler;if(!we)return null;const Tt=async()=>{await Promise.resolve(we(i))};return{id:String(f.id),label:y,disabled:!yt,action:Tt}}const U=n.computed(()=>{if(!T.visible||!T.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:b,i={target:T.target,controller:r};return t.map(f=>nt(f,i)).filter(f=>!!f)});function ot(){const t=W.value;if(!t)return;const i=8,{innerWidth:f,innerHeight:h}=window,y=t.getBoundingClientRect();let u=T.position.x,L=T.position.y;y.right>f-i&&(u=Math.max(i,f-y.width-i)),y.bottom>h-i&&(L=Math.max(i,h-y.height-i)),(u!==T.position.x||L!==T.position.y)&&(T.position.x=u,T.position.y=L)}function at(t,i){D.value[i]=t??null}function it(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function q(t,i,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+i+h)%h,!f[y].disabled)return y;return-1}function z(t){S.value=t,!(t<0)&&n.nextTick(()=>it(t))}function Te(t){const i=q(S.value,t);i!==-1&&z(i)}function rt(t){if(!T.visible)return;const i=t.key,f=U.value;if(!f.length)return;if(i==="Tab"){B();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(i))switch(t.preventDefault(),i){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=S.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 ie(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 lt(t){return g.value[t.id]||ie(t)}function re(t){return!(t.closable===!1||r.options.keepLastTab&&r.tabs.length<=1)}async function st(t){await r.closeTab(t.id)}function ct(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}r.activeId.value!==t.id&&r.openTab(t.to,!1)}function ut(t){return["router-tab__item",{"is-active":r.activeId.value===t.id,"is-closable":re(t),"is-dragging":l.dragging&&l.dragTab?.id===t.id,"is-drag-over":l.dropIndex===m(t.id)},t.tabClass]}function dt(t){return r.refreshingKey.value===r.getRouteKey(t)}function ft(t,i,f){e.sortable&&(l.dragging=!0,l.dragIndex=i,l.dragTab=t,f.dataTransfer&&(f.dataTransfer.effectAllowed="move",f.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:i}))}function bt(t,i){!e.sortable||!l.dragging||(i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="move"))}function mt(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function pt(){!e.sortable||l.dragging}function gt(t,i){if(!(!e.sortable||!l.dragging)){if(i.preventDefault(),l.dragIndex!==-1&&l.dragIndex!==t){const f=r.tabs.splice(l.dragIndex,1)[0];r.tabs.splice(t,0,f),a("tab-sorted",{tab:f,fromIndex:l.dragIndex,toIndex:t})}ve()}}function ve(){l.dragging=!1,l.dragIndex=-1,l.dropIndex=-1,l.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",B)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",B),o.appContext.config.globalProperties.$tabs=null,x.forEach(t=>{t.forEach(i=>i())}),x.clear(),I.clear()}),n.watch(()=>e.keepAlive,t=>{r.options.keepAlive=t}),n.watch(()=>r.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(D.value=new Array(t.length).fill(null),!T.visible)return;const i=q(-1,1,t);z(i)}),n.watch(()=>T.visible,t=>{t||(S.value=-1,D.value=[])});const ht=r.includeKeys;return{controller:r,tabs:r.tabs,includeKeys:ht,tabTransitionProps:oe,pageTransitionProps:ae,buildTabClass:ut,activate:ct,close:st,context:T,menuItems:U,handleMenuAction:ke,showContextMenu:tt,hideContextMenu:B,getTabTitle:ie,isClosable:re,isRefreshing:dt,hasCustomSlot:p,hasStartSlot:k,hasEndSlot:c,onDragStart:ft,onDragOver:bt,onDragEnter:mt,onDragLeave:pt,onDrop:gt,onDragEnd:ve,setupComponentWatching:j,cleanupComponentWatching:O,handleComponentRef:K,getReactiveTabTitle:lt,triggerTabUpdate:R,menuRef:W,highlightedIndex:S,setMenuItemRef:at,onMenuKeydown:rt,highlightMenuIndex:z}}}),$e=(e,a)=>{const o=e.__vccOpts||e;for(const[s,r]of a)o[s]=r;return o},Ke={class:"router-tab"},Le={class:"router-tab__header"},Ve={class:"router-tab__scroll"},Ne=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],je=["title"],Oe=["onClick"],Ue={class:"router-tab__container"},ze=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function _e(e,a,o,s,r,p){const k=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Ke,[n.createElementVNode("header",Le,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",Ve,[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:a[0]||(a[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(d,g),onDragend:a[1]||(a[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,je),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,Oe)):n.createCommentVNode("",!0)],42,Ne))),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",Ue,[n.createVNode(k,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.controller.getRouteKey(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.controller.getRouteKey(c.route)+(e.isRefreshing(c.route)?"-refresh":""),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:a[2]||(a[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,ze))),128))],36)):n.createCommentVNode("",!0)])}const te=$e(Me,[["render",_e]]),Ye={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),(o,s)=>(n.openBlock(),n.createElementBlock("span",Ye))}}),be="tab-theme-style",me="tab-theme-primary-color",Fe="system",pe="(prefers-color-scheme: dark)";let N=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"},He={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 Je(e){if(typeof window>"u")return null;const a=window.localStorage.getItem(e);if(!a)return null;try{const o=JSON.parse(a);return o&&typeof o=="object"?o:null}catch{return null}}function ne(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 ge(e){if(typeof document>"u")return;const a=document.documentElement,o=window.matchMedia(pe),s=()=>{a.dataset.theme=o.matches?"dark":"light"};N&&(o.removeEventListener("change",N),N=null),e==="system"?(s(),N=()=>s(),o.addEventListener("change",N)):a.dataset.theme=e}function he(e={}){if(typeof window>"u")return;const{styleKey:a=be,primaryKey:o=me,defaultStyle:s=Fe,defaultPrimary:r}=e,p=window.localStorage.getItem(a)??s;ge(p);const c=p==="dark"||p==="system"&&window.matchMedia(pe).matches?{...He}:{...C};r&&(c.primary=r);const d=Je(o);ne(d?{...c,...d}:c)}function We(e,a){if(typeof window>"u")return;const o=a?.styleKey??be;window.localStorage.setItem(o,e),ge(e)}function qe(e,a){if(typeof window>"u")return;const o=a?.primaryKey??me;window.localStorage.setItem(o,JSON.stringify(e)),ne(e)}function H(e,a){if(n.isRef(e)){const s=!n.isReadonly(e);return{value:e,update:s?r=>{e.value=r}:()=>{}}}if(typeof e=="function"){const s=e;return{value:n.computed(s),update:()=>{}}}const o=n.ref(e===void 0?a:e);return{value:o,update:s=>{o.value=s}}}function J(e={}){const a=H(e.title,"Untitled"),o=H(e.icon,""),s=H(e.closable,!0),r=H(e.meta,{});return{routeTabTitle:a.value,routeTabIcon:o.value,routeTabClosable:s.value,routeTabMeta:r.value,updateTitle:a.update,updateIcon:o.update,updateClosable:s.update,updateMeta:r.update}}function Ge(e,a="Page"){return J({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function Xe(e,a="Page",o="mdi-page"){return J({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":o)})}function Qe(e,a="Page"){const o={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(()=>a+o[e.value].suffix),icon:n.computed(()=>o[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let ye=!1;const Ze={install(e,a){if(ye)return;ye=!0;const{initTheme:o=!0,themeOptions:s,componentName:r=te.name||"RouterTab",tabsComponentName:p=F.name||"RouterTabs"}=a??{};o&&he(s??{}),e.component(r,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(k){k&&e.provide(V,k)}})}};w.RouterTab=te,w.RouterTabs=F,w.default=Ze,w.initRouterTabsTheme=he,w.routerTabsKey=V,w.setRouterTabsPrimary=qe,w.setRouterTabsTheme=We,w.useLoadingTab=Ge,w.useNotificationTab=Xe,w.useReactiveTab=J,w.useRouterTabs=Z,w.useRouterTabsPersistence=ee,w.useStatusTab=Qe,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
 
@@ -27,9 +30,9 @@
27
30
  @drop="onDrop(index, $event)"
28
31
  @dragend="onDragEnd"
29
32
  >
30
- <span class="router-tab__item-title" :title="getTabTitle(tab)">
31
- <i v-if="tab.icon" :class="['router-tab__item-icon', tab.icon]" />
32
- {{ getTabTitle(tab) }}
33
+ <i v-if="tab.icon" :class="['router-tab__item-icon', tab.icon]" />
34
+ <span class="router-tab__item-title" :title="getReactiveTabTitle(tab)">
35
+ {{ getReactiveTabTitle(tab) }}
33
36
  </span>
34
37
  <a
35
38
  v-if="isClosable(tab)"
@@ -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>
@@ -48,7 +54,14 @@
48
54
  <div class="router-tab__container">
49
55
  <RouterView v-slot="routerSlot">
50
56
  <template v-if="hasCustomSlot">
51
- <slot v-bind="{ ...routerSlot, controller }" />
57
+ <slot
58
+ v-bind="{
59
+ ...routerSlot,
60
+ controller,
61
+ // Expose a ref binder so custom slots can keep reactivity
62
+ pageRef: (el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))
63
+ }"
64
+ />
52
65
  </template>
53
66
  <template v-else>
54
67
  <transition
@@ -64,6 +77,7 @@
64
77
  v-if="!isRefreshing(routerSlot.route)"
65
78
  :is="routerSlot.Component"
66
79
  :key="controller.getRouteKey(routerSlot.route)"
80
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
67
81
  class="router-tab-page"
68
82
  />
69
83
  </KeepAlive>
@@ -77,6 +91,7 @@
77
91
  v-if="!controller.options.keepAlive || isRefreshing(routerSlot.route)"
78
92
  :is="routerSlot.Component"
79
93
  :key="controller.getRouteKey(routerSlot.route) + (isRefreshing(routerSlot.route) ? '-refresh' : '')"
94
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
80
95
  class="router-tab-page"
81
96
  />
82
97
  </transition>
@@ -86,18 +101,26 @@
86
101
 
87
102
  <div
88
103
  v-if="context.visible && context.target"
104
+ ref="menuRef"
89
105
  class="router-tab__contextmenu"
106
+ role="menu"
107
+ @keydown="onMenuKeydown"
90
108
  :style="{ left: context.position.x + 'px', top: context.position.y + 'px' }"
91
109
  >
92
110
  <a
93
- v-for="item in menuItems"
111
+ v-for="(item, index) in menuItems"
94
112
  :key="item.id"
95
- class="router-tab__contextmenu-item"
113
+ role="menuitem"
114
+ :class="['router-tab__contextmenu-item', { 'is-focused': index === highlightedIndex }]"
96
115
  :aria-disabled="item.disabled"
97
- @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)"
98
121
  >
99
122
  {{ item.label }}
100
- </a>
123
+ </a>
101
124
  </div>
102
125
  </div>
103
126
  </template>
@@ -107,10 +130,12 @@ import {
107
130
  computed,
108
131
  defineComponent,
109
132
  getCurrentInstance,
133
+ nextTick,
110
134
  onBeforeUnmount,
111
135
  onMounted,
112
136
  provide,
113
137
  reactive,
138
+ ref,
114
139
  watch
115
140
  } from 'vue'
116
141
  import { RouterView, type RouteLocationNormalizedLoaded } from 'vue-router'
@@ -189,10 +214,6 @@ export default defineComponent({
189
214
  sortable: {
190
215
  type: Boolean,
191
216
  default: true
192
- },
193
- titleResolver: {
194
- type: Function as PropType<(tab: TabRecord) => string>,
195
- default: null
196
217
  }
197
218
  },
198
219
  emits: ['tab-sort', 'tab-sorted'],
@@ -220,6 +241,150 @@ export default defineComponent({
220
241
  instance.appContext.config.globalProperties.$tabs = controller
221
242
 
222
243
  const hasCustomSlot = computed(() => Boolean(instance?.slots?.default))
244
+ const hasStartSlot = computed(() => Boolean(instance?.slots?.start))
245
+ const hasEndSlot = computed(() => Boolean(instance?.slots?.end))
246
+
247
+ // Force reactivity by creating a trigger ref
248
+ const tabUpdateTrigger = ref(0)
249
+
250
+ // Reactive tab titles that update when tabs change
251
+ const reactiveTabTitles = computed(() => {
252
+ // Access the trigger to ensure reactivity
253
+ tabUpdateTrigger.value
254
+ const titles: Record<string, string> = {}
255
+ controller.tabs.forEach(tab => {
256
+ // Use the tab's current title (which is updated by watchers)
257
+ const currentTitle = typeof tab.title === 'string' ? tab.title : String(tab.title || getTabTitle(tab))
258
+ titles[tab.id] = currentTitle
259
+ })
260
+ return titles
261
+ })
262
+
263
+ // Function to trigger reactivity updates
264
+ function triggerTabUpdate() {
265
+ tabUpdateTrigger.value++
266
+ }
267
+
268
+ // Reactive tab update system
269
+ const componentInstances = new Map<string, any>()
270
+ const watchedProperties = new Map<string, (() => void)[]>()
271
+
272
+ // Watch for component instance changes and set up reactivity
273
+ function setupComponentWatching(routeKey: string, componentInstance: any) {
274
+ if (!componentInstance || componentInstances.has(routeKey)) return
275
+
276
+ // setup watchers for exposed reactive tab props
277
+
278
+ componentInstances.set(routeKey, componentInstance)
279
+
280
+ // Find the tab for this route
281
+ const tab = controller.tabs.find(t => controller.getRouteKey(t.to) === routeKey)
282
+ if (!tab) return
283
+
284
+ const unwatchers: (() => void)[] = []
285
+
286
+ // Watch routeTabTitle for title updates
287
+ if (componentInstance.routeTabTitle !== undefined) {
288
+ const unwatchTitle = watch(
289
+ () => {
290
+ // Handle both ref values and regular values
291
+ const titleValue = componentInstance.routeTabTitle
292
+ return titleValue && typeof titleValue === 'object' && 'value' in titleValue ? titleValue.value : titleValue
293
+ },
294
+ (newTitle) => {
295
+ if (newTitle !== undefined && newTitle !== null) {
296
+ const titleString = String(newTitle)
297
+ tab.title = titleString
298
+ triggerTabUpdate() // Trigger reactivity
299
+ }
300
+ },
301
+ { immediate: true }
302
+ )
303
+ unwatchers.push(unwatchTitle)
304
+ }
305
+
306
+ // Watch routeTabIcon for icon updates
307
+ if (componentInstance.routeTabIcon !== undefined) {
308
+ const unwatchIcon = watch(
309
+ () => {
310
+ const iconValue = componentInstance.routeTabIcon
311
+ return iconValue && typeof iconValue === 'object' && 'value' in iconValue ? iconValue.value : iconValue
312
+ },
313
+ (newIcon) => {
314
+ if (newIcon !== undefined && newIcon !== null) {
315
+ tab.icon = String(newIcon)
316
+ triggerTabUpdate() // Trigger reactivity
317
+ }
318
+ },
319
+ { immediate: true }
320
+ )
321
+ unwatchers.push(unwatchIcon)
322
+ }
323
+
324
+ // Watch routeTabClosable for closable state updates
325
+ if (componentInstance.routeTabClosable !== undefined) {
326
+ const unwatchClosable = watch(
327
+ () => {
328
+ const closableValue = componentInstance.routeTabClosable
329
+ return closableValue && typeof closableValue === 'object' && 'value' in closableValue ? closableValue.value : closableValue
330
+ },
331
+ (newClosable) => {
332
+ if (newClosable !== undefined && newClosable !== null) {
333
+ tab.closable = Boolean(newClosable)
334
+ triggerTabUpdate() // Trigger reactivity
335
+ }
336
+ },
337
+ { immediate: true }
338
+ )
339
+ unwatchers.push(unwatchClosable)
340
+ }
341
+
342
+ // Watch routeTabMeta for general meta updates
343
+ if (componentInstance.routeTabMeta !== undefined) {
344
+ const unwatchMeta = watch(
345
+ () => {
346
+ const metaValue = componentInstance.routeTabMeta
347
+ return metaValue && typeof metaValue === 'object' && 'value' in metaValue ? metaValue.value : metaValue
348
+ },
349
+ (newMeta) => {
350
+ if (newMeta && typeof newMeta === 'object') {
351
+ Object.assign(tab, newMeta)
352
+ triggerTabUpdate() // Trigger reactivity
353
+ }
354
+ },
355
+ { immediate: true, deep: true }
356
+ )
357
+ unwatchers.push(unwatchMeta)
358
+ }
359
+
360
+ watchedProperties.set(routeKey, unwatchers)
361
+ }
362
+
363
+ // Cleanup watchers when component is unmounted
364
+ function cleanupComponentWatching(routeKey: string) {
365
+ const unwatchers = watchedProperties.get(routeKey)
366
+ if (unwatchers) {
367
+ unwatchers.forEach(unwatcher => unwatcher())
368
+ watchedProperties.delete(routeKey)
369
+ }
370
+ componentInstances.delete(routeKey)
371
+ }
372
+
373
+ // Handle component ref changes
374
+ function handleComponentRef(el: any, routeKey: string) {
375
+ if (el) {
376
+ // Component mounted - set up watching
377
+ // Check if properties are exposed directly on el (Vue 3 with defineExpose)
378
+ if (el.routeTabTitle !== undefined || el.routeTabIcon !== undefined || el.routeTabClosable !== undefined) {
379
+ setupComponentWatching(routeKey, el)
380
+ } else if (el.$ && (el.$.routeTabTitle !== undefined || el.$.routeTabIcon !== undefined || el.$.routeTabClosable !== undefined)) {
381
+ setupComponentWatching(routeKey, el.$)
382
+ }
383
+ } else if (el === null) {
384
+ // Component unmounted - cleanup
385
+ cleanupComponentWatching(routeKey)
386
+ }
387
+ }
223
388
 
224
389
  if (props.cookieKey !== null || props.persistence) {
225
390
  const options: RouterTabsPersistenceOptions = {
@@ -242,6 +407,10 @@ export default defineComponent({
242
407
  position: { x: 0, y: 0 }
243
408
  })
244
409
 
410
+ const menuRef = ref<HTMLElement | null>(null)
411
+ const menuItemRefs = ref<(HTMLElement | null)[]>([])
412
+ const highlightedIndex = ref(-1)
413
+
245
414
  // Drag and drop state
246
415
  const dragState = reactive({
247
416
  dragging: false,
@@ -352,6 +521,8 @@ export default defineComponent({
352
521
  function hideContextMenu() {
353
522
  context.visible = false
354
523
  context.target = null
524
+ highlightedIndex.value = -1
525
+ menuItemRefs.value = []
355
526
  }
356
527
 
357
528
  function showContextMenu(tab: TabRecord, event: MouseEvent) {
@@ -362,6 +533,9 @@ export default defineComponent({
362
533
  context.position.y = event.clientY
363
534
 
364
535
  document.addEventListener('click', hideContextMenu, { once: true })
536
+ nextTick(() => {
537
+ adjustMenuPosition()
538
+ })
365
539
  }
366
540
 
367
541
  function normalizeMenuItem(raw: MenuConfig, ctx: MenuActionContext): ResolvedMenuItem | null {
@@ -401,6 +575,128 @@ export default defineComponent({
401
575
  .filter((item): item is ResolvedMenuItem => !!item)
402
576
  })
403
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
+
404
700
  async function handleMenuAction(item: ResolvedMenuItem) {
405
701
  if (item.disabled) return
406
702
  hideContextMenu()
@@ -408,12 +704,16 @@ export default defineComponent({
408
704
  }
409
705
 
410
706
  function getTabTitle(tab: TabRecord): string {
411
- if (props.titleResolver) {
412
- return props.titleResolver(tab)
413
- }
414
- if (typeof tab.title === 'string') return tab.title
415
- if (Array.isArray(tab.title) && tab.title.length) return String(tab.title[0])
416
- return tab.fullPath
707
+ if (typeof tab.title === 'string' && tab.title.trim()) return tab.title
708
+ if (Array.isArray(tab.title) && tab.title.length && String(tab.title[0]).trim()) return String(tab.title[0])
709
+ return 'Untitled'
710
+ }
711
+
712
+ // Reactive function to get tab title
713
+ function getReactiveTabTitle(tab: TabRecord): string {
714
+ // Access the reactive titles to ensure reactivity
715
+ const reactiveTitles = reactiveTabTitles.value
716
+ return reactiveTitles[tab.id] || getTabTitle(tab)
417
717
  }
418
718
 
419
719
  function isClosable(tab: TabRecord) {
@@ -427,6 +727,15 @@ export default defineComponent({
427
727
  }
428
728
 
429
729
  function activate(tab: TabRecord) {
730
+ if (tab.href && typeof window !== 'undefined') {
731
+ if (tab.target && tab.target !== '_self') {
732
+ window.open(tab.href as string, tab.target)
733
+ } else {
734
+ window.location.assign(tab.href as string)
735
+ }
736
+ return
737
+ }
738
+
430
739
  if (controller.activeId.value === tab.id) return
431
740
  controller.openTab(tab.to, false)
432
741
  }
@@ -515,6 +824,13 @@ export default defineComponent({
515
824
  onBeforeUnmount(() => {
516
825
  document.removeEventListener('keydown', hideContextMenu)
517
826
  instance.appContext.config.globalProperties.$tabs = null
827
+
828
+ // Cleanup all component watchers
829
+ watchedProperties.forEach((unwatchers) => {
830
+ unwatchers.forEach(unwatcher => unwatcher())
831
+ })
832
+ watchedProperties.clear()
833
+ componentInstances.clear()
518
834
  })
519
835
 
520
836
  watch(
@@ -545,6 +861,23 @@ export default defineComponent({
545
861
  }
546
862
  )
547
863
 
864
+ watch(menuItems, items => {
865
+ menuItemRefs.value = new Array(items.length).fill(null)
866
+ if (!context.visible) return
867
+ const first = findNextEnabledIndex(-1, 1, items)
868
+ highlightMenuIndex(first)
869
+ })
870
+
871
+ watch(
872
+ () => context.visible,
873
+ visible => {
874
+ if (!visible) {
875
+ highlightedIndex.value = -1
876
+ menuItemRefs.value = []
877
+ }
878
+ }
879
+ )
880
+
548
881
  const includeKeys = controller.includeKeys
549
882
 
550
883
  return {
@@ -565,13 +898,25 @@ export default defineComponent({
565
898
  isClosable,
566
899
  isRefreshing,
567
900
  hasCustomSlot,
901
+ hasStartSlot,
902
+ hasEndSlot,
568
903
  onDragStart,
569
904
  onDragOver,
570
905
  onDragEnter,
571
906
  onDragLeave,
572
907
  onDrop,
573
- onDragEnd
908
+ onDragEnd,
909
+ setupComponentWatching,
910
+ cleanupComponentWatching,
911
+ handleComponentRef,
912
+ getReactiveTabTitle,
913
+ triggerTabUpdate,
914
+ menuRef,
915
+ highlightedIndex,
916
+ setMenuItemRef,
917
+ onMenuKeydown,
918
+ highlightMenuIndex
574
919
  }
575
920
  }
576
921
  })
577
- </script>
922
+ </script>