vue3-router-tab 1.3.9 → 1.4.1
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.
- package/README.md +0 -8
- package/dist/vue3-router-tab.css +1 -1
- package/dist/vue3-router-tab.js +576 -563
- package/dist/vue3-router-tab.umd.cjs +1 -1
- package/index.d.ts +1 -1
- package/lib/components/RouterTab.vue +24 -4
- package/lib/core/createRouterTabs.ts +25 -24
- package/lib/persistence.ts +30 -27
- package/lib/scss/index.scss +1 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
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"}})}));
|
|
1
|
+
(function(E,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):(E=typeof globalThis<"u"?globalThis:E||self,n(E["vue3-router-tab"]={},E.Vue,E.VueRouter))})(this,(function(E,n,De){"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 I(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 Me={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 V(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=Me[a.toLowerCase()];return r?r(e):a}return e.fullPath}function te(e,a){const r=e.meta?.keepAlive;return typeof r=="boolean"?r:a}function ne(e,a){const r=e.meta?.reuse;return typeof r=="boolean"?r:a}function he(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 W(e,a,r){const c=he(e);return{id:V(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:te(e,r),reusable:ne(e,!1),closable:c.closable??!0,renderKey:typeof a.renderKey=="number"?a.renderKey:0,...c,...a}}function oe(e,a,r,c){if(!e.find(h=>h.id===a.id)){if(r==="next"&&c){const h=e.findIndex(w=>w.id===c);if(h!==-1){e.splice(h+1,0,a);return}}e.push(a)}}function J(e,a,r,c){if(!a||a<=0)return;const i=e.filter(h=>h.alive);for(;i.length>a;){const h=i.shift();if(!h||h.id===r)continue;const w=e.findIndex(l=>l.id===h.id);if(w>-1){const l=e[w],g=`${l.id}::${l.renderKey??0}`;c.delete(g),l.alive=!1}}}function Le(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Ve(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 Ne(e,a={}){const r=xe(a),c=n.reactive([]),i=n.ref(null),h=n.shallowRef(),w=n.ref(null),l=n.reactive(new Set),g=n.computed(()=>Array.from(l));let y=!1;function k(u){const b=typeof u.matched=="object"?u:I(e,u);return{key:V(b),fullPath:b.fullPath,alive:te(b,r.keepAlive),reusable:ne(b,!1),matched:b}}function S(u){const b=V(u);let d=c.find(v=>v.id===b);const T=te(u,r.keepAlive);if(d){d.fullPath=u.fullPath,d.to=u.fullPath,d.matched=u,d.reusable=ne(u,d.reusable),typeof d.renderKey!="number"&&(d.renderKey=0);const v=`${b}::${d.renderKey}`;return T?l.has(v)?d.alive||(d.alive=!0):(l.add(v),d.alive=!0):d.alive&&(l.delete(v),d.alive=!1),Object.assign(d,he(u)),d}if(d=W(u,{},r.keepAlive),d.alive){const v=`${b}::${d.renderKey??0}`;l.add(v)}return oe(c,d,r.appendPosition,i.value),J(c,r.maxAlive,i.value,l),d}async function P(u,b=!1,d="sameTab"){const T=I(e,u),v=V(T),B=i.value===v;d==="sameTab"&&(d=B),d&&await M(v,!0),await e[b?"replace":"push"](T),B&&await _()}function D(u){const b=c.findIndex(K=>K.id===u);if(b===-1)return r.defaultRoute;const d=c[b+1],T=c[b-1],v=c.find(K=>K.id!==u),B=d||T||v;return B?B.to:r.defaultRoute}async function x(u=i.value,b={}){if(!u)return;if(!b.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&&b.redirect!==null,v=T?b.redirect??D(u):null;await U(u,{force:b.force}),b.redirect!==null&&T&&v&&await e.replace(v)}async function U(u,b={}){const d=c.findIndex(B=>B.id===u);if(d===-1)return;const T=c[d],v=`${u}::${T.renderKey??0}`;l.delete(v),T.alive=!1,c.splice(d,1),w.value===u&&(w.value=null),i.value===u&&(i.value=null,h.value=void 0)}async function M(u=i.value??void 0,b=!1){if(!u)return;const d=c.find(K=>K.id===u);if(!d)return;const T=r.keepAlive&&d.alive,v=`${u}::${d.renderKey??0}`;T&&(l.delete(v),d.alive=!1,await n.nextTick()),d.renderKey=(d.renderKey??0)+1;const B=`${u}::${d.renderKey}`;T&&(l.add(B),d.alive=!0),w.value=u,await n.nextTick(),b||await n.nextTick(),w.value=null}async function se(u=!1){for(const b of c)await M(b.id,u)}function ce(u,b){const d=c.find(v=>v.id===u);if(!d)return;const T=`${u}::${d.renderKey??0}`;b?(l.add(T),d.alive=!0,J(c,r.maxAlive,i.value,l)):(l.delete(T),d.alive=!1)}function R(u){const b=c.find(T=>T.id===u);if(!b)return;const d=`${u}::${b.renderKey??0}`;l.delete(d),b.alive=!1,b.renderKey=(b.renderKey??0)+1}function Z(){l.clear(),c.forEach(u=>{u.alive=!1})}function L(){return g.value.slice()}async function A(u=r.defaultRoute){c.splice(0,c.length),i.value=null,h.value=void 0;for(const b of r.initialTabs){const d=I(e,b.to),T=W(d,b,r.keepAlive);c.push(T)}await e.replace(u)}async function _(){const u=i.value;u&&await M(u,!0)}function z(u){return typeof u.matched=="object"?V(u):V(I(e,u))}function C(){const u=c.find(b=>b.id===i.value);return{tabs:c.map(Le),active:u?u.to:null}}async function ue(u){y=!0,c.splice(0,c.length),i.value=null,h.value=void 0;const b=u?.tabs??[];for(const T of b)try{const v=I(e,T.to),B=Ve(T),K=W(v,B,r.keepAlive);oe(c,K,"last",null)}catch(v){console.warn("[RouterTabs] Failed to restore tab",T,v)}y=!1;const d=u?.active??b[b.length-1]?.to??r.defaultRoute;if(d)try{const T=I(e,d),v=e.currentRoute.value;if(T.fullPath===v.fullPath){const B=S(v);i.value=B.id,h.value=B,J(c,r.maxAlive,i.value,l);return}await e.replace(T)}catch(T){console.warn("[RouterTabs] Failed to navigate to restored route",d,T)}}return n.watch(()=>e.currentRoute.value,u=>{if(y)return;const b=S(u);i.value=b.id,h.value=b,J(c,r.maxAlive,i.value,l)},{immediate:!0}),r.initialTabs.length&&r.initialTabs.forEach(u=>{const b=I(e,u.to),d=W(b,u,r.keepAlive);oe(c,d,"last",null)}),{options:r,tabs:c,activeId:i,current:h,includeKeys:g,refreshingKey:w,openTab:P,closeTab:x,removeTab:U,refreshTab:M,refreshAll:se,setTabAlive:ce,evictCache:R,clearCache:Z,getCacheKeys:L,reset:A,reload:_,getRouteKey:z,matchRoute:k,snapshot:C,hydrate:ue,ensureTab:S}}function ye(e){return e?typeof e=="string"?{name:e}:e:{}}const O=Symbol("RouterTabsContext"),q="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 h=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(h)return h;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const Oe=864e5;function je(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 Te(e,a,r){if(typeof document>"u")return;const{expiresInDays:c=7,path:i="/",domain:h,secure:w,sameSite:l="lax"}=r,g=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(c!==1/0){const y=new Date(Date.now()+c*Oe).toUTCString();g.push(`Expires=${y}`)}i&&g.push(`Path=${i}`),h&&g.push(`Domain=${h}`),w&&g.push("Secure"),l&&g.push(`SameSite=${l.charAt(0).toUpperCase()}${l.slice(1)}`),document.cookie=g.join("; ")}function ve(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 Ue=e=>JSON.stringify(e??null),_e=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ae(e={}){const{cookieKey:a=q,serialize:r=Ue,deserialize:c=_e}=e,i=re({optional:!0}),h=n.ref(!1),w=(l,g="hook")=>{const y=async()=>{h.value=!0;try{const k=c(je(a));if(k&&k.tabs?.length){if(await l.hydrate(k),k.active){await n.nextTick();const P=l.tabs.find(D=>D.to===k.active);P&&(l.activeId.value=P.id,l.current.value=P)}}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute")){const P=e.fallbackRoute??l.options.defaultRoute;await l.reset(P)}const S=l.snapshot();S.tabs.length?Te(a,r(S),e):ve(a,e)}finally{h.value=!1}};g==="immediate"?y():n.onBeforeMount(()=>{y()}),n.watch(()=>({tabs:l.tabs.map(k=>({to:k.to,title:k.title,tips:k.tips,icon:k.icon,tabClass:k.tabClass,closable:k.closable,renderKey:k.renderKey})),active:l.activeId.value}),()=>{if(h.value)return;const k=l.snapshot();k.tabs.length?Te(a,r(k),e):ve(a,e)},{deep:!0})};return i?w(i):n.onMounted(()=>{const l=re({optional:!0});l&&w(l,"immediate")}),{hydrating:h}}const ze=n.defineComponent({name:"RouterTab",components:{RouterView:De.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:q},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=Ne(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 h=n.computed(()=>!!r?.slots?.default),w=n.computed(()=>!!r?.slots?.start),l=n.computed(()=>!!r?.slots?.end),g=n.ref(0),y=n.computed(()=>{g.value;const t={};return i.tabs.forEach(o=>{const s=typeof o.title=="string"?o.title:String(o.title||de(o));t[o.id]=s}),t});function k(){g.value++}const S=new Map,P=new Map;function D(t,o){if(!(!o||S.has(t)))try{S.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 m=[];if(o.routeTabTitle!==void 0)try{const p=n.watch(()=>{const f=o.routeTabTitle;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{if(f!=null){const N=String(f);s.title=N,k()}},{immediate:!0});m.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(o.routeTabIcon!==void 0)try{const p=n.watch(()=>{const f=o.routeTabIcon;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(s.icon=String(f),k())},{immediate:!0});m.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(o.routeTabClosable!==void 0)try{const p=n.watch(()=>{const f=o.routeTabClosable;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(s.closable=!!f,k())},{immediate:!0});m.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(o.routeTabMeta!==void 0)try{const p=n.watch(()=>{const f=o.routeTabMeta;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f&&typeof f=="object"&&(Object.assign(s,f),k())},{immediate:!0,deep:!0});m.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}P.set(t,m)}catch(s){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,s),x(t)}}function x(t){try{const o=P.get(t);o&&(o.forEach(s=>{try{s()}catch(m){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,m)}}),P.delete(t)),S.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?D(o,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&D(o,t.$):t===null&&x(o)}catch(s){console.error(`[RouterTab] Error handling component ref for ${o}:`,s),x(o)}}n.onErrorCaptured((t,o,s)=>(console.error("[RouterTab] Error captured from component:",t,s),!1));let M=n.ref(!1);if(e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||q:t.cookieKey||(t.cookieKey=q),M=ae(t).hydrating}const se=n.computed(()=>ye(e.tabTransition)),ce=n.computed(()=>ye(e.pageTransition)),R=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),Z=n.ref(null),L=n.ref([]),A=n.ref(-1),_=n.ref(null),z=new Map,C=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),ue=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function u(t){return i.tabs.findIndex(o=>o.id===t)}function b(t){const o=u(t.id);return o>0?i.tabs.slice(0,o):[]}function d(t){const o=u(t.id);return o>-1?i.tabs.slice(o+1):[]}function T(t){return i.tabs.filter(o=>o.id!==t.id)}async function v(t,o){const s=t.filter(m=>m.closable!==!1);if(s.length){for(const m of s)i.activeId.value===m.id?await i.closeTab(m.id,{redirect:o.to,force:!0}):await i.removeTab(m.id,{force:!0});i.activeId.value!==o.id&&await i.openTab(o.to,!0,!1)}}const B={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})=>be(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await v(b(t),t)},enable:({target:t})=>b(t).some(o=>o.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await v(d(t),t)},enable:({target:t})=>d(t).some(o=>o.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await v(T(t),t)},enable:({target:t})=>T(t).some(o=>o.closable!==!1)}};function K(){R.visible=!1,R.target=null,A.value=-1,L.value=[]}function dt(t,o){e.contextmenu&&(R.target=t,R.position.x=o.clientX,R.position.y=o.clientY,n.nextTick(()=>{R.visible=!0,document.addEventListener("click",K,{once:!0}),n.nextTick(()=>{bt()})}))}function ft(t,o){const s=typeof t=="string"?{id:t}:t,m=B[s.id],p=s.label??m?.label??String(s.id),f=s.visible??m?.visible??!0;if(!(typeof f=="function"?f(o):f!==!1))return null;const ge=s.enable??m?.enable??!0,Mt=typeof ge=="function"?ge(o):ge!==!1,Ie=s.handler??m?.handler;if(!Ie)return null;const Lt=async()=>{await Promise.resolve(Ie(o))};return{id:String(s.id),label:p,disabled:!Mt,action:Lt}}const F=n.computed(()=>{if(!R.visible||!R.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:ue,o={target:R.target,controller:i};return t.map(s=>ft(s,o)).filter(s=>!!s)});function bt(){const t=Z.value;if(!t)return;const o=8,{innerWidth:s,innerHeight:m}=window,p=t.getBoundingClientRect();let f=R.position.x,N=R.position.y;p.right>s-o&&(f=Math.max(o,s-p.width-o)),p.bottom>m-o&&(N=Math.max(o,m-p.height-o)),(f!==R.position.x||N!==R.position.y)&&(R.position.x=f,R.position.y=N)}function mt(t,o){L.value[o]=t??null}function pt(t){if(t<0)return;L.value[t]?.focus({preventScroll:!0})}function ee(t,o,s=F.value){if(!s.length)return-1;const m=s.length;let p=t;for(let f=0;f<m;f++)if(p=(p+o+m)%m,!s[p].disabled)return p;return-1}function Y(t){A.value=t,!(t<0)&&n.nextTick(()=>pt(t))}function Be(t){const o=ee(A.value,t);o!==-1&&Y(o)}function gt(t){if(!R.visible)return;const o=t.key,s=F.value;if(!s.length)return;if(o==="Tab"){K();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(o))switch(t.preventDefault(),o){case"ArrowDown":case"ArrowRight":Be(1);break;case"ArrowUp":case"ArrowLeft":Be(-1);break;case"Home":Y(ee(-1,1));break;case"End":Y(ee(s.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=A.value;if(p>-1){const f=s[p];f.disabled||$e(f)}break}case"Escape":K();break}}async function $e(t){t.disabled||(K(),await t.action())}function de(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 ht(t){return y.value[t.id]||de(t)}function Pe(t,o){return n.defineComponent({name:o,setup(s,{attrs:m,slots:p}){return()=>n.h(t,m,p)}})}const H=new Map,fe=n.ref(0);function yt(t,o,s){H.has(s)||(H.set(s,o),fe.value++),t&&U(t,s)}function Tt(t,o){return t&&((!t.name||t.name!==o)&&(t.name=o),t)}function vt(t,o){if(!t)return t;const s=H.get(o);if(s)return s;const m=Pe(t,o);return H.set(o,m),fe.value++,m}function kt(t){const o=i.getRouteKey(t),s=c.currentRoute.value,m=i.getRouteKey(s);return o===m}function Se(t){const o=i.getRouteKey(t),s=i.tabs.find(f=>f.id===o);if(!s)return console.warn("[RouterTab] Tab not found for route:",o),`${o}::0`;const m=s.renderKey??0,p=`${o}::${m}`;return(o.includes("students")||o.includes("classroom")||o.includes("quiz"))&&console.log(`[getComponentCacheKey] Route: ${t.fullPath}`,{routeKey:o,renderKey:m,cacheKey:p,tabAlive:s.alive,includeKeys:pe.value,isIncluded:pe.value.includes(p)}),p}function wt(t){return`${Se(t)}::refresh`}function be(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function Rt(t){await i.closeTab(t.id)}function Ct(t,o){o?z.set(t,o):z.delete(t)}function me(t){n.nextTick(()=>{const o=z.get(t),s=_.value;if(o&&s){const m=o.getBoundingClientRect(),p=s.getBoundingClientRect();(m.left<p.left||m.right>p.right)&&o.scrollIntoView({behavior:"smooth",block:"nearest",inline:"nearest"})}})}function Et(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),me(t.id))}function Kt(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":be(t),"is-dragging":C.dragging&&C.dragTab?.id===t.id,"is-drag-over":C.dropIndex===u(t.id)},t.tabClass]}function Bt(t){return i.refreshingKey.value===i.getRouteKey(t)}function $t(t){const o=i.getRouteKey(t),s=i.tabs.find(m=>m.id===o);return s?s.alive:!1}function Pt(t){const o=i.getRouteKey(t);return!!i.tabs.find(m=>m.id===o)}function St(t,o,s){e.sortable&&(C.dragging=!0,C.dragIndex=o,C.dragTab=t,s.dataTransfer&&(s.dataTransfer.effectAllowed="move",s.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:o}))}function At(t,o){!e.sortable||!C.dragging||(o.preventDefault(),o.dataTransfer&&(o.dataTransfer.dropEffect="move"))}function It(t){!e.sortable||!C.dragging||(C.dropIndex=t)}function Dt(){!e.sortable||C.dragging}function xt(t,o){if(!(!e.sortable||!C.dragging)){if(o.preventDefault(),C.dragIndex!==-1&&C.dragIndex!==t){const s=i.tabs.splice(C.dragIndex,1)[0];i.tabs.splice(t,0,s),a("tab-sorted",{tab:s,fromIndex:C.dragIndex,toIndex:t})}Ae()}}function Ae(){C.dragging=!1,C.dragIndex=-1,C.dropIndex=-1,C.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",K)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",K),r.appContext.config.globalProperties.$tabs=null,P.forEach(t=>{t.forEach(o=>{try{o()}catch(s){console.error("[RouterTab] Error during cleanup:",s)}})}),P.clear(),S.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,t=>{t&&me(t),K()}),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(s=>s.id));Array.from(S.keys()).forEach(s=>{t.has(s)||(console.log(`[RouterTab] Cleaning up stale component instance: ${s}`),x(s))})}),n.watch(()=>e.contextmenu,t=>{t||K()}),n.watch(()=>F.value.length,t=>{R.visible&&t===0&&K()},{flush:"post"}),n.watch(F,t=>{if(!R.visible)return;L.value=new Array(t.length).fill(null);const o=ee(-1,1,t);Y(o)},{flush:"post"}),n.watch(()=>R.visible,t=>{t||(A.value=-1,L.value=[])});const pe=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:pe,persistenceHydrating:M,componentCache:H,componentCacheTrigger:fe,cacheCurrentComponent:yt,tabTransitionProps:se,pageTransitionProps:ce,buildTabClass:Kt,activate:Et,close:Rt,context:R,menuItems:F,handleMenuAction:$e,showContextMenu:dt,hideContextMenu:K,getTabTitle:de,isClosable:be,isRefreshing:Bt,isTabCached:$t,isTabReady:Pt,hasCustomSlot:h,hasStartSlot:w,hasEndSlot:l,onDragStart:St,onDragOver:At,onDragEnter:It,onDragLeave:Dt,onDrop:xt,onDragEnd:Ae,setupComponentWatching:D,cleanupComponentWatching:x,handleComponentRef:U,getReactiveTabTitle:ht,getComponentCacheKey:Se,getRefreshComponentKey:wt,createNamedComponent:Pe,ensureNamedComponent:Tt,getNamedComponent:vt,shouldRenderRoute:kt,triggerTabUpdate:k,menuRef:Z,highlightedIndex:A,setMenuItemRef:mt,onMenuKeydown:gt,highlightMenuIndex:Y,scrollContainer:_,setTabRef:Ct,scrollTabIntoView:me}}}),Fe=(e,a)=>{const r=e.__vccOpts||e;for(const[c,i]of a)r[c]=i;return r},Ye={class:"router-tab"},He={class:"router-tab__header"},We={class:"router-tab__scroll",ref:"scrollContainer"},Je=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],qe=["title"],Ge=["onClick"],Xe={class:"router-tab__container"},Qe={key:1,class:"router-tab__hydrating","aria-hidden":"true"},Ze=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function et(e,a,r,c,i,h){const w=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Ye,[n.createElementVNode("header",He,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",We,[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,g)=>(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,g,y),onDragover:y=>e.onDragOver(g,y),onDragenter:y=>e.onDragEnter(g),onDragleave:a[0]||(a[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(g,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,qe),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,Ge)):n.createCommentVNode("",!0)],42,Je))),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",Xe,[e.persistenceHydrating?(n.openBlock(),n.createElementBlock("div",Qe)):(n.openBlock(),n.createBlock(w,{key:0},{default:n.withCtx(({Component:l,route:g})=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{Component:l,route:g,controller:e.controller,pageRef:y=>e.handleComponentRef(y,e.controller.getRouteKey(g))}))):(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.getNamedComponent(l,e.getComponentCacheKey(g))),{key:e.isRefreshing(g)?e.getRefreshComponentKey(g):e.getComponentCacheKey(g),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(g)),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(g),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(g)),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,g)=>(n.openBlock(),n.createElementBlock("a",{key:l.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":g===e.highlightedIndex}]),"aria-disabled":l.disabled,disabled:l.disabled,tabindex:l.disabled?-1:g===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,g),onMouseenter:y=>!l.disabled&&e.highlightMenuIndex(g),onClick:y=>e.handleMenuAction(l)},n.toDisplayString(l.label),43,Ze))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const ie=Fe(ze,[["render",et]]),tt={class:"router-tabs","aria-hidden":"true"},G=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",tt))}}),ke="tab-theme-style",we="tab-theme-primary-color",nt="system",Re="(prefers-color-scheme: dark)";let j=null;const $={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"},ot={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 rt(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??$.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??$.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??$.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??$.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??$.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??$.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??$.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??$.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??$.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??$.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??$.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??$.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??$.iconColor))}function Ce(e){if(typeof document>"u")return;const a=document.documentElement,r=window.matchMedia(Re),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 Ee(e={}){if(typeof window>"u")return;const{styleKey:a=ke,primaryKey:r=we,defaultStyle:c=nt,defaultPrimary:i}=e,h=window.localStorage.getItem(a)??c;Ce(h);const l=h==="dark"||h==="system"&&window.matchMedia(Re).matches?{...ot}:{...$};i&&(l.primary=i);const g=rt(r);le(g?{...l,...g}:l)}function at(e,a){if(typeof window>"u")return;const r=a?.styleKey??ke;window.localStorage.setItem(r,e),Ce(e)}function it(e,a){if(typeof window>"u")return;const r=a?.primaryKey??we;window.localStorage.setItem(r,JSON.stringify(e)),le(e)}function X(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 Q(e={}){const a=X(e.title,"Untitled"),r=X(e.icon,""),c=X(e.closable,!0),i=X(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 lt(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 st(e,a="Page",r="mdi-page"){return Q({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":r)})}function ct(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 Q({title:n.computed(()=>a+r[e.value].suffix),icon:n.computed(()=>r[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let Ke=!1;const ut={install(e,a){if(Ke)return;Ke=!0;const{initTheme:r=!0,themeOptions:c,componentName:i=ie.name||"RouterTab",tabsComponentName:h=G.name||"RouterTabs"}=a??{};r&&Ee(c??{}),e.component(i,ie),e.component(h,G),h.toLowerCase()!=="router-tabs"&&e.component("router-tabs",G),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[O]},set(w){w&&e.provide(O,w)}})}};E.RouterTab=ie,E.RouterTabs=G,E.default=ut,E.initRouterTabsTheme=Ee,E.routerTabsKey=O,E.setRouterTabsPrimary=it,E.setRouterTabsTheme=at,E.useLoadingTab=lt,E.useNotificationTab=st,E.useReactiveTab=Q,E.useRouterTabs=re,E.useRouterTabsPersistence=ae,E.useStatusTab=ct,Object.defineProperties(E,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
package/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export declare const routerTabsKey: import('vue').InjectionKey<RouterTabsContext
|
|
|
56
56
|
|
|
57
57
|
export declare function useRouterTabs(options?: { optional?: boolean }): RouterTabsContext | null
|
|
58
58
|
|
|
59
|
-
export declare function useRouterTabsPersistence(options?: RouterTabsPersistenceOptions):
|
|
59
|
+
export declare function useRouterTabsPersistence(options?: RouterTabsPersistenceOptions): { hydrating: import('vue').Ref<boolean> }
|
|
60
60
|
|
|
61
61
|
export declare function initRouterTabsTheme(options?: RouterTabsThemeOptions): void
|
|
62
62
|
export declare function setRouterTabsTheme(style: 'light' | 'dark' | 'system', options?: RouterTabsThemeOptions): void
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
</header>
|
|
54
54
|
|
|
55
55
|
<div class="router-tab__container">
|
|
56
|
-
<RouterView v-slot="{ Component, route }">
|
|
56
|
+
<RouterView v-if="!persistenceHydrating" v-slot="{ Component, route }">
|
|
57
57
|
<template v-if="hasCustomSlot">
|
|
58
58
|
<slot
|
|
59
59
|
v-bind="{
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
<KeepAlive :include="includeKeys">
|
|
70
70
|
<component
|
|
71
71
|
v-if="Component"
|
|
72
|
-
:is="
|
|
73
|
-
:key="
|
|
72
|
+
:is="getNamedComponent(Component, getComponentCacheKey(route))"
|
|
73
|
+
:key="isRefreshing(route) ? getRefreshComponentKey(route) : getComponentCacheKey(route)"
|
|
74
74
|
:ref="(el: any) => handleComponentRef(el, controller.getRouteKey(route))"
|
|
75
75
|
class="router-tab-page"
|
|
76
76
|
/>
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
</template>
|
|
90
90
|
</template>
|
|
91
91
|
</RouterView>
|
|
92
|
+
<div v-else class="router-tab__hydrating" aria-hidden="true" />
|
|
92
93
|
</div>
|
|
93
94
|
|
|
94
95
|
<!-- Use v-show instead of v-if to keep DOM and prevent re-creation overhead -->
|
|
@@ -433,6 +434,7 @@ export default defineComponent({
|
|
|
433
434
|
return false
|
|
434
435
|
})
|
|
435
436
|
|
|
437
|
+
let persistenceHydrating = ref(false)
|
|
436
438
|
if (props.cookieKey !== null || props.persistence) {
|
|
437
439
|
const options: RouterTabsPersistenceOptions = {
|
|
438
440
|
...(props.persistence ?? {})
|
|
@@ -442,7 +444,8 @@ export default defineComponent({
|
|
|
442
444
|
} else if (!options.cookieKey) {
|
|
443
445
|
options.cookieKey = routerTabsCookie
|
|
444
446
|
}
|
|
445
|
-
useRouterTabsPersistence(options)
|
|
447
|
+
const persistenceState = useRouterTabsPersistence(options)
|
|
448
|
+
persistenceHydrating = persistenceState.hydrating
|
|
446
449
|
}
|
|
447
450
|
|
|
448
451
|
const tabTransitionProps = computed(() => getTransOpt(props.tabTransition))
|
|
@@ -822,6 +825,21 @@ export default defineComponent({
|
|
|
822
825
|
return component
|
|
823
826
|
}
|
|
824
827
|
|
|
828
|
+
/**
|
|
829
|
+
* Returns a component wrapper with a stable name for KeepAlive include matching.
|
|
830
|
+
* Caches the wrapper per cacheKey to avoid recreating components on each render.
|
|
831
|
+
*/
|
|
832
|
+
function getNamedComponent(component: any, cacheKey: string) {
|
|
833
|
+
if (!component) return component
|
|
834
|
+
const cached = componentCache.get(cacheKey)
|
|
835
|
+
if (cached) return cached
|
|
836
|
+
|
|
837
|
+
const wrapped = createNamedComponent(component, cacheKey)
|
|
838
|
+
componentCache.set(cacheKey, wrapped)
|
|
839
|
+
componentCacheTrigger.value++
|
|
840
|
+
return wrapped
|
|
841
|
+
}
|
|
842
|
+
|
|
825
843
|
/**
|
|
826
844
|
* Determines if a route should be rendered.
|
|
827
845
|
* Used to control which component is visible within KeepAlive.
|
|
@@ -1128,6 +1146,7 @@ export default defineComponent({
|
|
|
1128
1146
|
controller,
|
|
1129
1147
|
tabs: controller.tabs,
|
|
1130
1148
|
includeKeys,
|
|
1149
|
+
persistenceHydrating,
|
|
1131
1150
|
componentCache,
|
|
1132
1151
|
componentCacheTrigger,
|
|
1133
1152
|
cacheCurrentComponent,
|
|
@@ -1163,6 +1182,7 @@ export default defineComponent({
|
|
|
1163
1182
|
getRefreshComponentKey,
|
|
1164
1183
|
createNamedComponent,
|
|
1165
1184
|
ensureNamedComponent,
|
|
1185
|
+
getNamedComponent,
|
|
1166
1186
|
shouldRenderRoute,
|
|
1167
1187
|
triggerTabUpdate,
|
|
1168
1188
|
menuRef,
|
|
@@ -234,19 +234,6 @@ export function createRouterTabs(
|
|
|
234
234
|
// Generate the current cache key for this tab
|
|
235
235
|
const currentCacheKey = `${key}::${tab.renderKey}`
|
|
236
236
|
|
|
237
|
-
// Debug logging for specific routes
|
|
238
|
-
if (key.includes('students') || key.includes('classroom') || key.includes('quiz')) {
|
|
239
|
-
console.log(`[ensureTab] EXISTING tab: ${route.fullPath}`, {
|
|
240
|
-
key,
|
|
241
|
-
shouldBeAlive,
|
|
242
|
-
currentRenderKey: tab.renderKey,
|
|
243
|
-
currentCacheKey,
|
|
244
|
-
isInCache: aliveCache.has(currentCacheKey),
|
|
245
|
-
aliveCacheSize: aliveCache.size,
|
|
246
|
-
cacheContents: Array.from(aliveCache)
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
|
|
250
237
|
// Manage KeepAlive cache state
|
|
251
238
|
if (shouldBeAlive) {
|
|
252
239
|
// Check if tab's current key is in the cache
|
|
@@ -254,16 +241,15 @@ export function createRouterTabs(
|
|
|
254
241
|
// Tab was evicted or never added to cache - add it back
|
|
255
242
|
aliveCache.add(currentCacheKey)
|
|
256
243
|
tab.alive = true
|
|
257
|
-
if (key.includes('students') || key.includes('classroom') || key.includes('quiz')) {
|
|
258
|
-
console.log(`[ensureTab] ✅ Added to cache: ${currentCacheKey}`)
|
|
259
|
-
}
|
|
260
244
|
} else if (!tab.alive) {
|
|
261
245
|
// Tab is in cache but marked as not alive - just reactivate
|
|
262
246
|
tab.alive = true
|
|
263
|
-
|
|
264
|
-
console.log(`[ensureTab] ✅ Reactivated: ${currentCacheKey}`)
|
|
265
|
-
}
|
|
247
|
+
|
|
266
248
|
}
|
|
249
|
+
} else if (tab.alive) {
|
|
250
|
+
// Route no longer wants to be cached; drop from cache and mark inactive
|
|
251
|
+
aliveCache.delete(currentCacheKey)
|
|
252
|
+
tab.alive = false
|
|
267
253
|
}
|
|
268
254
|
|
|
269
255
|
Object.assign(tab, pickMeta(route))
|
|
@@ -277,10 +263,6 @@ export function createRouterTabs(
|
|
|
277
263
|
if (tab.alive) {
|
|
278
264
|
const cacheKey = `${key}::${tab.renderKey ?? 0}`
|
|
279
265
|
aliveCache.add(cacheKey)
|
|
280
|
-
|
|
281
|
-
if (key.includes('students') || key.includes('classroom') || key.includes('quiz')) {
|
|
282
|
-
console.log(`[ensureTab] NEW tab created and cached: ${cacheKey}`)
|
|
283
|
-
}
|
|
284
266
|
}
|
|
285
267
|
|
|
286
268
|
insertTab(tabs, tab, options.appendPosition, activeId.value)
|
|
@@ -345,6 +327,12 @@ export function createRouterTabs(
|
|
|
345
327
|
const index = tabs.findIndex(item => item.id === id)
|
|
346
328
|
if (index === -1) return
|
|
347
329
|
|
|
330
|
+
const tab = tabs[index]
|
|
331
|
+
// Remove KeepAlive cache entry if present
|
|
332
|
+
const cacheKey = `${id}::${tab.renderKey ?? 0}`
|
|
333
|
+
aliveCache.delete(cacheKey)
|
|
334
|
+
tab.alive = false
|
|
335
|
+
|
|
348
336
|
tabs.splice(index, 1)
|
|
349
337
|
|
|
350
338
|
if (refreshingKey.value === id) {
|
|
@@ -519,7 +507,20 @@ export function createRouterTabs(
|
|
|
519
507
|
const target = snapshot?.active ?? records[records.length - 1]?.to ?? options.defaultRoute
|
|
520
508
|
if (target) {
|
|
521
509
|
try {
|
|
522
|
-
|
|
510
|
+
const resolvedTarget = resolveRoute(router, target)
|
|
511
|
+
const currentRoute = router.currentRoute.value
|
|
512
|
+
|
|
513
|
+
// Avoid redundant navigation on page reload when the router is already at the target.
|
|
514
|
+
// A duplicate replace can trigger an extra mount cycle in tabbed layouts.
|
|
515
|
+
if (resolvedTarget.fullPath === currentRoute.fullPath) {
|
|
516
|
+
const tab = ensureTab(currentRoute as RouteLocationNormalizedLoaded)
|
|
517
|
+
activeId.value = tab.id
|
|
518
|
+
current.value = tab
|
|
519
|
+
enforceMaxAlive(tabs, options.maxAlive, activeId.value, aliveCache)
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
await router.replace(resolvedTarget)
|
|
523
524
|
} catch (error) {
|
|
524
525
|
console.warn('[RouterTabs] Failed to navigate to restored route', target, error)
|
|
525
526
|
}
|
package/lib/persistence.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
1
|
+
import { nextTick, onBeforeMount, onMounted, ref, watch, type Ref } from 'vue'
|
|
2
2
|
import type { RouteLocationRaw } from 'vue-router'
|
|
3
3
|
import { useRouterTabs } from './useRouterTabs'
|
|
4
4
|
import type { RouterTabsSnapshot } from './core/types'
|
|
@@ -98,15 +98,16 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
98
98
|
} = options
|
|
99
99
|
|
|
100
100
|
const controller = useRouterTabs({ optional: true })
|
|
101
|
-
const hydrating = ref(
|
|
101
|
+
const hydrating = ref(false)
|
|
102
102
|
|
|
103
|
-
const setup = (ctrl: NonNullable<typeof controller
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
const setup = (ctrl: NonNullable<typeof controller>, mode: 'hook' | 'immediate' = 'hook') => {
|
|
104
|
+
const run = async () => {
|
|
105
|
+
hydrating.value = true
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
try {
|
|
108
|
+
const initialSnapshot = deserialize(readCookie(cookieKey))
|
|
109
|
+
|
|
110
|
+
if (initialSnapshot && initialSnapshot.tabs?.length) {
|
|
110
111
|
await ctrl.hydrate(initialSnapshot)
|
|
111
112
|
if (initialSnapshot.active) {
|
|
112
113
|
await nextTick()
|
|
@@ -116,30 +117,30 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
116
117
|
ctrl.current.value = activeTab
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
}
|
|
120
|
-
hydrating.value = false
|
|
121
|
-
}
|
|
122
|
-
} else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
|
|
123
|
-
try {
|
|
124
|
-
hydrating.value = true
|
|
120
|
+
} else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
|
|
125
121
|
const fallback = options.fallbackRoute ?? ctrl.options.defaultRoute
|
|
126
122
|
await ctrl.reset(fallback)
|
|
127
|
-
} finally {
|
|
128
|
-
hydrating.value = false
|
|
129
123
|
}
|
|
130
|
-
} else {
|
|
131
|
-
hydrating.value = false
|
|
132
|
-
}
|
|
133
124
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
const snapshot = ctrl.snapshot()
|
|
126
|
+
if (!snapshot.tabs.length) {
|
|
127
|
+
removeCookie(cookieKey, options)
|
|
128
|
+
} else {
|
|
129
|
+
writeCookie(cookieKey, serialize(snapshot), options)
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
hydrating.value = false
|
|
139
133
|
}
|
|
134
|
+
}
|
|
140
135
|
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
if (mode === 'immediate') {
|
|
137
|
+
void run()
|
|
138
|
+
} else {
|
|
139
|
+
// Run before the first paint to avoid mounting pages twice during a restore.
|
|
140
|
+
onBeforeMount(() => {
|
|
141
|
+
void run()
|
|
142
|
+
})
|
|
143
|
+
}
|
|
143
144
|
|
|
144
145
|
watch(
|
|
145
146
|
() => ({
|
|
@@ -173,12 +174,14 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
173
174
|
onMounted(() => {
|
|
174
175
|
const lateController = useRouterTabs({ optional: true })
|
|
175
176
|
if (lateController) {
|
|
176
|
-
setup(lateController)
|
|
177
|
+
setup(lateController, 'immediate')
|
|
177
178
|
} else if (import.meta.env?.DEV) {
|
|
178
179
|
console.warn('[RouterTabs] Persistence helper must be used inside <router-tab>.')
|
|
179
180
|
}
|
|
180
181
|
})
|
|
181
182
|
}
|
|
183
|
+
|
|
184
|
+
return { hydrating: hydrating as Ref<boolean> }
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
export default useRouterTabsPersistence
|
package/lib/scss/index.scss
CHANGED
|
@@ -131,8 +131,7 @@
|
|
|
131
131
|
color: var(--router-tab-text);
|
|
132
132
|
font-size: var(--router-tab-font-size);
|
|
133
133
|
background-color: transparent;
|
|
134
|
-
border:
|
|
135
|
-
border-right: 1px solid var(--router-tab-border);
|
|
134
|
+
border: 1px solid var(--router-tab-border);
|
|
136
135
|
cursor: pointer;
|
|
137
136
|
user-select: none;
|
|
138
137
|
transition: var(--router-tab-transition);
|