vue3-router-tab 1.3.5 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +529 -47
- package/dist/vue3-router-tab.css +1 -1
- package/dist/vue3-router-tab.js +621 -572
- package/dist/vue3-router-tab.umd.cjs +1 -1
- package/lib/components/RouterTab.vue +56 -4
- package/lib/core/createRouterTabs.ts +37 -7
- package/lib/core/types.ts +5 -0
- package/lib/scss/index.scss +22 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(w,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],n):(w=typeof globalThis<"u"?globalThis:w||self,n(w["vue3-router-tab"]={},w.Vue,w.VueRouter))})(this,(function(w,n,Ce){"use strict";function Ee(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function x(e,o){const r=e.resolve(o);if(!r||!r.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return r}const Pe={path:e=>e.path,fullpath:e=>e.fullPath,fullname:e=>e.fullPath,full:e=>e.fullPath,name:e=>e.name?String(e.name):e.fullPath};function M(e){const o=e.meta?.key;if(typeof o=="function"){const r=o(e);if(typeof r=="string"&&r.length)return r}else if(typeof o=="string"&&o.length){const r=Pe[o.toLowerCase()];return r?r(e):o}return e.fullPath}function G(e,o){const r=e.meta?.keepAlive;return typeof r=="boolean"?r:o}function X(e,o){const r=e.meta?.reuse;return typeof r=="boolean"?r:o}function se(e){const o=e.meta??{},r={};return"title"in o&&(r.title=o.title),"tips"in o&&(r.tips=o.tips),"icon"in o&&(r.icon=o.icon),"closable"in o&&(r.closable=o.closable),"tabClass"in o&&(r.tabClass=o.tabClass),"target"in o&&(r.target=o.target),"href"in o&&(r.href=o.href),r}function _(e,o,r){const s=se(e);return{id:M(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:G(e,r),reusable:X(e,!1),closable:s.closable??!0,renderKey:typeof o.renderKey=="number"?o.renderKey:0,...s,...o}}function Q(e,o,r,s){if(!e.find(g=>g.id===o.id)){if(r==="next"&&s){const g=e.findIndex(v=>v.id===s);if(g!==-1){e.splice(g+1,0,o);return}}e.push(o)}}function ce(e,o,r){if(!o||o<=0)return;const s=e.filter(i=>i.alive);for(;s.length>o;){const i=s.shift();if(!i||i.id===r)continue;const g=e.findIndex(v=>v.id===i.id);g>-1&&(e[g].alive=!1)}}function Be(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Ae(e){const o={};return"title"in e&&(o.title=e.title),"tips"in e&&(o.tips=e.tips),"icon"in e&&(o.icon=e.icon),"tabClass"in e&&(o.tabClass=e.tabClass),"closable"in e&&(o.closable=e.closable),"renderKey"in e&&typeof e.renderKey=="number"&&(o.renderKey=e.renderKey),o}function Ie(e,o={}){const r=Ee(o),s=n.reactive([]),i=n.ref(null),g=n.shallowRef(),v=n.ref(null),c=n.computed(()=>s.filter(l=>l.alive).map(l=>`${l.id}::${l.renderKey??0}`));let f=!1;function y(l){const m=typeof l.matched=="object"?l:x(e,l);return{key:M(m),fullPath:m.fullPath,alive:G(m,r.keepAlive),reusable:X(m,!1),matched:m}}function E(l){const m=M(l);let b=s.find(k=>k.id===m);return b?(b.fullPath=l.fullPath,b.to=l.fullPath,b.matched=l,b.alive=G(l,r.keepAlive),b.reusable=X(l,b.reusable),typeof b.renderKey!="number"&&(b.renderKey=0),Object.assign(b,se(l)),b):(b=_(l,{},r.keepAlive),Q(s,b,r.appendPosition,i.value),ce(s,r.maxAlive,i.value),b)}async function A(l,m=!1,b=!0){const k=x(e,l),R=M(k),P=i.value===R;b==="sameTab"&&(b=P),b&&await L(R,!0),await e[m?"replace":"push"](k),P&&await T()}function S(l){const m=s.findIndex($=>$.id===l);if(m===-1)return r.defaultRoute;const b=s[m+1],k=s[m-1],R=s.find($=>$.id!==l),P=b||k||R;return P?P.to:r.defaultRoute}async function O(l=i.value,m={}){if(!l)return;if(!m.force&&r.keepLastTab&&s.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const k=i.value===l&&m.redirect!==null,R=k?m.redirect??S(l):null;await I(l,{force:m.force}),m.redirect!==null&&k&&R&&await e.replace(R)}async function I(l,m={}){const b=s.findIndex(k=>k.id===l);b!==-1&&(s.splice(b,1),v.value===l&&(v.value=null),i.value===l&&(i.value=null,g.value=void 0))}async function L(l=i.value??void 0,m=!1){if(!l)return;const b=s.find(R=>R.id===l);if(!b)return;const k=r.keepAlive&&b.alive;k&&(b.alive=!1,await n.nextTick()),b.renderKey=(b.renderKey??0)+1,k&&(b.alive=!0),v.value=l,await n.nextTick(),m||await n.nextTick(),v.value=null}async function oe(l=!1){for(const m of s)await L(m.id,l)}async function re(l=r.defaultRoute){s.splice(0,s.length),i.value=null,g.value=void 0;for(const m of r.initialTabs){const b=x(e,m.to),k=_(b,m,r.keepAlive);s.push(k)}await e.replace(l)}async function T(){const l=i.value;l&&await L(l,!0)}function J(l){return typeof l.matched=="object"?M(l):M(x(e,l))}function D(){const l=s.find(m=>m.id===i.value);return{tabs:s.map(Be),active:l?l.to:null}}async function K(l){f=!0,s.splice(0,s.length),i.value=null,g.value=void 0;const m=l?.tabs??[];for(const k of m)try{const R=x(e,k.to),P=Ae(k),$=_(R,P,r.keepAlive);Q(s,$,"last",null)}catch{}f=!1;const b=l?.active??m[m.length-1]?.to??r.defaultRoute;if(b)try{await e.replace(b)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(f)return;const m=E(l);i.value=m.id,g.value=m,ce(s,r.maxAlive,i.value)},{immediate:!0}),r.initialTabs.length&&r.initialTabs.forEach(l=>{const m=x(e,l.to),b=_(m,l,r.keepAlive);Q(s,b,"last",null)}),{options:r,tabs:s,activeId:i,current:g,includeKeys:c,refreshingKey:v,openTab:A,closeTab:O,removeTab:I,refreshTab:L,refreshAll:oe,reset:re,reload:T,getRouteKey:J,matchRoute:y,snapshot:D,hydrate:K}}function ue(e){return e?typeof e=="string"?{name:e}:e:{}}const j=Symbol("RouterTabsContext"),Y="router-tabs:snapshot";function Z(e={}){const{optional:o=!1}=e,r=n.inject(j,null);if(r)return r;const s=n.inject("$tabs",null);if(s)return s;const g=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(g)return g;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const $e=864e5;function Ke(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,r=document.cookie?document.cookie.split("; "):[];for(const s of r)if(s.startsWith(o))return decodeURIComponent(s.slice(o.length));return null}function de(e,o,r){if(typeof document>"u")return;const{expiresInDays:s=7,path:i="/",domain:g,secure:v,sameSite:c="lax"}=r,f=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(s!==1/0){const y=new Date(Date.now()+s*$e).toUTCString();f.push(`Expires=${y}`)}i&&f.push(`Path=${i}`),g&&f.push(`Domain=${g}`),v&&f.push("Secure"),c&&f.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=f.join("; ")}function fe(e,o){if(typeof document>"u")return;const{path:r="/",domain:s}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),r&&i.push(`Path=${r}`),s&&i.push(`Domain=${s}`),document.cookie=i.join("; ")}const Se=e=>JSON.stringify(e??null),De=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ee(e={}){const{cookieKey:o=Y,serialize:r=Se,deserialize:s=De}=e,i=Z({optional:!0}),g=n.ref(!0),v=c=>{n.onMounted(async()=>{const f=s(Ke(o));if(f&&f.tabs?.length)try{if(g.value=!0,await c.hydrate(f),f.active){await n.nextTick();const E=c.tabs.find(A=>A.to===f.active);E&&(c.activeId.value=E.id,c.current.value=E)}}finally{g.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{g.value=!0;const E=e.fallbackRoute??c.options.defaultRoute;await c.reset(E)}finally{g.value=!1}else g.value=!1;const y=c.snapshot();y.tabs.length?de(o,r(y),e):fe(o,e),g.value=!1}),n.watch(()=>({tabs:c.tabs.map(f=>({to:f.to,title:f.title,tips:f.tips,icon:f.icon,tabClass:f.tabClass,closable:f.closable,renderKey:f.renderKey})),active:c.activeId.value}),()=>{if(g.value)return;const f=c.snapshot();f.tabs.length?de(o,r(f),e):fe(o,e)},{deep:!0})};i?v(i):n.onMounted(()=>{const c=Z({optional:!0});c&&v(c)})}const xe=n.defineComponent({name:"RouterTab",components:{RouterView:Ce.RouterView},props:{tabs:{type:Array,default:()=>[]},keepAlive:{type:Boolean,default:!0},maxAlive:{type:Number,default:0},keepLastTab:{type:Boolean,default:!0},append:{type:String,default:"last"},defaultPage:{type:[String,Object],default:"/"},tabTransition:{type:[String,Object],default:"router-tab-zoom"},pageTransition:{type:[String,Object],default:()=>({name:"router-tab-swap",mode:"out-in"})},contextmenu:{type:[Boolean,Array],default:!0},cookieKey:{type:String,default:Y},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:o}){const r=n.getCurrentInstance();if(!r)throw new Error("[RouterTab] component must be used within a Vue application context.");const s=r.appContext.app.config.globalProperties.$router;if(!s)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Ie(s,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(j,i),r.appContext.config.globalProperties.$tabs=i;const g=n.computed(()=>!!r?.slots?.default),v=n.computed(()=>!!r?.slots?.start),c=n.computed(()=>!!r?.slots?.end),f=n.ref(0),y=n.computed(()=>{f.value;const t={};return i.tabs.forEach(a=>{const u=typeof a.title=="string"?a.title:String(a.title||ae(a));t[a.id]=u}),t});function E(){f.value++}const A=new Map,S=new Map;function O(t,a){if(!(!a||A.has(t)))try{A.set(t,a);const u=i.tabs.find(p=>i.getRouteKey(p.to)===t);if(!u){console.warn(`[RouterTab] Cannot setup watching: tab not found for ${t}`);return}const h=[];if(a.routeTabTitle!==void 0)try{const p=n.watch(()=>{const d=a.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const V=String(d);u.title=V,E()}},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(a.routeTabIcon!==void 0)try{const p=n.watch(()=>{const d=a.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(u.icon=String(d),E())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(a.routeTabClosable!==void 0)try{const p=n.watch(()=>{const d=a.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(u.closable=!!d,E())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(a.routeTabMeta!==void 0)try{const p=n.watch(()=>{const d=a.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(u,d),E())},{immediate:!0,deep:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}S.set(t,h)}catch(u){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,u),I(t)}}function I(t){try{const a=S.get(t);a&&(a.forEach(u=>{try{u()}catch(h){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,h)}}),S.delete(t)),A.delete(t)}catch(a){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,a)}}function L(t,a){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?O(a,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&O(a,t.$):t===null&&I(a)}catch(u){console.error(`[RouterTab] Error handling component ref for ${a}:`,u),I(a)}}if(n.onErrorCaptured((t,a,u)=>{if(console.error("[RouterTab] Error captured from component:",t,u),a&&i.activeId.value){const h=i.activeId.value;I(h);const p=i.tabs.find(d=>d.id===h);p&&p.alive&&(console.warn(`[RouterTab] Removing errored component ${h} from KeepAlive cache`),p.alive=!1,n.nextTick(()=>{p.alive=!0}))}return!1}),e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||Y:t.cookieKey||(t.cookieKey=Y),ee(t)}const oe=n.computed(()=>ue(e.tabTransition)),re=n.computed(()=>ue(e.pageTransition)),T=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),J=n.ref(null),D=n.ref([]),K=n.ref(-1),l=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),m=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function b(t){return i.tabs.findIndex(a=>a.id===t)}function k(t){const a=b(t.id);return a>0?i.tabs.slice(0,a):[]}function R(t){const a=b(t.id);return a>-1?i.tabs.slice(a+1):[]}function P(t){return i.tabs.filter(a=>a.id!==t.id)}async function $(t,a){const u=t.filter(h=>h.closable!==!1);if(u.length){for(const h of u)i.activeId.value===h.id?await i.closeTab(h.id,{redirect:a.to,force:!0}):await i.removeTab(h.id,{force:!0});i.activeId.value!==a.id&&await i.openTab(a.to,!0,!1)}}const tt={refresh:{label:"Refresh",handler:async({target:t})=>{await i.refreshTab(t.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:t})=>{await i.closeTab(t.id)},enable:({target:t})=>ie(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await $(k(t),t)},enable:({target:t})=>k(t).some(a=>a.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await $(R(t),t)},enable:({target:t})=>R(t).some(a=>a.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await $(P(t),t)},enable:({target:t})=>P(t).some(a=>a.closable!==!1)}};function B(){T.visible=!1,T.target=null,K.value=-1,D.value=[]}function nt(t,a){e.contextmenu&&(T.target=t,T.position.x=a.clientX,T.position.y=a.clientY,n.nextTick(()=>{T.visible=!0,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{rt()})}))}function ot(t,a){const u=typeof t=="string"?{id:t}:t,h=tt[u.id],p=u.label??h?.label??String(u.id),d=u.visible??h?.visible??!0;if(!(typeof d=="function"?d(a):d!==!1))return null;const le=u.enable??h?.enable??!0,vt=typeof le=="function"?le(a):le!==!1,Re=u.handler??h?.handler;if(!Re)return null;const wt=async()=>{await Promise.resolve(Re(a))};return{id:String(u.id),label:p,disabled:!vt,action:wt}}const U=n.computed(()=>{if(!T.visible||!T.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:m,a={target:T.target,controller:i};return t.map(u=>ot(u,a)).filter(u=>!!u)});function rt(){const t=J.value;if(!t)return;const a=8,{innerWidth:u,innerHeight:h}=window,p=t.getBoundingClientRect();let d=T.position.x,V=T.position.y;p.right>u-a&&(d=Math.max(a,u-p.width-a)),p.bottom>h-a&&(V=Math.max(a,h-p.height-a)),(d!==T.position.x||V!==T.position.y)&&(T.position.x=d,T.position.y=V)}function at(t,a){D.value[a]=t??null}function it(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function q(t,a,u=U.value){if(!u.length)return-1;const h=u.length;let p=t;for(let d=0;d<h;d++)if(p=(p+a+h)%h,!u[p].disabled)return p;return-1}function z(t){K.value=t,!(t<0)&&n.nextTick(()=>it(t))}function Te(t){const a=q(K.value,t);a!==-1&&z(a)}function lt(t){if(!T.visible)return;const a=t.key,u=U.value;if(!u.length)return;if(a==="Tab"){B();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(a))switch(t.preventDefault(),a){case"ArrowDown":case"ArrowRight":Te(1);break;case"ArrowUp":case"ArrowLeft":Te(-1);break;case"Home":z(q(-1,1));break;case"End":z(q(u.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=K.value;if(p>-1){const d=u[p];d.disabled||ke(d)}break}case"Escape":B();break}}async function ke(t){t.disabled||(B(),await t.action())}function ae(t){return typeof t.title=="string"&&t.title.trim()?t.title:Array.isArray(t.title)&&t.title.length&&String(t.title[0]).trim()?String(t.title[0]):"Untitled"}function st(t){return y.value[t.id]||ae(t)}function ve(t){const a=i.getRouteKey(t),u=i.tabs.find(p=>p.id===a);if(!u)return console.warn("[RouterTab] Tab not found for route:",a),`${a}::0`;const h=u.renderKey??0;return`${a}::${h}`}function ct(t){return`${ve(t)}::refresh`}function ie(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function ut(t){await i.closeTab(t.id)}function dt(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}i.activeId.value!==t.id&&i.openTab(t.to,!1)}function ft(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":ie(t),"is-dragging":l.dragging&&l.dragTab?.id===t.id,"is-drag-over":l.dropIndex===b(t.id)},t.tabClass]}function bt(t){return i.refreshingKey.value===i.getRouteKey(t)}function mt(t){const a=i.getRouteKey(t);return i.tabs.some(u=>u.id===a)}function pt(t,a,u){e.sortable&&(l.dragging=!0,l.dragIndex=a,l.dragTab=t,u.dataTransfer&&(u.dataTransfer.effectAllowed="move",u.dataTransfer.setData("text/plain",t.id)),o("tab-sort",{tab:t,index:a}))}function gt(t,a){!e.sortable||!l.dragging||(a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="move"))}function ht(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function yt(){!e.sortable||l.dragging}function Tt(t,a){if(!(!e.sortable||!l.dragging)){if(a.preventDefault(),l.dragIndex!==-1&&l.dragIndex!==t){const u=i.tabs.splice(l.dragIndex,1)[0];i.tabs.splice(t,0,u),o("tab-sorted",{tab:u,fromIndex:l.dragIndex,toIndex:t})}we()}}function we(){l.dragging=!1,l.dragIndex=-1,l.dropIndex=-1,l.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",B)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",B),r.appContext.config.globalProperties.$tabs=null,S.forEach(t=>{t.forEach(a=>{try{a()}catch(u){console.error("[RouterTab] Error during cleanup:",u)}})}),S.clear(),A.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,()=>B()),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(u=>u.id));Array.from(A.keys()).forEach(u=>{t.has(u)||(console.log(`[RouterTab] Cleaning up stale component instance: ${u}`),I(u))})}),n.watch(()=>e.contextmenu,t=>{t||B()}),n.watch(()=>U.value.length,t=>{T.visible&&t===0&&B()},{flush:"post"}),n.watch(U,t=>{if(!T.visible)return;D.value=new Array(t.length).fill(null);const a=q(-1,1,t);z(a)},{flush:"post"}),n.watch(()=>T.visible,t=>{t||(K.value=-1,D.value=[])});const kt=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:kt,tabTransitionProps:oe,pageTransitionProps:re,buildTabClass:ft,activate:dt,close:ut,context:T,menuItems:U,handleMenuAction:ke,showContextMenu:nt,hideContextMenu:B,getTabTitle:ae,isClosable:ie,isRefreshing:bt,isTabReady:mt,hasCustomSlot:g,hasStartSlot:v,hasEndSlot:c,onDragStart:pt,onDragOver:gt,onDragEnter:ht,onDragLeave:yt,onDrop:Tt,onDragEnd:we,setupComponentWatching:O,cleanupComponentWatching:I,handleComponentRef:L,getReactiveTabTitle:st,getComponentCacheKey:ve,getRefreshComponentKey:ct,triggerTabUpdate:E,menuRef:J,highlightedIndex:K,setMenuItemRef:at,onMenuKeydown:lt,highlightMenuIndex:z}}}),Me=(e,o)=>{const r=e.__vccOpts||e;for(const[s,i]of o)r[s]=i;return r},Le={class:"router-tab"},Ve={class:"router-tab__header"},je={class:"router-tab__scroll"},Ne=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Oe=["title"],Ue=["onClick"],ze={class:"router-tab__container"},_e=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Ye(e,o,r,s,i,g){const v=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Le,[n.createElementVNode("header",Ve,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",je,[n.createVNode(n.TransitionGroup,n.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:n.withCtx(()=>[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.tabs,(c,f)=>(n.openBlock(),n.createElementBlock("li",{key:c.id,class:n.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:y=>e.activate(c),onAuxclick:n.withModifiers(y=>e.close(c),["middle","prevent"]),onContextmenu:n.withModifiers(y=>e.showContextMenu(c,y),["prevent"]),onDragstart:y=>e.onDragStart(c,f,y),onDragover:y=>e.onDragOver(f,y),onDragenter:y=>e.onDragEnter(f),onDragleave:o[0]||(o[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(f,y),onDragend:o[1]||(o[1]=(...y)=>e.onDragEnd&&e.onDragEnd(...y))},[c.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},n.toDisplayString(e.getReactiveTabTitle(c)),9,Oe),e.isClosable(c)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(y=>e.close(c),["stop"])},null,8,Ue)):n.createCommentVNode("",!0)],42,Ne))),128))]),_:1},16)]),n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-end",{"has-content":e.hasEndSlot}])},[n.renderSlot(e.$slots,"end")],2)]),n.createElementVNode("div",ze,[n.createVNode(v,null,{default:n.withCtx(c=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{...c,controller:e.controller,pageRef:f=>e.handleComponentRef(f,e.controller.getRouteKey(c.route))}))):(n.openBlock(),n.createBlock(n.Transition,n.mergeProps({key:1},e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[e.isRefreshing(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getRefreshComponentKey(c.route),ref:f=>e.handleComponentRef(f,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:1,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isTabReady(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getComponentCacheKey(c.route),ref:f=>e.handleComponentRef(f,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)],1032,["include","max"])):e.isTabReady(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getComponentCacheKey(c.route),ref:f=>e.handleComponentRef(f,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)]),_:2},1040))]),_:3})]),n.withDirectives(n.createElementVNode("div",{ref:"menuRef",class:"router-tab__contextmenu",role:"menu",onKeydown:o[2]||(o[2]=(...c)=>e.onMenuKeydown&&e.onMenuKeydown(...c)),style:n.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.menuItems,(c,f)=>(n.openBlock(),n.createElementBlock("a",{key:c.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":f===e.highlightedIndex}]),"aria-disabled":c.disabled,disabled:c.disabled,tabindex:c.disabled?-1:f===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,f),onMouseenter:y=>!c.disabled&&e.highlightMenuIndex(f),onClick:y=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,_e))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const te=Me(xe,[["render",Ye]]),Fe={class:"router-tabs","aria-hidden":"true"},F=n.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return ee(e),(r,s)=>(n.openBlock(),n.createElementBlock("span",Fe))}}),be="tab-theme-style",me="tab-theme-primary-color",He="system",pe="(prefers-color-scheme: dark)";let N=null;const C={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},We={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function Je(e){if(typeof window>"u")return null;const o=window.localStorage.getItem(e);if(!o)return null;try{const r=JSON.parse(o);return r&&typeof r=="object"?r:null}catch{return null}}function ne(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??C.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??C.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??C.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??C.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??C.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??C.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??C.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??C.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??C.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??C.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??C.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??C.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??C.iconColor))}function ge(e){if(typeof document>"u")return;const o=document.documentElement,r=window.matchMedia(pe),s=()=>{o.dataset.theme=r.matches?"dark":"light"};N&&(r.removeEventListener("change",N),N=null),e==="system"?(s(),N=()=>s(),r.addEventListener("change",N)):o.dataset.theme=e}function he(e={}){if(typeof window>"u")return;const{styleKey:o=be,primaryKey:r=me,defaultStyle:s=He,defaultPrimary:i}=e,g=window.localStorage.getItem(o)??s;ge(g);const c=g==="dark"||g==="system"&&window.matchMedia(pe).matches?{...We}:{...C};i&&(c.primary=i);const f=Je(r);ne(f?{...c,...f}:c)}function qe(e,o){if(typeof window>"u")return;const r=o?.styleKey??be;window.localStorage.setItem(r,e),ge(e)}function Ge(e,o){if(typeof window>"u")return;const r=o?.primaryKey??me;window.localStorage.setItem(r,JSON.stringify(e)),ne(e)}function H(e,o){if(n.isRef(e)){const s=!n.isReadonly(e);return{value:e,update:s?i=>{e.value=i}:()=>{}}}if(typeof e=="function"){const s=e;return{value:n.computed(s),update:()=>{}}}const r=n.ref(e===void 0?o:e);return{value:r,update:s=>{r.value=s}}}function W(e={}){const o=H(e.title,"Untitled"),r=H(e.icon,""),s=H(e.closable,!0),i=H(e.meta,{});return{routeTabTitle:o.value,routeTabIcon:r.value,routeTabClosable:s.value,routeTabMeta:i.value,updateTitle:o.update,updateIcon:r.update,updateClosable:s.update,updateMeta:i.update}}function Xe(e,o="Page"){return W({title:n.computed(()=>e.value?"Loading...":o),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function Qe(e,o="Page",r="mdi-page"){return W({title:n.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:n.computed(()=>e.value>0?"mdi-bell-badge":r)})}function Ze(e,o="Page"){const r={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return W({title:n.computed(()=>o+r[e.value].suffix),icon:n.computed(()=>r[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let ye=!1;const et={install(e,o){if(ye)return;ye=!0;const{initTheme:r=!0,themeOptions:s,componentName:i=te.name||"RouterTab",tabsComponentName:g=F.name||"RouterTabs"}=o??{};r&&he(s??{}),e.component(i,te),e.component(g,F),g.toLowerCase()!=="router-tabs"&&e.component("router-tabs",F),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[j]},set(v){v&&e.provide(j,v)}})}};w.RouterTab=te,w.RouterTabs=F,w.default=et,w.initRouterTabsTheme=he,w.routerTabsKey=j,w.setRouterTabsPrimary=Ge,w.setRouterTabsTheme=qe,w.useLoadingTab=Xe,w.useNotificationTab=Qe,w.useReactiveTab=W,w.useRouterTabs=Z,w.useRouterTabsPersistence=ee,w.useStatusTab=Ze,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
1
|
+
(function(R,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],n):(R=typeof globalThis<"u"?globalThis:R||self,n(R["vue3-router-tab"]={},R.Vue,R.VueRouter))})(this,(function(R,n,Ae){"use strict";function Ke(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function x(e,a){const o=e.resolve(a);if(!o||!o.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return o}const Ie={path:e=>e.path,fullpath:e=>e.fullPath,fullname:e=>e.fullPath,full:e=>e.fullPath,name:e=>e.name?String(e.name):e.fullPath};function M(e){const a=e.meta?.key;if(typeof a=="function"){const o=a(e);if(typeof o=="string"&&o.length)return o}else if(typeof a=="string"&&a.length){const o=Ie[a.toLowerCase()];return o?o(e):a}return e.fullPath}function Q(e,a){const o=e.meta?.keepAlive;return typeof o=="boolean"?o:a}function Z(e,a){const o=e.meta?.reuse;return typeof o=="boolean"?o:a}function be(e){const a=e.meta??{},o={};return"title"in a&&(o.title=a.title),"tips"in a&&(o.tips=a.tips),"icon"in a&&(o.icon=a.icon),"closable"in a&&(o.closable=a.closable),"tabClass"in a&&(o.tabClass=a.tabClass),"target"in a&&(o.target=a.target),"href"in a&&(o.href=a.href),o}function Y(e,a,o){const l=be(e);return{id:M(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:Q(e,o),reusable:Z(e,!1),closable:l.closable??!0,renderKey:typeof a.renderKey=="number"?a.renderKey:0,...l,...a}}function ee(e,a,o,l){if(!e.find(g=>g.id===a.id)){if(o==="next"&&l){const g=e.findIndex(v=>v.id===l);if(g!==-1){e.splice(g+1,0,a);return}}e.push(a)}}function te(e,a,o){if(!a||a<=0)return;const l=e.filter(i=>i.alive);for(;l.length>a;){const i=l.shift();if(!i||i.id===o)continue;const g=e.findIndex(v=>v.id===i.id);g>-1&&(e[g].alive=!1)}}function $e(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable,renderKey:e.renderKey}}function Se(e){const a={};return"title"in e&&(a.title=e.title),"tips"in e&&(a.tips=e.tips),"icon"in e&&(a.icon=e.icon),"tabClass"in e&&(a.tabClass=e.tabClass),"closable"in e&&(a.closable=e.closable),"renderKey"in e&&typeof e.renderKey=="number"&&(a.renderKey=e.renderKey),a}function De(e,a={}){const o=Ke(a),l=n.reactive([]),i=n.ref(null),g=n.shallowRef(),v=n.ref(null),c=n.computed(()=>l.filter(s=>s.alive).map(s=>`${s.id}::${s.renderKey??0}`));let d=!1;function y(s){const b=typeof s.matched=="object"?s:x(e,s);return{key:M(b),fullPath:b.fullPath,alive:Q(b,o.keepAlive),reusable:Z(b,!1),matched:b}}function P(s){const b=M(s);let m=l.find(T=>T.id===b);return m?(m.fullPath=s.fullPath,m.to=s.fullPath,m.matched=s,m.alive=Q(s,o.keepAlive),m.reusable=Z(s,m.reusable),typeof m.renderKey!="number"&&(m.renderKey=0),Object.assign(m,be(s)),m):(m=Y(s,{},o.keepAlive),ee(l,m,o.appendPosition,i.value),te(l,o.maxAlive,i.value),m)}async function A(s,b=!1,m=!0){const T=x(e,s),C=M(T),I=i.value===C;m==="sameTab"&&(m=I),m&&await L(C,!0),await e[b?"replace":"push"](T),I&&await U()}function S(s){const b=l.findIndex(E=>E.id===s);if(b===-1)return o.defaultRoute;const m=l[b+1],T=l[b-1],C=l.find(E=>E.id!==s),I=m||T||C;return I?I.to:o.defaultRoute}async function N(s=i.value,b={}){if(!s)return;if(!b.force&&o.keepLastTab&&l.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const T=i.value===s&&b.redirect!==null,C=T?b.redirect??S(s):null;await K(s,{force:b.force}),b.redirect!==null&&T&&C&&await e.replace(C)}async function K(s,b={}){const m=l.findIndex(T=>T.id===s);m!==-1&&(l.splice(m,1),v.value===s&&(v.value=null),i.value===s&&(i.value=null,g.value=void 0))}async function L(s=i.value??void 0,b=!1){if(!s)return;const m=l.find(C=>C.id===s);if(!m)return;const T=o.keepAlive&&m.alive;T&&(m.alive=!1,await n.nextTick()),m.renderKey=(m.renderKey??0)+1,T&&(m.alive=!0),v.value=s,await n.nextTick(),b||await n.nextTick(),v.value=null}async function ie(s=!1){for(const b of l)await L(b.id,s)}function le(s,b){const m=l.find(T=>T.id===s);m&&(m.alive=!!b,m.alive&&te(l,o.maxAlive,i.value))}function k(s){const b=l.find(m=>m.id===s);b&&(b.alive&&(b.alive=!1),b.renderKey=(b.renderKey??0)+1)}function G(){l.forEach(s=>{s.alive=!1})}function D(){return c.value.slice()}async function $(s=o.defaultRoute){l.splice(0,l.length),i.value=null,g.value=void 0;for(const b of o.initialTabs){const m=x(e,b.to),T=Y(m,b,o.keepAlive);l.push(T)}await e.replace(s)}async function U(){const s=i.value;s&&await L(s,!0)}function z(s){return typeof s.matched=="object"?M(s):M(x(e,s))}function w(){const s=l.find(b=>b.id===i.value);return{tabs:l.map($e),active:s?s.to:null}}async function se(s){d=!0,l.splice(0,l.length),i.value=null,g.value=void 0;const b=s?.tabs??[];for(const T of b)try{const C=x(e,T.to),I=Se(T),E=Y(C,I,o.keepAlive);ee(l,E,"last",null)}catch(C){console.warn("[RouterTabs] Failed to restore tab",T,C)}d=!1;const m=s?.active??b[b.length-1]?.to??o.defaultRoute;if(m)try{await e.replace(m)}catch(T){console.warn("[RouterTabs] Failed to navigate to restored route",m,T)}}return n.watch(()=>e.currentRoute.value,s=>{if(d)return;const b=P(s);i.value=b.id,g.value=b,te(l,o.maxAlive,i.value)},{immediate:!0}),o.initialTabs.length&&o.initialTabs.forEach(s=>{const b=x(e,s.to),m=Y(b,s,o.keepAlive);ee(l,m,"last",null)}),{options:o,tabs:l,activeId:i,current:g,includeKeys:c,refreshingKey:v,openTab:A,closeTab:N,removeTab:K,refreshTab:L,refreshAll:ie,setTabAlive:le,evictCache:k,clearCache:G,getCacheKeys:D,reset:$,reload:U,getRouteKey:z,matchRoute:y,snapshot:w,hydrate:se,ensureTab:P}}function me(e){return e?typeof e=="string"?{name:e}:e:{}}const O=Symbol("RouterTabsContext"),H="router-tabs:snapshot";function ne(e={}){const{optional:a=!1}=e,o=n.inject(O,null);if(o)return o;const l=n.inject("$tabs",null);if(l)return l;const g=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(g)return g;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const xe=864e5;function Me(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,o=document.cookie?document.cookie.split("; "):[];for(const l of o)if(l.startsWith(a))return decodeURIComponent(l.slice(a.length));return null}function pe(e,a,o){if(typeof document>"u")return;const{expiresInDays:l=7,path:i="/",domain:g,secure:v,sameSite:c="lax"}=o,d=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(l!==1/0){const y=new Date(Date.now()+l*xe).toUTCString();d.push(`Expires=${y}`)}i&&d.push(`Path=${i}`),g&&d.push(`Domain=${g}`),v&&d.push("Secure"),c&&d.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=d.join("; ")}function ge(e,a){if(typeof document>"u")return;const{path:o="/",domain:l}=a,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),o&&i.push(`Path=${o}`),l&&i.push(`Domain=${l}`),document.cookie=i.join("; ")}const Le=e=>JSON.stringify(e??null),Ve=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function oe(e={}){const{cookieKey:a=H,serialize:o=Le,deserialize:l=Ve}=e,i=ne({optional:!0}),g=n.ref(!0),v=c=>{n.onMounted(async()=>{const d=l(Me(a));if(d&&d.tabs?.length)try{if(g.value=!0,await c.hydrate(d),d.active){await n.nextTick();const P=c.tabs.find(A=>A.to===d.active);P&&(c.activeId.value=P.id,c.current.value=P)}}finally{g.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{g.value=!0;const P=e.fallbackRoute??c.options.defaultRoute;await c.reset(P)}finally{g.value=!1}else g.value=!1;const y=c.snapshot();y.tabs.length?pe(a,o(y),e):ge(a,e),g.value=!1}),n.watch(()=>({tabs:c.tabs.map(d=>({to:d.to,title:d.title,tips:d.tips,icon:d.icon,tabClass:d.tabClass,closable:d.closable,renderKey:d.renderKey})),active:c.activeId.value}),()=>{if(g.value)return;const d=c.snapshot();d.tabs.length?pe(a,o(d),e):ge(a,e)},{deep:!0})};i?v(i):n.onMounted(()=>{const c=ne({optional:!0});c&&v(c)})}const Oe=n.defineComponent({name:"RouterTab",components:{RouterView:Ae.RouterView},props:{tabs:{type:Array,default:()=>[]},keepAlive:{type:Boolean,default:!0},maxAlive:{type:Number,default:0},keepLastTab:{type:Boolean,default:!0},append:{type:String,default:"last"},defaultPage:{type:[String,Object],default:"/"},tabTransition:{type:[String,Object],default:"router-tab-zoom"},pageTransition:{type:[String,Object],default:()=>({name:"router-tab-swap",mode:"out-in"})},contextmenu:{type:[Boolean,Array],default:!0},cookieKey:{type:String,default:H},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:a}){const o=n.getCurrentInstance();if(!o)throw new Error("[RouterTab] component must be used within a Vue application context.");const l=o.appContext.app.config.globalProperties.$router;if(!l)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=De(l,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(O,i),o.appContext.config.globalProperties.$tabs=i;const g=n.computed(()=>!!o?.slots?.default),v=n.computed(()=>!!o?.slots?.start),c=n.computed(()=>!!o?.slots?.end),d=n.ref(0),y=n.computed(()=>{d.value;const t={};return i.tabs.forEach(r=>{const u=typeof r.title=="string"?r.title:String(r.title||ce(r));t[r.id]=u}),t});function P(){d.value++}const A=new Map,S=new Map;function N(t,r){if(!(!r||A.has(t)))try{A.set(t,r);const u=i.tabs.find(p=>i.getRouteKey(p.to)===t);if(!u){console.warn(`[RouterTab] Cannot setup watching: tab not found for ${t}`);return}const h=[];if(r.routeTabTitle!==void 0)try{const p=n.watch(()=>{const f=r.routeTabTitle;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{if(f!=null){const V=String(f);u.title=V,P()}},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabTitle for ${t}:`,p)}if(r.routeTabIcon!==void 0)try{const p=n.watch(()=>{const f=r.routeTabIcon;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(u.icon=String(f),P())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabIcon for ${t}:`,p)}if(r.routeTabClosable!==void 0)try{const p=n.watch(()=>{const f=r.routeTabClosable;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f!=null&&(u.closable=!!f,P())},{immediate:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabClosable for ${t}:`,p)}if(r.routeTabMeta!==void 0)try{const p=n.watch(()=>{const f=r.routeTabMeta;return f&&typeof f=="object"&&"value"in f?f.value:f},f=>{f&&typeof f=="object"&&(Object.assign(u,f),P())},{immediate:!0,deep:!0});h.push(p)}catch(p){console.error(`[RouterTab] Error watching routeTabMeta for ${t}:`,p)}S.set(t,h)}catch(u){console.error(`[RouterTab] Error in setupComponentWatching for ${t}:`,u),K(t)}}function K(t){try{const r=S.get(t);r&&(r.forEach(u=>{try{u()}catch(h){console.error(`[RouterTab] Error cleaning up watcher for ${t}:`,h)}}),S.delete(t)),A.delete(t)}catch(r){console.error(`[RouterTab] Error in cleanupComponentWatching for ${t}:`,r)}}function L(t,r){try{t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?N(r,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&N(r,t.$):t===null&&K(r)}catch(u){console.error(`[RouterTab] Error handling component ref for ${r}:`,u),K(r)}}if(n.onErrorCaptured((t,r,u)=>{if(console.error("[RouterTab] Error captured from component:",t,u),r&&i.activeId.value){const h=i.activeId.value;K(h);const p=i.tabs.find(f=>f.id===h);p&&p.alive&&(console.warn(`[RouterTab] Removing errored component ${h} from KeepAlive cache`),p.alive=!1,n.nextTick(()=>{p.alive=!0}))}return!1}),e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||H:t.cookieKey||(t.cookieKey=H),oe(t)}const ie=n.computed(()=>me(e.tabTransition)),le=n.computed(()=>me(e.pageTransition)),k=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),G=n.ref(null),D=n.ref([]),$=n.ref(-1),U=n.ref(null),z=new Map,w=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),se=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function s(t){return i.tabs.findIndex(r=>r.id===t)}function b(t){const r=s(t.id);return r>0?i.tabs.slice(0,r):[]}function m(t){const r=s(t.id);return r>-1?i.tabs.slice(r+1):[]}function T(t){return i.tabs.filter(r=>r.id!==t.id)}async function C(t,r){const u=t.filter(h=>h.closable!==!1);if(u.length){for(const h of u)i.activeId.value===h.id?await i.closeTab(h.id,{redirect:r.to,force:!0}):await i.removeTab(h.id,{force:!0});i.activeId.value!==r.id&&await i.openTab(r.to,!0,!1)}}const I={refresh:{label:"Refresh",handler:async({target:t})=>{await i.refreshTab(t.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:t})=>{await i.closeTab(t.id)},enable:({target:t})=>ue(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await C(b(t),t)},enable:({target:t})=>b(t).some(r=>r.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await C(m(t),t)},enable:({target:t})=>m(t).some(r=>r.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await C(T(t),t)},enable:({target:t})=>T(t).some(r=>r.closable!==!1)}};function E(){k.visible=!1,k.target=null,$.value=-1,D.value=[]}function at(t,r){e.contextmenu&&(k.target=t,k.position.x=r.clientX,k.position.y=r.clientY,n.nextTick(()=>{k.visible=!0,document.addEventListener("click",E,{once:!0}),n.nextTick(()=>{lt()})}))}function it(t,r){const u=typeof t=="string"?{id:t}:t,h=I[u.id],p=u.label??h?.label??String(u.id),f=u.visible??h?.visible??!0;if(!(typeof f=="function"?f(r):f!==!1))return null;const de=u.enable??h?.enable??!0,Et=typeof de=="function"?de(r):de!==!1,Pe=u.handler??h?.handler;if(!Pe)return null;const Bt=async()=>{await Promise.resolve(Pe(r))};return{id:String(u.id),label:p,disabled:!Et,action:Bt}}const _=n.computed(()=>{if(!k.visible||!k.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:se,r={target:k.target,controller:i};return t.map(u=>it(u,r)).filter(u=>!!u)});function lt(){const t=G.value;if(!t)return;const r=8,{innerWidth:u,innerHeight:h}=window,p=t.getBoundingClientRect();let f=k.position.x,V=k.position.y;p.right>u-r&&(f=Math.max(r,u-p.width-r)),p.bottom>h-r&&(V=Math.max(r,h-p.height-r)),(f!==k.position.x||V!==k.position.y)&&(k.position.x=f,k.position.y=V)}function st(t,r){D.value[r]=t??null}function ct(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function X(t,r,u=_.value){if(!u.length)return-1;const h=u.length;let p=t;for(let f=0;f<h;f++)if(p=(p+r+h)%h,!u[p].disabled)return p;return-1}function F(t){$.value=t,!(t<0)&&n.nextTick(()=>ct(t))}function Re(t){const r=X($.value,t);r!==-1&&F(r)}function ut(t){if(!k.visible)return;const r=t.key,u=_.value;if(!u.length)return;if(r==="Tab"){E();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(r))switch(t.preventDefault(),r){case"ArrowDown":case"ArrowRight":Re(1);break;case"ArrowUp":case"ArrowLeft":Re(-1);break;case"Home":F(X(-1,1));break;case"End":F(X(u.length,-1));break;case"Enter":case" ":case"Spacebar":{const p=$.value;if(p>-1){const f=u[p];f.disabled||Ce(f)}break}case"Escape":E();break}}async function Ce(t){t.disabled||(E(),await t.action())}function ce(t){return typeof t.title=="string"&&t.title.trim()?t.title:Array.isArray(t.title)&&t.title.length&&String(t.title[0]).trim()?String(t.title[0]):"Untitled"}function ft(t){return y.value[t.id]||ce(t)}function Ee(t){const r=i.getRouteKey(t),u=i.tabs.find(p=>p.id===r);if(!u)return console.warn("[RouterTab] Tab not found for route:",r),`${r}::0`;const h=u.renderKey??0;return`${r}::${h}`}function dt(t){return`${Ee(t)}::refresh`}function ue(t){return!(t.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function bt(t){await i.closeTab(t.id)}function mt(t,r){r?z.set(t,r):z.delete(t)}function fe(t){n.nextTick(()=>{const r=z.get(t),u=U.value;if(r&&u){const h=r.getBoundingClientRect(),p=u.getBoundingClientRect();(h.left<p.left||h.right>p.right)&&r.scrollIntoView({behavior:"smooth",block:"nearest",inline:"nearest"})}})}function pt(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}i.activeId.value!==t.id&&(i.openTab(t.to,!1),fe(t.id))}function gt(t){return["router-tab__item",{"is-active":i.activeId.value===t.id,"is-closable":ue(t),"is-dragging":w.dragging&&w.dragTab?.id===t.id,"is-drag-over":w.dropIndex===s(t.id)},t.tabClass]}function ht(t){return i.refreshingKey.value===i.getRouteKey(t)}function yt(t){const r=i.getRouteKey(t),u=i.tabs.find(h=>h.id===r);return u?i.options.keepAlive?u.alive:!0:!1}function Tt(t,r,u){e.sortable&&(w.dragging=!0,w.dragIndex=r,w.dragTab=t,u.dataTransfer&&(u.dataTransfer.effectAllowed="move",u.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:r}))}function vt(t,r){!e.sortable||!w.dragging||(r.preventDefault(),r.dataTransfer&&(r.dataTransfer.dropEffect="move"))}function kt(t){!e.sortable||!w.dragging||(w.dropIndex=t)}function wt(){!e.sortable||w.dragging}function Rt(t,r){if(!(!e.sortable||!w.dragging)){if(r.preventDefault(),w.dragIndex!==-1&&w.dragIndex!==t){const u=i.tabs.splice(w.dragIndex,1)[0];i.tabs.splice(t,0,u),a("tab-sorted",{tab:u,fromIndex:w.dragIndex,toIndex:t})}Be()}}function Be(){w.dragging=!1,w.dragIndex=-1,w.dropIndex=-1,w.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",E)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",E),o.appContext.config.globalProperties.$tabs=null,S.forEach(t=>{t.forEach(r=>{try{r()}catch(u){console.error("[RouterTab] Error during cleanup:",u)}})}),S.clear(),A.clear()}),n.watch(()=>e.keepAlive,t=>{i.options.keepAlive=t}),n.watch(()=>i.activeId.value,t=>{t&&fe(t),E()}),n.watch(()=>i.tabs.length,()=>{const t=new Set(i.tabs.map(u=>u.id));Array.from(A.keys()).forEach(u=>{t.has(u)||(console.log(`[RouterTab] Cleaning up stale component instance: ${u}`),K(u))})}),n.watch(()=>e.contextmenu,t=>{t||E()}),n.watch(()=>_.value.length,t=>{k.visible&&t===0&&E()},{flush:"post"}),n.watch(_,t=>{if(!k.visible)return;D.value=new Array(t.length).fill(null);const r=X(-1,1,t);F(r)},{flush:"post"}),n.watch(()=>k.visible,t=>{t||($.value=-1,D.value=[])});const Ct=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:Ct,tabTransitionProps:ie,pageTransitionProps:le,buildTabClass:gt,activate:pt,close:bt,context:k,menuItems:_,handleMenuAction:Ce,showContextMenu:at,hideContextMenu:E,getTabTitle:ce,isClosable:ue,isRefreshing:ht,isTabReady:yt,hasCustomSlot:g,hasStartSlot:v,hasEndSlot:c,onDragStart:Tt,onDragOver:vt,onDragEnter:kt,onDragLeave:wt,onDrop:Rt,onDragEnd:Be,setupComponentWatching:N,cleanupComponentWatching:K,handleComponentRef:L,getReactiveTabTitle:ft,getComponentCacheKey:Ee,getRefreshComponentKey:dt,triggerTabUpdate:P,menuRef:G,highlightedIndex:$,setMenuItemRef:st,onMenuKeydown:ut,highlightMenuIndex:F,scrollContainer:U,setTabRef:mt,scrollTabIntoView:fe}}}),je=(e,a)=>{const o=e.__vccOpts||e;for(const[l,i]of a)o[l]=i;return o},Ne={class:"router-tab"},Ue={class:"router-tab__header"},ze={class:"router-tab__scroll",ref:"scrollContainer"},_e=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Fe=["title"],Ye=["onClick"],He={class:"router-tab__container"},We=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function Je(e,a,o,l,i,g){const v=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Ne,[n.createElementVNode("header",Ue,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",ze,[n.createVNode(n.TransitionGroup,n.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:n.withCtx(()=>[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.tabs,(c,d)=>(n.openBlock(),n.createElementBlock("li",{key:c.id,class:n.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,ref_for:!0,ref:y=>e.setTabRef(c.id,y),onClick:y=>e.activate(c),onAuxclick:n.withModifiers(y=>e.close(c),["middle","prevent"]),onContextmenu:n.withModifiers(y=>e.showContextMenu(c,y),["prevent"]),onDragstart:y=>e.onDragStart(c,d,y),onDragover:y=>e.onDragOver(d,y),onDragenter:y=>e.onDragEnter(d),onDragleave:a[0]||(a[0]=(...y)=>e.onDragLeave&&e.onDragLeave(...y)),onDrop:y=>e.onDrop(d,y),onDragend:a[1]||(a[1]=(...y)=>e.onDragEnd&&e.onDragEnd(...y))},[c.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},n.toDisplayString(e.getReactiveTabTitle(c)),9,Fe),e.isClosable(c)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(y=>e.close(c),["stop"])},null,8,Ye)):n.createCommentVNode("",!0)],42,_e))),128))]),_:1},16)],512),n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-end",{"has-content":e.hasEndSlot}])},[n.renderSlot(e.$slots,"end")],2)]),n.createElementVNode("div",He,[n.createVNode(v,null,{default:n.withCtx(c=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{...c,controller:e.controller,pageRef:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route))}))):(n.openBlock(),n.createBlock(n.Transition,n.mergeProps({key:1},e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[e.isRefreshing(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getRefreshComponentKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:1,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isTabReady(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getComponentCacheKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)],1032,["include","max"])):e.isTabReady(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.getComponentCacheKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)]),_:2},1040))]),_:3})]),n.withDirectives(n.createElementVNode("div",{ref:"menuRef",class:"router-tab__contextmenu",role:"menu",onKeydown:a[2]||(a[2]=(...c)=>e.onMenuKeydown&&e.onMenuKeydown(...c)),style:n.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.menuItems,(c,d)=>(n.openBlock(),n.createElementBlock("a",{key:c.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":d===e.highlightedIndex}]),"aria-disabled":c.disabled,disabled:c.disabled,tabindex:c.disabled?-1:d===e.highlightedIndex?0:-1,ref_for:!0,ref:y=>e.setMenuItemRef(y,d),onMouseenter:y=>!c.disabled&&e.highlightMenuIndex(d),onClick:y=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,We))),128))],36),[[n.vShow,e.context.visible&&e.context.target]])])}const re=je(Oe,[["render",Je]]),qe={class:"router-tabs","aria-hidden":"true"},W=n.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return oe(e),(o,l)=>(n.openBlock(),n.createElementBlock("span",qe))}}),he="tab-theme-style",ye="tab-theme-primary-color",Ge="system",Te="(prefers-color-scheme: dark)";let j=null;const B={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Xe={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function Qe(e){if(typeof window>"u")return null;const a=window.localStorage.getItem(e);if(!a)return null;try{const o=JSON.parse(a);return o&&typeof o=="object"?o:null}catch{return null}}function ae(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??B.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??B.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??B.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??B.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??B.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??B.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??B.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??B.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??B.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??B.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??B.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??B.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??B.iconColor))}function ve(e){if(typeof document>"u")return;const a=document.documentElement,o=window.matchMedia(Te),l=()=>{a.dataset.theme=o.matches?"dark":"light"};j&&(o.removeEventListener("change",j),j=null),e==="system"?(l(),j=()=>l(),o.addEventListener("change",j)):a.dataset.theme=e}function ke(e={}){if(typeof window>"u")return;const{styleKey:a=he,primaryKey:o=ye,defaultStyle:l=Ge,defaultPrimary:i}=e,g=window.localStorage.getItem(a)??l;ve(g);const c=g==="dark"||g==="system"&&window.matchMedia(Te).matches?{...Xe}:{...B};i&&(c.primary=i);const d=Qe(o);ae(d?{...c,...d}:c)}function Ze(e,a){if(typeof window>"u")return;const o=a?.styleKey??he;window.localStorage.setItem(o,e),ve(e)}function et(e,a){if(typeof window>"u")return;const o=a?.primaryKey??ye;window.localStorage.setItem(o,JSON.stringify(e)),ae(e)}function J(e,a){if(n.isRef(e)){const l=!n.isReadonly(e);return{value:e,update:l?i=>{e.value=i}:()=>{}}}if(typeof e=="function"){const l=e;return{value:n.computed(l),update:()=>{}}}const o=n.ref(e===void 0?a:e);return{value:o,update:l=>{o.value=l}}}function q(e={}){const a=J(e.title,"Untitled"),o=J(e.icon,""),l=J(e.closable,!0),i=J(e.meta,{});return{routeTabTitle:a.value,routeTabIcon:o.value,routeTabClosable:l.value,routeTabMeta:i.value,updateTitle:a.update,updateIcon:o.update,updateClosable:l.update,updateMeta:i.update}}function tt(e,a="Page"){return q({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function nt(e,a="Page",o="mdi-page"){return q({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":o)})}function ot(e,a="Page"){const o={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return q({title:n.computed(()=>a+o[e.value].suffix),icon:n.computed(()=>o[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let we=!1;const rt={install(e,a){if(we)return;we=!0;const{initTheme:o=!0,themeOptions:l,componentName:i=re.name||"RouterTab",tabsComponentName:g=W.name||"RouterTabs"}=a??{};o&&ke(l??{}),e.component(i,re),e.component(g,W),g.toLowerCase()!=="router-tabs"&&e.component("router-tabs",W),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[O]},set(v){v&&e.provide(O,v)}})}};R.RouterTab=re,R.RouterTabs=W,R.default=rt,R.initRouterTabsTheme=ke,R.routerTabsKey=O,R.setRouterTabsPrimary=et,R.setRouterTabsTheme=Ze,R.useLoadingTab=tt,R.useNotificationTab=nt,R.useReactiveTab=q,R.useRouterTabs=ne,R.useRouterTabsPersistence=oe,R.useStatusTab=ot,Object.defineProperties(R,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<slot name="start" />
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
<div class="router-tab__scroll">
|
|
11
|
+
<div class="router-tab__scroll" ref="scrollContainer">
|
|
12
12
|
<transition-group
|
|
13
13
|
tag="ul"
|
|
14
14
|
class="router-tab__nav"
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
:class="buildTabClass(tab)"
|
|
21
21
|
:data-title="getTabTitle(tab)"
|
|
22
22
|
:draggable="sortable"
|
|
23
|
+
:ref="(el) => setTabRef(tab.id, el as HTMLElement)"
|
|
23
24
|
@click="activate(tab)"
|
|
24
25
|
@auxclick.middle.prevent="close(tab)"
|
|
25
26
|
@contextmenu.prevent="showContextMenu(tab, $event)"
|
|
@@ -480,6 +481,8 @@ export default defineComponent({
|
|
|
480
481
|
const menuRef = ref<HTMLElement | null>(null)
|
|
481
482
|
const menuItemRefs = ref<(HTMLElement | null)[]>([])
|
|
482
483
|
const highlightedIndex = ref(-1)
|
|
484
|
+
const scrollContainer = ref<HTMLElement | null>(null)
|
|
485
|
+
const tabRefs = new Map<string, HTMLElement>()
|
|
483
486
|
|
|
484
487
|
// Drag and drop state
|
|
485
488
|
const dragState = reactive({
|
|
@@ -824,6 +827,41 @@ export default defineComponent({
|
|
|
824
827
|
await controller.closeTab(tab.id)
|
|
825
828
|
}
|
|
826
829
|
|
|
830
|
+
// Store tab element references for scroll-into-view
|
|
831
|
+
function setTabRef(tabId: string, el: HTMLElement | null) {
|
|
832
|
+
if (el) {
|
|
833
|
+
tabRefs.set(tabId, el)
|
|
834
|
+
} else {
|
|
835
|
+
tabRefs.delete(tabId)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Scroll tab into view when activated
|
|
840
|
+
function scrollTabIntoView(tabId: string) {
|
|
841
|
+
nextTick(() => {
|
|
842
|
+
const tabEl = tabRefs.get(tabId)
|
|
843
|
+
const container = scrollContainer.value
|
|
844
|
+
|
|
845
|
+
if (tabEl && container) {
|
|
846
|
+
// Calculate if tab is within view
|
|
847
|
+
const tabRect = tabEl.getBoundingClientRect()
|
|
848
|
+
const containerRect = container.getBoundingClientRect()
|
|
849
|
+
|
|
850
|
+
// Check if tab is outside the visible area
|
|
851
|
+
const isOutOfView = tabRect.left < containerRect.left || tabRect.right > containerRect.right
|
|
852
|
+
|
|
853
|
+
if (isOutOfView) {
|
|
854
|
+
// Scroll the tab into view with smooth behavior
|
|
855
|
+
tabEl.scrollIntoView({
|
|
856
|
+
behavior: 'smooth',
|
|
857
|
+
block: 'nearest',
|
|
858
|
+
inline: 'nearest'
|
|
859
|
+
})
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
|
|
827
865
|
function activate(tab: TabRecord) {
|
|
828
866
|
if (tab.href && typeof window !== 'undefined') {
|
|
829
867
|
if (tab.target && tab.target !== '_self') {
|
|
@@ -836,6 +874,7 @@ export default defineComponent({
|
|
|
836
874
|
|
|
837
875
|
if (controller.activeId.value === tab.id) return
|
|
838
876
|
controller.openTab(tab.to, false)
|
|
877
|
+
scrollTabIntoView(tab.id)
|
|
839
878
|
}
|
|
840
879
|
|
|
841
880
|
function buildTabClass(tab: TabRecord) {
|
|
@@ -857,7 +896,12 @@ export default defineComponent({
|
|
|
857
896
|
|
|
858
897
|
function isTabReady(route: RouteLocationNormalizedLoaded) {
|
|
859
898
|
const routeKey = controller.getRouteKey(route)
|
|
860
|
-
|
|
899
|
+
const tab = controller.tabs.find(tab => tab.id === routeKey)
|
|
900
|
+
if (!tab) return false
|
|
901
|
+
|
|
902
|
+
// If KeepAlive is enabled, only render if tab is marked as alive (included in cache)
|
|
903
|
+
// If KeepAlive is disabled, render if tab exists
|
|
904
|
+
return controller.options.keepAlive ? tab.alive : true
|
|
861
905
|
}
|
|
862
906
|
|
|
863
907
|
// Drag and drop handlers
|
|
@@ -951,7 +995,12 @@ export default defineComponent({
|
|
|
951
995
|
|
|
952
996
|
watch(
|
|
953
997
|
() => controller.activeId.value,
|
|
954
|
-
() =>
|
|
998
|
+
(newActiveId) => {
|
|
999
|
+
if (newActiveId) {
|
|
1000
|
+
scrollTabIntoView(newActiveId)
|
|
1001
|
+
}
|
|
1002
|
+
hideContextMenu()
|
|
1003
|
+
}
|
|
955
1004
|
)
|
|
956
1005
|
|
|
957
1006
|
// Clean up stale component instances when tabs are closed
|
|
@@ -1045,7 +1094,10 @@ export default defineComponent({
|
|
|
1045
1094
|
highlightedIndex,
|
|
1046
1095
|
setMenuItemRef,
|
|
1047
1096
|
onMenuKeydown,
|
|
1048
|
-
highlightMenuIndex
|
|
1097
|
+
highlightMenuIndex,
|
|
1098
|
+
scrollContainer,
|
|
1099
|
+
setTabRef,
|
|
1100
|
+
scrollTabIntoView
|
|
1049
1101
|
}
|
|
1050
1102
|
}
|
|
1051
1103
|
})
|
|
@@ -318,6 +318,35 @@ export function createRouterTabs(
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// Programmatic control: set whether a tab is kept alive
|
|
322
|
+
function setTabAlive(id: string, alive: boolean) {
|
|
323
|
+
const tab = tabs.find(t => t.id === id)
|
|
324
|
+
if (!tab) return
|
|
325
|
+
tab.alive = Boolean(alive)
|
|
326
|
+
// enforce max alive if turning alive on
|
|
327
|
+
if (tab.alive) enforceMaxAlive(tabs, options.maxAlive, activeId.value)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Evict a tab from the KeepAlive cache and increment renderKey so it mounts fresh
|
|
331
|
+
function evictCache(id: string) {
|
|
332
|
+
const tab = tabs.find(t => t.id === id)
|
|
333
|
+
if (!tab) return
|
|
334
|
+
if (tab.alive) tab.alive = false
|
|
335
|
+
// bump renderKey to ensure a fresh key when re-enabled
|
|
336
|
+
tab.renderKey = (tab.renderKey ?? 0) + 1
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Clear keep-alive for all tabs
|
|
340
|
+
function clearCache() {
|
|
341
|
+
tabs.forEach(tab => {
|
|
342
|
+
tab.alive = false
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function getCacheKeys() {
|
|
347
|
+
return includeKeys.value.slice()
|
|
348
|
+
}
|
|
349
|
+
|
|
321
350
|
async function reset(route: RouteLocationRaw = options.defaultRoute) {
|
|
322
351
|
tabs.splice(0, tabs.length)
|
|
323
352
|
activeId.value = null
|
|
@@ -368,9 +397,7 @@ export function createRouterTabs(
|
|
|
368
397
|
const tab = createTabFromRoute(route, base, options.keepAlive)
|
|
369
398
|
insertTab(tabs, tab, 'last', null)
|
|
370
399
|
} catch (error) {
|
|
371
|
-
|
|
372
|
-
console.warn('[RouterTabs] Failed to restore tab', record, error)
|
|
373
|
-
}
|
|
400
|
+
console.warn('[RouterTabs] Failed to restore tab', record, error)
|
|
374
401
|
}
|
|
375
402
|
}
|
|
376
403
|
|
|
@@ -381,9 +408,7 @@ export function createRouterTabs(
|
|
|
381
408
|
try {
|
|
382
409
|
await router.replace(target)
|
|
383
410
|
} catch (error) {
|
|
384
|
-
|
|
385
|
-
console.warn('[RouterTabs] Failed to navigate to restored route', target, error)
|
|
386
|
-
}
|
|
411
|
+
console.warn('[RouterTabs] Failed to navigate to restored route', target, error)
|
|
387
412
|
}
|
|
388
413
|
}
|
|
389
414
|
}
|
|
@@ -420,11 +445,16 @@ export function createRouterTabs(
|
|
|
420
445
|
removeTab,
|
|
421
446
|
refreshTab,
|
|
422
447
|
refreshAll,
|
|
448
|
+
setTabAlive,
|
|
449
|
+
evictCache,
|
|
450
|
+
clearCache,
|
|
451
|
+
getCacheKeys,
|
|
423
452
|
reset,
|
|
424
453
|
reload,
|
|
425
454
|
getRouteKey,
|
|
426
455
|
matchRoute,
|
|
427
456
|
snapshot,
|
|
428
|
-
hydrate
|
|
457
|
+
hydrate,
|
|
458
|
+
ensureTab
|
|
429
459
|
}
|
|
430
460
|
}
|
package/lib/core/types.ts
CHANGED
|
@@ -115,6 +115,11 @@ export interface RouterTabsContext {
|
|
|
115
115
|
matchRoute: (route: RouteLocationNormalizedLoaded | RouteLocationRaw) => RouteMatchResult
|
|
116
116
|
snapshot: () => RouterTabsSnapshot
|
|
117
117
|
hydrate: (snapshot: RouterTabsSnapshot) => Promise<void>
|
|
118
|
+
setTabAlive: (id: string, alive: boolean) => void
|
|
119
|
+
evictCache: (id: string) => void
|
|
120
|
+
clearCache: () => void
|
|
121
|
+
getCacheKeys: () => string[]
|
|
122
|
+
ensureTab: (route: RouteLocationNormalizedLoaded) => TabRecord | undefined
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export interface RouterTabsPersistenceOptions {
|
package/lib/scss/index.scss
CHANGED
|
@@ -70,7 +70,28 @@
|
|
|
70
70
|
position: relative;
|
|
71
71
|
flex: 1 1 0px;
|
|
72
72
|
height: 100%;
|
|
73
|
-
overflow:
|
|
73
|
+
overflow-x: auto;
|
|
74
|
+
overflow-y: hidden;
|
|
75
|
+
scroll-behavior: smooth;
|
|
76
|
+
scrollbar-width: thin;
|
|
77
|
+
scrollbar-color: var(--router-tab-border) transparent;
|
|
78
|
+
|
|
79
|
+
&::-webkit-scrollbar {
|
|
80
|
+
height: 4px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&::-webkit-scrollbar-track {
|
|
84
|
+
background: transparent;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&::-webkit-scrollbar-thumb {
|
|
88
|
+
background: var(--router-tab-border);
|
|
89
|
+
border-radius: 2px;
|
|
90
|
+
|
|
91
|
+
&:hover {
|
|
92
|
+
background: color-mix(in srgb, var(--router-tab-primary) 50%, transparent);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
74
95
|
|
|
75
96
|
&-container {
|
|
76
97
|
width: 100%;
|