vue3-router-tab 1.4.0 → 1.4.2
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/dist/vue3-router-tab.js +571 -568
- package/dist/vue3-router-tab.umd.cjs +1 -1
- package/index.d.ts +1 -1
- package/lib/components/RouterTab.vue +7 -17
- package/lib/core/createRouterTabs.ts +14 -1
- package/lib/persistence.ts +31 -26
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
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 x(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 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=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:M(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(k=>k.id===c);if(h!==-1){e.splice(h+1,0,a);return}}e.push(a)}}function re(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 k=e.findIndex(s=>s.id===h.id);if(k>-1){const s=e[k],b=`${s.id}::${s.renderKey??0}`;c.delete(b),s.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(),k=n.ref(null),s=n.reactive(new Set),b=n.computed(()=>Array.from(s));let y=!1;function $(u){const m=typeof u.matched=="object"?u:x(e,u);return{key:M(m),fullPath:m.fullPath,alive:te(m,r.keepAlive),reusable:ne(m,!1),matched:m}}function B(u){const m=M(u);let f=c.find(v=>v.id===m);const T=te(u,r.keepAlive);if(f){f.fullPath=u.fullPath,f.to=u.fullPath,f.matched=u,f.reusable=ne(u,f.reusable),typeof f.renderKey!="number"&&(f.renderKey=0);const v=`${m}::${f.renderKey}`;return T?s.has(v)?f.alive||(f.alive=!0):(s.add(v),f.alive=!0):f.alive&&(s.delete(v),f.alive=!1),Object.assign(f,he(u)),f}if(f=W(u,{},r.keepAlive),f.alive){const v=`${m}::${f.renderKey??0}`;s.add(v)}return oe(c,f,r.appendPosition,i.value),re(c,r.maxAlive,i.value,s),f}async function A(u,m=!1,f="sameTab"){const T=x(e,u),v=M(T),R=i.value===v;f==="sameTab"&&(f=R),f&&await L(v,!0),await e[m?"replace":"push"](T),R&&await V()}function U(u){const m=c.findIndex(P=>P.id===u);if(m===-1)return r.defaultRoute;const f=c[m+1],T=c[m-1],v=c.find(P=>P.id!==u),R=f||T||v;return R?R.to:r.defaultRoute}async function I(u=i.value,m={}){if(!u)return;if(!m.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&&m.redirect!==null,v=T?m.redirect??U(u):null;await z(u,{force:m.force}),m.redirect!==null&&T&&v&&await e.replace(v)}async function z(u,m={}){const f=c.findIndex(R=>R.id===u);if(f===-1)return;const T=c[f],v=`${u}::${T.renderKey??0}`;s.delete(v),T.alive=!1,c.splice(f,1),k.value===u&&(k.value=null),i.value===u&&(i.value=null,h.value=void 0)}async function L(u=i.value??void 0,m=!1){if(!u)return;const f=c.find(P=>P.id===u);if(!f)return;const T=r.keepAlive&&f.alive,v=`${u}::${f.renderKey??0}`;T&&(s.delete(v),f.alive=!1,await n.nextTick()),f.renderKey=(f.renderKey??0)+1;const R=`${u}::${f.renderKey}`;T&&(s.add(R),f.alive=!0),k.value=u,await n.nextTick(),m||await n.nextTick(),k.value=null}async function ce(u=!1){for(const m of c)await L(m.id,u)}function w(u,m){const f=c.find(v=>v.id===u);if(!f)return;const T=`${u}::${f.renderKey??0}`;m?(s.add(T),f.alive=!0,re(c,r.maxAlive,i.value,s)):(s.delete(T),f.alive=!1)}function Q(u){const m=c.find(T=>T.id===u);if(!m)return;const f=`${u}::${m.renderKey??0}`;s.delete(f),m.alive=!1,m.renderKey=(m.renderKey??0)+1}function D(){s.clear(),c.forEach(u=>{u.alive=!1})}function S(){return b.value.slice()}async function Z(u=r.defaultRoute){c.splice(0,c.length),i.value=null,h.value=void 0;for(const m of r.initialTabs){const f=x(e,m.to),T=W(f,m,r.keepAlive);c.push(T)}await e.replace(u)}async function V(){const u=i.value;u&&await L(u,!0)}function C(u){return typeof u.matched=="object"?M(u):M(x(e,u))}function ue(){const u=c.find(m=>m.id===i.value);return{tabs:c.map(Le),active:u?u.to:null}}async function _(u){y=!0,c.splice(0,c.length),i.value=null,h.value=void 0;const m=u?.tabs??[];for(const T of m)try{const v=x(e,T.to),R=Ve(T),P=W(v,R,r.keepAlive);oe(c,P,"last",null)}catch(v){console.warn("[RouterTabs] Failed to restore tab",T,v)}y=!1;const f=u?.active??m[m.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 m=B(u);i.value=m.id,h.value=m,re(c,r.maxAlive,i.value,s)},{immediate:!0}),r.initialTabs.length&&r.initialTabs.forEach(u=>{const m=x(e,u.to),f=W(m,u,r.keepAlive);oe(c,f,"last",null)}),{options:r,tabs:c,activeId:i,current:h,includeKeys:b,refreshingKey:k,openTab:A,closeTab:I,removeTab:z,refreshTab:L,refreshAll:ce,setTabAlive:w,evictCache:Q,clearCache:D,getCacheKeys:S,reset:Z,reload:V,getRouteKey:C,matchRoute:$,snapshot:ue,hydrate:_,ensureTab:B}}function ye(e){return e?typeof e=="string"?{name:e}:e:{}}const O=Symbol("RouterTabsContext"),J="router-tabs:snapshot";function ae(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:k,sameSite:s="lax"}=r,b=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(c!==1/0){const y=new Date(Date.now()+c*Oe).toUTCString();b.push(`Expires=${y}`)}i&&b.push(`Path=${i}`),h&&b.push(`Domain=${h}`),k&&b.push("Secure"),s&&b.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=b.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),ze=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ie(e={}){const{cookieKey:a=J,serialize:r=Ue,deserialize:c=ze}=e,i=ae({optional:!0}),h=n.ref(!0),k=s=>{n.onMounted(async()=>{const b=c(je(a));if(b&&b.tabs?.length)try{if(h.value=!0,await s.hydrate(b),b.active){await n.nextTick();const $=s.tabs.find(B=>B.to===b.active);$&&(s.activeId.value=$.id,s.current.value=$)}}finally{h.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{h.value=!0;const $=e.fallbackRoute??s.options.defaultRoute;await s.reset($)}finally{h.value=!1}else h.value=!1;const y=s.snapshot();y.tabs.length?Te(a,r(y),e):ve(a,e),h.value=!1}),n.watch(()=>({tabs:s.tabs.map(b=>({to:b.to,title:b.title,tips:b.tips,icon:b.icon,tabClass:b.tabClass,closable:b.closable,renderKey:b.renderKey})),active:s.activeId.value}),()=>{if(h.value)return;const b=s.snapshot();b.tabs.length?Te(a,r(b),e):ve(a,e)},{deep:!0})};i?k(i):n.onMounted(()=>{const s=ae({optional:!0});s&&k(s)})}const _e=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:J},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),k=n.computed(()=>!!r?.slots?.start),s=n.computed(()=>!!r?.slots?.end),b=n.ref(0),y=n.computed(()=>{b.value;const t={};return i.tabs.forEach(o=>{const l=typeof o.title=="string"?o.title:String(o.title||fe(o));t[o.id]=l}),t});function $(){b.value++}const B=new Map,A=new Map;function U(t,o){if(!(!o||B.has(t)))try{B.set(t,o);const l=i.tabs.find(g=>i.getRouteKey(g.to)===t);if(!l){console.warn(`[RouterTab] Cannot setup watching: tab not found for ${t}`);return}const p=[];if(o.routeTabTitle!==void 0)try{const g=n.watch(()=>{const d=o.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const N=String(d);l.title=N,$()}},{immediate:!0});p.push(g)}catch(g){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,g)}if(o.routeTabIcon!==void 0)try{const g=n.watch(()=>{const d=o.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(l.icon=String(d),$())},{immediate:!0});p.push(g)}catch(g){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,g)}if(o.routeTabClosable!==void 0)try{const g=n.watch(()=>{const d=o.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(l.closable=!!d,$())},{immediate:!0});p.push(g)}catch(g){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,g)}if(o.routeTabMeta!==void 0)try{const g=n.watch(()=>{const d=o.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(l,d),$())},{immediate:!0,deep:!0});p.push(g)}catch(g){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,g)}A.set(t,p)}catch(l){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,l),I(t)}}function I(t){try{const o=A.get(t);o&&(o.forEach(l=>{try{l()}catch(p){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,p)}}),A.delete(t)),B.delete(t)}catch(o){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,o)}}function z(t,o){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?U(o,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&U(o,t.$):t===null&&I(o)}catch(l){console.error(`[RouterTab] Error handling component ref for ${o}:`,l),I(o)}}if(n.onErrorCaptured((t,o,l)=>(console.error("[RouterTab] Error captured from component:",t,l),!1)),e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||J:t.cookieKey||(t.cookieKey=J),ie(t)}const L=n.computed(()=>ye(e.tabTransition)),ce=n.computed(()=>ye(e.pageTransition)),w=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),Q=n.ref(null),D=n.ref([]),S=n.ref(-1),Z=n.ref(null),V=new Map,C=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),ue=["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 m(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 l=t.filter(p=>p.closable!==!1);if(l.length){for(const p of l)i.activeId.value===p.id?await i.closeTab(p.id,{redirect:o.to,force:!0}):await i.removeTab(p.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})=>be(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(m(t),t)},enable:({target:t})=>m(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 R(){w.visible=!1,w.target=null,S.value=-1,D.value=[]}function P(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",R,{once:!0}),n.nextTick(()=>{ft()})}))}function ut(t,o){const l=typeof t=="string"?{id:t}:t,p=v[l.id],g=l.label??p?.label??String(l.id),d=l.visible??p?.visible??!0;if(!(typeof d=="function"?d(o):d!==!1))return null;const ge=l.enable??p?.enable??!0,Dt=typeof ge=="function"?ge(o):ge!==!1,Ie=l.handler??p?.handler;if(!Ie)return null;const xt=async()=>{await Promise.resolve(Ie(o))};return{id:String(l.id),label:g,disabled:!Dt,action:xt}}const F=n.computed(()=>{if(!w.visible||!w.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:ue,o={target:w.target,controller:i};return t.map(l=>ut(l,o)).filter(l=>!!l)});function ft(){const t=Q.value;if(!t)return;const o=8,{innerWidth:l,innerHeight:p}=window,g=t.getBoundingClientRect();let d=w.position.x,N=w.position.y;g.right>l-o&&(d=Math.max(o,l-g.width-o)),g.bottom>p-o&&(N=Math.max(o,p-g.height-o)),(d!==w.position.x||N!==w.position.y)&&(w.position.x=d,w.position.y=N)}function dt(t,o){D.value[o]=t??null}function bt(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function ee(t,o,l=F.value){if(!l.length)return-1;const p=l.length;let g=t;for(let d=0;d<p;d++)if(g=(g+o+p)%p,!l[g].disabled)return g;return-1}function Y(t){S.value=t,!(t<0)&&n.nextTick(()=>bt(t))}function $e(t){const o=ee(S.value,t);o!==-1&&Y(o)}function mt(t){if(!w.visible)return;const o=t.key,l=F.value;if(!l.length)return;if(o==="Tab"){R();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(o))switch(t.preventDefault(),o){case"ArrowDown":case"ArrowRight":$e(1);break;case"ArrowUp":case"ArrowLeft":$e(-1);break;case"Home":Y(ee(-1,1));break;case"End":Y(ee(l.length,-1));break;case"Enter":case" ":case"Spacebar":{const g=S.value;if(g>-1){const d=l[g];d.disabled||Be(d)}break}case"Escape":R();break}}async function Be(t){t.disabled||(R(),await t.action())}function fe(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 pt(t){return y.value[t.id]||fe(t)}function Pe(t,o){return n.defineComponent({name:o,setup(l,{attrs:p,slots:g}){return()=>n.h(t,p,g)}})}const H=new Map,de=n.ref(0);function gt(t,o,l){H.has(l)||(H.set(l,o),de.value++),t&&z(t,l)}function ht(t,o){return t&&((!t.name||t.name!==o)&&(t.name=o),t)}function yt(t,o){if(!t)return t;const l=H.get(o);if(l)return l;const p=Pe(t,o);return H.set(o,p),de.value++,p}function Tt(t){const o=i.getRouteKey(t),l=c.currentRoute.value,p=i.getRouteKey(l);return o===p}function Se(t){const o=i.getRouteKey(t),l=i.tabs.find(d=>d.id===o);if(!l)return console.warn("[RouterTab] Tab not found for route:",o),`${o}::0`;const p=l.renderKey??0,g=`${o}::${p}`;return(o.includes("students")||o.includes("classroom")||o.includes("quiz"))&&console.log(`[getComponentCacheKey] Route: ${t.fullPath}`,{routeKey:o,renderKey:p,cacheKey:g,tabAlive:l.alive,includeKeys:pe.value,isIncluded:pe.value.includes(g)}),g}function vt(t){return`${Se(t)}::refresh`}function be(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function kt(t){await i.closeTab(t.id)}function wt(t,o){o?V.set(t,o):V.delete(t)}function me(t){n.nextTick(()=>{const o=V.get(t),l=Z.value;if(o&&l){const p=o.getBoundingClientRect(),g=l.getBoundingClientRect();(p.left<g.left||p.right>g.right)&&o.scrollIntoView({behavior:"smooth",block:"nearest",inline:"nearest"})}})}function Rt(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 Ct(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===_(t.id)},t.tabClass]}function Et(t){return i.refreshingKey.value===i.getRouteKey(t)}function Kt(t){const o=i.getRouteKey(t),l=i.tabs.find(p=>p.id===o);return l?l.alive:!1}function $t(t){const o=i.getRouteKey(t);return!!i.tabs.find(p=>p.id===o)}function Bt(t,o,l){e.sortable&&(C.dragging=!0,C.dragIndex=o,C.dragTab=t,l.dataTransfer&&(l.dataTransfer.effectAllowed="move",l.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:o}))}function Pt(t,o){!e.sortable||!C.dragging||(o.preventDefault(),o.dataTransfer&&(o.dataTransfer.dropEffect="move"))}function St(t){!e.sortable||!C.dragging||(C.dropIndex=t)}function At(){!e.sortable||C.dragging}function It(t,o){if(!(!e.sortable||!C.dragging)){if(o.preventDefault(),C.dragIndex!==-1&&C.dragIndex!==t){const l=i.tabs.splice(C.dragIndex,1)[0];i.tabs.splice(t,0,l),a("tab-sorted",{tab:l,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",R)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",R),r.appContext.config.globalProperties.$tabs=null,A.forEach(t=>{t.forEach(o=>{try{o()}catch(l){console.error("[RouterTab] Error during cleanup:",l)}})}),A.clear(),B.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,t=>{t&&me(t),R()}),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(l=>l.id));Array.from(B.keys()).forEach(l=>{t.has(l)||(console.log(`[RouterTab] Cleaning up stale component instance: ${l}`),I(l))})}),n.watch(()=>e.contextmenu,t=>{t||R()}),n.watch(()=>F.value.length,t=>{w.visible&&t===0&&R()},{flush:"post"}),n.watch(F,t=>{if(!w.visible)return;D.value=new Array(t.length).fill(null);const o=ee(-1,1,t);Y(o)},{flush:"post"}),n.watch(()=>w.visible,t=>{t||(S.value=-1,D.value=[])});const pe=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:pe,componentCache:H,componentCacheTrigger:de,cacheCurrentComponent:gt,tabTransitionProps:L,pageTransitionProps:ce,buildTabClass:Ct,activate:Rt,close:kt,context:w,menuItems:F,handleMenuAction:Be,showContextMenu:P,hideContextMenu:R,getTabTitle:fe,isClosable:be,isRefreshing:Et,isTabCached:Kt,isTabReady:$t,hasCustomSlot:h,hasStartSlot:k,hasEndSlot:s,onDragStart:Bt,onDragOver:Pt,onDragEnter:St,onDragLeave:At,onDrop:It,onDragEnd:Ae,setupComponentWatching:U,cleanupComponentWatching:I,handleComponentRef:z,getReactiveTabTitle:pt,getComponentCacheKey:Se,getRefreshComponentKey:vt,createNamedComponent:Pe,ensureNamedComponent:ht,getNamedComponent:yt,shouldRenderRoute:Tt,triggerTabUpdate:$,menuRef:Q,highlightedIndex:S,setMenuItemRef:dt,onMenuKeydown:mt,highlightMenuIndex:Y,scrollContainer:Z,setTabRef:wt,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=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Ze(e,a,r,c,i,h){const k=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,(s,b)=>(n.openBlock(),n.createElementBlock("li",{key:s.id,class:n.normalizeClass(e.buildTabClass(s)),"data-title":e.getTabTitle(s),draggable:e.sortable,ref_for:!0,ref:y=>e.setTabRef(s.id,y),onClick:y=>e.activate(s),onAuxclick:n.withModifiers(y=>e.close(s),["middle","prevent"]),onContextmenu:n.withModifiers(y=>e.showContextMenu(s,y),["prevent"]),onDragstart:y=>e.onDragStart(s,b,y),onDragover:y=>e.onDragOver(b,y),onDragenter:y=>e.onDragEnter(b),onDragleave:a[0]||(a[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(b,y),onDragend:a[1]||(a[1]=(...y)=>e.onDragEnd&&e.onDragEnd(...y))},[s.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",s.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(s)},n.toDisplayString(e.getReactiveTabTitle(s)),9,qe),e.isClosable(s)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(y=>e.close(s),["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,[n.createVNode(k,null,{default:n.withCtx(({Component:s,route:b})=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{Component:s,route:b,controller:e.controller,pageRef:y=>e.handleComponentRef(y,e.controller.getRouteKey(b))}))):(n.openBlock(),n.createElementBlock(n.Fragment,{key:1},[e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:0,include:e.includeKeys},[s?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(e.getNamedComponent(s,e.getComponentCacheKey(b))),{key:e.isRefreshing(b)?e.getRefreshComponentKey(b):e.getComponentCacheKey(b),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(b)),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(()=>[s?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(s),{key:e.controller.getRouteKey(b),ref:y=>e.handleComponentRef(y,e.controller.getRouteKey(b)),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]=(...s)=>e.onMenuKeydown&&e.onMenuKeydown(...s)),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,(s,b)=>(n.openBlock(),n.createElementBlock("a",{key:s.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":b===e.highlightedIndex}]),"aria-disabled":s.disabled,disabled:s.disabled,tabindex:s.disabled?-1:b===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,b),onMouseenter:y=>!s.disabled&&e.highlightMenuIndex(b),onClick:y=>e.handleMenuAction(s)},n.toDisplayString(s.label),43,Qe))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const le=Fe(_e,[["render",Ze]]),et={class:"router-tabs","aria-hidden":"true"},q=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 ie(e),(r,c)=>(n.openBlock(),n.createElementBlock("span",et))}}),ke="tab-theme-style",we="tab-theme-primary-color",tt="system",Re="(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"},nt={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 ot(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 se(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 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=tt,defaultPrimary:i}=e,h=window.localStorage.getItem(a)??c;Ce(h);const s=h==="dark"||h==="system"&&window.matchMedia(Re).matches?{...nt}:{...K};i&&(s.primary=i);const b=ot(r);se(b?{...s,...b}:s)}function rt(e,a){if(typeof window>"u")return;const r=a?.styleKey??ke;window.localStorage.setItem(r,e),Ce(e)}function at(e,a){if(typeof window>"u")return;const r=a?.primaryKey??we;window.localStorage.setItem(r,JSON.stringify(e)),se(e)}function G(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 X(e={}){const a=G(e.title,"Untitled"),r=G(e.icon,""),c=G(e.closable,!0),i=G(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 it(e,a="Page"){return X({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function lt(e,a="Page",r="mdi-page"){return X({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":r)})}function st(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 X({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 ct={install(e,a){if(Ke)return;Ke=!0;const{initTheme:r=!0,themeOptions:c,componentName:i=le.name||"RouterTab",tabsComponentName:h=q.name||"RouterTabs"}=a??{};r&&Ee(c??{}),e.component(i,le),e.component(h,q),h.toLowerCase()!=="router-tabs"&&e.component("router-tabs",q),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[O]},set(k){k&&e.provide(O,k)}})}};E.RouterTab=le,E.RouterTabs=q,E.default=ct,E.initRouterTabsTheme=Ee,E.routerTabsKey=O,E.setRouterTabsPrimary=at,E.setRouterTabsTheme=rt,E.useLoadingTab=it,E.useNotificationTab=lt,E.useReactiveTab=X,E.useRouterTabs=ae,E.useRouterTabsPersistence=ie,E.useStatusTab=st,Object.defineProperties(E,{__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,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 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 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 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=De[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(g=>g.id===a.id)){if(r==="next"&&c){const g=e.findIndex(w=>w.id===c);if(g!==-1){e.splice(g+1,0,a);return}}e.push(a)}}function J(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 w=e.findIndex(l=>l.id===g.id);if(w>-1){const l=e[w],y=`${l.id}::${l.renderKey??0}`;c.delete(y),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(),w=n.ref(null),l=n.reactive(new Set),y=n.computed(()=>Array.from(l));let h=!1;function v(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(k=>k.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 k=`${b}::${d.renderKey}`;return T?l.has(k)?d.alive||(d.alive=!0):(l.add(k),d.alive=!0):d.alive&&(l.delete(k),d.alive=!1),Object.assign(d,he(u)),d}if(d=W(u,{},r.keepAlive),d.alive){const k=`${b}::${d.renderKey??0}`;l.add(k)}return oe(c,d,r.appendPosition,i.value),J(c,r.maxAlive,i.value,l),d}async function $(u,b=!1,d="sameTab"){const T=I(e,u),k=V(T),B=i.value===k;d==="sameTab"&&(d=B),d&&await M(k,!0),await e[b?"replace":"push"](T),B&&await U()}function x(u){const b=c.findIndex(K=>K.id===u);if(b===-1)return r.defaultRoute;const d=c[b+1],T=c[b-1],k=c.find(K=>K.id!==u),B=d||T||k;return B?B.to:r.defaultRoute}async function D(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,k=T?b.redirect??x(u):null;await _(u,{force:b.force}),b.redirect!==null&&T&&k&&await e.replace(k)}async function _(u,b={}){const d=c.findIndex(B=>B.id===u);if(d===-1)return;const T=c[d],k=`${u}::${T.renderKey??0}`;l.delete(k),T.alive=!1,c.splice(d,1),w.value===u&&(w.value=null),i.value===u&&(i.value=null,g.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,k=`${u}::${d.renderKey??0}`;T&&(l.delete(k),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(k=>k.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 C(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 y.value.slice()}async function A(u=r.defaultRoute){c.splice(0,c.length),i.value=null,g.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 U(){const u=i.value;u&&await M(u,!0)}function z(u){return typeof u.matched=="object"?V(u):V(I(e,u))}function R(){const u=c.find(b=>b.id===i.value);return{tabs:c.map(Me),active:u?u.to:null}}async function ue(u){h=!0,c.splice(0,c.length),i.value=null,g.value=void 0;const b=u?.tabs??[];for(const T of b)try{const k=I(e,T.to),B=Le(T),K=W(k,B,r.keepAlive);oe(c,K,"last",null)}catch(k){console.warn("[RouterTabs] Failed to restore tab",T,k)}h=!1;const d=u?.active??b[b.length-1]?.to??r.defaultRoute;if(d)try{const T=I(e,d),k=e.currentRoute.value;if(T.fullPath===k.fullPath){const B=S(k);i.value=B.id,g.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(h)return;const b=S(u);i.value=b.id,g.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:g,includeKeys:y,refreshingKey:w,openTab:$,closeTab:D,removeTab:_,refreshTab:M,refreshAll:se,setTabAlive:ce,evictCache:C,clearCache:Z,getCacheKeys:L,reset:A,reload:U,getRouteKey:z,matchRoute:v,snapshot:R,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 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 Te(e,a,r){if(typeof document>"u")return;const{expiresInDays:c=7,path:i="/",domain:g,secure:w,sameSite:l="lax"}=r,y=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(c!==1/0){const h=new Date(Date.now()+c*Ne).toUTCString();y.push(`Expires=${h}`)}i&&y.push(`Path=${i}`),g&&y.push(`Domain=${g}`),w&&y.push("Secure"),l&&y.push(`SameSite=${l.charAt(0).toUpperCase()}${l.slice(1)}`),document.cookie=y.join("; ")}function ke(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),_e=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ae(e={}){const{cookieKey:a=q,serialize:r=je,deserialize:c=_e}=e,i=re({optional:!0}),g=n.ref(!0),w=(l,y="hook")=>{const h=async()=>{g.value=!0;try{const v=c(Oe(a));if(v&&v.tabs?.length){if(await l.hydrate(v),v.active){await n.nextTick();const $=l.tabs.find(x=>x.to===v.active);$&&(l.activeId.value=$.id,l.current.value=$)}}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute")){const $=e.fallbackRoute??l.options.defaultRoute;await l.reset($)}const S=l.snapshot();S.tabs.length?Te(a,r(S),e):ke(a,e)}finally{g.value=!1}};y==="immediate"?h():n.onBeforeMount(()=>{h()}),n.watch(()=>({tabs:l.tabs.map(v=>({to:v.to,title:v.title,tips:v.tips,icon:v.icon,tabClass:v.tabClass,closable:v.closable,renderKey:v.renderKey})),active:l.activeId.value}),()=>{if(g.value)return;const v=l.snapshot();v.tabs.length?Te(a,r(v),e):ke(a,e)},{deep:!0})};return i?w(i):n.onMounted(()=>{const l=re({optional:!0});l&&w(l,"immediate")}),{hydrating:g}}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: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=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),w=n.computed(()=>!!r?.slots?.start),l=n.computed(()=>!!r?.slots?.end),y=n.ref(0),h=n.computed(()=>{y.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 v(){y.value++}const S=new Map,$=new Map;function x(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,v()}},{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),v())},{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,v())},{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),v())},{immediate:!0,deep:!0});m.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}$.set(t,m)}catch(s){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,s),D(t)}}function D(t){try{const o=$.get(t);o&&(o.forEach(s=>{try{s()}catch(m){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,m)}}),$.delete(t)),S.delete(t)}catch(o){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,o)}}function _(t,o){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?x(o,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&x(o,t.$):t===null&&D(o)}catch(s){console.error(`[RouterTab] Error handling component ref for ${o}:`,s),D(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)),C=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),Z=n.ref(null),L=n.ref([]),A=n.ref(-1),U=n.ref(null),z=new Map,R=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 k(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 k(b(t),t)},enable:({target:t})=>b(t).some(o=>o.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await k(d(t),t)},enable:({target:t})=>d(t).some(o=>o.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await k(T(t),t)},enable:({target:t})=>T(t).some(o=>o.closable!==!1)}};function K(){C.visible=!1,C.target=null,A.value=-1,L.value=[]}function ut(t,o){e.contextmenu&&(C.target=t,C.position.x=o.clientX,C.position.y=o.clientY,n.nextTick(()=>{C.visible=!0,document.addEventListener("click",K,{once:!0}),n.nextTick(()=>{ft()})}))}function dt(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,xt=typeof ge=="function"?ge(o):ge!==!1,Ae=s.handler??m?.handler;if(!Ae)return null;const Dt=async()=>{await Promise.resolve(Ae(o))};return{id:String(s.id),label:p,disabled:!xt,action:Dt}}const F=n.computed(()=>{if(!C.visible||!C.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:ue,o={target:C.target,controller:i};return t.map(s=>dt(s,o)).filter(s=>!!s)});function ft(){const t=Z.value;if(!t)return;const o=8,{innerWidth:s,innerHeight:m}=window,p=t.getBoundingClientRect();let f=C.position.x,N=C.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!==C.position.x||N!==C.position.y)&&(C.position.x=f,C.position.y=N)}function bt(t,o){L.value[o]=t??null}function mt(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(()=>mt(t))}function Be(t){const o=ee(A.value,t);o!==-1&&Y(o)}function pt(t){if(!C.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||Pe(f)}break}case"Escape":K();break}}async function Pe(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 gt(t){return h.value[t.id]||de(t)}function $e(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 ht(t,o,s){H.has(s)||(H.set(s,o),fe.value++),t&&_(t,s)}function yt(t,o){return t&&((!t.name||t.name!==o)&&(t.name=o),t)}function Tt(t,o){if(!t)return t;const s=H.get(o);if(s)return s;const m=$e(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 vt(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 be(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function wt(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=U.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 Rt(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 Et(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":be(t),"is-dragging":R.dragging&&R.dragTab?.id===t.id,"is-drag-over":R.dropIndex===u(t.id)},t.tabClass]}function Kt(t){const o=i.getRouteKey(t),s=i.tabs.find(m=>m.id===o);return s?s.alive:!1}function Bt(t){const o=i.getRouteKey(t);return!!i.tabs.find(m=>m.id===o)}function Pt(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 $t(t,o){!e.sortable||!R.dragging||(o.preventDefault(),o.dataTransfer&&(o.dataTransfer.dropEffect="move"))}function St(t){!e.sortable||!R.dragging||(R.dropIndex=t)}function At(){!e.sortable||R.dragging}function It(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",K)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",K),r.appContext.config.globalProperties.$tabs=null,$.forEach(t=>{t.forEach(o=>{try{o()}catch(s){console.error("[RouterTab] Error during cleanup:",s)}})}),$.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}`),D(s))})}),n.watch(()=>e.contextmenu,t=>{t||K()}),n.watch(()=>F.value.length,t=>{C.visible&&t===0&&K()},{flush:"post"}),n.watch(F,t=>{if(!C.visible)return;L.value=new Array(t.length).fill(null);const o=ee(-1,1,t);Y(o)},{flush:"post"}),n.watch(()=>C.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:ht,tabTransitionProps:se,pageTransitionProps:ce,buildTabClass:Et,activate:Rt,close:wt,context:C,menuItems:F,handleMenuAction:Pe,showContextMenu:ut,hideContextMenu:K,getTabTitle:de,isClosable:be,isTabCached:Kt,isTabReady:Bt,hasCustomSlot:g,hasStartSlot:w,hasEndSlot:l,onDragStart:Pt,onDragOver:$t,onDragEnter:St,onDragLeave:At,onDrop:It,onDragEnd:Se,setupComponentWatching:x,cleanupComponentWatching:D,handleComponentRef:_,getReactiveTabTitle:gt,getComponentCacheKey:vt,createNamedComponent:$e,ensureNamedComponent:yt,getNamedComponent:Tt,shouldRenderRoute:kt,triggerTabUpdate:v,menuRef:Z,highlightedIndex:A,setMenuItemRef:bt,onMenuKeydown:pt,highlightMenuIndex:Y,scrollContainer:U,setTabRef:Ct,scrollTabIntoView:me}}}),ze=(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"},He={class:"router-tab__scroll",ref:"scrollContainer"},We=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Je=["title"],qe=["onClick"],Ge={class:"router-tab__container"},Xe={key:1,class:"router-tab__hydrating","aria-hidden":"true"},Qe=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Ze(e,a,r,c,i,g){const w=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",He,[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,y)=>(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:h=>e.setTabRef(l.id,h),onClick:h=>e.activate(l),onAuxclick:n.withModifiers(h=>e.close(l),["middle","prevent"]),onContextmenu:n.withModifiers(h=>e.showContextMenu(l,h),["prevent"]),onDragstart:h=>e.onDragStart(l,y,h),onDragover:h=>e.onDragOver(y,h),onDragenter:h=>e.onDragEnter(y),onDragleave:a[0]||(a[0]=(...h)=>e.onDragLeave&&e.onDragLeave(...h)),onDrop:h=>e.onDrop(y,h),onDragend:a[1]||(a[1]=(...h)=>e.onDragEnd&&e.onDragEnd(...h))},[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,Je),e.isClosable(l)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(h=>e.close(l),["stop"])},null,8,qe)):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,[e.persistenceHydrating?(n.openBlock(),n.createElementBlock("div",Xe)):(n.openBlock(),n.createBlock(w,{key:0},{default:n.withCtx(({Component:l,route:y})=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{Component:l,route:y,controller:e.controller,pageRef:h=>e.handleComponentRef(h,e.controller.getRouteKey(y))}))):(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(y))),{key:e.getComponentCacheKey(y),ref:h=>e.handleComponentRef(h,e.controller.getRouteKey(y)),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(y),ref:h=>e.handleComponentRef(h,e.controller.getRouteKey(y)),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,y)=>(n.openBlock(),n.createElementBlock("a",{key:l.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":y===e.highlightedIndex}]),"aria-disabled":l.disabled,disabled:l.disabled,tabindex:l.disabled?-1:y===e.highlightedIndex?0:-1,ref_for:!0,ref:h=>e.setMenuItemRef(h,y),onMouseenter:h=>!l.disabled&&e.highlightMenuIndex(y),onClick:h=>e.handleMenuAction(l)},n.toDisplayString(l.label),43,Qe))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const ie=ze(Ue,[["render",Ze]]),et={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",et))}}),ve="tab-theme-style",we="tab-theme-primary-color",tt="system",Ce="(prefers-color-scheme: dark)";let j=null;const P={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"},nt={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 ot(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??P.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??P.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??P.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??P.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??P.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??P.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??P.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??P.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??P.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??P.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??P.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??P.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??P.iconColor))}function Re(e){if(typeof document>"u")return;const a=document.documentElement,r=window.matchMedia(Ce),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=ve,primaryKey:r=we,defaultStyle:c=tt,defaultPrimary:i}=e,g=window.localStorage.getItem(a)??c;Re(g);const l=g==="dark"||g==="system"&&window.matchMedia(Ce).matches?{...nt}:{...P};i&&(l.primary=i);const y=ot(r);le(y?{...l,...y}:l)}function rt(e,a){if(typeof window>"u")return;const r=a?.styleKey??ve;window.localStorage.setItem(r,e),Re(e)}function at(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 it(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 lt(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 st(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 ct={install(e,a){if(Ke)return;Ke=!0;const{initTheme:r=!0,themeOptions:c,componentName:i=ie.name||"RouterTab",tabsComponentName:g=G.name||"RouterTabs"}=a??{};r&&Ee(c??{}),e.component(i,ie),e.component(g,G),g.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=ct,E.initRouterTabsTheme=Ee,E.routerTabsKey=O,E.setRouterTabsPrimary=at,E.setRouterTabsTheme=rt,E.useLoadingTab=it,E.useNotificationTab=lt,E.useReactiveTab=Q,E.useRouterTabs=re,E.useRouterTabsPersistence=ae,E.useStatusTab=st,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="{
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
<component
|
|
71
71
|
v-if="Component"
|
|
72
72
|
:is="getNamedComponent(Component, getComponentCacheKey(route))"
|
|
73
|
-
:key="
|
|
73
|
+
:key="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))
|
|
@@ -885,14 +888,6 @@ export default defineComponent({
|
|
|
885
888
|
return cacheKey
|
|
886
889
|
}
|
|
887
890
|
|
|
888
|
-
/**
|
|
889
|
-
* Generates a special key for components in refreshing state.
|
|
890
|
-
* Appends '::refresh' to ensure it's treated as separate from cached instance.
|
|
891
|
-
*/
|
|
892
|
-
function getRefreshComponentKey(route: RouteLocationNormalizedLoaded): string {
|
|
893
|
-
return `${getComponentCacheKey(route)}::refresh`
|
|
894
|
-
}
|
|
895
|
-
|
|
896
891
|
function isClosable(tab: TabRecord) {
|
|
897
892
|
if (tab.closable === false) return false
|
|
898
893
|
if (controller.options.keepLastTab && controller.tabs.length <= 1) return false
|
|
@@ -966,10 +961,6 @@ export default defineComponent({
|
|
|
966
961
|
]
|
|
967
962
|
}
|
|
968
963
|
|
|
969
|
-
function isRefreshing(route: RouteLocationNormalizedLoaded) {
|
|
970
|
-
return controller.refreshingKey.value === controller.getRouteKey(route)
|
|
971
|
-
}
|
|
972
|
-
|
|
973
964
|
function isTabCached(route: RouteLocationNormalizedLoaded) {
|
|
974
965
|
const routeKey = controller.getRouteKey(route)
|
|
975
966
|
const tab = controller.tabs.find(tab => tab.id === routeKey)
|
|
@@ -1143,6 +1134,7 @@ export default defineComponent({
|
|
|
1143
1134
|
controller,
|
|
1144
1135
|
tabs: controller.tabs,
|
|
1145
1136
|
includeKeys,
|
|
1137
|
+
persistenceHydrating,
|
|
1146
1138
|
componentCache,
|
|
1147
1139
|
componentCacheTrigger,
|
|
1148
1140
|
cacheCurrentComponent,
|
|
@@ -1158,7 +1150,6 @@ export default defineComponent({
|
|
|
1158
1150
|
hideContextMenu,
|
|
1159
1151
|
getTabTitle,
|
|
1160
1152
|
isClosable,
|
|
1161
|
-
isRefreshing,
|
|
1162
1153
|
isTabCached,
|
|
1163
1154
|
isTabReady,
|
|
1164
1155
|
hasCustomSlot,
|
|
@@ -1175,7 +1166,6 @@ export default defineComponent({
|
|
|
1175
1166
|
handleComponentRef,
|
|
1176
1167
|
getReactiveTabTitle,
|
|
1177
1168
|
getComponentCacheKey,
|
|
1178
|
-
getRefreshComponentKey,
|
|
1179
1169
|
createNamedComponent,
|
|
1180
1170
|
ensureNamedComponent,
|
|
1181
1171
|
getNamedComponent,
|
|
@@ -507,7 +507,20 @@ export function createRouterTabs(
|
|
|
507
507
|
const target = snapshot?.active ?? records[records.length - 1]?.to ?? options.defaultRoute
|
|
508
508
|
if (target) {
|
|
509
509
|
try {
|
|
510
|
-
|
|
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)
|
|
511
524
|
} catch (error) {
|
|
512
525
|
console.warn('[RouterTabs] Failed to navigate to restored route', target, error)
|
|
513
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,18 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
98
98
|
} = options
|
|
99
99
|
|
|
100
100
|
const controller = useRouterTabs({ optional: true })
|
|
101
|
+
// Start as `true` so pages won't mount until hydration decides what to do.
|
|
102
|
+
// This prevents double-mount / double-fetch on initial load when restoring tabs.
|
|
101
103
|
const hydrating = ref(true)
|
|
102
104
|
|
|
103
|
-
const setup = (ctrl: NonNullable<typeof controller
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
const setup = (ctrl: NonNullable<typeof controller>, mode: 'hook' | 'immediate' = 'hook') => {
|
|
106
|
+
const run = async () => {
|
|
107
|
+
hydrating.value = true
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
try {
|
|
110
|
+
const initialSnapshot = deserialize(readCookie(cookieKey))
|
|
111
|
+
|
|
112
|
+
if (initialSnapshot && initialSnapshot.tabs?.length) {
|
|
110
113
|
await ctrl.hydrate(initialSnapshot)
|
|
111
114
|
if (initialSnapshot.active) {
|
|
112
115
|
await nextTick()
|
|
@@ -116,30 +119,30 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
116
119
|
ctrl.current.value = activeTab
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
|
-
}
|
|
120
|
-
hydrating.value = false
|
|
121
|
-
}
|
|
122
|
-
} else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
|
|
123
|
-
try {
|
|
124
|
-
hydrating.value = true
|
|
122
|
+
} else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
|
|
125
123
|
const fallback = options.fallbackRoute ?? ctrl.options.defaultRoute
|
|
126
124
|
await ctrl.reset(fallback)
|
|
127
|
-
} finally {
|
|
128
|
-
hydrating.value = false
|
|
129
125
|
}
|
|
130
|
-
} else {
|
|
131
|
-
hydrating.value = false
|
|
132
|
-
}
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
const snapshot = ctrl.snapshot()
|
|
128
|
+
if (!snapshot.tabs.length) {
|
|
129
|
+
removeCookie(cookieKey, options)
|
|
130
|
+
} else {
|
|
131
|
+
writeCookie(cookieKey, serialize(snapshot), options)
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
hydrating.value = false
|
|
139
135
|
}
|
|
136
|
+
}
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
if (mode === 'immediate') {
|
|
139
|
+
void run()
|
|
140
|
+
} else {
|
|
141
|
+
// Run before the first paint to avoid mounting pages twice during a restore.
|
|
142
|
+
onBeforeMount(() => {
|
|
143
|
+
void run()
|
|
144
|
+
})
|
|
145
|
+
}
|
|
143
146
|
|
|
144
147
|
watch(
|
|
145
148
|
() => ({
|
|
@@ -173,12 +176,14 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
173
176
|
onMounted(() => {
|
|
174
177
|
const lateController = useRouterTabs({ optional: true })
|
|
175
178
|
if (lateController) {
|
|
176
|
-
setup(lateController)
|
|
179
|
+
setup(lateController, 'immediate')
|
|
177
180
|
} else if (import.meta.env?.DEV) {
|
|
178
181
|
console.warn('[RouterTabs] Persistence helper must be used inside <router-tab>.')
|
|
179
182
|
}
|
|
180
183
|
})
|
|
181
184
|
}
|
|
185
|
+
|
|
186
|
+
return { hydrating: hydrating as Ref<boolean> }
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
export default useRouterTabsPersistence
|