vue3-router-tab 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(y,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],t):(y=typeof globalThis<"u"?globalThis:y||self,t(y["vue3-router-tab"]={},y.Vue,y.VueRouter))})(this,(function(y,t,ce){"use strict";function ue(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 P(e,a){const n=e.resolve(a);if(!n||!n.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return n}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 E(e){const a=e.meta?.key;if(typeof a=="function"){const n=a(e);if(typeof n=="string"&&n.length)return n}else if(typeof a=="string"&&a.length){const n=de[a.toLowerCase()];return n?n(e):a}return e.fullPath}function N(e,a){const n=e.meta?.keepAlive;return typeof n=="boolean"?n:a}function j(e,a){const n=e.meta?.reuse;return typeof n=="boolean"?n:a}function Z(e){const a=e.meta??{},n={};return"title"in a&&(n.title=a.title),"tips"in a&&(n.tips=a.tips),"icon"in a&&(n.icon=a.icon),"closable"in a&&(n.closable=a.closable),"tabClass"in a&&(n.tabClass=a.tabClass),"target"in a&&(n.target=a.target),"href"in a&&(n.href=a.href),n}function S(e,a,n){const i=Z(e);return{id:E(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:N(e,n),reusable:j(e,!1),closable:i.closable??!0,...i,...a}}function O(e,a,n,i){if(!e.find(m=>m.id===a.id)){if(n==="next"&&i){const m=e.findIndex(g=>g.id===i);if(m>-1){e.splice(m+1,0,a);return}}e.push(a)}}function ee(e,a,n){if(!a||a<=0)return;const i=e.filter(r=>r.alive);for(;i.length>a;){const r=i.shift();if(!r||r.id===n)continue;const m=e.findIndex(g=>g.id===r.id);m>-1&&(e[m].alive=!1)}}function fe(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function be(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),a}function me(e,a={}){const n=ue(a),i=t.reactive([]),r=t.ref(null),m=t.shallowRef(),g=t.ref(null),s=t.computed(()=>i.filter(l=>l.alive).map(l=>l.id));let c=!1;function u(l){const d=typeof l.matched=="object"?l:P(e,l);return{key:E(d),fullPath:d.fullPath,alive:N(d,n.keepAlive),reusable:j(d,!1),matched:d}}function C(l){const d=E(l);let b=i.find(h=>h.id===d);return b?(b.fullPath=l.fullPath,b.to=l.fullPath,b.matched=l,b.alive=N(l,n.keepAlive),b.reusable=j(l,b.reusable),Object.assign(b,Z(l)),b):(b=S(l,{},n.keepAlive),O(i,b,n.appendPosition,r.value),ee(i,n.maxAlive,r.value),b)}async function R(l,d=!1,b=!0){const h=P(e,l),B=E(h),A=r.value===B;b==="sameTab"&&(b=A),b&&await w(B,!0),await e[d?"replace":"push"](h),A&&await V()}function $(l){const d=i.findIndex(h=>h.id===l),b=i[d]||i[d-1]||i[0];return b?b.to:n.defaultRoute}async function L(l=r.value,d={}){if(l){if(!d.force&&n.keepLastTab&&i.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");if(await D(l,{force:d.force}),d.redirect!==null)if(r.value===l){const b=d.redirect??$(l);b&&await e.replace(b)}else d.redirect&&await e.replace(d.redirect)}}async function D(l,d={}){const b=i.findIndex(h=>h.id===l);b!==-1&&(i.splice(b,1),g.value===l&&(g.value=null),r.value===l&&(r.value=null,m.value=void 0))}async function w(l=r.value??void 0,d=!1){l&&(g.value=l,await t.nextTick(),d||await t.nextTick(),g.value=null)}async function q(l=!1){for(const d of i)await w(d.id,l)}async function T(l=n.defaultRoute){i.splice(0,i.length),r.value=null,m.value=void 0;for(const d of n.initialTabs){const b=P(e,d.to),h=S(b,d,n.keepAlive);i.push(h)}await e.replace(l)}async function V(){const l=r.value;l&&await w(l,!0)}function G(l){return typeof l.matched=="object"?E(l):E(P(e,l))}function M(){const l=i.find(d=>d.id===r.value);return{tabs:i.map(fe),active:l?l.to:null}}async function H(l){c=!0,i.splice(0,i.length),r.value=null,m.value=void 0;const d=l?.tabs??[];for(const h of d)try{const B=P(e,h.to),A=be(h),Q=S(B,A,n.keepAlive);O(i,Q,"last",null)}catch{}c=!1;const b=l?.active??d[d.length-1]?.to??n.defaultRoute;if(b)try{await e.replace(b)}catch{}}return t.watch(()=>e.currentRoute.value,l=>{if(c)return;const d=C(l);r.value=d.id,m.value=d,ee(i,n.maxAlive,r.value)},{immediate:!0}),n.initialTabs.length&&n.initialTabs.forEach(l=>{const d=P(e,l.to),b=S(d,l,n.keepAlive);O(i,b,"last",null)}),{options:n,tabs:i,activeId:r,current:m,includeKeys:s,refreshingKey:g,openTab:R,closeTab:L,removeTab:D,refreshTab:w,refreshAll:q,reset:T,reload:V,getRouteKey:G,matchRoute:u,snapshot:M,hydrate:H}}function te(e){return e?typeof e=="string"?{name:e}:e:{}}const I=Symbol("RouterTabsContext"),K="router-tabs:snapshot";function z(e={}){const{optional:a=!1}=e,n=t.inject(I,null);if(n)return n;const i=t.inject("$tabs",null);if(i)return i;const m=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(m)return m;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const pe=864e5;function ge(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,n=document.cookie?document.cookie.split("; "):[];for(const i of n)if(i.startsWith(a))return decodeURIComponent(i.slice(a.length));return null}function ne(e,a,n){if(typeof document>"u")return;const{expiresInDays:i=7,path:r="/",domain:m,secure:g,sameSite:s="lax"}=n,c=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(i!==1/0){const u=new Date(Date.now()+i*pe).toUTCString();c.push(`Expires=${u}`)}r&&c.push(`Path=${r}`),m&&c.push(`Domain=${m}`),g&&c.push("Secure"),s&&c.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=c.join("; ")}function oe(e,a){if(typeof document>"u")return;const{path:n="/",domain:i}=a,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),n&&r.push(`Path=${n}`),i&&r.push(`Domain=${i}`),document.cookie=r.join("; ")}const ye=e=>JSON.stringify(e??null),he=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function U(e={}){const{cookieKey:a=K,serialize:n=ye,deserialize:i=he}=e,r=z({optional:!0}),m=t.ref(!0),g=s=>{t.onMounted(async()=>{const c=i(ge(a));if(c&&c.tabs?.length)try{if(m.value=!0,await s.hydrate(c),c.active){await t.nextTick();const C=s.tabs.find(R=>R.to===c.active);C&&(s.activeId.value=C.id,s.current.value=C)}}finally{m.value=!1}else try{m.value=!0;const C=e.fallbackRoute??s.options.defaultRoute;await s.reset(C)}finally{m.value=!1}const u=s.snapshot();u.tabs.length?ne(a,n(u),e):oe(a,e),m.value=!1}),t.watch(()=>({tabs:s.tabs.map(c=>({to:c.to,title:c.title,tips:c.tips,icon:c.icon,tabClass:c.tabClass,closable:c.closable})),active:s.activeId.value}),()=>{if(m.value)return;const c=s.snapshot();c.tabs.length?ne(a,n(c),e):oe(a,e)},{deep:!0})};r?g(r):t.onMounted(()=>{const s=z({optional:!0});s&&g(s)})}const ke=t.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:K},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0},titleResolver:{type:Function,default:null}},emits:["tab-sort","tab-sorted"],setup(e,{emit:a}){const n=t.getCurrentInstance();if(!n)throw new Error("[RouterTab] component must be used within a Vue application context.");const i=n.appContext.app.config.globalProperties.$router;if(!i)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const r=me(i,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(I,r),n.appContext.config.globalProperties.$tabs=r;const m=t.computed(()=>!!n?.slots?.default);if(e.cookieKey!==null||e.persistence){const o={...e.persistence??{}};e.cookieKey!==null?o.cookieKey=e.cookieKey||K:o.cookieKey||(o.cookieKey=K),U(o)}const g=t.computed(()=>te(e.tabTransition)),s=t.computed(()=>te(e.pageTransition)),c=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),u=t.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),C=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function R(o){return r.tabs.findIndex(f=>f.id===o)}function $(o){const f=R(o.id);return f>0?r.tabs.slice(0,f):[]}function L(o){const f=R(o.id);return f>-1?r.tabs.slice(f+1):[]}function D(o){return r.tabs.filter(f=>f.id!==o.id)}async function w(o,f){const p=o.filter(v=>v.closable!==!1);if(p.length){for(const v of p)r.activeId.value===v.id?await r.closeTab(v.id,{redirect:f.to,force:!0}):await r.removeTab(v.id,{force:!0});r.activeId.value!==f.id&&await r.openTab(f.to,!0,!1)}}const q={refresh:{label:"Refresh",handler:async({target:o})=>{await r.refreshTab(o.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await r.refreshAll(!0)}},close:{label:"Close",handler:async({target:o})=>{await r.closeTab(o.id)},enable:({target:o})=>d(o)},closeLefts:{label:"Close to the Left",handler:async({target:o})=>{await w($(o),o)},enable:({target:o})=>$(o).some(f=>f.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:o})=>{await w(L(o),o)},enable:({target:o})=>L(o).some(f=>f.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:o})=>{await w(D(o),o)},enable:({target:o})=>D(o).some(f=>f.closable!==!1)}};function T(){c.visible=!1,c.target=null}function V(o,f){e.contextmenu&&(c.visible=!0,c.target=o,c.position.x=f.clientX,c.position.y=f.clientY,document.addEventListener("click",T,{once:!0}))}function G(o,f){const p=typeof o=="string"?{id:o}:o,v=q[p.id],Fe=p.label??v?.label??String(p.id),W=p.visible??v?.visible??!0;if(!(typeof W=="function"?W(f):W!==!1))return null;const X=p.enable??v?.enable??!0,Ye=typeof X=="function"?X(f):X!==!1,se=p.handler??v?.handler;if(!se)return null;const Je=async()=>{await Promise.resolve(se(f))};return{id:String(p.id),label:Fe,disabled:!Ye,action:Je}}const M=t.computed(()=>{if(!c.visible||!c.target||e.contextmenu===!1)return[];const o=Array.isArray(e.contextmenu)?e.contextmenu:C,f={target:c.target,controller:r};return o.map(p=>G(p,f)).filter(p=>!!p)});async function H(o){o.disabled||(T(),await o.action())}function l(o){return e.titleResolver?e.titleResolver(o):typeof o.title=="string"?o.title:Array.isArray(o.title)&&o.title.length?String(o.title[0]):o.fullPath}function d(o){return!(o.closable===!1||r.options.keepLastTab&&r.tabs.length<=1)}async function b(o){await r.closeTab(o.id)}function h(o){r.activeId.value!==o.id&&r.openTab(o.to,!1)}function B(o){return["router-tab__item",{"is-active":r.activeId.value===o.id,"is-closable":d(o),"is-dragging":u.dragging&&u.dragTab?.id===o.id,"is-drag-over":u.dropIndex===R(o.id)},o.tabClass]}function A(o){return r.refreshingKey.value===r.getRouteKey(o)}function Q(o,f,p){e.sortable&&(u.dragging=!0,u.dragIndex=f,u.dragTab=o,p.dataTransfer&&(p.dataTransfer.effectAllowed="move",p.dataTransfer.setData("text/plain",o.id)),a("tab-sort",{tab:o,index:f}))}function Ne(o,f){!e.sortable||!u.dragging||(f.preventDefault(),f.dataTransfer&&(f.dataTransfer.dropEffect="move"))}function je(o){!e.sortable||!u.dragging||(u.dropIndex=o)}function Oe(){!e.sortable||u.dragging}function ze(o,f){if(!(!e.sortable||!u.dragging)){if(f.preventDefault(),u.dragIndex!==-1&&u.dragIndex!==o){const p=r.tabs.splice(u.dragIndex,1)[0];r.tabs.splice(o,0,p),a("tab-sorted",{tab:p,fromIndex:u.dragIndex,toIndex:o})}le()}}function le(){u.dragging=!1,u.dragIndex=-1,u.dropIndex=-1,u.dragTab=null}t.onMounted(()=>{document.addEventListener("keydown",T)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",T),n.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,o=>{r.options.keepAlive=o}),t.watch(()=>r.activeId.value,()=>T()),t.watch(()=>e.contextmenu,o=>{o||T()}),t.watch(()=>M.value.length,o=>{c.visible&&o===0&&T()});const Ue=r.includeKeys;return{controller:r,tabs:r.tabs,includeKeys:Ue,tabTransitionProps:g,pageTransitionProps:s,buildTabClass:B,activate:h,close:b,context:c,menuItems:M,handleMenuAction:H,showContextMenu:V,hideContextMenu:T,getTabTitle:l,isClosable:d,isRefreshing:A,hasCustomSlot:m,onDragStart:Q,onDragOver:Ne,onDragEnter:je,onDragLeave:Oe,onDrop:ze,onDragEnd:le}}}),Te=(e,a)=>{const n=e.__vccOpts||e;for(const[i,r]of a)n[i]=r;return n},ve={class:"router-tab"},Ce={class:"router-tab__header"},we={class:"router-tab__slot-start"},Re={class:"router-tab__scroll"},Be=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],Pe=["title"],Ee=["onClick"],Ae={class:"router-tab__slot-end"},Ie={class:"router-tab__container"},xe=["aria-disabled","onClick"];function De(e,a,n,i,r,m){const g=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",ve,[t.createElementVNode("header",Ce,[t.createElementVNode("div",we,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",Re,[t.createVNode(t.TransitionGroup,t.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:t.withCtx(()=>[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.tabs,(s,c)=>(t.openBlock(),t.createElementBlock("li",{key:s.id,class:t.normalizeClass(e.buildTabClass(s)),"data-title":e.getTabTitle(s),draggable:e.sortable,onClick:u=>e.activate(s),onAuxclick:t.withModifiers(u=>e.close(s),["middle","prevent"]),onContextmenu:t.withModifiers(u=>e.showContextMenu(s,u),["prevent"]),onDragstart:u=>e.onDragStart(s,c,u),onDragover:u=>e.onDragOver(c,u),onDragenter:u=>e.onDragEnter(c),onDragleave:a[0]||(a[0]=(...u)=>e.onDragLeave&&e.onDragLeave(...u)),onDrop:u=>e.onDrop(c,u),onDragend:a[1]||(a[1]=(...u)=>e.onDragEnd&&e.onDragEnd(...u))},[t.createElementVNode("span",{class:"router-tab__item-title",title:e.getTabTitle(s)},[s.icon?(t.openBlock(),t.createElementBlock("i",{key:0,class:t.normalizeClass(["router-tab__item-icon",s.icon])},null,2)):t.createCommentVNode("",!0),t.createTextVNode(" "+t.toDisplayString(e.getTabTitle(s)),1)],8,Pe),e.isClosable(s)?(t.openBlock(),t.createElementBlock("a",{key:0,class:"router-tab__item-close",onClick:t.withModifiers(u=>e.close(s),["stop"])},null,8,Ee)):t.createCommentVNode("",!0)],42,Be))),128))]),_:1},16)]),t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Ie,[t.createVNode(g,null,{default:t.withCtx(s=>[e.hasCustomSlot?t.renderSlot(e.$slots,"default",t.normalizeProps(t.mergeProps({key:0},{...s,controller:e.controller}))):(t.openBlock(),t.createElementBlock(t.Fragment,{key:1},[t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[e.controller.options.keepAlive?(t.openBlock(),t.createBlock(t.KeepAlive,{key:0,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isRefreshing(s.route)?t.createCommentVNode("",!0):(t.openBlock(),t.createBlock(t.resolveDynamicComponent(s.Component),{key:e.controller.getRouteKey(s.route),class:"router-tab-page"}))],1032,["include","max"])):t.createCommentVNode("",!0)]),_:2},1040),t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[!e.controller.options.keepAlive||e.isRefreshing(s.route)?(t.openBlock(),t.createBlock(t.resolveDynamicComponent(s.Component),{key:e.controller.getRouteKey(s.route)+(e.isRefreshing(s.route)?"-refresh":""),class:"router-tab-page"})):t.createCommentVNode("",!0)]),_:2},1040)],64))]),_:3})]),e.context.visible&&e.context.target?(t.openBlock(),t.createElementBlock("div",{key:0,class:"router-tab__contextmenu",style:t.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.menuItems,s=>(t.openBlock(),t.createElementBlock("a",{key:s.id,class:"router-tab__contextmenu-item","aria-disabled":s.disabled,onClick:t.withModifiers(c=>e.handleMenuAction(s),["prevent"])},t.toDisplayString(s.label),9,xe))),128))],4)):t.createCommentVNode("",!0)])}const F=Te(ke,[["render",De]]),Se={class:"router-tabs","aria-hidden":"true"},_=t.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return U(e),(n,i)=>(t.openBlock(),t.createElementBlock("span",Se))}}),ae="tab-theme-style",Ke="tab-theme-primary-color",_e="system",$e="(prefers-color-scheme: dark)";let x=null;const k={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Le={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 Y(e){typeof document>"u"||(console.log("applyPrimary",e),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))}function re(e){if(typeof document>"u")return;const a=document.documentElement,n=window.matchMedia($e),i=()=>{a.dataset.theme=n.matches?"dark":"light"};x&&(n.removeEventListener("change",x),x=null),e==="system"?(i(),x=()=>i(),n.addEventListener("change",x)):a.dataset.theme=e}function ie(e={}){if(typeof window>"u")return;const{styleKey:a=ae,defaultStyle:n=_e}=e,i=window.localStorage.getItem(a)??n;re(i),Y(i==="dark"?Le:k)}function Ve(e,a){if(typeof window>"u")return;const n=a?.styleKey??ae;window.localStorage.setItem(n,e),re(e)}function Me(e,a){if(typeof window>"u")return;const n=a?.primaryKey??Ke;window.localStorage.setItem(n,JSON.stringify(e)),Y(e)}const J={install(e){if(J._installed)return;J._installed=!0,ie();const a=F.name||"RouterTab",n=_.name||"RouterTabs";e.component(a,F),e.component(n,_),n!=="router-tabs"&&e.component("router-tabs",_),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[I]},set(i){i&&e.provide(I,i)}})}};y.RouterTab=F,y.RouterTabs=_,y.default=J,y.initRouterTabsTheme=ie,y.routerTabsKey=I,y.setRouterTabsPrimary=Me,y.setRouterTabsTheme=Ve,y.useRouterTabs=z,y.useRouterTabsPersistence=U,Object.defineProperties(y,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(T,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],t):(T=typeof globalThis<"u"?globalThis:T||self,t(T["vue3-router-tab"]={},T.Vue,T.VueRouter))})(this,(function(T,t,me){"use strict";function pe(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 $(e,o){const a=e.resolve(o);if(!a||!a.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return a}const ge={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 x(e){const o=e.meta?.key;if(typeof o=="function"){const a=o(e);if(typeof a=="string"&&a.length)return a}else if(typeof o=="string"&&o.length){const a=ge[o.toLowerCase()];return a?a(e):o}return e.fullPath}function J(e,o){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:o}function q(e,o){const a=e.meta?.reuse;return typeof a=="boolean"?a:o}function oe(e){const o=e.meta??{},a={};return"title"in o&&(a.title=o.title),"tips"in o&&(a.tips=o.tips),"icon"in o&&(a.icon=o.icon),"closable"in o&&(a.closable=o.closable),"tabClass"in o&&(a.tabClass=o.tabClass),"target"in o&&(a.target=o.target),"href"in o&&(a.href=o.href),a}function _(e,o,a){const r=oe(e);return{id:x(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:J(e,a),reusable:q(e,!1),closable:r.closable??!0,...r,...o}}function G(e,o,a,r){e.find(p=>p.id===o.id)||e.push(o)}function ae(e,o,a){if(!o||o<=0)return;const r=e.filter(i=>i.alive);for(;r.length>o;){const i=r.shift();if(!i||i.id===a)continue;const p=e.findIndex(k=>k.id===i.id);p>-1&&(e[p].alive=!1)}}function he(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ye(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),o}function Te(e,o={}){const a=pe(o),r=t.reactive([]),i=t.ref(null),p=t.shallowRef(),k=t.ref(null),c=t.computed(()=>r.filter(s=>s.alive).map(s=>s.id));let u=!1;function g(s){const f=typeof s.matched=="object"?s:$(e,s);return{key:x(f),fullPath:f.fullPath,alive:J(f,a.keepAlive),reusable:q(f,!1),matched:f}}function R(s){const f=x(s);let b=r.find(h=>h.id===f);return b?(b.fullPath=s.fullPath,b.to=s.fullPath,b.matched=s,b.alive=J(s,a.keepAlive),b.reusable=q(s,b.reusable),Object.assign(b,oe(s)),b):(b=_(s,{},a.keepAlive),G(r,b,a.appendPosition,i.value),ae(r,a.maxAlive,i.value),b)}async function I(s,f=!1,b=!0){const h=$(e,s),B=x(h),A=i.value===B;b==="sameTab"&&(b=A),b&&await M(B,!0),await e[f?"replace":"push"](h),A&&await z()}function K(s){const f=r.findIndex(D=>D.id===s);if(f===-1)return a.defaultRoute;const b=r[f+1],h=r[f-1],B=r.find(D=>D.id!==s),A=b||h||B;return A?A.to:a.defaultRoute}async function E(s=i.value,f={}){if(!s)return;if(!f.force&&a.keepLastTab&&r.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const h=i.value===s&&f.redirect!==null,B=h?f.redirect??K(s):null;await U(s,{force:f.force}),f.redirect!==null&&h&&B&&await e.replace(B)}async function U(s,f={}){const b=r.findIndex(h=>h.id===s);b!==-1&&(r.splice(b,1),k.value===s&&(k.value=null),i.value===s&&(i.value=null,p.value=void 0))}async function M(s=i.value??void 0,f=!1){s&&(k.value=s,await t.nextTick(),f||await t.nextTick(),k.value=null)}async function w(s=!1){for(const f of r)await M(f.id,s)}async function y(s=a.defaultRoute){r.splice(0,r.length),i.value=null,p.value=void 0;for(const f of a.initialTabs){const b=$(e,f.to),h=_(b,f,a.keepAlive);r.push(h)}await e.replace(s)}async function z(){const s=i.value;s&&await M(s,!0)}function V(s){return typeof s.matched=="object"?x(s):x($(e,s))}function F(){const s=r.find(f=>f.id===i.value);return{tabs:r.map(he),active:s?s.to:null}}async function Y(s){u=!0,r.splice(0,r.length),i.value=null,p.value=void 0;const f=s?.tabs??[];for(const h of f)try{const B=$(e,h.to),A=ye(h),D=_(B,A,a.keepAlive);G(r,D,"last",null)}catch{}u=!1;const b=s?.active??f[f.length-1]?.to??a.defaultRoute;if(b)try{await e.replace(b)}catch{}}return t.watch(()=>e.currentRoute.value,s=>{if(u)return;const f=R(s);i.value=f.id,p.value=f,ae(r,a.maxAlive,i.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(s=>{const f=$(e,s.to),b=_(f,s,a.keepAlive);G(r,b)}),{options:a,tabs:r,activeId:i,current:p,includeKeys:c,refreshingKey:k,openTab:I,closeTab:E,removeTab:U,refreshTab:M,refreshAll:w,reset:y,reload:z,getRouteKey:V,matchRoute:g,snapshot:F,hydrate:Y}}function ie(e){return e?typeof e=="string"?{name:e}:e:{}}const S=Symbol("RouterTabsContext"),N="router-tabs:snapshot";function W(e={}){const{optional:o=!1}=e,a=t.inject(S,null);if(a)return a;const r=t.inject("$tabs",null);if(r)return r;const p=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const ke=864e5;function ve(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const r of a)if(r.startsWith(o))return decodeURIComponent(r.slice(o.length));return null}function re(e,o,a){if(typeof document>"u")return;const{expiresInDays:r=7,path:i="/",domain:p,secure:k,sameSite:c="lax"}=a,u=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(r!==1/0){const g=new Date(Date.now()+r*ke).toUTCString();u.push(`Expires=${g}`)}i&&u.push(`Path=${i}`),p&&u.push(`Domain=${p}`),k&&u.push("Secure"),c&&u.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=u.join("; ")}function le(e,o){if(typeof document>"u")return;const{path:a="/",domain:r}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),a&&i.push(`Path=${a}`),r&&i.push(`Domain=${r}`),document.cookie=i.join("; ")}const Ce=e=>JSON.stringify(e??null),Re=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function H(e={}){const{cookieKey:o=N,serialize:a=Ce,deserialize:r=Re}=e,i=W({optional:!0}),p=t.ref(!0),k=c=>{t.onMounted(async()=>{const u=r(ve(o));if(u&&u.tabs?.length)try{if(p.value=!0,await c.hydrate(u),u.active){await t.nextTick();const R=c.tabs.find(I=>I.to===u.active);R&&(c.activeId.value=R.id,c.current.value=R)}}finally{p.value=!1}else try{p.value=!0;const R=e.fallbackRoute??c.options.defaultRoute;await c.reset(R)}finally{p.value=!1}const g=c.snapshot();g.tabs.length?re(o,a(g),e):le(o,e),p.value=!1}),t.watch(()=>({tabs:c.tabs.map(u=>({to:u.to,title:u.title,tips:u.tips,icon:u.icon,tabClass:u.tabClass,closable:u.closable})),active:c.activeId.value}),()=>{if(p.value)return;const u=c.snapshot();u.tabs.length?re(o,a(u),e):le(o,e)},{deep:!0})};i?k(i):t.onMounted(()=>{const c=W({optional:!0});c&&k(c)})}const we=t.defineComponent({name:"RouterTab",components:{RouterView:me.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:N},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:o}){const a=t.getCurrentInstance();if(!a)throw new Error("[RouterTab] component must be used within a Vue application context.");const r=a.appContext.app.config.globalProperties.$router;if(!r)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Te(r,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(S,i),a.appContext.config.globalProperties.$tabs=i;const p=t.computed(()=>!!a?.slots?.default),k=t.ref(0),c=t.computed(()=>{k.value;const n={};return i.tabs.forEach(l=>{const m=typeof l.title=="string"?l.title:String(l.title||ee(l));n[l.id]=m}),n});function u(){k.value++}const g=new Map,R=new Map;function I(n,l){if(!l||g.has(n))return;g.set(n,l);const m=i.tabs.find(P=>i.getRouteKey(P.to)===n);if(!m)return;const v=[];if(l.routeTabTitle!==void 0){const P=t.watch(()=>{const d=l.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const fe=String(d);m.title=fe,u()}},{immediate:!0});v.push(P)}if(l.routeTabIcon!==void 0){const P=t.watch(()=>{const d=l.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.icon=String(d),u())},{immediate:!0});v.push(P)}if(l.routeTabClosable!==void 0){const P=t.watch(()=>{const d=l.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.closable=!!d,u())},{immediate:!0});v.push(P)}if(l.routeTabMeta!==void 0){const P=t.watch(()=>{const d=l.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(m,d),u())},{immediate:!0,deep:!0});v.push(P)}R.set(n,v)}function K(n){const l=R.get(n);l&&(l.forEach(m=>m()),R.delete(n)),g.delete(n)}function E(n,l){n?n.routeTabTitle!==void 0||n.routeTabIcon!==void 0||n.routeTabClosable!==void 0?I(l,n):n.$&&(n.$.routeTabTitle!==void 0||n.$.routeTabIcon!==void 0||n.$.routeTabClosable!==void 0)&&I(l,n.$):n===null&&K(l)}if(e.cookieKey!==null||e.persistence){const n={...e.persistence??{}};e.cookieKey!==null?n.cookieKey=e.cookieKey||N:n.cookieKey||(n.cookieKey=N),H(n)}const U=t.computed(()=>ie(e.tabTransition)),M=t.computed(()=>ie(e.pageTransition)),w=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),y=t.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),z=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function V(n){return i.tabs.findIndex(l=>l.id===n)}function F(n){const l=V(n.id);return l>0?i.tabs.slice(0,l):[]}function Y(n){const l=V(n.id);return l>-1?i.tabs.slice(l+1):[]}function s(n){return i.tabs.filter(l=>l.id!==n.id)}async function f(n,l){const m=n.filter(v=>v.closable!==!1);if(m.length){for(const v of m)i.activeId.value===v.id?await i.closeTab(v.id,{redirect:l.to,force:!0}):await i.removeTab(v.id,{force:!0});i.activeId.value!==l.id&&await i.openTab(l.to,!0,!1)}}const b={refresh:{label:"Refresh",handler:async({target:n})=>{await i.refreshTab(n.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:n})=>{await i.closeTab(n.id)},enable:({target:n})=>te(n)},closeLefts:{label:"Close to the Left",handler:async({target:n})=>{await f(F(n),n)},enable:({target:n})=>F(n).some(l=>l.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:n})=>{await f(Y(n),n)},enable:({target:n})=>Y(n).some(l=>l.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:n})=>{await f(s(n),n)},enable:({target:n})=>s(n).some(l=>l.closable!==!1)}};function h(){w.visible=!1,w.target=null}function B(n,l){e.contextmenu&&(w.visible=!0,w.target=n,w.position.x=l.clientX,w.position.y=l.clientY,document.addEventListener("click",h,{once:!0}))}function A(n,l){const m=typeof n=="string"?{id:n}:n,v=b[m.id],P=m.label??v?.label??String(m.id),d=m.visible??v?.visible??!0;if(!(typeof d=="function"?d(l):d!==!1))return null;const ne=m.enable??v?.enable??!0,it=typeof ne=="function"?ne(l):ne!==!1,be=m.handler??v?.handler;if(!be)return null;const rt=async()=>{await Promise.resolve(be(l))};return{id:String(m.id),label:P,disabled:!it,action:rt}}const D=t.computed(()=>{if(!w.visible||!w.target||e.contextmenu===!1)return[];const n=Array.isArray(e.contextmenu)?e.contextmenu:z,l={target:w.target,controller:i};return n.map(m=>A(m,l)).filter(m=>!!m)});async function qe(n){n.disabled||(h(),await n.action())}function ee(n){return typeof n.title=="string"&&n.title.trim()?n.title:Array.isArray(n.title)&&n.title.length&&String(n.title[0]).trim()?String(n.title[0]):"Untitled"}function Ge(n){return c.value[n.id]||ee(n)}function te(n){return!(n.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function We(n){await i.closeTab(n.id)}function He(n){i.activeId.value!==n.id&&i.openTab(n.to,!1)}function Qe(n){return["router-tab__item",{"is-active":i.activeId.value===n.id,"is-closable":te(n),"is-dragging":y.dragging&&y.dragTab?.id===n.id,"is-drag-over":y.dropIndex===V(n.id)},n.tabClass]}function Xe(n){return i.refreshingKey.value===i.getRouteKey(n)}function Ze(n,l,m){e.sortable&&(y.dragging=!0,y.dragIndex=l,y.dragTab=n,m.dataTransfer&&(m.dataTransfer.effectAllowed="move",m.dataTransfer.setData("text/plain",n.id)),o("tab-sort",{tab:n,index:l}))}function et(n,l){!e.sortable||!y.dragging||(l.preventDefault(),l.dataTransfer&&(l.dataTransfer.dropEffect="move"))}function tt(n){!e.sortable||!y.dragging||(y.dropIndex=n)}function nt(){!e.sortable||y.dragging}function ot(n,l){if(!(!e.sortable||!y.dragging)){if(l.preventDefault(),y.dragIndex!==-1&&y.dragIndex!==n){const m=i.tabs.splice(y.dragIndex,1)[0];i.tabs.splice(n,0,m),o("tab-sorted",{tab:m,fromIndex:y.dragIndex,toIndex:n})}de()}}function de(){y.dragging=!1,y.dragIndex=-1,y.dropIndex=-1,y.dragTab=null}t.onMounted(()=>{document.addEventListener("keydown",h)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",h),a.appContext.config.globalProperties.$tabs=null,R.forEach(n=>{n.forEach(l=>l())}),R.clear(),g.clear()}),t.watch(()=>e.keepAlive,n=>{i.options.keepAlive=n}),t.watch(()=>i.activeId.value,()=>h()),t.watch(()=>e.contextmenu,n=>{n||h()}),t.watch(()=>D.value.length,n=>{w.visible&&n===0&&h()});const at=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:at,tabTransitionProps:U,pageTransitionProps:M,buildTabClass:Qe,activate:He,close:We,context:w,menuItems:D,handleMenuAction:qe,showContextMenu:B,hideContextMenu:h,getTabTitle:ee,isClosable:te,isRefreshing:Xe,hasCustomSlot:p,onDragStart:Ze,onDragOver:et,onDragEnter:tt,onDragLeave:nt,onDrop:ot,onDragEnd:de,setupComponentWatching:I,cleanupComponentWatching:K,handleComponentRef:E,getReactiveTabTitle:Ge,triggerTabUpdate:u}}}),Be=(e,o)=>{const a=e.__vccOpts||e;for(const[r,i]of o)a[r]=i;return a},Ee={class:"router-tab"},Pe={class:"router-tab__header"},Ae={class:"router-tab__slot-start"},Ie={class:"router-tab__scroll"},De=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],$e=["title"],xe=["onClick"],Me={class:"router-tab__slot-end"},Se={class:"router-tab__container"},Le=["aria-disabled","onClick"];function Ke(e,o,a,r,i,p){const k=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",Ee,[t.createElementVNode("header",Pe,[t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",Ie,[t.createVNode(t.TransitionGroup,t.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:t.withCtx(()=>[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.tabs,(c,u)=>(t.openBlock(),t.createElementBlock("li",{key:c.id,class:t.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:g=>e.activate(c),onAuxclick:t.withModifiers(g=>e.close(c),["middle","prevent"]),onContextmenu:t.withModifiers(g=>e.showContextMenu(c,g),["prevent"]),onDragstart:g=>e.onDragStart(c,u,g),onDragover:g=>e.onDragOver(u,g),onDragenter:g=>e.onDragEnter(u),onDragleave:o[0]||(o[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(u,g),onDragend:o[1]||(o[1]=(...g)=>e.onDragEnd&&e.onDragEnd(...g))},[c.icon?(t.openBlock(),t.createElementBlock("i",{key:0,class:t.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):t.createCommentVNode("",!0),t.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},t.toDisplayString(e.getReactiveTabTitle(c)),9,$e),e.isClosable(c)?(t.openBlock(),t.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:t.withModifiers(g=>e.close(c),["stop"])},null,8,xe)):t.createCommentVNode("",!0)],42,De))),128))]),_:1},16)]),t.createElementVNode("div",Me,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Se,[t.createVNode(k,null,{default:t.withCtx(c=>[e.hasCustomSlot?t.renderSlot(e.$slots,"default",t.normalizeProps(t.mergeProps({key:0},{...c,controller:e.controller,pageRef:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route))}))):(t.openBlock(),t.createElementBlock(t.Fragment,{key:1},[t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[e.controller.options.keepAlive?(t.openBlock(),t.createBlock(t.KeepAlive,{key:0,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isRefreshing(c.route)?t.createCommentVNode("",!0):(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route)),class:"router-tab-page"}))],1032,["include","max"])):t.createCommentVNode("",!0)]),_:2},1040),t.createVNode(t.Transition,t.mergeProps(e.pageTransitionProps,{appear:""}),{default:t.withCtx(()=>[!e.controller.options.keepAlive||e.isRefreshing(c.route)?(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route)+(e.isRefreshing(c.route)?"-refresh":""),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):t.createCommentVNode("",!0)]),_:2},1040)],64))]),_:3})]),e.context.visible&&e.context.target?(t.openBlock(),t.createElementBlock("div",{key:0,class:"router-tab__contextmenu",style:t.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.menuItems,c=>(t.openBlock(),t.createElementBlock("a",{key:c.id,class:"router-tab__contextmenu-item","aria-disabled":c.disabled,onClick:t.withModifiers(u=>e.handleMenuAction(c),["prevent"])},t.toDisplayString(c.label),9,Le))),128))],4)):t.createCommentVNode("",!0)])}const Q=Be(we,[["render",Ke]]),Ve={class:"router-tabs","aria-hidden":"true"},j=t.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return H(e),(a,r)=>(t.openBlock(),t.createElementBlock("span",Ve))}}),se="tab-theme-style",_e="tab-theme-primary-color",Ne="system",je="(prefers-color-scheme: dark)";let L=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"},Oe={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 X(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 ce(e){if(typeof document>"u")return;const o=document.documentElement,a=window.matchMedia(je),r=()=>{o.dataset.theme=a.matches?"dark":"light"};L&&(a.removeEventListener("change",L),L=null),e==="system"?(r(),L=()=>r(),a.addEventListener("change",L)):o.dataset.theme=e}function ue(e={}){if(typeof window>"u")return;const{styleKey:o=se,defaultStyle:a=Ne}=e,r=window.localStorage.getItem(o)??a;ce(r),X(r==="dark"?Oe:C)}function Ue(e,o){if(typeof window>"u")return;const a=o?.styleKey??se;window.localStorage.setItem(a,e),ce(e)}function ze(e,o){if(typeof window>"u")return;const a=o?.primaryKey??_e;window.localStorage.setItem(a,JSON.stringify(e)),X(e)}function O(e={}){const o=t.ref(e.title||"Untitled"),a=t.ref(e.icon||"mdi-tab"),r=t.ref(e.closable!==!1),i=t.ref(e.meta||{}),p=typeof e.title=="function"?t.computed(e.title):o,k=typeof e.icon=="function"?t.computed(e.icon):a,c=typeof e.closable=="function"?t.computed(e.closable):r,u=typeof e.meta=="function"?t.computed(e.meta):i;return{routeTabTitle:p,routeTabIcon:k,routeTabClosable:c,routeTabMeta:u,updateTitle:E=>{"value"in o&&(o.value=E)},updateIcon:E=>{"value"in a&&(a.value=E)},updateClosable:E=>{"value"in r&&(r.value=E)},updateMeta:E=>{"value"in i&&(i.value=E)}}}function Fe(e,o="Page"){return O({title:t.computed(()=>e.value?"Loading...":o),icon:t.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:t.computed(()=>!e.value)})}function Ye(e,o="Page",a="mdi-page"){return O({title:t.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:t.computed(()=>e.value>0?"mdi-bell-badge":a)})}function Je(e,o="Page"){const a={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return O({title:t.computed(()=>o+a[e.value].suffix),icon:t.computed(()=>a[e.value].icon),closable:t.computed(()=>e.value!=="loading")})}const Z={install(e){if(Z._installed)return;Z._installed=!0,ue();const o=Q.name||"RouterTab",a=j.name||"RouterTabs";e.component(o,Q),e.component(a,j),a!=="router-tabs"&&e.component("router-tabs",j),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[S]},set(r){r&&e.provide(S,r)}})}};T.RouterTab=Q,T.RouterTabs=j,T.default=Z,T.initRouterTabsTheme=ue,T.routerTabsKey=S,T.setRouterTabsPrimary=ze,T.setRouterTabsTheme=Ue,T.useLoadingTab=Fe,T.useNotificationTab=Ye,T.useReactiveTab=O,T.useRouterTabs=W,T.useRouterTabsPersistence=H,T.useStatusTab=Je,Object.defineProperties(T,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -27,9 +27,9 @@
27
27
  @drop="onDrop(index, $event)"
28
28
  @dragend="onDragEnd"
29
29
  >
30
- <span class="router-tab__item-title" :title="getTabTitle(tab)">
31
- <i v-if="tab.icon" :class="['router-tab__item-icon', tab.icon]" />
32
- {{ getTabTitle(tab) }}
30
+ <i v-if="tab.icon" :class="['router-tab__item-icon', tab.icon]" />
31
+ <span class="router-tab__item-title" :title="getReactiveTabTitle(tab)">
32
+ {{ getReactiveTabTitle(tab) }}
33
33
  </span>
34
34
  <a
35
35
  v-if="isClosable(tab)"
@@ -48,7 +48,14 @@
48
48
  <div class="router-tab__container">
49
49
  <RouterView v-slot="routerSlot">
50
50
  <template v-if="hasCustomSlot">
51
- <slot v-bind="{ ...routerSlot, controller }" />
51
+ <slot
52
+ v-bind="{
53
+ ...routerSlot,
54
+ controller,
55
+ // Expose a ref binder so custom slots can keep reactivity
56
+ pageRef: (el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))
57
+ }"
58
+ />
52
59
  </template>
53
60
  <template v-else>
54
61
  <transition
@@ -64,6 +71,7 @@
64
71
  v-if="!isRefreshing(routerSlot.route)"
65
72
  :is="routerSlot.Component"
66
73
  :key="controller.getRouteKey(routerSlot.route)"
74
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
67
75
  class="router-tab-page"
68
76
  />
69
77
  </KeepAlive>
@@ -77,6 +85,7 @@
77
85
  v-if="!controller.options.keepAlive || isRefreshing(routerSlot.route)"
78
86
  :is="routerSlot.Component"
79
87
  :key="controller.getRouteKey(routerSlot.route) + (isRefreshing(routerSlot.route) ? '-refresh' : '')"
88
+ :ref="(el: any) => handleComponentRef(el, controller.getRouteKey(routerSlot.route))"
80
89
  class="router-tab-page"
81
90
  />
82
91
  </transition>
@@ -111,6 +120,7 @@ import {
111
120
  onMounted,
112
121
  provide,
113
122
  reactive,
123
+ ref,
114
124
  watch
115
125
  } from 'vue'
116
126
  import { RouterView, type RouteLocationNormalizedLoaded } from 'vue-router'
@@ -189,10 +199,6 @@ export default defineComponent({
189
199
  sortable: {
190
200
  type: Boolean,
191
201
  default: true
192
- },
193
- titleResolver: {
194
- type: Function as PropType<(tab: TabRecord) => string>,
195
- default: null
196
202
  }
197
203
  },
198
204
  emits: ['tab-sort', 'tab-sorted'],
@@ -221,6 +227,148 @@ export default defineComponent({
221
227
 
222
228
  const hasCustomSlot = computed(() => Boolean(instance?.slots?.default))
223
229
 
230
+ // Force reactivity by creating a trigger ref
231
+ const tabUpdateTrigger = ref(0)
232
+
233
+ // Reactive tab titles that update when tabs change
234
+ const reactiveTabTitles = computed(() => {
235
+ // Access the trigger to ensure reactivity
236
+ tabUpdateTrigger.value
237
+ const titles: Record<string, string> = {}
238
+ controller.tabs.forEach(tab => {
239
+ // Use the tab's current title (which is updated by watchers)
240
+ const currentTitle = typeof tab.title === 'string' ? tab.title : String(tab.title || getTabTitle(tab))
241
+ titles[tab.id] = currentTitle
242
+ })
243
+ return titles
244
+ })
245
+
246
+ // Function to trigger reactivity updates
247
+ function triggerTabUpdate() {
248
+ tabUpdateTrigger.value++
249
+ }
250
+
251
+ // Reactive tab update system
252
+ const componentInstances = new Map<string, any>()
253
+ const watchedProperties = new Map<string, (() => void)[]>()
254
+
255
+ // Watch for component instance changes and set up reactivity
256
+ function setupComponentWatching(routeKey: string, componentInstance: any) {
257
+ if (!componentInstance || componentInstances.has(routeKey)) return
258
+
259
+ // setup watchers for exposed reactive tab props
260
+
261
+ componentInstances.set(routeKey, componentInstance)
262
+
263
+ // Find the tab for this route
264
+ const tab = controller.tabs.find(t => controller.getRouteKey(t.to) === routeKey)
265
+ if (!tab) return
266
+
267
+ const unwatchers: (() => void)[] = []
268
+
269
+ // Watch routeTabTitle for title updates
270
+ if (componentInstance.routeTabTitle !== undefined) {
271
+ const unwatchTitle = watch(
272
+ () => {
273
+ // Handle both ref values and regular values
274
+ const titleValue = componentInstance.routeTabTitle
275
+ return titleValue && typeof titleValue === 'object' && 'value' in titleValue ? titleValue.value : titleValue
276
+ },
277
+ (newTitle) => {
278
+ if (newTitle !== undefined && newTitle !== null) {
279
+ const titleString = String(newTitle)
280
+ tab.title = titleString
281
+ triggerTabUpdate() // Trigger reactivity
282
+ }
283
+ },
284
+ { immediate: true }
285
+ )
286
+ unwatchers.push(unwatchTitle)
287
+ }
288
+
289
+ // Watch routeTabIcon for icon updates
290
+ if (componentInstance.routeTabIcon !== undefined) {
291
+ const unwatchIcon = watch(
292
+ () => {
293
+ const iconValue = componentInstance.routeTabIcon
294
+ return iconValue && typeof iconValue === 'object' && 'value' in iconValue ? iconValue.value : iconValue
295
+ },
296
+ (newIcon) => {
297
+ if (newIcon !== undefined && newIcon !== null) {
298
+ tab.icon = String(newIcon)
299
+ triggerTabUpdate() // Trigger reactivity
300
+ }
301
+ },
302
+ { immediate: true }
303
+ )
304
+ unwatchers.push(unwatchIcon)
305
+ }
306
+
307
+ // Watch routeTabClosable for closable state updates
308
+ if (componentInstance.routeTabClosable !== undefined) {
309
+ const unwatchClosable = watch(
310
+ () => {
311
+ const closableValue = componentInstance.routeTabClosable
312
+ return closableValue && typeof closableValue === 'object' && 'value' in closableValue ? closableValue.value : closableValue
313
+ },
314
+ (newClosable) => {
315
+ if (newClosable !== undefined && newClosable !== null) {
316
+ tab.closable = Boolean(newClosable)
317
+ triggerTabUpdate() // Trigger reactivity
318
+ }
319
+ },
320
+ { immediate: true }
321
+ )
322
+ unwatchers.push(unwatchClosable)
323
+ }
324
+
325
+ // Watch routeTabMeta for general meta updates
326
+ if (componentInstance.routeTabMeta !== undefined) {
327
+ const unwatchMeta = watch(
328
+ () => {
329
+ const metaValue = componentInstance.routeTabMeta
330
+ return metaValue && typeof metaValue === 'object' && 'value' in metaValue ? metaValue.value : metaValue
331
+ },
332
+ (newMeta) => {
333
+ if (newMeta && typeof newMeta === 'object') {
334
+ Object.assign(tab, newMeta)
335
+ triggerTabUpdate() // Trigger reactivity
336
+ }
337
+ },
338
+ { immediate: true, deep: true }
339
+ )
340
+ unwatchers.push(unwatchMeta)
341
+ }
342
+
343
+ watchedProperties.set(routeKey, unwatchers)
344
+ }
345
+
346
+ // Cleanup watchers when component is unmounted
347
+ function cleanupComponentWatching(routeKey: string) {
348
+ const unwatchers = watchedProperties.get(routeKey)
349
+ if (unwatchers) {
350
+ unwatchers.forEach(unwatcher => unwatcher())
351
+ watchedProperties.delete(routeKey)
352
+ }
353
+ componentInstances.delete(routeKey)
354
+ }
355
+
356
+ // Handle component ref changes
357
+ function handleComponentRef(el: any, routeKey: string) {
358
+ if (el) {
359
+ // Component mounted - set up watching
360
+ // Check if properties are exposed directly on el (Vue 3 with defineExpose)
361
+ if (el.routeTabTitle !== undefined || el.routeTabIcon !== undefined || el.routeTabClosable !== undefined) {
362
+ setupComponentWatching(routeKey, el)
363
+ } else if (el.$ && (el.$.routeTabTitle !== undefined || el.$.routeTabIcon !== undefined || el.$.routeTabClosable !== undefined)) {
364
+ setupComponentWatching(routeKey, el.$)
365
+ }
366
+ } else if (el === null) {
367
+ // Component unmounted - cleanup
368
+ cleanupComponentWatching(routeKey)
369
+ }
370
+ }
371
+
224
372
  if (props.cookieKey !== null || props.persistence) {
225
373
  const options: RouterTabsPersistenceOptions = {
226
374
  ...(props.persistence ?? {})
@@ -408,12 +556,16 @@ export default defineComponent({
408
556
  }
409
557
 
410
558
  function getTabTitle(tab: TabRecord): string {
411
- if (props.titleResolver) {
412
- return props.titleResolver(tab)
413
- }
414
- if (typeof tab.title === 'string') return tab.title
415
- if (Array.isArray(tab.title) && tab.title.length) return String(tab.title[0])
416
- return tab.fullPath
559
+ if (typeof tab.title === 'string' && tab.title.trim()) return tab.title
560
+ if (Array.isArray(tab.title) && tab.title.length && String(tab.title[0]).trim()) return String(tab.title[0])
561
+ return 'Untitled'
562
+ }
563
+
564
+ // Reactive function to get tab title
565
+ function getReactiveTabTitle(tab: TabRecord): string {
566
+ // Access the reactive titles to ensure reactivity
567
+ const reactiveTitles = reactiveTabTitles.value
568
+ return reactiveTitles[tab.id] || getTabTitle(tab)
417
569
  }
418
570
 
419
571
  function isClosable(tab: TabRecord) {
@@ -515,6 +667,13 @@ export default defineComponent({
515
667
  onBeforeUnmount(() => {
516
668
  document.removeEventListener('keydown', hideContextMenu)
517
669
  instance.appContext.config.globalProperties.$tabs = null
670
+
671
+ // Cleanup all component watchers
672
+ watchedProperties.forEach((unwatchers) => {
673
+ unwatchers.forEach(unwatcher => unwatcher())
674
+ })
675
+ watchedProperties.clear()
676
+ componentInstances.clear()
518
677
  })
519
678
 
520
679
  watch(
@@ -570,7 +729,12 @@ export default defineComponent({
570
729
  onDragEnter,
571
730
  onDragLeave,
572
731
  onDrop,
573
- onDragEnd
732
+ onDragEnd,
733
+ setupComponentWatching,
734
+ cleanupComponentWatching,
735
+ handleComponentRef,
736
+ getReactiveTabTitle,
737
+ triggerTabUpdate
574
738
  }
575
739
  }
576
740
  })
@@ -105,14 +105,6 @@ function insertTab(tabs: TabRecord[], tab: TabRecord, position: 'last' | 'next',
105
105
  const exists = tabs.find(item => item.id === tab.id)
106
106
  if (exists) return
107
107
 
108
- if (position === 'next' && referenceId) {
109
- const idx = tabs.findIndex(item => item.id === referenceId)
110
- if (idx > -1) {
111
- tabs.splice(idx + 1, 0, tab)
112
- return
113
- }
114
- }
115
-
116
108
  tabs.push(tab)
117
109
  }
118
110
 
@@ -211,7 +203,14 @@ export function createRouterTabs(
211
203
 
212
204
  function fallbackAfterClose(closedId: string): RouteLocationRaw | null {
213
205
  const idx = tabs.findIndex(item => item.id === closedId)
214
- const candidate = tabs[idx] || tabs[idx - 1] || tabs[0]
206
+ if (idx === -1) return options.defaultRoute
207
+
208
+ // Priority: next tab -> previous tab -> first available tab
209
+ const nextTab = tabs[idx + 1] // Next tab (after the one being closed)
210
+ const prevTab = tabs[idx - 1] // Previous tab
211
+ const firstTab = tabs.find(tab => tab.id !== closedId) // First available tab (excluding the one being closed)
212
+
213
+ const candidate = nextTab || prevTab || firstTab
215
214
  if (candidate) return candidate.to
216
215
  return options.defaultRoute
217
216
  }
@@ -222,15 +221,20 @@ export function createRouterTabs(
222
221
  throw new Error('[RouterTabs] Unable to close the final tab when keepLastTab is true.')
223
222
  }
224
223
 
224
+ // Calculate fallback route BEFORE removing the tab
225
+ const isClosingActiveTab = activeId.value === id
226
+ const shouldRedirect = isClosingActiveTab && closeOptions.redirect !== null
227
+ const fallbackRoute = shouldRedirect ? (closeOptions.redirect ?? fallbackAfterClose(id)) : null
228
+
229
+
230
+
225
231
  await removeTab(id, { force: closeOptions.force })
226
232
 
233
+ // Only skip redirect if explicitly set to null
227
234
  if (closeOptions.redirect === null) return
228
235
 
229
- if (activeId.value === id) {
230
- const redirect = closeOptions.redirect ?? fallbackAfterClose(id)
231
- if (redirect) await router.replace(redirect)
232
- } else if (closeOptions.redirect) {
233
- await router.replace(closeOptions.redirect)
236
+ if (shouldRedirect && fallbackRoute) {
237
+ await router.replace(fallbackRoute)
234
238
  }
235
239
  }
236
240
 
package/lib/index.ts CHANGED
@@ -33,6 +33,18 @@ export {
33
33
  setRouterTabsPrimary
34
34
  }
35
35
 
36
+ export {
37
+ useReactiveTab,
38
+ useLoadingTab,
39
+ useNotificationTab,
40
+ useStatusTab
41
+ } from './useReactiveTab'
42
+
43
+ export type {
44
+ ReactiveTabState,
45
+ ReactiveTabReturn
46
+ } from './useReactiveTab'
47
+
36
48
  import "./scss/index.scss";
37
49
 
38
50
  const plugin: Plugin = {
@@ -12,6 +12,18 @@
12
12
  --router-tab-header-height: #{$router-tab-header-height};
13
13
  --router-tab-padding: #{$router-tab-padding};
14
14
  --router-tab-font-size: #{$router-tab-font-size};
15
+ --router-tab-transition: #{$router-tab-transition};
16
+
17
+ // Colors (will be overridden by theme system)
18
+ --router-tab-primary: #{$router-tab-primary};
19
+ --router-tab-background: #{$router-tab-bg-light};
20
+ --router-tab-text: #{$router-tab-text-light};
21
+ --router-tab-border: #{$router-tab-border-light};
22
+ --router-tab-header-bg: #{$router-tab-bg-light};
23
+ --router-tab-active-background: #{$router-tab-primary};
24
+ --router-tab-active-text: #ffffff;
25
+ --router-tab-active-border: #{$router-tab-primary};
26
+ --router-tab-icon-color: #475569;
15
27
  }
16
28
 
17
29
  // =============================================================================
package/lib/theme.ts CHANGED
@@ -46,7 +46,7 @@ const defaultColors: ColorStyle = {
46
46
  activeText: "#ffffff",
47
47
  activeBorder: "#034960",
48
48
 
49
- headerBackground: "#ffff",
49
+ headerBackground: "#ffffff",
50
50
 
51
51
  buttonBackground: "#f8fafc",
52
52
  buttonColor: "#034960",
@@ -78,7 +78,6 @@ const defaultDarkColor: ColorStyle = {
78
78
 
79
79
  function applyPrimary(color: ColorStyle) {
80
80
  if (typeof document === 'undefined') return
81
- console.log('applyPrimary', color)
82
81
  document.documentElement.style.setProperty('--router-tab-primary', color.primary ?? defaultColors.primary)
83
82
  document.documentElement.style.setProperty('--router-tab-header-bg', color.headerBackground ?? defaultColors.headerBackground)
84
83
  document.documentElement.style.setProperty('--router-tab-background', color.background ?? defaultColors.background)
@@ -91,6 +90,7 @@ function applyPrimary(color: ColorStyle) {
91
90
  document.documentElement.style.setProperty('--router-tab-active-button-color', color.activeButtonColor ?? defaultColors.activeButtonColor)
92
91
  document.documentElement.style.setProperty('--router-tab-button-background', color.buttonBackground ?? defaultColors.buttonBackground)
93
92
  document.documentElement.style.setProperty('--router-tab-active-button-background', color.activeButtonBackground ?? defaultColors.activeButtonBackground)
93
+ document.documentElement.style.setProperty('--router-tab-icon-color', color.iconColor ?? defaultColors.iconColor)
94
94
  }
95
95
 
96
96
  function applyStyle(style: 'light' | 'dark' | 'system') {
@@ -0,0 +1,130 @@
1
+ import { ref, computed, type Ref, type ComputedRef } from 'vue'
2
+
3
+ export interface ReactiveTabState {
4
+ title?: string | Ref<string> | ComputedRef<string>
5
+ icon?: string | Ref<string> | ComputedRef<string>
6
+ closable?: boolean | Ref<boolean> | ComputedRef<boolean>
7
+ meta?: any | Ref<any> | ComputedRef<any>
8
+ }
9
+
10
+ export interface ReactiveTabReturn {
11
+ routeTabTitle: Ref<string> | ComputedRef<string>
12
+ routeTabIcon: Ref<string> | ComputedRef<string>
13
+ routeTabClosable: Ref<boolean> | ComputedRef<boolean>
14
+ routeTabMeta: Ref<any> | ComputedRef<any>
15
+ updateTitle: (title: string) => void
16
+ updateIcon: (icon: string) => void
17
+ updateClosable: (closable: boolean) => void
18
+ updateMeta: (meta: any) => void
19
+ }
20
+
21
+ /**
22
+ * Composable for managing reactive tab properties
23
+ * RouterTab will automatically watch these properties and update the tab accordingly
24
+ */
25
+ export function useReactiveTab(initialState: ReactiveTabState = {}): ReactiveTabReturn {
26
+ // Create reactive references
27
+ const tabTitle = ref(initialState.title || 'Untitled')
28
+ const tabIcon = ref(initialState.icon || 'mdi-tab')
29
+ const tabClosable = ref(initialState.closable !== false)
30
+ const tabMeta = ref(initialState.meta || {})
31
+
32
+ // Convert to computed if needed
33
+ const routeTabTitle = typeof initialState.title === 'function'
34
+ ? computed(initialState.title as () => string)
35
+ : tabTitle
36
+
37
+ const routeTabIcon = typeof initialState.icon === 'function'
38
+ ? computed(initialState.icon as () => string)
39
+ : tabIcon
40
+
41
+ const routeTabClosable = typeof initialState.closable === 'function'
42
+ ? computed(initialState.closable as () => boolean)
43
+ : tabClosable
44
+
45
+ const routeTabMeta = typeof initialState.meta === 'function'
46
+ ? computed(initialState.meta as () => any)
47
+ : tabMeta
48
+
49
+ // Update functions
50
+ const updateTitle = (title: string) => {
51
+ if ('value' in tabTitle) {
52
+ tabTitle.value = title
53
+ }
54
+ }
55
+
56
+ const updateIcon = (icon: string) => {
57
+ if ('value' in tabIcon) {
58
+ tabIcon.value = icon
59
+ }
60
+ }
61
+
62
+ const updateClosable = (closable: boolean) => {
63
+ if ('value' in tabClosable) {
64
+ tabClosable.value = closable
65
+ }
66
+ }
67
+
68
+ const updateMeta = (meta: any) => {
69
+ if ('value' in tabMeta) {
70
+ tabMeta.value = meta
71
+ }
72
+ }
73
+
74
+ return {
75
+ routeTabTitle,
76
+ routeTabIcon,
77
+ routeTabClosable,
78
+ routeTabMeta,
79
+ updateTitle,
80
+ updateIcon,
81
+ updateClosable,
82
+ updateMeta
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Preset for loading state tabs
88
+ */
89
+ export function useLoadingTab(loadingState: Ref<boolean>, baseTitle = 'Page') {
90
+ return useReactiveTab({
91
+ title: computed(() => loadingState.value ? 'Loading...' : baseTitle),
92
+ icon: computed(() => loadingState.value ? 'mdi-loading mdi-spin' : 'mdi-page'),
93
+ closable: computed(() => !loadingState.value)
94
+ })
95
+ }
96
+
97
+ /**
98
+ * Preset for notification-aware tabs
99
+ */
100
+ export function useNotificationTab(
101
+ count: Ref<number>,
102
+ baseTitle = 'Page',
103
+ baseIcon = 'mdi-page'
104
+ ) {
105
+ return useReactiveTab({
106
+ title: computed(() => count.value > 0 ? `${baseTitle} (${count.value})` : baseTitle),
107
+ icon: computed(() => count.value > 0 ? 'mdi-bell-badge' : baseIcon)
108
+ })
109
+ }
110
+
111
+ /**
112
+ * Preset for status-aware tabs
113
+ */
114
+ export function useStatusTab(
115
+ status: Ref<'normal' | 'loading' | 'error' | 'success'>,
116
+ baseTitle = 'Page'
117
+ ) {
118
+ const statusConfig = {
119
+ normal: { suffix: '', icon: 'mdi-page' },
120
+ loading: { suffix: ' - Loading', icon: 'mdi-loading mdi-spin' },
121
+ error: { suffix: ' - Error', icon: 'mdi-alert' },
122
+ success: { suffix: ' - Success', icon: 'mdi-check-circle' }
123
+ }
124
+
125
+ return useReactiveTab({
126
+ title: computed(() => baseTitle + statusConfig[status.value].suffix),
127
+ icon: computed(() => statusConfig[status.value].icon),
128
+ closable: computed(() => status.value !== 'loading')
129
+ })
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -20,7 +20,8 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "scripts": {
23
- "build": "vite build"
23
+ "build": "vite build",
24
+ "prepublishOnly": "npm run build"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@vitejs/plugin-vue": "^6.0.1",
@@ -46,7 +47,11 @@
46
47
  ],
47
48
  "license": "MIT",
48
49
  "homepage": "https://github.com/anilshr25/vue3-router-tab/#readme",
49
- "description": "Simple Confirm Dialog verification plugin with Vue 3.",
50
+ "description": "Vue 3 Router Tabs component: tabbed navigation for Vue Router with persistence, context menu, drag-and-drop reordering, theming, and reactive page-driven tab titles.",
51
+ "sideEffects": [
52
+ "dist/vue3-router-tab.css",
53
+ "lib/scss/index.scss"
54
+ ],
50
55
  "repository": {
51
56
  "type": "git",
52
57
  "url": "https://github.com/anilshr25/vue3-router-tab.git"