vue3-router-tab 1.3.5 → 1.3.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(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 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 r=e.resolve(o);if(!r||!r.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return r}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 M(e){const o=e.meta?.key;if(typeof o=="function"){const r=o(e);if(typeof r=="string"&&r.length)return r}else if(typeof o=="string"&&o.length){const r=Pe[o.toLowerCase()];return r?r(e):o}return e.fullPath}function G(e,o){const r=e.meta?.keepAlive;return typeof r=="boolean"?r:o}function X(e,o){const r=e.meta?.reuse;return typeof r=="boolean"?r:o}function se(e){const o=e.meta??{},r={};return"title"in o&&(r.title=o.title),"tips"in o&&(r.tips=o.tips),"icon"in o&&(r.icon=o.icon),"closable"in o&&(r.closable=o.closable),"tabClass"in o&&(r.tabClass=o.tabClass),"target"in o&&(r.target=o.target),"href"in o&&(r.href=o.href),r}function _(e,o,r){const s=se(e);return{id:M(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:G(e,r),reusable:X(e,!1),closable:s.closable??!0,renderKey:typeof o.renderKey=="number"?o.renderKey:0,...s,...o}}function Q(e,o,r,s){if(!e.find(g=>g.id===o.id)){if(r==="next"&&s){const g=e.findIndex(v=>v.id===s);if(g!==-1){e.splice(g+1,0,o);return}}e.push(o)}}function ce(e,o,r){if(!o||o<=0)return;const s=e.filter(i=>i.alive);for(;s.length>o;){const i=s.shift();if(!i||i.id===r)continue;const g=e.findIndex(v=>v.id===i.id);g>-1&&(e[g].alive=!1)}}function Be(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Ae(e){const o={};return"title"in e&&(o.title=e.title),"tips"in e&&(o.tips=e.tips),"icon"in e&&(o.icon=e.icon),"tabClass"in e&&(o.tabClass=e.tabClass),"closable"in e&&(o.closable=e.closable),"renderKey"in e&&typeof e.renderKey=="number"&&(o.renderKey=e.renderKey),o}function Ie(e,o={}){const r=Ee(o),s=n.reactive([]),i=n.ref(null),g=n.shallowRef(),v=n.ref(null),c=n.computed(()=>s.filter(l=>l.alive).map(l=>`${l.id}::${l.renderKey??0}`));let f=!1;function y(l){const m=typeof l.matched=="object"?l:x(e,l);return{key:M(m),fullPath:m.fullPath,alive:G(m,r.keepAlive),reusable:X(m,!1),matched:m}}function E(l){const m=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,r.keepAlive),b.reusable=X(l,b.reusable),typeof b.renderKey!="number"&&(b.renderKey=0),Object.assign(b,se(l)),b):(b=_(l,{},r.keepAlive),Q(s,b,r.appendPosition,i.value),ce(s,r.maxAlive,i.value),b)}async function A(l,m=!1,b=!0){const k=x(e,l),R=M(k),P=i.value===R;b==="sameTab"&&(b=P),b&&await L(R,!0),await e[m?"replace":"push"](k),P&&await T()}function S(l){const m=s.findIndex($=>$.id===l);if(m===-1)return r.defaultRoute;const b=s[m+1],k=s[m-1],R=s.find($=>$.id!==l),P=b||k||R;return P?P.to:r.defaultRoute}async function O(l=i.value,m={}){if(!l)return;if(!m.force&&r.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,R=k?m.redirect??S(l):null;await I(l,{force:m.force}),m.redirect!==null&&k&&R&&await e.replace(R)}async function I(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,g.value=void 0))}async function L(l=i.value??void 0,m=!1){if(!l)return;const b=s.find(R=>R.id===l);if(!b)return;const k=r.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 L(m.id,l)}async function re(l=r.defaultRoute){s.splice(0,s.length),i.value=null,g.value=void 0;for(const m of r.initialTabs){const b=x(e,m.to),k=_(b,m,r.keepAlive);s.push(k)}await e.replace(l)}async function T(){const l=i.value;l&&await L(l,!0)}function J(l){return typeof l.matched=="object"?M(l):M(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 K(l){f=!0,s.splice(0,s.length),i.value=null,g.value=void 0;const m=l?.tabs??[];for(const k of m)try{const R=x(e,k.to),P=Ae(k),$=_(R,P,r.keepAlive);Q(s,$,"last",null)}catch{}f=!1;const b=l?.active??m[m.length-1]?.to??r.defaultRoute;if(b)try{await e.replace(b)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(f)return;const m=E(l);i.value=m.id,g.value=m,ce(s,r.maxAlive,i.value)},{immediate:!0}),r.initialTabs.length&&r.initialTabs.forEach(l=>{const m=x(e,l.to),b=_(m,l,r.keepAlive);Q(s,b,"last",null)}),{options:r,tabs:s,activeId:i,current:g,includeKeys:c,refreshingKey:v,openTab:A,closeTab:O,removeTab:I,refreshTab:L,refreshAll:oe,reset:re,reload:T,getRouteKey:J,matchRoute:y,snapshot:D,hydrate:K}}function ue(e){return e?typeof e=="string"?{name:e}:e:{}}const j=Symbol("RouterTabsContext"),Y="router-tabs:snapshot";function Z(e={}){const{optional:o=!1}=e,r=n.inject(j,null);if(r)return r;const s=n.inject("$tabs",null);if(s)return s;const g=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(g)return g;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const $e=864e5;function Ke(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,r=document.cookie?document.cookie.split("; "):[];for(const s of r)if(s.startsWith(o))return decodeURIComponent(s.slice(o.length));return null}function de(e,o,r){if(typeof document>"u")return;const{expiresInDays:s=7,path:i="/",domain:g,secure:v,sameSite:c="lax"}=r,f=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(s!==1/0){const y=new Date(Date.now()+s*$e).toUTCString();f.push(`Expires=${y}`)}i&&f.push(`Path=${i}`),g&&f.push(`Domain=${g}`),v&&f.push("Secure"),c&&f.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=f.join("; ")}function fe(e,o){if(typeof document>"u")return;const{path:r="/",domain:s}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),r&&i.push(`Path=${r}`),s&&i.push(`Domain=${s}`),document.cookie=i.join("; ")}const Se=e=>JSON.stringify(e??null),De=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ee(e={}){const{cookieKey:o=Y,serialize:r=Se,deserialize:s=De}=e,i=Z({optional:!0}),g=n.ref(!0),v=c=>{n.onMounted(async()=>{const f=s(Ke(o));if(f&&f.tabs?.length)try{if(g.value=!0,await c.hydrate(f),f.active){await n.nextTick();const E=c.tabs.find(A=>A.to===f.active);E&&(c.activeId.value=E.id,c.current.value=E)}}finally{g.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{g.value=!0;const E=e.fallbackRoute??c.options.defaultRoute;await c.reset(E)}finally{g.value=!1}else g.value=!1;const y=c.snapshot();y.tabs.length?de(o,r(y),e):fe(o,e),g.value=!1}),n.watch(()=>({tabs:c.tabs.map(f=>({to:f.to,title:f.title,tips:f.tips,icon:f.icon,tabClass:f.tabClass,closable:f.closable,renderKey:f.renderKey})),active:c.activeId.value}),()=>{if(g.value)return;const f=c.snapshot();f.tabs.length?de(o,r(f),e):fe(o,e)},{deep:!0})};i?v(i):n.onMounted(()=>{const c=Z({optional:!0});c&&v(c)})}const xe=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:o}){const r=n.getCurrentInstance();if(!r)throw new Error("[RouterTab] component must be used within a Vue application context.");const s=r.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=Ie(s,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(j,i),r.appContext.config.globalProperties.$tabs=i;const g=n.computed(()=>!!r?.slots?.default),v=n.computed(()=>!!r?.slots?.start),c=n.computed(()=>!!r?.slots?.end),f=n.ref(0),y=n.computed(()=>{f.value;const t={};return i.tabs.forEach(a=>{const u=typeof a.title=="string"?a.title:String(a.title||ae(a));t[a.id]=u}),t});function E(){f.value++}const A=new Map,S=new Map;function O(t,a){if(!(!a||A.has(t)))try{A.set(t,a);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(a.routeTabTitle!==void 0)try{const p=n.watch(()=>{const d=a.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const V=String(d);u.title=V,E()}},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(a.routeTabIcon!==void 0)try{const p=n.watch(()=>{const d=a.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(u.icon=String(d),E())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(a.routeTabClosable!==void 0)try{const p=n.watch(()=>{const d=a.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(u.closable=!!d,E())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(a.routeTabMeta!==void 0)try{const p=n.watch(()=>{const d=a.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(u,d),E())},{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),I(t)}}function I(t){try{const a=S.get(t);a&&(a.forEach(u=>{try{u()}catch(h){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,h)}}),S.delete(t)),A.delete(t)}catch(a){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,a)}}function L(t,a){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?O(a,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&O(a,t.$):t===null&&I(a)}catch(u){console.error(`[RouterTab] Error handling component ref for ${a}:`,u),I(a)}}if(n.onErrorCaptured((t,a,u)=>{if(console.error("[RouterTab] Error captured from component:",t,u),a&&i.activeId.value){const h=i.activeId.value;I(h);const p=i.tabs.find(d=>d.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||Y:t.cookieKey||(t.cookieKey=Y),ee(t)}const oe=n.computed(()=>ue(e.tabTransition)),re=n.computed(()=>ue(e.pageTransition)),T=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),J=n.ref(null),D=n.ref([]),K=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(a=>a.id===t)}function k(t){const a=b(t.id);return a>0?i.tabs.slice(0,a):[]}function R(t){const a=b(t.id);return a>-1?i.tabs.slice(a+1):[]}function P(t){return i.tabs.filter(a=>a.id!==t.id)}async function $(t,a){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:a.to,force:!0}):await i.removeTab(h.id,{force:!0});i.activeId.value!==a.id&&await i.openTab(a.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(t),t)},enable:({target:t})=>k(t).some(a=>a.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await $(R(t),t)},enable:({target:t})=>R(t).some(a=>a.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await $(P(t),t)},enable:({target:t})=>P(t).some(a=>a.closable!==!1)}};function B(){T.visible=!1,T.target=null,K.value=-1,D.value=[]}function nt(t,a){e.contextmenu&&(T.target=t,T.position.x=a.clientX,T.position.y=a.clientY,n.nextTick(()=>{T.visible=!0,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{rt()})}))}function ot(t,a){const u=typeof t=="string"?{id:t}:t,h=tt[u.id],p=u.label??h?.label??String(u.id),d=u.visible??h?.visible??!0;if(!(typeof d=="function"?d(a):d!==!1))return null;const le=u.enable??h?.enable??!0,vt=typeof le=="function"?le(a):le!==!1,Re=u.handler??h?.handler;if(!Re)return null;const wt=async()=>{await Promise.resolve(Re(a))};return{id:String(u.id),label:p,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,a={target:T.target,controller:i};return t.map(u=>ot(u,a)).filter(u=>!!u)});function rt(){const t=J.value;if(!t)return;const a=8,{innerWidth:u,innerHeight:h}=window,p=t.getBoundingClientRect();let d=T.position.x,V=T.position.y;p.right>u-a&&(d=Math.max(a,u-p.width-a)),p.bottom>h-a&&(V=Math.max(a,h-p.height-a)),(d!==T.position.x||V!==T.position.y)&&(T.position.x=d,T.position.y=V)}function at(t,a){D.value[a]=t??null}function it(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function q(t,a,u=U.value){if(!u.length)return-1;const h=u.length;let p=t;for(let d=0;d<h;d++)if(p=(p+a+h)%h,!u[p].disabled)return p;return-1}function z(t){K.value=t,!(t<0)&&n.nextTick(()=>it(t))}function Te(t){const a=q(K.value,t);a!==-1&&z(a)}function lt(t){if(!T.visible)return;const a=t.key,u=U.value;if(!u.length)return;if(a==="Tab"){B();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(a))switch(t.preventDefault(),a){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(u.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=K.value;if(p>-1){const d=u[p];d.disabled||ke(d)}break}case"Escape":B();break}}async function ke(t){t.disabled||(B(),await t.action())}function ae(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 y.value[t.id]||ae(t)}function ve(t){const a=i.getRouteKey(t),u=i.tabs.find(p=>p.id===a);if(!u)return console.warn("[RouterTab] Tab not found for route:",a),`${a}::0`;const h=u.renderKey??0;return`${a}::${h}`}function ct(t){return`${ve(t)}::refresh`}function ie(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function ut(t){await i.closeTab(t.id)}function dt(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}i.activeId.value!==t.id&&i.openTab(t.to,!1)}function ft(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":ie(t),"is-dragging":l.dragging&&l.dragTab?.id===t.id,"is-drag-over":l.dropIndex===b(t.id)},t.tabClass]}function bt(t){return i.refreshingKey.value===i.getRouteKey(t)}function mt(t){const a=i.getRouteKey(t);return i.tabs.some(u=>u.id===a)}function pt(t,a,u){e.sortable&&(l.dragging=!0,l.dragIndex=a,l.dragTab=t,u.dataTransfer&&(u.dataTransfer.effectAllowed="move",u.dataTransfer.setData("text/plain",t.id)),o("tab-sort",{tab:t,index:a}))}function gt(t,a){!e.sortable||!l.dragging||(a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="move"))}function ht(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function yt(){!e.sortable||l.dragging}function Tt(t,a){if(!(!e.sortable||!l.dragging)){if(a.preventDefault(),l.dragIndex!==-1&&l.dragIndex!==t){const u=i.tabs.splice(l.dragIndex,1)[0];i.tabs.splice(t,0,u),o("tab-sorted",{tab:u,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),r.appContext.config.globalProperties.$tabs=null,S.forEach(t=>{t.forEach(a=>{try{a()}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,()=>B()),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}`),I(u))})}),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 a=q(-1,1,t);z(a)},{flush:"post"}),n.watch(()=>T.visible,t=>{t||(K.value=-1,D.value=[])});const kt=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:kt,tabTransitionProps:oe,pageTransitionProps:re,buildTabClass:ft,activate:dt,close:ut,context:T,menuItems:U,handleMenuAction:ke,showContextMenu:nt,hideContextMenu:B,getTabTitle:ae,isClosable:ie,isRefreshing:bt,isTabReady:mt,hasCustomSlot:g,hasStartSlot:v,hasEndSlot:c,onDragStart:pt,onDragOver:gt,onDragEnter:ht,onDragLeave:yt,onDrop:Tt,onDragEnd:we,setupComponentWatching:O,cleanupComponentWatching:I,handleComponentRef:L,getReactiveTabTitle:st,getComponentCacheKey:ve,getRefreshComponentKey:ct,triggerTabUpdate:E,menuRef:J,highlightedIndex:K,setMenuItemRef:at,onMenuKeydown:lt,highlightMenuIndex:z}}}),Me=(e,o)=>{const r=e.__vccOpts||e;for(const[s,i]of o)r[s]=i;return r},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,r,s,i,g){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,f)=>(n.openBlock(),n.createElementBlock("li",{key:c.id,class:n.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,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,f,y),onDragover:y=>e.onDragOver(f,y),onDragenter:y=>e.onDragEnter(f),onDragleave:o[0]||(o[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(f,y),onDragend:o[1]||(o[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,Oe),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,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:f=>e.handleComponentRef(f,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:f=>e.handleComponentRef(f,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:f=>e.handleComponentRef(f,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:f=>e.handleComponentRef(f,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,f)=>(n.openBlock(),n.createElementBlock("a",{key:c.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":f===e.highlightedIndex}]),"aria-disabled":c.disabled,disabled:c.disabled,tabindex:c.disabled?-1:f===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,f),onMouseenter:y=>!c.disabled&&e.highlightMenuIndex(f),onClick:y=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,_e))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const te=Me(xe,[["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),(r,s)=>(n.openBlock(),n.createElementBlock("span",Fe))}}),be="tab-theme-style",me="tab-theme-primary-color",He="system",pe="(prefers-color-scheme: dark)";let N=null;const 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"},We={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 o=window.localStorage.getItem(e);if(!o)return null;try{const r=JSON.parse(o);return r&&typeof r=="object"?r: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 o=document.documentElement,r=window.matchMedia(pe),s=()=>{o.dataset.theme=r.matches?"dark":"light"};N&&(r.removeEventListener("change",N),N=null),e==="system"?(s(),N=()=>s(),r.addEventListener("change",N)):o.dataset.theme=e}function he(e={}){if(typeof window>"u")return;const{styleKey:o=be,primaryKey:r=me,defaultStyle:s=He,defaultPrimary:i}=e,g=window.localStorage.getItem(o)??s;ge(g);const c=g==="dark"||g==="system"&&window.matchMedia(pe).matches?{...We}:{...C};i&&(c.primary=i);const f=Je(r);ne(f?{...c,...f}:c)}function qe(e,o){if(typeof window>"u")return;const r=o?.styleKey??be;window.localStorage.setItem(r,e),ge(e)}function Ge(e,o){if(typeof window>"u")return;const r=o?.primaryKey??me;window.localStorage.setItem(r,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 r=n.ref(e===void 0?o:e);return{value:r,update:s=>{r.value=s}}}function W(e={}){const o=H(e.title,"Untitled"),r=H(e.icon,""),s=H(e.closable,!0),i=H(e.meta,{});return{routeTabTitle:o.value,routeTabIcon:r.value,routeTabClosable:s.value,routeTabMeta:i.value,updateTitle:o.update,updateIcon:r.update,updateClosable:s.update,updateMeta:i.update}}function Xe(e,o="Page"){return W({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",r="mdi-page"){return W({title:n.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:n.computed(()=>e.value>0?"mdi-bell-badge":r)})}function Ze(e,o="Page"){const r={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 W({title:n.computed(()=>o+r[e.value].suffix),icon:n.computed(()=>r[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let ye=!1;const et={install(e,o){if(ye)return;ye=!0;const{initTheme:r=!0,themeOptions:s,componentName:i=te.name||"RouterTab",tabsComponentName:g=F.name||"RouterTabs"}=o??{};r&&he(s??{}),e.component(i,te),e.component(g,F),g.toLowerCase()!=="router-tabs"&&e.component("router-tabs",F),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[j]},set(v){v&&e.provide(j,v)}})}};w.RouterTab=te,w.RouterTabs=F,w.default=et,w.initRouterTabsTheme=he,w.routerTabsKey=j,w.setRouterTabsPrimary=Ge,w.setRouterTabsTheme=qe,w.useLoadingTab=Xe,w.useNotificationTab=Qe,w.useReactiveTab=W,w.useRouterTabs=Z,w.useRouterTabsPersistence=ee,w.useStatusTab=Ze,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(C,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):(C=typeof globalThis<"u"?globalThis:C||self,n(C["vue3-router-tab"]={},C.Vue,C.VueRouter))})(this,(function(C,n,Ie){"use strict";function xe(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function D(e,a){const r=e.resolve(a);if(!r||!r.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return r}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 M(e){const a=e.meta?.key;if(typeof a=="function"){const r=a(e);if(typeof r=="string"&&r.length)return r}else if(typeof a=="string"&&a.length){const r=De[a.toLowerCase()];return r?r(e):a}return e.fullPath}function ee(e,a){const r=e.meta?.keepAlive;return typeof r=="boolean"?r:a}function te(e,a){const r=e.meta?.reuse;return typeof r=="boolean"?r:a}function ge(e){const a=e.meta??{},r={};return"title"in a&&(r.title=a.title),"tips"in a&&(r.tips=a.tips),"icon"in a&&(r.icon=a.icon),"closable"in a&&(r.closable=a.closable),"tabClass"in a&&(r.tabClass=a.tabClass),"target"in a&&(r.target=a.target),"href"in a&&(r.href=a.href),r}function q(e,a,r){const c=ge(e);return{id:M(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:ee(e,r),reusable:te(e,!1),closable:c.closable??!0,renderKey:typeof a.renderKey=="number"?a.renderKey:0,...c,...a}}function ne(e,a,r,c){if(!e.find(g=>g.id===a.id)){if(r==="next"&&c){const g=e.findIndex(k=>k.id===c);if(g!==-1){e.splice(g+1,0,a);return}}e.push(a)}}function oe(e,a,r,c){if(!a||a<=0)return;const i=e.filter(g=>g.alive);for(;i.length>a;){const g=i.shift();if(!g||g.id===r)continue;const k=e.findIndex(l=>l.id===g.id);if(k>-1){const l=e[k],m=`${l.id}::${l.renderKey??0}`;c.delete(m),l.alive=!1}}}function Me(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Le(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 Ve(e,a={}){const r=xe(a),c=n.reactive([]),i=n.ref(null),g=n.shallowRef(),k=n.ref(null),l=n.reactive(new Set),m=n.computed(()=>Array.from(l));let y=!1;function $(u){const d=typeof u.matched=="object"?u:D(e,u);return{key:M(d),fullPath:d.fullPath,alive:ee(d,r.keepAlive),reusable:te(d,!1),matched:d}}function P(u){const d=M(u);let f=c.find(v=>v.id===d);const T=ee(u,r.keepAlive);if(f){f.fullPath=u.fullPath,f.to=u.fullPath,f.matched=u,f.reusable=te(u,f.reusable),typeof f.renderKey!="number"&&(f.renderKey=0);const v=`${d}::${f.renderKey}`;return(d.includes("students")||d.includes("classroom")||d.includes("quiz"))&&console.log(`[ensureTab] EXISTING tab: ${u.fullPath}`,{key:d,shouldBeAlive:T,currentRenderKey:f.renderKey,currentCacheKey:v,isInCache:l.has(v),aliveCacheSize:l.size,cacheContents:Array.from(l)}),T&&(l.has(v)?f.alive||(f.alive=!0,(d.includes("students")||d.includes("classroom")||d.includes("quiz"))&&console.log(`[ensureTab] ✅ Reactivated: ${v}`)):(l.add(v),f.alive=!0,(d.includes("students")||d.includes("classroom")||d.includes("quiz"))&&console.log(`[ensureTab] ✅ Added to cache: ${v}`))),Object.assign(f,ge(u)),f}if(f=q(u,{},r.keepAlive),f.alive){const v=`${d}::${f.renderKey??0}`;l.add(v),(d.includes("students")||d.includes("classroom")||d.includes("quiz"))&&console.log(`[ensureTab] NEW tab created and cached: ${v}`)}return ne(c,f,r.appendPosition,i.value),oe(c,r.maxAlive,i.value,l),f}async function A(u,d=!1,f="sameTab"){const T=D(e,u),v=M(T),E=i.value===v;f==="sameTab"&&(f=E),f&&await L(v,!0),await e[d?"replace":"push"](T),E&&await V()}function z(u){const d=c.findIndex(B=>B.id===u);if(d===-1)return r.defaultRoute;const f=c[d+1],T=c[d-1],v=c.find(B=>B.id!==u),E=f||T||v;return E?E.to:r.defaultRoute}async function I(u=i.value,d={}){if(!u)return;if(!d.force&&r.keepLastTab&&c.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const T=i.value===u&&d.redirect!==null,v=T?d.redirect??z(u):null;await U(u,{force:d.force}),d.redirect!==null&&T&&v&&await e.replace(v)}async function U(u,d={}){const f=c.findIndex(T=>T.id===u);f!==-1&&(c.splice(f,1),k.value===u&&(k.value=null),i.value===u&&(i.value=null,g.value=void 0))}async function L(u=i.value??void 0,d=!1){if(!u)return;const f=c.find(B=>B.id===u);if(!f)return;const T=r.keepAlive&&f.alive,v=`${u}::${f.renderKey??0}`;T&&(l.delete(v),f.alive=!1,await n.nextTick()),f.renderKey=(f.renderKey??0)+1;const E=`${u}::${f.renderKey}`;T&&(l.add(E),f.alive=!0),k.value=u,await n.nextTick(),d||await n.nextTick(),k.value=null}async function se(u=!1){for(const d of c)await L(d.id,u)}function w(u,d){const f=c.find(v=>v.id===u);if(!f)return;const T=`${u}::${f.renderKey??0}`;d?(l.add(T),f.alive=!0,oe(c,r.maxAlive,i.value,l)):(l.delete(T),f.alive=!1)}function X(u){const d=c.find(T=>T.id===u);if(!d)return;const f=`${u}::${d.renderKey??0}`;l.delete(f),d.alive=!1,d.renderKey=(d.renderKey??0)+1}function x(){l.clear(),c.forEach(u=>{u.alive=!1})}function S(){return m.value.slice()}async function Q(u=r.defaultRoute){c.splice(0,c.length),i.value=null,g.value=void 0;for(const d of r.initialTabs){const f=D(e,d.to),T=q(f,d,r.keepAlive);c.push(T)}await e.replace(u)}async function V(){const u=i.value;u&&await L(u,!0)}function R(u){return typeof u.matched=="object"?M(u):M(D(e,u))}function ce(){const u=c.find(d=>d.id===i.value);return{tabs:c.map(Me),active:u?u.to:null}}async function _(u){y=!0,c.splice(0,c.length),i.value=null,g.value=void 0;const d=u?.tabs??[];for(const T of d)try{const v=D(e,T.to),E=Le(T),B=q(v,E,r.keepAlive);ne(c,B,"last",null)}catch(v){console.warn("[RouterTabs] Failed to restore tab",T,v)}y=!1;const f=u?.active??d[d.length-1]?.to??r.defaultRoute;if(f)try{await e.replace(f)}catch(T){console.warn("[RouterTabs] Failed to navigate to restored route",f,T)}}return n.watch(()=>e.currentRoute.value,u=>{if(y)return;const d=P(u);i.value=d.id,g.value=d,oe(c,r.maxAlive,i.value,l)},{immediate:!0}),r.initialTabs.length&&r.initialTabs.forEach(u=>{const d=D(e,u.to),f=q(d,u,r.keepAlive);ne(c,f,"last",null)}),{options:r,tabs:c,activeId:i,current:g,includeKeys:m,refreshingKey:k,openTab:A,closeTab:I,removeTab:U,refreshTab:L,refreshAll:se,setTabAlive:w,evictCache:X,clearCache:x,getCacheKeys:S,reset:Q,reload:V,getRouteKey:R,matchRoute:$,snapshot:ce,hydrate:_,ensureTab:P}}function he(e){return e?typeof e=="string"?{name:e}:e:{}}const O=Symbol("RouterTabsContext"),W="router-tabs:snapshot";function re(e={}){const{optional:a=!1}=e,r=n.inject(O,null);if(r)return r;const c=n.inject("$tabs",null);if(c)return c;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 Ne=864e5;function Oe(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,r=document.cookie?document.cookie.split("; "):[];for(const c of r)if(c.startsWith(a))return decodeURIComponent(c.slice(a.length));return null}function ye(e,a,r){if(typeof document>"u")return;const{expiresInDays:c=7,path:i="/",domain:g,secure:k,sameSite:l="lax"}=r,m=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(c!==1/0){const y=new Date(Date.now()+c*Ne).toUTCString();m.push(`Expires=${y}`)}i&&m.push(`Path=${i}`),g&&m.push(`Domain=${g}`),k&&m.push("Secure"),l&&m.push(`SameSite=${l.charAt(0).toUpperCase()}${l.slice(1)}`),document.cookie=m.join("; ")}function Te(e,a){if(typeof document>"u")return;const{path:r="/",domain:c}=a,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),r&&i.push(`Path=${r}`),c&&i.push(`Domain=${c}`),document.cookie=i.join("; ")}const je=e=>JSON.stringify(e??null),ze=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ae(e={}){const{cookieKey:a=W,serialize:r=je,deserialize:c=ze}=e,i=re({optional:!0}),g=n.ref(!0),k=l=>{n.onMounted(async()=>{const m=c(Oe(a));if(m&&m.tabs?.length)try{if(g.value=!0,await l.hydrate(m),m.active){await n.nextTick();const $=l.tabs.find(P=>P.to===m.active);$&&(l.activeId.value=$.id,l.current.value=$)}}finally{g.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{g.value=!0;const $=e.fallbackRoute??l.options.defaultRoute;await l.reset($)}finally{g.value=!1}else g.value=!1;const y=l.snapshot();y.tabs.length?ye(a,r(y),e):Te(a,e),g.value=!1}),n.watch(()=>({tabs:l.tabs.map(m=>({to:m.to,title:m.title,tips:m.tips,icon:m.icon,tabClass:m.tabClass,closable:m.closable,renderKey:m.renderKey})),active:l.activeId.value}),()=>{if(g.value)return;const m=l.snapshot();m.tabs.length?ye(a,r(m),e):Te(a,e)},{deep:!0})};i?k(i):n.onMounted(()=>{const l=re({optional:!0});l&&k(l)})}const Ue=n.defineComponent({name:"RouterTab",components:{RouterView:Ie.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:W},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:a}){const r=n.getCurrentInstance();if(!r)throw new Error("[RouterTab] component must be used within a Vue application context.");const c=r.appContext.app.config.globalProperties.$router;if(!c)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Ve(c,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(O,i),r.appContext.config.globalProperties.$tabs=i;const g=n.computed(()=>!!r?.slots?.default),k=n.computed(()=>!!r?.slots?.start),l=n.computed(()=>!!r?.slots?.end),m=n.ref(0),y=n.computed(()=>{m.value;const t={};return i.tabs.forEach(o=>{const s=typeof o.title=="string"?o.title:String(o.title||ue(o));t[o.id]=s}),t});function $(){m.value++}const P=new Map,A=new Map;function z(t,o){if(!(!o||P.has(t)))try{P.set(t,o);const s=i.tabs.find(p=>i.getRouteKey(p.to)===t);if(!s){console.warn(`[RouterTab] Cannot setup watching: tab not found for ${t}`);return}const h=[];if(o.routeTabTitle!==void 0)try{const p=n.watch(()=>{const b=o.routeTabTitle;return b&&typeof b=="object"&&"value"in b?b.value:b},b=>{if(b!=null){const N=String(b);s.title=N,$()}},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(o.routeTabIcon!==void 0)try{const p=n.watch(()=>{const b=o.routeTabIcon;return b&&typeof b=="object"&&"value"in b?b.value:b},b=>{b!=null&&(s.icon=String(b),$())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(o.routeTabClosable!==void 0)try{const p=n.watch(()=>{const b=o.routeTabClosable;return b&&typeof b=="object"&&"value"in b?b.value:b},b=>{b!=null&&(s.closable=!!b,$())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(o.routeTabMeta!==void 0)try{const p=n.watch(()=>{const b=o.routeTabMeta;return b&&typeof b=="object"&&"value"in b?b.value:b},b=>{b&&typeof b=="object"&&(Object.assign(s,b),$())},{immediate:!0,deep:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}A.set(t,h)}catch(s){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,s),I(t)}}function I(t){try{const o=A.get(t);o&&(o.forEach(s=>{try{s()}catch(h){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,h)}}),A.delete(t)),P.delete(t)}catch(o){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,o)}}function U(t,o){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?z(o,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&z(o,t.$):t===null&&I(o)}catch(s){console.error(`[RouterTab] Error handling component ref for ${o}:`,s),I(o)}}if(n.onErrorCaptured((t,o,s)=>(console.error("[RouterTab] Error captured from component:",t,s),!1)),e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||W:t.cookieKey||(t.cookieKey=W),ae(t)}const L=n.computed(()=>he(e.tabTransition)),se=n.computed(()=>he(e.pageTransition)),w=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),X=n.ref(null),x=n.ref([]),S=n.ref(-1),Q=n.ref(null),V=new Map,R=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),ce=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function _(t){return i.tabs.findIndex(o=>o.id===t)}function u(t){const o=_(t.id);return o>0?i.tabs.slice(0,o):[]}function d(t){const o=_(t.id);return o>-1?i.tabs.slice(o+1):[]}function f(t){return i.tabs.filter(o=>o.id!==t.id)}async function T(t,o){const s=t.filter(h=>h.closable!==!1);if(s.length){for(const h of s)i.activeId.value===h.id?await i.closeTab(h.id,{redirect:o.to,force:!0}):await i.removeTab(h.id,{force:!0});i.activeId.value!==o.id&&await i.openTab(o.to,!0,!1)}}const v={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})=>fe(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await T(u(t),t)},enable:({target:t})=>u(t).some(o=>o.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await T(d(t),t)},enable:({target:t})=>d(t).some(o=>o.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await T(f(t),t)},enable:({target:t})=>f(t).some(o=>o.closable!==!1)}};function E(){w.visible=!1,w.target=null,S.value=-1,x.value=[]}function B(t,o){e.contextmenu&&(w.target=t,w.position.x=o.clientX,w.position.y=o.clientY,n.nextTick(()=>{w.visible=!0,document.addEventListener("click",E,{once:!0}),n.nextTick(()=>{ut()})}))}function ct(t,o){const s=typeof t=="string"?{id:t}:t,h=v[s.id],p=s.label??h?.label??String(s.id),b=s.visible??h?.visible??!0;if(!(typeof b=="function"?b(o):b!==!1))return null;const pe=s.enable??h?.enable??!0,It=typeof pe=="function"?pe(o):pe!==!1,Ae=s.handler??h?.handler;if(!Ae)return null;const xt=async()=>{await Promise.resolve(Ae(o))};return{id:String(s.id),label:p,disabled:!It,action:xt}}const F=n.computed(()=>{if(!w.visible||!w.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:ce,o={target:w.target,controller:i};return t.map(s=>ct(s,o)).filter(s=>!!s)});function ut(){const t=X.value;if(!t)return;const o=8,{innerWidth:s,innerHeight:h}=window,p=t.getBoundingClientRect();let b=w.position.x,N=w.position.y;p.right>s-o&&(b=Math.max(o,s-p.width-o)),p.bottom>h-o&&(N=Math.max(o,h-p.height-o)),(b!==w.position.x||N!==w.position.y)&&(w.position.x=b,w.position.y=N)}function dt(t,o){x.value[o]=t??null}function ft(t){if(t<0)return;x.value[t]?.focus({preventScroll:!0})}function Z(t,o,s=F.value){if(!s.length)return-1;const h=s.length;let p=t;for(let b=0;b<h;b++)if(p=(p+o+h)%h,!s[p].disabled)return p;return-1}function Y(t){S.value=t,!(t<0)&&n.nextTick(()=>ft(t))}function Ke(t){const o=Z(S.value,t);o!==-1&&Y(o)}function bt(t){if(!w.visible)return;const o=t.key,s=F.value;if(!s.length)return;if(o==="Tab"){E();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(o))switch(t.preventDefault(),o){case"ArrowDown":case"ArrowRight":Ke(1);break;case"ArrowUp":case"ArrowLeft":Ke(-1);break;case"Home":Y(Z(-1,1));break;case"End":Y(Z(s.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=S.value;if(p>-1){const b=s[p];b.disabled||$e(b)}break}case"Escape":E();break}}async function $e(t){t.disabled||(E(),await t.action())}function ue(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 mt(t){return y.value[t.id]||ue(t)}function pt(t,o){return n.defineComponent({name:o,setup(s,{attrs:h,slots:p}){return()=>n.h(t,h,p)}})}const de=new Map,Pe=n.ref(0);function gt(t,o,s){de.has(s)||(de.set(s,o),Pe.value++),t&&U(t,s)}function ht(t,o){return t&&((!t.name||t.name!==o)&&(t.name=o),t)}function yt(t){const o=i.getRouteKey(t),s=c.currentRoute.value,h=i.getRouteKey(s);return o===h}function Be(t){const o=i.getRouteKey(t),s=i.tabs.find(b=>b.id===o);if(!s)return console.warn("[RouterTab] Tab not found for route:",o),`${o}::0`;const h=s.renderKey??0,p=`${o}::${h}`;return(o.includes("students")||o.includes("classroom")||o.includes("quiz"))&&console.log(`[getComponentCacheKey] Route: ${t.fullPath}`,{routeKey:o,renderKey:h,cacheKey:p,tabAlive:s.alive,includeKeys:me.value,isIncluded:me.value.includes(p)}),p}function Tt(t){return`${Be(t)}::refresh`}function fe(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function vt(t){await i.closeTab(t.id)}function kt(t,o){o?V.set(t,o):V.delete(t)}function be(t){n.nextTick(()=>{const o=V.get(t),s=Q.value;if(o&&s){const h=o.getBoundingClientRect(),p=s.getBoundingClientRect();(h.left<p.left||h.right>p.right)&&o.scrollIntoView({behavior:"smooth",block:"nearest",inline:"nearest"})}})}function wt(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),be(t.id))}function Rt(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":fe(t),"is-dragging":R.dragging&&R.dragTab?.id===t.id,"is-drag-over":R.dropIndex===_(t.id)},t.tabClass]}function Ct(t){return i.refreshingKey.value===i.getRouteKey(t)}function Et(t){const o=i.getRouteKey(t),s=i.tabs.find(h=>h.id===o);return s?s.alive:!1}function Kt(t){const o=i.getRouteKey(t);return!!i.tabs.find(h=>h.id===o)}function $t(t,o,s){e.sortable&&(R.dragging=!0,R.dragIndex=o,R.dragTab=t,s.dataTransfer&&(s.dataTransfer.effectAllowed="move",s.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:o}))}function Pt(t,o){!e.sortable||!R.dragging||(o.preventDefault(),o.dataTransfer&&(o.dataTransfer.dropEffect="move"))}function Bt(t){!e.sortable||!R.dragging||(R.dropIndex=t)}function St(){!e.sortable||R.dragging}function At(t,o){if(!(!e.sortable||!R.dragging)){if(o.preventDefault(),R.dragIndex!==-1&&R.dragIndex!==t){const s=i.tabs.splice(R.dragIndex,1)[0];i.tabs.splice(t,0,s),a("tab-sorted",{tab:s,fromIndex:R.dragIndex,toIndex:t})}Se()}}function Se(){R.dragging=!1,R.dragIndex=-1,R.dropIndex=-1,R.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",E)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",E),r.appContext.config.globalProperties.$tabs=null,A.forEach(t=>{t.forEach(o=>{try{o()}catch(s){console.error("[RouterTab] Error during cleanup:",s)}})}),A.clear(),P.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,t=>{t&&be(t),E()}),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(s=>s.id));Array.from(P.keys()).forEach(s=>{t.has(s)||(console.log(`[RouterTab] Cleaning up stale component instance: ${s}`),I(s))})}),n.watch(()=>e.contextmenu,t=>{t||E()}),n.watch(()=>F.value.length,t=>{w.visible&&t===0&&E()},{flush:"post"}),n.watch(F,t=>{if(!w.visible)return;x.value=new Array(t.length).fill(null);const o=Z(-1,1,t);Y(o)},{flush:"post"}),n.watch(()=>w.visible,t=>{t||(S.value=-1,x.value=[])});const me=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:me,componentCache:de,componentCacheTrigger:Pe,cacheCurrentComponent:gt,tabTransitionProps:L,pageTransitionProps:se,buildTabClass:Rt,activate:wt,close:vt,context:w,menuItems:F,handleMenuAction:$e,showContextMenu:B,hideContextMenu:E,getTabTitle:ue,isClosable:fe,isRefreshing:Ct,isTabCached:Et,isTabReady:Kt,hasCustomSlot:g,hasStartSlot:k,hasEndSlot:l,onDragStart:$t,onDragOver:Pt,onDragEnter:Bt,onDragLeave:St,onDrop:At,onDragEnd:Se,setupComponentWatching:z,cleanupComponentWatching:I,handleComponentRef:U,getReactiveTabTitle:mt,getComponentCacheKey:Be,getRefreshComponentKey:Tt,createNamedComponent:pt,ensureNamedComponent:ht,shouldRenderRoute:yt,triggerTabUpdate:$,menuRef:X,highlightedIndex:S,setMenuItemRef:dt,onMenuKeydown:bt,highlightMenuIndex:Y,scrollContainer:Q,setTabRef:kt,scrollTabIntoView:be}}}),_e=(e,a)=>{const r=e.__vccOpts||e;for(const[c,i]of a)r[c]=i;return r},Fe={class:"router-tab"},Ye={class:"router-tab__header"},qe={class:"router-tab__scroll",ref:"scrollContainer"},We=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],He=["title"],Je=["onClick"],Ge={class:"router-tab__container"},Xe=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Qe(e,a,r,c,i,g){const k=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Fe,[n.createElementVNode("header",Ye,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",qe,[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,(l,m)=>(n.openBlock(),n.createElementBlock("li",{key:l.id,class:n.normalizeClass(e.buildTabClass(l)),"data-title":e.getTabTitle(l),draggable:e.sortable,ref_for:!0,ref:y=>e.setTabRef(l.id,y),onClick:y=>e.activate(l),onAuxclick:n.withModifiers(y=>e.close(l),["middle","prevent"]),onContextmenu:n.withModifiers(y=>e.showContextMenu(l,y),["prevent"]),onDragstart:y=>e.onDragStart(l,m,y),onDragover:y=>e.onDragOver(m,y),onDragenter:y=>e.onDragEnter(m),onDragleave:a[0]||(a[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(m,y),onDragend:a[1]||(a[1]=(...y)=>e.onDragEnd&&e.onDragEnd(...y))},[l.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",l.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(l)},n.toDisplayString(e.getReactiveTabTitle(l)),9,He),e.isClosable(l)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(y=>e.close(l),["stop"])},null,8,Je)):n.createCommentVNode("",!0)],42,We))),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",Ge,[n.createVNode(k,null,{default:n.withCtx(({Component:l,route:m})=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{Component:l,route:m,controller:e.controller,pageRef:y=>e.handleComponentRef(y,e.controller.getRouteKey(m))}))):(n.openBlock(),n.createElementBlock(n.Fragment,{key:1},[e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:0,include:e.includeKeys},[l?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(e.ensureNamedComponent(l,e.controller.getRouteKey(m))),{key:e.controller.getRouteKey(m),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(m)),class:"router-tab-page"})):n.createCommentVNode("",!0)],1032,["include"])):(n.openBlock(),n.createBlock(n.Transition,n.mergeProps({key:1},e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[l?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(l),{key:e.controller.getRouteKey(m),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(m)),class:"router-tab-page"})):n.createCommentVNode("",!0)]),_:2},1040))],64))]),_:3})]),n.withDirectives(n.createElementVNode("div",{ref:"menuRef",class:"router-tab__contextmenu",role:"menu",onKeydown:a[2]||(a[2]=(...l)=>e.onMenuKeydown&&e.onMenuKeydown(...l)),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,(l,m)=>(n.openBlock(),n.createElementBlock("a",{key:l.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":m===e.highlightedIndex}]),"aria-disabled":l.disabled,disabled:l.disabled,tabindex:l.disabled?-1:m===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,m),onMouseenter:y=>!l.disabled&&e.highlightMenuIndex(m),onClick:y=>e.handleMenuAction(l)},n.toDisplayString(l.label),43,Xe))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const ie=_e(Ue,[["render",Qe]]),Ze={class:"router-tabs","aria-hidden":"true"},H=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 ae(e),(r,c)=>(n.openBlock(),n.createElementBlock("span",Ze))}}),ve="tab-theme-style",ke="tab-theme-primary-color",et="system",we="(prefers-color-scheme: dark)";let j=null;const K={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"},tt={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 nt(e){if(typeof window>"u")return null;const a=window.localStorage.getItem(e);if(!a)return null;try{const r=JSON.parse(a);return r&&typeof r=="object"?r:null}catch{return null}}function le(e){typeof document>"u"||(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),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??K.iconColor))}function Re(e){if(typeof document>"u")return;const a=document.documentElement,r=window.matchMedia(we),c=()=>{a.dataset.theme=r.matches?"dark":"light"};j&&(r.removeEventListener("change",j),j=null),e==="system"?(c(),j=()=>c(),r.addEventListener("change",j)):a.dataset.theme=e}function Ce(e={}){if(typeof window>"u")return;const{styleKey:a=ve,primaryKey:r=ke,defaultStyle:c=et,defaultPrimary:i}=e,g=window.localStorage.getItem(a)??c;Re(g);const l=g==="dark"||g==="system"&&window.matchMedia(we).matches?{...tt}:{...K};i&&(l.primary=i);const m=nt(r);le(m?{...l,...m}:l)}function ot(e,a){if(typeof window>"u")return;const r=a?.styleKey??ve;window.localStorage.setItem(r,e),Re(e)}function rt(e,a){if(typeof window>"u")return;const r=a?.primaryKey??ke;window.localStorage.setItem(r,JSON.stringify(e)),le(e)}function J(e,a){if(n.isRef(e)){const c=!n.isReadonly(e);return{value:e,update:c?i=>{e.value=i}:()=>{}}}if(typeof e=="function"){const c=e;return{value:n.computed(c),update:()=>{}}}const r=n.ref(e===void 0?a:e);return{value:r,update:c=>{r.value=c}}}function G(e={}){const a=J(e.title,"Untitled"),r=J(e.icon,""),c=J(e.closable,!0),i=J(e.meta,{});return{routeTabTitle:a.value,routeTabIcon:r.value,routeTabClosable:c.value,routeTabMeta:i.value,updateTitle:a.update,updateIcon:r.update,updateClosable:c.update,updateMeta:i.update}}function at(e,a="Page"){return G({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function it(e,a="Page",r="mdi-page"){return G({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":r)})}function lt(e,a="Page"){const r={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 G({title:n.computed(()=>a+r[e.value].suffix),icon:n.computed(()=>r[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let Ee=!1;const st={install(e,a){if(Ee)return;Ee=!0;const{initTheme:r=!0,themeOptions:c,componentName:i=ie.name||"RouterTab",tabsComponentName:g=H.name||"RouterTabs"}=a??{};r&&Ce(c??{}),e.component(i,ie),e.component(g,H),g.toLowerCase()!=="router-tabs"&&e.component("router-tabs",H),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[O]},set(k){k&&e.provide(O,k)}})}};C.RouterTab=ie,C.RouterTabs=H,C.default=st,C.initRouterTabsTheme=Ce,C.routerTabsKey=O,C.setRouterTabsPrimary=rt,C.setRouterTabsTheme=ot,C.useLoadingTab=at,C.useNotificationTab=it,C.useReactiveTab=G,C.useRouterTabs=re,C.useRouterTabsPersistence=ae,C.useStatusTab=lt,Object.defineProperties(C,{__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)"
@@ -52,50 +53,40 @@
52
53
  </header>
53
54
 
54
55
  <div class="router-tab__container">
55
- <RouterView v-slot="routerSlot">
56
+ <RouterView v-slot="{ Component, route }">
56
57
  <template v-if="hasCustomSlot">
57
58
  <slot
58
59
  v-bind="{
59
- ...routerSlot,
60
+ Component,
61
+ route,
60
62
  controller,
61
- // Expose a ref binder so custom slots can keep reactivity
62
- pageRef: (el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))
63
+ pageRef: (el: any) => handleComponentRef(el, controller.getRouteKey(route))
63
64
  }"
64
65
  />
65
66
  </template>
66
67
  <template v-else>
67
- <transition
68
- v-bind="pageTransitionProps"
69
- appear
70
- >
71
- <component
72
- v-if="isRefreshing(routerSlot.route)"
73
- :is="routerSlot.Component"
74
- :key="getRefreshComponentKey(routerSlot.route)"
75
- :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
76
- class="router-tab-page"
77
- />
78
- <KeepAlive
79
- v-else-if="controller.options.keepAlive"
80
- :include="includeKeys"
81
- :max="controller.options.maxAlive || undefined"
82
- >
68
+ <template v-if="controller.options.keepAlive">
69
+ <KeepAlive :include="includeKeys">
83
70
  <component
84
- v-if="isTabReady(routerSlot.route)"
85
- :is="routerSlot.Component"
86
- :key="getComponentCacheKey(routerSlot.route)"
87
- :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
71
+ v-if="Component"
72
+ :is="ensureNamedComponent(Component, controller.getRouteKey(route))"
73
+ :key="controller.getRouteKey(route)"
74
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(route))"
88
75
  class="router-tab-page"
89
76
  />
90
77
  </KeepAlive>
91
- <component
92
- v-else-if="isTabReady(routerSlot.route)"
93
- :is="routerSlot.Component"
94
- :key="getComponentCacheKey(routerSlot.route)"
95
- :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
96
- class="router-tab-page"
97
- />
98
- </transition>
78
+ </template>
79
+ <template v-else>
80
+ <transition v-bind="pageTransitionProps" appear>
81
+ <component
82
+ v-if="Component"
83
+ :is="Component"
84
+ :key="controller.getRouteKey(route)"
85
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(route))"
86
+ class="router-tab-page"
87
+ />
88
+ </transition>
89
+ </template>
99
90
  </template>
