vue3-router-tab 1.3.4 → 1.3.6

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(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 x(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 Ke(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 Ae(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??0}`));let d=!1;function g(l){const m=typeof l.matched=="object"?l:x(e,l);return{key:$(m),fullPath:m.fullPath,alive:G(m,a.keepAlive),reusable:X(m,!1),matched:m}}function E(l){const m=$(l);let b=s.find(k=>k.id===m);return b?(b.fullPath=l.fullPath,b.to=l.fullPath,b.matched=l,b.alive=G(l,a.keepAlive),b.reusable=X(l,b.reusable),typeof b.renderKey!="number"&&(b.renderKey=0),Object.assign(b,se(l)),b):(b=_(l,{},a.keepAlive),Q(s,b,a.appendPosition,i.value),ce(s,a.maxAlive,i.value),b)}async function A(l,m=!1,b=!0){const k=x(e,l),C=$(k),P=i.value===C;b==="sameTab"&&(b=P),b&&await M(C,!0),await e[m?"replace":"push"](k),P&&await T()}function S(l){const m=s.findIndex(K=>K.id===l);if(m===-1)return a.defaultRoute;const b=s[m+1],k=s[m-1],C=s.find(K=>K.id!==l),P=b||k||C;return P?P.to:a.defaultRoute}async function N(l=i.value,m={}){if(!l)return;if(!m.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&&m.redirect!==null,C=k?m.redirect??S(l):null;await O(l,{force:m.force}),m.redirect!==null&&k&&C&&await e.replace(C)}async function O(l,m={}){const b=s.findIndex(k=>k.id===l);b!==-1&&(s.splice(b,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,m=!1){if(!l)return;const b=s.find(C=>C.id===l);if(!b)return;const k=a.keepAlive&&b.alive;k&&(b.alive=!1,await n.nextTick()),b.renderKey=(b.renderKey??0)+1,k&&(b.alive=!0),v.value=l,await n.nextTick(),m||await n.nextTick(),v.value=null}async function oe(l=!1){for(const m of s)await M(m.id,l)}async function ae(l=a.defaultRoute){s.splice(0,s.length),i.value=null,p.value=void 0;for(const m of a.initialTabs){const b=x(e,m.to),k=_(b,m,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):$(x(e,l))}function D(){const l=s.find(m=>m.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 m=l?.tabs??[];for(const k of m)try{const C=x(e,k.to),P=Ke(k),K=_(C,P,a.keepAlive);Q(s,K,"last",null)}catch{}d=!1;const b=l?.active??m[m.length-1]?.to??a.defaultRoute;if(b)try{await e.replace(b)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(d)return;const m=E(l);i.value=m.id,p.value=m,ce(s,a.maxAlive,i.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(l=>{const m=x(e,l.to),b=_(m,l,a.keepAlive);Q(s,b,"last",null)}),{options:a,tabs:s,activeId:i,current:p,includeKeys:c,refreshingKey:v,openTab:A,closeTab:N,removeTab:O,refreshTab:M,refreshAll:oe,reset:ae,reload:T,getRouteKey:W,matchRoute:g,snapshot:D,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 De=e=>JSON.stringify(e??null),xe=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ee(e={}){const{cookieKey:o=Y,serialize:a=De,deserialize:s=xe}=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(A=>A.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=Ae(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 A=new Map,S=new Map;function N(t,r){if(!r||A.has(t))return;A.set(t,r);const f=i.tabs.find(h=>i.getRouteKey(h.to)===t);if(!f)return;const y=[];if(r.routeTabTitle!==void 0){const h=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});y.push(h)}if(r.routeTabIcon!==void 0){const h=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});y.push(h)}if(r.routeTabClosable!==void 0){const h=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});y.push(h)}if(r.routeTabMeta!==void 0){const h=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});y.push(h)}S.set(t,y)}function O(t){const r=S.get(t);r&&(r.forEach(f=>f()),S.delete(t)),A.delete(t)}function M(t,r){t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?N(r,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&N(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),D=n.ref([]),I=n.ref(-1),l=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),m=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function b(t){return i.tabs.findIndex(r=>r.id===t)}function k(t){const r=b(t.id);return r>0?i.tabs.slice(0,r):[]}function C(t){const r=b(t.id);return r>-1?i.tabs.slice(r+1):[]}function P(t){return i.tabs.filter(r=>r.id!==t.id)}async function K(t,r){const f=t.filter(y=>y.closable!==!1);if(f.length){for(const y of f)i.activeId.value===y.id?await i.closeTab(y.id,{redirect:r.to,force:!0}):await i.removeTab(y.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 K(k(t),t)},enable:({target:t})=>k(t).some(r=>r.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await K(C(t),t)},enable:({target:t})=>C(t).some(r=>r.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await K(P(t),t)},enable:({target:t})=>P(t).some(r=>r.closable!==!1)}};function B(){T.visible=!1,T.target=null,I.value=-1,D.value=[]}function nt(t,r){e.contextmenu&&(T.target=t,T.position.x=r.clientX,T.position.y=r.clientY,n.nextTick(()=>{T.visible=!0,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{at()})}))}function ot(t,r){const f=typeof t=="string"?{id:t}:t,y=tt[f.id],h=f.label??y?.label??String(f.id),u=f.visible??y?.visible??!0;if(!(typeof u=="function"?u(r):u!==!1))return null;const le=f.enable??y?.enable??!0,vt=typeof le=="function"?le(r):le!==!1,Ce=f.handler??y?.handler;if(!Ce)return null;const wt=async()=>{await Promise.resolve(Ce(r))};return{id:String(f.id),label:h,disabled:!vt,action:wt}}const U=n.computed(()=>{if(!T.visible||!T.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:m,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:y}=window,h=t.getBoundingClientRect();let u=T.position.x,L=T.position.y;h.right>f-r&&(u=Math.max(r,f-h.width-r)),h.bottom>y-r&&(L=Math.max(r,y-h.height-r)),(u!==T.position.x||L!==T.position.y)&&(T.position.x=u,T.position.y=L)}function rt(t,r){D.value[r]=t??null}function it(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function q(t,r,f=U.value){if(!f.length)return-1;const y=f.length;let h=t;for(let u=0;u<y;u++)if(h=(h+r+y)%y,!f[h].disabled)return h;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 h=I.value;if(h>-1){const u=f[h];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),f=i.tabs.find(h=>h.id===r);if(!f)return console.warn("[RouterTab] Tab not found for route:",r),`${r}::0`;const y=f.renderKey??0;return`${r}::${y}`}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===b(t.id)},t.tabClass]}function bt(t){return i.refreshingKey.value===i.getRouteKey(t)}function mt(t){const r=i.getRouteKey(t);return i.tabs.some(f=>f.id===r)}function pt(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 gt(t,r){!e.sortable||!l.dragging||(r.preventDefault(),r.dataTransfer&&(r.dataTransfer.dropEffect="move"))}function yt(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function ht(){!e.sortable||l.dragging}function Tt(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(),A.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()},{flush:"post"}),n.watch(U,t=>{if(!T.visible)return;D.value=new Array(t.length).fill(null);const r=q(-1,1,t);z(r)},{flush:"post"}),n.watch(()=>T.visible,t=>{t||(I.value=-1,D.value=[])});const kt=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:kt,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,isTabReady:mt,hasCustomSlot:p,hasStartSlot:v,hasEndSlot:c,onDragStart:pt,onDragOver:gt,onDragEnter:yt,onDragLeave:ht,onDrop:Tt,onDragEnd:we,setupComponentWatching:N,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"},je={class:"router-tab__scroll"},Ne=["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",je,[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,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",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.createBlock(n.Transition,n.mergeProps({key:1},e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[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"})):e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:1,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isTabReady(c.route)?(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"})):n.createCommentVNode("",!0)],1032,["include","max"])):e.isTabReady(c.route)?(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"})):n.createCommentVNode("",!0)]),_:2},1040))]),_:3})]),n.withDirectives(n.createElementVNode("div",{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.vShow,e.context.visible&&e.context.target]])])}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 j=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"};j&&(a.removeEventListener("change",j),j=null),e==="system"?(s(),j=()=>s(),a.addEventListener("change",j)):o.dataset.theme=e}function ye(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 he=!1;const et={install(e,o){if(he)return;he=!0;const{initTheme:a=!0,themeOptions:s,componentName:i=te.name||"RouterTab",tabsComponentName:p=F.name||"RouterTabs"}=o??{};a&&ye(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=ye,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
+ (function(R,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):(R=typeof globalThis<"u"?globalThis:R||self,n(R["vue3-router-tab"]={},R.Vue,R.VueRouter))})(this,(function(R,n,Ae){"use strict";function Ke(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 x(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 Ie={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 M(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=Ie[a.toLowerCase()];return o?o(e):a}return e.fullPath}function Q(e,a){const o=e.meta?.keepAlive;return typeof o=="boolean"?o:a}function Z(e,a){const o=e.meta?.reuse;return typeof o=="boolean"?o:a}function be(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 Y(e,a,o){const l=be(e);return{id:M(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:Q(e,o),reusable:Z(e,!1),closable:l.closable??!0,renderKey:typeof a.renderKey=="number"?a.renderKey:0,...l,...a}}function ee(e,a,o,l){if(!e.find(g=>g.id===a.id)){if(o==="next"&&l){const g=e.findIndex(v=>v.id===l);if(g!==-1){e.splice(g+1,0,a);return}}e.push(a)}}function te(e,a,o){if(!a||a<=0)return;const l=e.filter(i=>i.alive);for(;l.length>a;){const i=l.shift();if(!i||i.id===o)continue;const g=e.findIndex(v=>v.id===i.id);g>-1&&(e[g].alive=!1)}}function $e(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Se(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),"renderKey"in e&&typeof e.renderKey=="number"&&(a.renderKey=e.renderKey),a}function De(e,a={}){const o=Ke(a),l=n.reactive([]),i=n.ref(null),g=n.shallowRef(),v=n.ref(null),c=n.computed(()=>l.filter(s=>s.alive).map(s=>`${s.id}::${s.renderKey??0}`));let d=!1;function y(s){const b=typeof s.matched=="object"?s:x(e,s);return{key:M(b),fullPath:b.fullPath,alive:Q(b,o.keepAlive),reusable:Z(b,!1),matched:b}}function P(s){const b=M(s);let m=l.find(T=>T.id===b);return m?(m.fullPath=s.fullPath,m.to=s.fullPath,m.matched=s,m.alive=Q(s,o.keepAlive),m.reusable=Z(s,m.reusable),typeof m.renderKey!="number"&&(m.renderKey=0),Object.assign(m,be(s)),m):(m=Y(s,{},o.keepAlive),ee(l,m,o.appendPosition,i.value),te(l,o.maxAlive,i.value),m)}async function A(s,b=!1,m=!0){const T=x(e,s),C=M(T),I=i.value===C;m==="sameTab"&&(m=I),m&&await L(C,!0),await e[b?"replace":"push"](T),I&&await U()}function S(s){const b=l.findIndex(E=>E.id===s);if(b===-1)return o.defaultRoute;const m=l[b+1],T=l[b-1],C=l.find(E=>E.id!==s),I=m||T||C;return I?I.to:o.defaultRoute}async function N(s=i.value,b={}){if(!s)return;if(!b.force&&o.keepLastTab&&l.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const T=i.value===s&&b.redirect!==null,C=T?b.redirect??S(s):null;await K(s,{force:b.force}),b.redirect!==null&&T&&C&&await e.replace(C)}async function K(s,b={}){const m=l.findIndex(T=>T.id===s);m!==-1&&(l.splice(m,1),v.value===s&&(v.value=null),i.value===s&&(i.value=null,g.value=void 0))}async function L(s=i.value??void 0,b=!1){if(!s)return;const m=l.find(C=>C.id===s);if(!m)return;const T=o.keepAlive&&m.alive;T&&(m.alive=!1,await n.nextTick()),m.renderKey=(m.renderKey??0)+1,T&&(m.alive=!0),v.value=s,await n.nextTick(),b||await n.nextTick(),v.value=null}async function ie(s=!1){for(const b of l)await L(b.id,s)}function le(s,b){const m=l.find(T=>T.id===s);m&&(m.alive=!!b,m.alive&&te(l,o.maxAlive,i.value))}function k(s){const b=l.find(m=>m.id===s);b&&(b.alive&&(b.alive=!1),b.renderKey=(b.renderKey??0)+1)}function G(){l.forEach(s=>{s.alive=!1})}function D(){return c.value.slice()}async function $(s=o.defaultRoute){l.splice(0,l.length),i.value=null,g.value=void 0;for(const b of o.initialTabs){const m=x(e,b.to),T=Y(m,b,o.keepAlive);l.push(T)}await e.replace(s)}async function U(){const s=i.value;s&&await L(s,!0)}function z(s){return typeof s.matched=="object"?M(s):M(x(e,s))}function w(){const s=l.find(b=>b.id===i.value);return{tabs:l.map($e),active:s?s.to:null}}async function se(s){d=!0,l.splice(0,l.length),i.value=null,g.value=void 0;const b=s?.tabs??[];for(const T of b)try{const C=x(e,T.to),I=Se(T),E=Y(C,I,o.keepAlive);ee(l,E,"last",null)}catch(C){console.warn("[RouterTabs] Failed to restore tab",T,C)}d=!1;const m=s?.active??b[b.length-1]?.to??o.defaultRoute;if(m)try{await e.replace(m)}catch(T){console.warn("[RouterTabs] Failed to navigate to restored route",m,T)}}return n.watch(()=>e.currentRoute.value,s=>{if(d)return;const b=P(s);i.value=b.id,g.value=b,te(l,o.maxAlive,i.value)},{immediate:!0}),o.initialTabs.length&&o.initialTabs.forEach(s=>{const b=x(e,s.to),m=Y(b,s,o.keepAlive);ee(l,m,"last",null)}),{options:o,tabs:l,activeId:i,current:g,includeKeys:c,refreshingKey:v,openTab:A,closeTab:N,removeTab:K,refreshTab:L,refreshAll:ie,setTabAlive:le,evictCache:k,clearCache:G,getCacheKeys:D,reset:$,reload:U,getRouteKey:z,matchRoute:y,snapshot:w,hydrate:se,ensureTab:P}}function me(e){return e?typeof e=="string"?{name:e}:e:{}}const O=Symbol("RouterTabsContext"),H="router-tabs:snapshot";function ne(e={}){const{optional:a=!1}=e,o=n.inject(O,null);if(o)return o;const l=n.inject("$tabs",null);if(l)return l;const g=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(g)return g;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const xe=864e5;function Me(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,o=document.cookie?document.cookie.split("; "):[];for(const l of o)if(l.startsWith(a))return decodeURIComponent(l.slice(a.length));return null}function pe(e,a,o){if(typeof document>"u")return;const{expiresInDays:l=7,path:i="/",domain:g,secure:v,sameSite:c="lax"}=o,d=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(l!==1/0){const y=new Date(Date.now()+l*xe).toUTCString();d.push(`Expires=${y}`)}i&&d.push(`Path=${i}`),g&&d.push(`Domain=${g}`),v&&d.push("Secure"),c&&d.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=d.join("; ")}function ge(e,a){if(typeof document>"u")return;const{path:o="/",domain:l}=a,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),o&&i.push(`Path=${o}`),l&&i.push(`Domain=${l}`),document.cookie=i.join("; ")}const Le=e=>JSON.stringify(e??null),Ve=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function oe(e={}){const{cookieKey:a=H,serialize:o=Le,deserialize:l=Ve}=e,i=ne({optional:!0}),g=n.ref(!0),v=c=>{n.onMounted(async()=>{const d=l(Me(a));if(d&&d.tabs?.length)try{if(g.value=!0,await c.hydrate(d),d.active){await n.nextTick();const P=c.tabs.find(A=>A.to===d.active);P&&(c.activeId.value=P.id,c.current.value=P)}}finally{g.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{g.value=!0;const P=e.fallbackRoute??c.options.defaultRoute;await c.reset(P)}finally{g.value=!1}else g.value=!1;const y=c.snapshot();y.tabs.length?pe(a,o(y),e):ge(a,e),g.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(g.value)return;const d=c.snapshot();d.tabs.length?pe(a,o(d),e):ge(a,e)},{deep:!0})};i?v(i):n.onMounted(()=>{const c=ne({optional:!0});c&&v(c)})}const Oe=n.defineComponent({name:"RouterTab",components:{RouterView:Ae.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:H},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 l=o.appContext.app.config.globalProperties.$router;if(!l)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=De(l,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(O,i),o.appContext.config.globalProperties.$tabs=i;const g=n.computed(()=>!!o?.slots?.default),v=n.computed(()=>!!o?.slots?.start),c=n.computed(()=>!!o?.slots?.end),d=n.ref(0),y=n.computed(()=>{d.value;const t={};return i.tabs.forEach(r=>{const u=typeof r.title=="string"?r.title:String(r.title||ce(r));t[r.id]=u}),t});function P(){d.value++}const A=new Map,S=new Map;function N(t,r){if(!(!r||A.has(t)))try{A.set(t,r);const u=i.tabs.find(p=>i.getRouteKey(p.to)===t);if(!u){console.warn(`[RouterTab] Cannot setup watching: tab not found for ${t}`);return}const h=[];if(r.routeTabTitle!==void 0)try{const p=n.watch(()=>{const f=r.routeTabTitle;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{if(f!=null){const V=String(f);u.title=V,P()}},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(r.routeTabIcon!==void 0)try{const p=n.watch(()=>{const f=r.routeTabIcon;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(u.icon=String(f),P())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(r.routeTabClosable!==void 0)try{const p=n.watch(()=>{const f=r.routeTabClosable;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(u.closable=!!f,P())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(r.routeTabMeta!==void 0)try{const p=n.watch(()=>{const f=r.routeTabMeta;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f&&typeof f=="object"&&(Object.assign(u,f),P())},{immediate:!0,deep:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}S.set(t,h)}catch(u){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,u),K(t)}}function K(t){try{const r=S.get(t);r&&(r.forEach(u=>{try{u()}catch(h){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,h)}}),S.delete(t)),A.delete(t)}catch(r){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,r)}}function L(t,r){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?N(r,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&N(r,t.$):t===null&&K(r)}catch(u){console.error(`[RouterTab] Error handling component ref for ${r}:`,u),K(r)}}if(n.onErrorCaptured((t,r,u)=>{if(console.error("[RouterTab] Error captured from component:",t,u),r&&i.activeId.value){const h=i.activeId.value;K(h);const p=i.tabs.find(f=>f.id===h);p&&p.alive&&(console.warn(`[RouterTab] Removing errored component ${h} from KeepAlive cache`),p.alive=!1,n.nextTick(()=>{p.alive=!0}))}return!1}),e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||H:t.cookieKey||(t.cookieKey=H),oe(t)}const ie=n.computed(()=>me(e.tabTransition)),le=n.computed(()=>me(e.pageTransition)),k=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),G=n.ref(null),D=n.ref([]),$=n.ref(-1),U=n.ref(null),z=new Map,w=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),se=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function s(t){return i.tabs.findIndex(r=>r.id===t)}function b(t){const r=s(t.id);return r>0?i.tabs.slice(0,r):[]}function m(t){const r=s(t.id);return r>-1?i.tabs.slice(r+1):[]}function T(t){return i.tabs.filter(r=>r.id!==t.id)}async function C(t,r){const u=t.filter(h=>h.closable!==!1);if(u.length){for(const h of u)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 I={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})=>ue(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await C(b(t),t)},enable:({target:t})=>b(t).some(r=>r.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await C(m(t),t)},enable:({target:t})=>m(t).some(r=>r.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await C(T(t),t)},enable:({target:t})=>T(t).some(r=>r.closable!==!1)}};function E(){k.visible=!1,k.target=null,$.value=-1,D.value=[]}function at(t,r){e.contextmenu&&(k.target=t,k.position.x=r.clientX,k.position.y=r.clientY,n.nextTick(()=>{k.visible=!0,document.addEventListener("click",E,{once:!0}),n.nextTick(()=>{lt()})}))}function it(t,r){const u=typeof t=="string"?{id:t}:t,h=I[u.id],p=u.label??h?.label??String(u.id),f=u.visible??h?.visible??!0;if(!(typeof f=="function"?f(r):f!==!1))return null;const de=u.enable??h?.enable??!0,Et=typeof de=="function"?de(r):de!==!1,Pe=u.handler??h?.handler;if(!Pe)return null;const Bt=async()=>{await Promise.resolve(Pe(r))};return{id:String(u.id),label:p,disabled:!Et,action:Bt}}const _=n.computed(()=>{if(!k.visible||!k.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:se,r={target:k.target,controller:i};return t.map(u=>it(u,r)).filter(u=>!!u)});function lt(){const t=G.value;if(!t)return;const r=8,{innerWidth:u,innerHeight:h}=window,p=t.getBoundingClientRect();let f=k.position.x,V=k.position.y;p.right>u-r&&(f=Math.max(r,u-p.width-r)),p.bottom>h-r&&(V=Math.max(r,h-p.height-r)),(f!==k.position.x||V!==k.position.y)&&(k.position.x=f,k.position.y=V)}function st(t,r){D.value[r]=t??null}function ct(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function X(t,r,u=_.value){if(!u.length)return-1;const h=u.length;let p=t;for(let f=0;f<h;f++)if(p=(p+r+h)%h,!u[p].disabled)return p;return-1}function F(t){$.value=t,!(t<0)&&n.nextTick(()=>ct(t))}function Re(t){const r=X($.value,t);r!==-1&&F(r)}function ut(t){if(!k.visible)return;const r=t.key,u=_.value;if(!u.length)return;if(r==="Tab"){E();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(r))switch(t.preventDefault(),r){case"ArrowDown":case"ArrowRight":Re(1);break;case"ArrowUp":case"ArrowLeft":Re(-1);break;case"Home":F(X(-1,1));break;case"End":F(X(u.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=$.value;if(p>-1){const f=u[p];f.disabled||Ce(f)}break}case"Escape":E();break}}async function Ce(t){t.disabled||(E(),await t.action())}function ce(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 ft(t){return y.value[t.id]||ce(t)}function Ee(t){const r=i.getRouteKey(t),u=i.tabs.find(p=>p.id===r);if(!u)return console.warn("[RouterTab] Tab not found for route:",r),`${r}::0`;const h=u.renderKey??0;return`${r}::${h}`}function dt(t){return`${Ee(t)}::refresh`}function ue(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function bt(t){await i.closeTab(t.id)}function mt(t,r){r?z.set(t,r):z.delete(t)}function fe(t){n.nextTick(()=>{const r=z.get(t),u=U.value;if(r&&u){const h=r.getBoundingClientRect(),p=u.getBoundingClientRect();(h.left<p.left||h.right>p.right)&&r.scrollIntoView({behavior:"smooth",block:"nearest",inline:"nearest"})}})}function pt(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),fe(t.id))}function gt(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":ue(t),"is-dragging":w.dragging&&w.dragTab?.id===t.id,"is-drag-over":w.dropIndex===s(t.id)},t.tabClass]}function ht(t){return i.refreshingKey.value===i.getRouteKey(t)}function yt(t){const r=i.getRouteKey(t),u=i.tabs.find(h=>h.id===r);return u?i.options.keepAlive?u.alive:!0:!1}function Tt(t,r,u){e.sortable&&(w.dragging=!0,w.dragIndex=r,w.dragTab=t,u.dataTransfer&&(u.dataTransfer.effectAllowed="move",u.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:r}))}function vt(t,r){!e.sortable||!w.dragging||(r.preventDefault(),r.dataTransfer&&(r.dataTransfer.dropEffect="move"))}function kt(t){!e.sortable||!w.dragging||(w.dropIndex=t)}function wt(){!e.sortable||w.dragging}function Rt(t,r){if(!(!e.sortable||!w.dragging)){if(r.preventDefault(),w.dragIndex!==-1&&w.dragIndex!==t){const u=i.tabs.splice(w.dragIndex,1)[0];i.tabs.splice(t,0,u),a("tab-sorted",{tab:u,fromIndex:w.dragIndex,toIndex:t})}Be()}}function Be(){w.dragging=!1,w.dragIndex=-1,w.dropIndex=-1,w.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",E)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",E),o.appContext.config.globalProperties.$tabs=null,S.forEach(t=>{t.forEach(r=>{try{r()}catch(u){console.error("[RouterTab] Error during cleanup:",u)}})}),S.clear(),A.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,t=>{t&&fe(t),E()}),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(u=>u.id));Array.from(A.keys()).forEach(u=>{t.has(u)||(console.log(`[RouterTab] Cleaning up stale component instance: ${u}`),K(u))})}),n.watch(()=>e.contextmenu,t=>{t||E()}),n.watch(()=>_.value.length,t=>{k.visible&&t===0&&E()},{flush:"post"}),n.watch(_,t=>{if(!k.visible)return;D.value=new Array(t.length).fill(null);const r=X(-1,1,t);F(r)},{flush:"post"}),n.watch(()=>k.visible,t=>{t||($.value=-1,D.value=[])});const Ct=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:Ct,tabTransitionProps:ie,pageTransitionProps:le,buildTabClass:gt,activate:pt,close:bt,context:k,menuItems:_,handleMenuAction:Ce,showContextMenu:at,hideContextMenu:E,getTabTitle:ce,isClosable:ue,isRefreshing:ht,isTabReady:yt,hasCustomSlot:g,hasStartSlot:v,hasEndSlot:c,onDragStart:Tt,onDragOver:vt,onDragEnter:kt,onDragLeave:wt,onDrop:Rt,onDragEnd:Be,setupComponentWatching:N,cleanupComponentWatching:K,handleComponentRef:L,getReactiveTabTitle:ft,getComponentCacheKey:Ee,getRefreshComponentKey:dt,triggerTabUpdate:P,menuRef:G,highlightedIndex:$,setMenuItemRef:st,onMenuKeydown:ut,highlightMenuIndex:F,scrollContainer:U,setTabRef:mt,scrollTabIntoView:fe}}}),je=(e,a)=>{const o=e.__vccOpts||e;for(const[l,i]of a)o[l]=i;return o},Ne={class:"router-tab"},Ue={class:"router-tab__header"},ze={class:"router-tab__scroll",ref:"scrollContainer"},_e=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Fe=["title"],Ye=["onClick"],He={class:"router-tab__container"},We=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Je(e,a,o,l,i,g){const v=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Ne,[n.createElementVNode("header",Ue,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",ze,[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,ref_for:!0,ref:y=>e.setTabRef(c.id,y),onClick:y=>e.activate(c),onAuxclick:n.withModifiers(y=>e.close(c),["middle","prevent"]),onContextmenu:n.withModifiers(y=>e.showContextMenu(c,y),["prevent"]),onDragstart:y=>e.onDragStart(c,d,y),onDragover:y=>e.onDragOver(d,y),onDragenter:y=>e.onDragEnter(d),onDragleave:a[0]||(a[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(d,y),onDragend:a[1]||(a[1]=(...y)=>e.onDragEnd&&e.onDragEnd(...y))},[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,Fe),e.isClosable(c)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(y=>e.close(c),["stop"])},null,8,Ye)):n.createCommentVNode("",!0)],42,_e))),128))]),_:1},16)],512),n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-end",{"has-content":e.hasEndSlot}])},[n.renderSlot(e.$slots,"end")],2)]),n.createElementVNode("div",He,[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.createBlock(n.Transition,n.mergeProps({key:1},e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[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"})):e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:1,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isTabReady(c.route)?(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"})):n.createCommentVNode("",!0)],1032,["include","max"])):e.isTabReady(c.route)?(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"})):n.createCommentVNode("",!0)]),_:2},1040))]),_:3})]),n.withDirectives(n.createElementVNode("div",{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:y=>e.setMenuItemRef(y,d),onMouseenter:y=>!c.disabled&&e.highlightMenuIndex(d),onClick:y=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,We))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const re=je(Oe,[["render",Je]]),qe={class:"router-tabs","aria-hidden":"true"},W=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 oe(e),(o,l)=>(n.openBlock(),n.createElementBlock("span",qe))}}),he="tab-theme-style",ye="tab-theme-primary-color",Ge="system",Te="(prefers-color-scheme: dark)";let j=null;const B={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"},Xe={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 Qe(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 ae(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??B.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??B.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??B.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??B.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??B.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??B.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??B.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??B.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??B.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??B.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??B.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??B.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??B.iconColor))}function ve(e){if(typeof document>"u")return;const a=document.documentElement,o=window.matchMedia(Te),l=()=>{a.dataset.theme=o.matches?"dark":"light"};j&&(o.removeEventListener("change",j),j=null),e==="system"?(l(),j=()=>l(),o.addEventListener("change",j)):a.dataset.theme=e}function ke(e={}){if(typeof window>"u")return;const{styleKey:a=he,primaryKey:o=ye,defaultStyle:l=Ge,defaultPrimary:i}=e,g=window.localStorage.getItem(a)??l;ve(g);const c=g==="dark"||g==="system"&&window.matchMedia(Te).matches?{...Xe}:{...B};i&&(c.primary=i);const d=Qe(o);ae(d?{...c,...d}:c)}function Ze(e,a){if(typeof window>"u")return;const o=a?.styleKey??he;window.localStorage.setItem(o,e),ve(e)}function et(e,a){if(typeof window>"u")return;const o=a?.primaryKey??ye;window.localStorage.setItem(o,JSON.stringify(e)),ae(e)}function J(e,a){if(n.isRef(e)){const l=!n.isReadonly(e);return{value:e,update:l?i=>{e.value=i}:()=>{}}}if(typeof e=="function"){const l=e;return{value:n.computed(l),update:()=>{}}}const o=n.ref(e===void 0?a:e);return{value:o,update:l=>{o.value=l}}}function q(e={}){const a=J(e.title,"Untitled"),o=J(e.icon,""),l=J(e.closable,!0),i=J(e.meta,{});return{routeTabTitle:a.value,routeTabIcon:o.value,routeTabClosable:l.value,routeTabMeta:i.value,updateTitle:a.update,updateIcon:o.update,updateClosable:l.update,updateMeta:i.update}}function tt(e,a="Page"){return q({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function nt(e,a="Page",o="mdi-page"){return q({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":o)})}function ot(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 q({title:n.computed(()=>a+o[e.value].suffix),icon:n.computed(()=>o[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let we=!1;const rt={install(e,a){if(we)return;we=!0;const{initTheme:o=!0,themeOptions:l,componentName:i=re.name||"RouterTab",tabsComponentName:g=W.name||"RouterTabs"}=a??{};o&&ke(l??{}),e.component(i,re),e.component(g,W),g.toLowerCase()!=="router-tabs"&&e.component("router-tabs",W),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[O]},set(v){v&&e.provide(O,v)}})}};R.RouterTab=re,R.RouterTabs=W,R.default=rt,R.initRouterTabsTheme=ke,R.routerTabsKey=O,R.setRouterTabsPrimary=et,R.setRouterTabsTheme=Ze,R.useLoadingTab=tt,R.useNotificationTab=nt,R.useReactiveTab=q,R.useRouterTabs=ne,R.useRouterTabsPersistence=oe,R.useStatusTab=ot,Object.defineProperties(R,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -8,7 +8,7 @@
8
8
  <slot name="start" />
9
9
  </div>
10
10
 
11
- <div class="router-tab__scroll">
11
+ <div class="router-tab__scroll" ref="scrollContainer">
12
12
  <transition-group
13
13
  tag="ul"
14
14
  class="router-tab__nav"
@@ -20,6 +20,7 @@
20
20
  :class="buildTabClass(tab)"
21
21
  :data-title="getTabTitle(tab)"
22
22
  :draggable="sortable"
23
+ :ref="(el) => setTabRef(tab.id, el as HTMLElement)"
23
24
  @click="activate(tab)"
24
25
  @auxclick.middle.prevent="close(tab)"
25
26
  @contextmenu.prevent="showContextMenu(tab, $event)"
@@ -134,6 +135,7 @@ import {
134
135
  getCurrentInstance,
135
136
  nextTick,
136
137
  onBeforeUnmount,
138
+ onErrorCaptured,
137
139
  onMounted,
138
140
  provide,
139
141
  reactive,
@@ -275,119 +277,186 @@ export default defineComponent({
275
277
  function setupComponentWatching(routeKey: string, componentInstance: any) {
276
278
  if (!componentInstance || componentInstances.has(routeKey)) return
277
279
 
278
- // setup watchers for exposed reactive tab props
279
-
280
- componentInstances.set(routeKey, componentInstance)
281
-
282
- // Find the tab for this route
283
- const tab = controller.tabs.find(t => controller.getRouteKey(t.to) === routeKey)
284
- if (!tab) return
285
-
286
- const unwatchers: (() => void)[] = []
287
-
288
- // Watch routeTabTitle for title updates
289
- if (componentInstance.routeTabTitle !== undefined) {
290
- const unwatchTitle = watch(
291
- () => {
292
- // Handle both ref values and regular values
293
- const titleValue = componentInstance.routeTabTitle
294
- return titleValue && typeof titleValue === 'object' && 'value' in titleValue ? titleValue.value : titleValue
295
- },
296
- (newTitle) => {
297
- if (newTitle !== undefined && newTitle !== null) {
298
- const titleString = String(newTitle)
299
- tab.title = titleString
300
- triggerTabUpdate() // Trigger reactivity
301
- }
302
- },
303
- { immediate: true }
304
- )
305
- unwatchers.push(unwatchTitle)
306
- }
307
-
308
- // Watch routeTabIcon for icon updates
309
- if (componentInstance.routeTabIcon !== undefined) {
310
- const unwatchIcon = watch(
311
- () => {
312
- const iconValue = componentInstance.routeTabIcon
313
- return iconValue && typeof iconValue === 'object' && 'value' in iconValue ? iconValue.value : iconValue
314
- },
315
- (newIcon) => {
316
- if (newIcon !== undefined && newIcon !== null) {
317
- tab.icon = String(newIcon)
318
- triggerTabUpdate() // Trigger reactivity
319
- }
320
- },
321
- { immediate: true }
322
- )
323
- unwatchers.push(unwatchIcon)
324
- }
325
-
326
- // Watch routeTabClosable for closable state updates
327
- if (componentInstance.routeTabClosable !== undefined) {
328
- const unwatchClosable = watch(
329
- () => {
330
- const closableValue = componentInstance.routeTabClosable
331
- return closableValue && typeof closableValue === 'object' && 'value' in closableValue ? closableValue.value : closableValue
332
- },
333
- (newClosable) => {
334
- if (newClosable !== undefined && newClosable !== null) {
335
- tab.closable = Boolean(newClosable)
336
- triggerTabUpdate() // Trigger reactivity
337
- }
338
- },
339
- { immediate: true }
340
- )
341
- unwatchers.push(unwatchClosable)
342
- }
343
-
344
- // Watch routeTabMeta for general meta updates
345
- if (componentInstance.routeTabMeta !== undefined) {
346
- const unwatchMeta = watch(
347
- () => {
348
- const metaValue = componentInstance.routeTabMeta
349
- return metaValue && typeof metaValue === 'object' && 'value' in metaValue ? metaValue.value : metaValue
350
- },
351
- (newMeta) => {
352
- if (newMeta && typeof newMeta === 'object') {
353
- Object.assign(tab, newMeta)
354
- triggerTabUpdate() // Trigger reactivity
355
- }
356
- },
357
- { immediate: true, deep: true }
358
- )
359
- unwatchers.push(unwatchMeta)
280
+ try {
281
+ // setup watchers for exposed reactive tab props
282
+
283
+ componentInstances.set(routeKey, componentInstance)
284
+
285
+ // Find the tab for this route
286
+ const tab = controller.tabs.find(t => controller.getRouteKey(t.to) === routeKey)
287
+ if (!tab) {
288
+ console.warn(`[RouterTab] Cannot setup watching: tab not found for ${routeKey}`)
289
+ return
290
+ }
291
+
292
+ const unwatchers: (() => void)[] = []
293
+
294
+ // Watch routeTabTitle for title updates
295
+ if (componentInstance.routeTabTitle !== undefined) {
296
+ try {
297
+ const unwatchTitle = watch(
298
+ () => {
299
+ // Handle both ref values and regular values
300
+ const titleValue = componentInstance.routeTabTitle
301
+ return titleValue && typeof titleValue === 'object' && 'value' in titleValue ? titleValue.value : titleValue
302
+ },
303
+ (newTitle) => {
304
+ if (newTitle !== undefined && newTitle !== null) {
305
+ const titleString = String(newTitle)
306
+ tab.title = titleString
307
+ triggerTabUpdate() // Trigger reactivity
308
+ }
309
+ },
310
+ { immediate: true }
311
+ )
312
+ unwatchers.push(unwatchTitle)
313
+ } catch (error) {
314
+ console.error(`[RouterTab] Error watching routeTabTitle for ${routeKey}:`, error)
315
+ }
316
+ }
317
+
318
+ // Watch routeTabIcon for icon updates
319
+ if (componentInstance.routeTabIcon !== undefined) {
320
+ try {
321
+ const unwatchIcon = watch(
322
+ () => {
323
+ const iconValue = componentInstance.routeTabIcon
324
+ return iconValue && typeof iconValue === 'object' && 'value' in iconValue ? iconValue.value : iconValue
325
+ },
326
+ (newIcon) => {
327
+ if (newIcon !== undefined && newIcon !== null) {
328
+ tab.icon = String(newIcon)
329
+ triggerTabUpdate() // Trigger reactivity
330
+ }
331
+ },
332
+ { immediate: true }
333
+ )
334
+ unwatchers.push(unwatchIcon)
335
+ } catch (error) {
336
+ console.error(`[RouterTab] Error watching routeTabIcon for ${routeKey}:`, error)
337
+ }
338
+ }
339
+
340
+ // Watch routeTabClosable for closable state updates
341
+ if (componentInstance.routeTabClosable !== undefined) {
342
+ try {
343
+ const unwatchClosable = watch(
344
+ () => {
345
+ const closableValue = componentInstance.routeTabClosable
346
+ return closableValue && typeof closableValue === 'object' && 'value' in closableValue ? closableValue.value : closableValue
347
+ },
348
+ (newClosable) => {
349
+ if (newClosable !== undefined && newClosable !== null) {
350
+ tab.closable = Boolean(newClosable)
351
+ triggerTabUpdate() // Trigger reactivity
352
+ }
353
+ },
354
+ { immediate: true }
355
+ )
356
+ unwatchers.push(unwatchClosable)
357
+ } catch (error) {
358
+ console.error(`[RouterTab] Error watching routeTabClosable for ${routeKey}:`, error)
359
+ }
360
+ }
361
+
362
+ // Watch routeTabMeta for general meta updates
363
+ if (componentInstance.routeTabMeta !== undefined) {
364
+ try {
365
+ const unwatchMeta = watch(
366
+ () => {
367
+ const metaValue = componentInstance.routeTabMeta
368
+ return metaValue && typeof metaValue === 'object' && 'value' in metaValue ? metaValue.value : metaValue
369
+ },
370
+ (newMeta) => {
371
+ if (newMeta && typeof newMeta === 'object') {
372
+ Object.assign(tab, newMeta)
373
+ triggerTabUpdate() // Trigger reactivity
374
+ }
375
+ },
376
+ { immediate: true, deep: true }
377
+ )
378
+ unwatchers.push(unwatchMeta)
379
+ } catch (error) {
380
+ console.error(`[RouterTab] Error watching routeTabMeta for ${routeKey}:`, error)
381
+ }
382
+ }
383
+
384
+ watchedProperties.set(routeKey, unwatchers)
385
+ } catch (error) {
386
+ console.error(`[RouterTab] Error in setupComponentWatching for ${routeKey}:`, error)
387
+ // Clean up on error
388
+ cleanupComponentWatching(routeKey)
360
389
  }
361
-
362
- watchedProperties.set(routeKey, unwatchers)
363
390
  }
364
391
 
365
392
  // Cleanup watchers when component is unmounted
366
393
  function cleanupComponentWatching(routeKey: string) {
367
- const unwatchers = watchedProperties.get(routeKey)
368
- if (unwatchers) {
369
- unwatchers.forEach(unwatcher => unwatcher())
370
- watchedProperties.delete(routeKey)
394
+ try {
395
+ const unwatchers = watchedProperties.get(routeKey)
396
+ if (unwatchers) {
397
+ unwatchers.forEach(unwatcher => {
398
+ try {
399
+ unwatcher()
400
+ } catch (error) {
401
+ console.error(`[RouterTab] Error cleaning up watcher for ${routeKey}:`, error)
402
+ }
403
+ })
404
+ watchedProperties.delete(routeKey)
405
+ }
406
+ componentInstances.delete(routeKey)
407
+ } catch (error) {
408
+ console.error(`[RouterTab] Error in cleanupComponentWatching for ${routeKey}:`, error)
371
409
  }
372
- componentInstances.delete(routeKey)
373
410
  }
374
411
 
375
412
  // Handle component ref changes
376
413
  function handleComponentRef(el: any, routeKey: string) {
377
- if (el) {
378
- // Component mounted - set up watching
379
- // Check if properties are exposed directly on el (Vue 3 with defineExpose)
380
- if (el.routeTabTitle !== undefined || el.routeTabIcon !== undefined || el.routeTabClosable !== undefined) {
381
- setupComponentWatching(routeKey, el)
382
- } else if (el.$ && (el.$.routeTabTitle !== undefined || el.$.routeTabIcon !== undefined || el.$.routeTabClosable !== undefined)) {
383
- setupComponentWatching(routeKey, el.$)
414
+ try {
415
+ if (el) {
416
+ // Component mounted - set up watching
417
+ // Check if properties are exposed directly on el (Vue 3 with defineExpose)
418
+ if (el.routeTabTitle !== undefined || el.routeTabIcon !== undefined || el.routeTabClosable !== undefined) {
419
+ setupComponentWatching(routeKey, el)
420
+ } else if (el.$ && (el.$.routeTabTitle !== undefined || el.$.routeTabIcon !== undefined || el.$.routeTabClosable !== undefined)) {
421
+ setupComponentWatching(routeKey, el.$)
422
+ }
423
+ } else if (el === null) {
424
+ // Component unmounted - cleanup
425
+ cleanupComponentWatching(routeKey)
384
426
  }
385
- } else if (el === null) {
386
- // Component unmounted - cleanup
427
+ } catch (error) {
428
+ console.error(`[RouterTab] Error handling component ref for ${routeKey}:`, error)
429
+ // Clean up on error to prevent stale state
387
430
  cleanupComponentWatching(routeKey)
388
431
  }
389
432
  }
390
433
 
434
+ // Error handling: Capture errors from child components
435
+ onErrorCaptured((err, instance, info) => {
436
+ console.error('[RouterTab] Error captured from component:', err, info)
437
+
438
+ // Try to find the route key for the erroring component
439
+ if (instance && controller.activeId.value) {
440
+ const routeKey = controller.activeId.value
441
+
442
+ // Clean up the component instance to prevent stale state
443
+ cleanupComponentWatching(routeKey)
444
+
445
+ // Remove from KeepAlive cache if it's cached
446
+ const tab = controller.tabs.find(t => t.id === routeKey)
447
+ if (tab && tab.alive) {
448
+ console.warn(`[RouterTab] Removing errored component ${routeKey} from KeepAlive cache`)
449
+ tab.alive = false
450
+ nextTick(() => {
451
+ tab.alive = true
452
+ })
453
+ }
454
+ }
455
+
456
+ // Return false to propagate the error to parent
457
+ return false
458
+ })
459
+
391
460
  if (props.cookieKey !== null || props.persistence) {
392
461
  const options: RouterTabsPersistenceOptions = {
393
462
  ...(props.persistence ?? {})
@@ -412,6 +481,8 @@ export default defineComponent({
412
481
  const menuRef = ref<HTMLElement | null>(null)
413
482
  const menuItemRefs = ref<(HTMLElement | null)[]>([])
414
483
  const highlightedIndex = ref(-1)
484
+ const scrollContainer = ref<HTMLElement | null>(null)
485
+ const tabRefs = new Map<string, HTMLElement>()
415
486
 
416
487
  // Drag and drop state
417
488
  const dragState = reactive({
@@ -756,6 +827,41 @@ export default defineComponent({
756
827
  await controller.closeTab(tab.id)
757
828
  }
758
829
 
830
+ // Store tab element references for scroll-into-view
831
+ function setTabRef(tabId: string, el: HTMLElement | null) {
832
+ if (el) {
833
+ tabRefs.set(tabId, el)
834
+ } else {
835
+ tabRefs.delete(tabId)
836
+ }
837
+ }
838
+
839
+ // Scroll tab into view when activated
840
+ function scrollTabIntoView(tabId: string) {
841
+ nextTick(() => {
842
+ const tabEl = tabRefs.get(tabId)
843
+ const container = scrollContainer.value
844
+
845
+ if (tabEl && container) {
846
+ // Calculate if tab is within view
847
+ const tabRect = tabEl.getBoundingClientRect()
848
+ const containerRect = container.getBoundingClientRect()
849
+
850
+ // Check if tab is outside the visible area
851
+ const isOutOfView = tabRect.left < containerRect.left || tabRect.right > containerRect.right
852
+
853
+ if (isOutOfView) {
854
+ // Scroll the tab into view with smooth behavior
855
+ tabEl.scrollIntoView({
856
+ behavior: 'smooth',
857
+ block: 'nearest',
858
+ inline: 'nearest'
859
+ })
860
+ }
861
+ }
862
+ })
863
+ }
864
+
759
865
  function activate(tab: TabRecord) {
760
866
  if (tab.href && typeof window !== 'undefined') {
761
867
  if (tab.target && tab.target !== '_self') {
@@ -768,6 +874,7 @@ export default defineComponent({
768
874
 
769
875
  if (controller.activeId.value === tab.id) return
770
876
  controller.openTab(tab.to, false)
877
+ scrollTabIntoView(tab.id)
771
878
  }
772
879
 
773
880
  function buildTabClass(tab: TabRecord) {
@@ -789,7 +896,12 @@ export default defineComponent({
789
896
 
790
897
  function isTabReady(route: RouteLocationNormalizedLoaded) {
791
898
  const routeKey = controller.getRouteKey(route)
792
- return controller.tabs.some(tab => tab.id === routeKey)
899
+ const tab = controller.tabs.find(tab => tab.id === routeKey)
900
+ if (!tab) return false
901
+
902
+ // If KeepAlive is enabled, only render if tab is marked as alive (included in cache)
903
+ // If KeepAlive is disabled, render if tab exists
904
+ return controller.options.keepAlive ? tab.alive : true
793
905
  }
794
906
 
795
907
  // Drag and drop handlers
@@ -862,7 +974,13 @@ export default defineComponent({
862
974
 
863
975
  // Cleanup all component watchers
864
976
  watchedProperties.forEach((unwatchers) => {
865
- unwatchers.forEach(unwatcher => unwatcher())
977
+ unwatchers.forEach(unwatcher => {
978
+ try {
979
+ unwatcher()
980
+ } catch (error) {
981
+ console.error('[RouterTab] Error during cleanup:', error)
982
+ }
983
+ })
866
984
  })
867
985
  watchedProperties.clear()
868
986
  componentInstances.clear()
@@ -877,7 +995,29 @@ export default defineComponent({
877
995
 
878
996
  watch(
879
997
  () => controller.activeId.value,
880
- () => hideContextMenu()
998
+ (newActiveId) => {
999
+ if (newActiveId) {
1000
+ scrollTabIntoView(newActiveId)
1001
+ }
1002
+ hideContextMenu()
1003
+ }
1004
+ )
1005
+
1006
+ // Clean up stale component instances when tabs are closed
1007
+ watch(
1008
+ () => controller.tabs.length,
1009
+ () => {
1010
+ // Check for stale component instances
1011
+ const currentTabIds = new Set(controller.tabs.map(tab => tab.id))
1012
+ const instanceKeys = Array.from(componentInstances.keys())
1013
+
1014
+ instanceKeys.forEach(key => {
1015
+ if (!currentTabIds.has(key)) {
1016
+ console.log(`[RouterTab] Cleaning up stale component instance: ${key}`)
1017
+ cleanupComponentWatching(key)
1018
+ }
1019
+ })
1020
+ }
881
1021
  )
882
1022
 
883
1023
  watch(
@@ -954,7 +1094,10 @@ export default defineComponent({
954
1094
  highlightedIndex,
955
1095
  setMenuItemRef,
956
1096
  onMenuKeydown,
957
- highlightMenuIndex
1097
+ highlightMenuIndex,
1098
+ scrollContainer,
1099
+ setTabRef,
1100
+ scrollTabIntoView
958
1101
  }
959
1102
  }
960
1103
  })