100
91
  </RouterView>
101
92
  </div>
@@ -132,6 +123,7 @@ import {
132
123
  computed,
133
124
  defineComponent,
134
125
  getCurrentInstance,
126
+ h,
135
127
  nextTick,
136
128
  onBeforeUnmount,
137
129
  onErrorCaptured,
@@ -434,23 +426,8 @@ export default defineComponent({
434
426
  onErrorCaptured((err, instance, info) => {
435
427
  console.error('[RouterTab] Error captured from component:', err, info)
436
428
 
437
- // Try to find the route key for the erroring component
438
- if (instance && controller.activeId.value) {
439
- const routeKey = controller.activeId.value
440
-
441
- // Clean up the component instance to prevent stale state
442
- cleanupComponentWatching(routeKey)
443
-
444
- // Remove from KeepAlive cache if it's cached
445
- const tab = controller.tabs.find(t => t.id === routeKey)
446
- if (tab && tab.alive) {
447
- console.warn(`[RouterTab] Removing errored component ${routeKey} from KeepAlive cache`)
448
- tab.alive = false
449
- nextTick(() => {
450
- tab.alive = true
451
- })
452
- }
453
- }
429
+ // Just log the error, don't try to manipulate the cache
430
+ // as it can cause infinite loops with KeepAlive
454
431
 
455
432
  // Return false to propagate the error to parent
456
433
  return false
@@ -480,6 +457,8 @@ export default defineComponent({
480
457
  const menuRef = ref<HTMLElement | null>(null)
481
458
  const menuItemRefs = ref<(HTMLElement | null)[]>([])
482
459
  const highlightedIndex = ref(-1)
460
+ const scrollContainer = ref<HTMLElement | null>(null)
461
+ const tabRefs = new Map<string, HTMLElement>()
483
462
 
484
463
  // Drag and drop state
485
464
  const dragState = reactive({
@@ -795,21 +774,106 @@ export default defineComponent({
795
774
  return reactiveTitles[tab.id] || getTabTitle(tab)
796
775
  }
797
776
 
777
+ /**
778
+ * Creates a wrapper component with a specific name for KeepAlive caching.
779
+ * This is necessary because KeepAlive's include prop matches component names.
780
+ */
781
+ function createNamedComponent(component: any, name: string) {
782
+ return defineComponent({
783
+ name,
784
+ setup(_, { attrs, slots }) {
785
+ return () => h(component, attrs, slots)
786
+ }
787
+ })
788
+ }
789
+
790
+ // Cache for component references from RouterView (non-reactive to avoid circular reference issues)
791
+ const componentCache = new Map<string, any>()
792
+ const componentCacheTrigger = ref(0) // Trigger reactivity manually
793
+
794
+ /**
795
+ * Cache the current component without triggering infinite loops.
796
+ * Called from ref callback, only caches once per route key.
797
+ */
798
+ function cacheCurrentComponent(el: any, component: any, key: string): void {
799
+ if (!componentCache.has(key)) {
800
+ componentCache.set(key, component)
801
+ componentCacheTrigger.value++
802
+ }
803
+ // Always handle the component ref to set up watchers
804
+ if (el) {
805
+ handleComponentRef(el, key)
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Ensures a component has a name for KeepAlive to cache properly.
811
+ * Assigns the cache key as the component's name directly.
812
+ */
813
+ function ensureNamedComponent(component: any, cacheKey: string) {
814
+ if (!component) return component
815
+
816
+ // Directly assign the name to the component
817
+ // This mutates the component but is necessary for KeepAlive matching
818
+ if (!component.name || component.name !== cacheKey) {
819
+ component.name = cacheKey
820
+ }
821
+
822
+ return component
823
+ }
824
+
825
+ /**
826
+ * Determines if a route should be rendered.
827
+ * Used to control which component is visible within KeepAlive.
828
+ */
829
+ function shouldRenderRoute(route: RouteLocationNormalizedLoaded): boolean {
830
+ const currentRouteKey = controller.getRouteKey(route)
831
+ const currentRoute = router.currentRoute.value
832
+ const activeRouteKey = controller.getRouteKey(currentRoute)
833
+
834
+ return currentRouteKey === activeRouteKey
835
+ }
836
+
837
+ /**
838
+ * Generates the KeepAlive cache key for a component.
839
+ * Format: `{routeKey}::{renderKey}`
840
+ *
841
+ * Example: '/quiz-results::0' (first mount), '/quiz-results::1' (after refresh)
842
+ *
843
+ * The renderKey is incremented on manual refresh to force component recreation.
844
+ * Stable renderKey during navigation allows KeepAlive to preserve component state.
845
+ */
798
846
  function getComponentCacheKey(route: RouteLocationNormalizedLoaded): string {
799
847
  const routeKey = controller.getRouteKey(route)
800
848
  const tab = controller.tabs.find(item => item.id === routeKey)
801
849
 
802
- // If tab doesn't exist yet, ensure it's created
803
850
  if (!tab) {
804
- // This shouldn't happen, but handle it gracefully
805
851
  console.warn('[RouterTab] Tab not found for route:', routeKey)
806
852
  return `${routeKey}::0`
807
853
  }
808
854
 
809
855
  const renderKey = tab.renderKey ?? 0
810
- return `${routeKey}::${renderKey}`
856
+ const cacheKey = `${routeKey}::${renderKey}`
857
+
858
+ // Debug logging for specific routes
859
+ if (routeKey.includes('students') || routeKey.includes('classroom') || routeKey.includes('quiz')) {
860
+ console.log(`[getComponentCacheKey] Route: ${route.fullPath}`, {
861
+ routeKey,
862
+ renderKey,
863
+ cacheKey,
864
+ tabAlive: tab.alive,
865
+ includeKeys: includeKeys.value,
866
+ isIncluded: includeKeys.value.includes(cacheKey)
867
+ })
868
+ }
869
+
870
+ return cacheKey
811
871
  }
812
872
 
873
+ /**
874
+ * Generates a special key for components in refreshing state.
875
+ * Appends '::refresh' to ensure it's treated as separate from cached instance.
876
+ */
813
877
  function getRefreshComponentKey(route: RouteLocationNormalizedLoaded): string {
814
878
  return `${getComponentCacheKey(route)}::refresh`
815
879
  }
@@ -824,6 +888,41 @@ export default defineComponent({
824
888
  await controller.closeTab(tab.id)
825
889
  }
826
890
 
891
+ // Store tab element references for scroll-into-view
892
+ function setTabRef(tabId: string, el: HTMLElement | null) {
893
+ if (el) {
894
+ tabRefs.set(tabId, el)
895
+ } else {
896
+ tabRefs.delete(tabId)
897
+ }
898
+ }
899
+
900
+ // Scroll tab into view when activated
901
+ function scrollTabIntoView(tabId: string) {
902
+ nextTick(() => {
903
+ const tabEl = tabRefs.get(tabId)
904
+ const container = scrollContainer.value
905
+
906
+ if (tabEl && container) {
907
+ // Calculate if tab is within view
908
+ const tabRect = tabEl.getBoundingClientRect()
909
+ const containerRect = container.getBoundingClientRect()
910
+
911
+ // Check if tab is outside the visible area
912
+ const isOutOfView = tabRect.left < containerRect.left || tabRect.right > containerRect.right
913
+
914
+ if (isOutOfView) {
915
+ // Scroll the tab into view with smooth behavior
916
+ tabEl.scrollIntoView({
917
+ behavior: 'smooth',
918
+ block: 'nearest',
919
+ inline: 'nearest'
920
+ })
921
+ }
922
+ }
923
+ })
924
+ }
925
+
827
926
  function activate(tab: TabRecord) {
828
927
  if (tab.href && typeof window !== 'undefined') {
829
928
  if (tab.target && tab.target !== '_self') {
@@ -836,6 +935,7 @@ export default defineComponent({
836
935
 
837
936
  if (controller.activeId.value === tab.id) return
838
937
  controller.openTab(tab.to, false)
938
+ scrollTabIntoView(tab.id)
839
939
  }
840
940
 
841
941
  function buildTabClass(tab: TabRecord) {
@@ -855,9 +955,21 @@ export default defineComponent({
855
955
  return controller.refreshingKey.value === controller.getRouteKey(route)
856
956
  }
857
957
 
958
+ function isTabCached(route: RouteLocationNormalizedLoaded) {
959
+ const routeKey = controller.getRouteKey(route)
960
+ const tab = controller.tabs.find(tab => tab.id === routeKey)
961
+ return tab ? tab.alive : false
962
+ }
963
+
858
964
  function isTabReady(route: RouteLocationNormalizedLoaded) {
859
965
  const routeKey = controller.getRouteKey(route)
860
- return controller.tabs.some(tab => tab.id === routeKey)
966
+ const tab = controller.tabs.find(tab => tab.id === routeKey)
967
+
968
+ if (!tab) return false
969
+
970
+ // Always return true - let KeepAlive handle the caching logic
971
+ // The tab exists in the tabs array, so it should be rendered
972
+ return true
861
973
  }
862
974
 
863
975
  // Drag and drop handlers
@@ -951,7 +1063,12 @@ export default defineComponent({
951
1063
 
952
1064
  watch(
953
1065
  () => controller.activeId.value,
954
- () => hideContextMenu()
1066
+ (newActiveId) => {
1067
+ if (newActiveId) {
1068
+ scrollTabIntoView(newActiveId)
1069
+ }
1070
+ hideContextMenu()
1071
+ }
955
1072
  )
956
1073
 
957
1074
  // Clean up stale component instances when tabs are closed
@@ -1011,6 +1128,9 @@ export default defineComponent({
1011
1128
  controller,
1012
1129
  tabs: controller.tabs,
1013
1130
  includeKeys,
1131
+ componentCache,
1132
+ componentCacheTrigger,
1133
+ cacheCurrentComponent,
1014
1134
  tabTransitionProps,
1015
1135
  pageTransitionProps,
1016
1136
  buildTabClass,
@@ -1024,6 +1144,7 @@ export default defineComponent({
1024
1144
  getTabTitle,
1025
1145
  isClosable,
1026
1146
  isRefreshing,
1147
+ isTabCached,
1027
1148
  isTabReady,
1028
1149
  hasCustomSlot,
1029
1150
  hasStartSlot,
@@ -1040,12 +1161,18 @@ export default defineComponent({
1040
1161
  getReactiveTabTitle,
1041
1162
  getComponentCacheKey,
1042
1163
  getRefreshComponentKey,
1164
+ createNamedComponent,
1165
+ ensureNamedComponent,
1166
+ shouldRenderRoute,
1043
1167
  triggerTabUpdate,
1044
1168
  menuRef,
1045
1169
  highlightedIndex,
1046
1170
  setMenuItemRef,
1047
1171
  onMenuKeydown,
1048
- highlightMenuIndex
1172
+ highlightMenuIndex,
1173
+ scrollContainer,
1174
+ setTabRef,
1175
+ scrollTabIntoView
1049
1176
  }
1050
1177
  }
1051
1178
  })