vue3-router-tab 1.1.3 → 1.1.5

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
- :root{--theme-primary: #635bff;--router-tab-primary: var(--theme-primary);--router-tab-background: #ffffff;--router-tab-foreground: #1e293b;--router-tab-border: #e2e8f0;--router-tab-header-bg: #ffffff;color-scheme:light}:root[data-theme=light]{color-scheme:light;--router-tab-background: #ffffff;--router-tab-foreground: #1e293b;--router-tab-border: #e2e8f0;--router-tab-header-bg: #ffffff}:root[data-theme=dark]{color-scheme:dark;--router-tab-background: #0f172a;--router-tab-foreground: #e2e8f0;--router-tab-border: rgba(148, 163, 184, .35);--router-tab-header-bg: rgb(21.7105263158, 33.2894736842, 60.7894736842)}@media (prefers-color-scheme: dark){:root:not([data-theme]){color-scheme:dark;--router-tab-background: #0f172a;--router-tab-foreground: #e2e8f0;--router-tab-border: rgba(148, 163, 184, .35);--router-tab-header-bg: rgb(21.7105263158, 33.2894736842, 60.7894736842)}}.router-tab{display:flex;flex-direction:column;min-height:300px;background-color:var(--router-tab-background);color:var(--router-tab-foreground)}.router-tab__header{position:relative;z-index:9;display:flex;flex:none;box-sizing:border-box;height:40px;border-bottom:1px solid var(--router-tab-border);background-color:var(--router-tab-header-bg);transition:all .2s ease-in-out}.router-tab__scroll{position:relative;flex:1 1 0px;height:40px;overflow:hidden}.router-tab__scroll-container{width:100%;height:100%;overflow:hidden}.router-tab__scroll-container.is-mobile{overflow-x:auto;overflow-y:hidden}.router-tab__scrollbar{position:absolute;right:0;bottom:0;left:0;height:3px;background-color:#0000001a;border-radius:3px;opacity:0;transition:opacity .3s ease-in-out}.router-tab__scroll:hover .router-tab__scrollbar,.router-tab__scrollbar.is-dragging{opacity:1}.router-tab__scrollbar-thumb{position:absolute;top:0;left:0;height:100%;background-color:#0000001a;border-radius:3px;transition:background-color .3s ease-in-out}.router-tab__scrollbar-thumb:hover,.router-tab__scrollbar.is-dragging .router-tab__scrollbar-thumb{background-color:#0003}.router-tab__nav{position:relative;display:inline-flex;flex-wrap:nowrap;height:100%;margin:0;padding:0;list-style:none}.router-tab__item{position:relative;display:flex;flex:none;align-items:center;padding:0 20px;color:inherit;font-size:14px;border:1px solid var(--router-tab-border);border-left:none;transform-origin:left bottom;cursor:pointer;transition:all .3s ease-in-out;-webkit-user-select:none;user-select:none;background-color:transparent}.router-tab__item:first-child{border-left:1px solid var(--router-tab-border)}.router-tab__item.is-contextmenu{color:var(--router-tab-primary)}.router-tab__item.is-drag-over{background:#0000000d;transition:background .15s ease}.router-tab__item-title{min-width:30px;max-width:120px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.router-tab__item-icon{margin-right:5px;font-size:16px}.router-tab__item:hover,.router-tab__item.is-active{color:var(--router-tab-primary)}.router-tab__item:hover.is-closable,.router-tab__item.is-active.is-closable{padding:0 11.5px}.router-tab__item:hover .router-tab__item-close,.router-tab__item.is-active .router-tab__item-close{width:13px;margin-left:4px}.router-tab__item:hover .router-tab__item-close:before,.router-tab__item:hover .router-tab__item-close:after,.router-tab__item.is-active .router-tab__item-close:before,.router-tab__item.is-active .router-tab__item-close:after{background-color:#fff}.router-tab__item.is-active{border-bottom-color:var(--router-tab-background)}.router-tab__item-close{position:relative;display:block;width:0;height:13px;margin-left:0;overflow:hidden;border-radius:50%;cursor:pointer;transition:all .3s ease-in-out;background:transparent;border:none}.router-tab__item-close:before,.router-tab__item-close:after{position:absolute;top:6px;left:50%;display:block;width:8px;height:1px;margin-left:-4px;background-color:currentColor;transition:background-color .2s ease-in-out;content:""}.router-tab__item-close:before{transform:rotate(-45deg)}.router-tab__item-close:after{transform:rotate(45deg)}.router-tab__item-close:hover{background-color:color-mix(in srgb,var(--router-tab-primary) 40%,#ffffff 60%)}.router-tab__item-close:hover:before,.router-tab__item-close:hover:after{background-color:#fff}.router-tab__contextmenu{position:fixed;z-index:1000;min-width:140px;padding:8px 0;font-size:14px;background:var(--router-tab-background);border:1px solid var(--router-tab-border);box-shadow:0 10px 30px #0f172a26;border-radius:8px}.router-tab__contextmenu a,.router-tab__contextmenu button{display:block;width:100%;padding:0 16px;line-height:30px;text-align:left;background:transparent;border:none;color:inherit;cursor:pointer;font:inherit;transition:background-color .2s ease-in-out}.router-tab__contextmenu a[aria-disabled=true],.router-tab__contextmenu button[aria-disabled=true]{color:#94a3b899;pointer-events:none}.router-tab__contextmenu a:hover,.router-tab__contextmenu a:focus-visible,.router-tab__contextmenu button:hover,.router-tab__contextmenu button:focus-visible{background:color-mix(in srgb,var(--router-tab-primary) 20%,#ffffff 80%);color:var(--router-tab-primary)}.router-tab__container{position:relative;flex:1 1 auto;background-color:var(--router-tab-background)}.router-tab__item.is-active+.router-tab__item{border-left-color:var(--router-tab-border)}
1
+ .router-tab{display:flex;flex-direction:column;min-height:300px;background-color:transparent;color:inherit}.router-tab__header{position:relative;z-index:9;display:flex;flex:none;box-sizing:border-box;height:40px;border-bottom:1px solid var(--router-tab-border, var(--theme-border, rgba(15, 23, 42, .08)));background-color:var(--router-tab-header-bg, var(--router-tab-background, var(--theme-background, #ffffff)));transition:all .2s ease-in-out}.router-tab__scroll{position:relative;flex:1 1 0px;height:40px;overflow:hidden}.router-tab__scroll-container{width:100%;height:100%;overflow:hidden}.router-tab__scroll-container.is-mobile{overflow-x:auto;overflow-y:hidden}.router-tab__scrollbar{position:absolute;right:0;bottom:0;left:0;height:3px;background-color:#0000001a;border-radius:3px;opacity:0;transition:opacity .3s ease-in-out}.router-tab__scroll:hover .router-tab__scrollbar,.router-tab__scrollbar.is-dragging{opacity:1}.router-tab__scrollbar-thumb{position:absolute;top:0;left:0;height:100%;background-color:#0000001a;border-radius:3px;transition:background-color .3s ease-in-out}.router-tab__scrollbar-thumb:hover,.router-tab__scrollbar.is-dragging .router-tab__scrollbar-thumb{background-color:#0003}.router-tab__nav{position:relative;display:inline-flex;flex-wrap:nowrap;height:100%;margin:0;padding:0;list-style:none}.router-tab__item{position:relative;display:flex;flex:none;align-items:center;padding:0 20px;color:inherit;font-size:14px;border:1px solid var(--router-tab-border, var(--theme-border, rgba(15, 23, 42, .08)));border-left:none;transform-origin:left bottom;cursor:pointer;transition:all .3s ease-in-out;-webkit-user-select:none;user-select:none;background-color:transparent}.router-tab__item:first-child{border-left:1px solid var(--router-tab-border, var(--theme-border, rgba(15, 23, 42, .08)))}.router-tab__item.is-contextmenu{color:var(--router-tab-primary, var(--theme-primary, #635bff))}.router-tab__item.is-drag-over{background:#0000000d;transition:background .15s ease}.router-tab__item-title{min-width:30px;max-width:120px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.router-tab__item-icon{margin-right:5px;font-size:16px}.router-tab__item:hover,.router-tab__item.is-active{color:var(--router-tab-primary, var(--theme-primary, #635bff))}.router-tab__item:hover.is-closable,.router-tab__item.is-active.is-closable{padding:0 11.5px}.router-tab__item:hover .router-tab__item-close,.router-tab__item.is-active .router-tab__item-close{width:13px;margin-left:4px}.router-tab__item:hover .router-tab__item-close:before,.router-tab__item:hover .router-tab__item-close:after,.router-tab__item.is-active .router-tab__item-close:before,.router-tab__item.is-active .router-tab__item-close:after{background-color:#fff}.router-tab__item.is-active{border-bottom-color:var(--router-tab-background, var(--theme-background, #ffffff));background:color-mix(in srgb,var(--router-tab-primary, var(--theme-primary, #635bff)) 12%,var(--router-tab-background, var(--theme-background, #ffffff)) 88%);box-shadow:inset 0 -2px color-mix(in srgb,var(--router-tab-primary, var(--theme-primary, #635bff)) 65%,transparent);color:color-mix(in srgb,var(--router-tab-primary, var(--theme-primary, #635bff)) 80%,var(--router-tab-foreground, var(--theme-foreground, #1e293b)) 20%)}.router-tab__item.is-active:after{content:"";position:absolute;left:16px;right:16px;bottom:6px;height:2px;border-radius:999px;background:var(--router-tab-primary, var(--theme-primary, #635bff));opacity:.6;pointer-events:none}.router-tab__item-close{position:relative;display:block;width:0;height:13px;margin-left:0;overflow:hidden;border-radius:50%;cursor:pointer;transition:all .3s ease-in-out;background:transparent;border:none}.router-tab__item-close:before,.router-tab__item-close:after{position:absolute;top:6px;left:50%;display:block;width:8px;height:1px;margin-left:-4px;background-color:currentColor;transition:background-color .2s ease-in-out;content:""}.router-tab__item-close:before{transform:rotate(-45deg)}.router-tab__item-close:after{transform:rotate(45deg)}.router-tab__item-close:hover{background-color:color-mix(in srgb,var(--router-tab-primary, var(--theme-primary, #635bff)) 40%,#ffffff 60%)}.router-tab__item-close:hover:before,.router-tab__item-close:hover:after{background-color:#fff}.router-tab__contextmenu{position:fixed;z-index:1000;min-width:140px;padding:8px 0;font-size:14px;background:var(--router-tab-background, var(--theme-background, #ffffff));border:1px solid var(--router-tab-border, var(--theme-border, rgba(15, 23, 42, .08)));box-shadow:0 10px 30px #0f172a26;border-radius:8px}.router-tab__contextmenu a,.router-tab__contextmenu button{display:block;width:100%;padding:0 16px;line-height:30px;text-align:left;background:transparent;border:none;color:inherit;cursor:pointer;font:inherit;transition:background-color .2s ease-in-out}.router-tab__contextmenu a[aria-disabled=true],.router-tab__contextmenu button[aria-disabled=true]{color:#94a3b899;pointer-events:none}.router-tab__contextmenu a:hover,.router-tab__contextmenu a:focus-visible,.router-tab__contextmenu button:hover,.router-tab__contextmenu button:focus-visible{background:color-mix(in srgb,var(--router-tab-primary, var(--theme-primary, #635bff)) 20%,#ffffff 80%);color:var(--router-tab-primary, var(--theme-primary, #635bff))}.router-tab__container{padding:1rem;position:relative;flex:1 1 auto;background-color:transparent}.router-tab__item.is-active+.router-tab__item{border-left-color:var(--router-tab-border, var(--theme-border, rgba(15, 23, 42, .08)))}
@@ -680,7 +680,7 @@ const he = /* @__PURE__ */ We(Qe, [["render", rt]]), ct = {
680
680
  setup(e) {
681
681
  return Re(e), (o, t) => (h(), g("span", ct));
682
682
  }
683
- }), Ce = "theme-style", Ae = "theme-primary-color", ut = "system", ft = "#635bff", dt = "(prefers-color-scheme: dark)";
683
+ }), Ce = "tab-theme-style", Ae = "tab-theme-primary-color", ut = "system", ft = "#635bff", dt = "(prefers-color-scheme: dark)";
684
684
  let S = null;
685
685
  function _e(e) {
686
686
  typeof document > "u" || (document.documentElement.style.setProperty("--theme-primary", e), document.documentElement.style.setProperty("--router-tab-primary", e));
@@ -1 +1 @@
1
- (function(b,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):(b=typeof globalThis<"u"?globalThis:b||self,t(b["vue3-router-tab"]={},b.Vue,b.VueRouter))})(this,(function(b,t,le){"use strict";function se(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 R(e,o){const i=e.resolve(o);if(!i||!i.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return i}const re={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 C(e){const o=e.meta?.key;if(typeof o=="function"){const i=o(e);if(typeof i=="string"&&i.length)return i}else if(typeof o=="string"&&o.length){const i=re[o.toLowerCase()];return i?i(e):o}return e.fullPath}function M(e,o){const i=e.meta?.keepAlive;return typeof i=="boolean"?i:o}function L(e,o){const i=e.meta?.reuse;return typeof i=="boolean"?i:o}function H(e){const o=e.meta??{},i={};return"title"in o&&(i.title=o.title),"tips"in o&&(i.tips=o.tips),"icon"in o&&(i.icon=o.icon),"closable"in o&&(i.closable=o.closable),"tabClass"in o&&(i.tabClass=o.tabClass),"target"in o&&(i.target=o.target),"href"in o&&(i.href=o.href),i}function K(e,o,i){const n=H(e);return{id:C(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:M(e,i),reusable:L(e,!1),closable:n.closable??!0,...n,...o}}function N(e,o,i,n){if(!e.find(d=>d.id===o.id)){if(i==="next"&&n){const d=e.findIndex(m=>m.id===n);if(d>-1){e.splice(d+1,0,o);return}}e.push(o)}}function Q(e,o,i){if(!o||o<=0)return;const n=e.filter(r=>r.alive);for(;n.length>o;){const r=n.shift();if(!r||r.id===i)continue;const d=e.findIndex(m=>m.id===r.id);d>-1&&(e[d].alive=!1)}}function ce(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ue(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 fe(e,o={}){const i=se(o),n=t.reactive([]),r=t.ref(null),d=t.shallowRef(),m=t.ref(null),s=t.computed(()=>n.filter(l=>l.alive).map(l=>l.id));let f=!1;function T(l){const c=typeof l.matched=="object"?l:R(e,l);return{key:C(c),fullPath:c.fullPath,alive:M(c,i.keepAlive),reusable:L(c,!1),matched:c}}function P(l){const c=C(l);let u=n.find(h=>h.id===c);return u?(u.fullPath=l.fullPath,u.to=l.fullPath,u.matched=l,u.alive=M(l,i.keepAlive),u.reusable=L(l,u.reusable),Object.assign(u,H(l)),u):(u=K(l,{},i.keepAlive),N(n,u,i.appendPosition,r.value),Q(n,i.maxAlive,r.value),u)}async function B(l,c=!1,u=!0){const h=R(e,l),w=C(h),a=r.value===w;u==="sameTab"&&(u=a),u&&await g(w,!0),await e[c?"replace":"push"](h),a&&await E()}function $(l){const c=n.findIndex(h=>h.id===l),u=n[c]||n[c-1]||n[0];return u?u.to:i.defaultRoute}async function v(l=r.value,c={}){if(l){if(!c.force&&i.keepLastTab&&n.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");if(await V(l,{force:c.force}),c.redirect!==null)if(r.value===l){const u=c.redirect??$(l);u&&await e.replace(u)}else c.redirect&&await e.replace(c.redirect)}}async function V(l,c={}){const u=n.findIndex(h=>h.id===l);u!==-1&&(n.splice(u,1),m.value===l&&(m.value=null),r.value===l&&(r.value=null,d.value=void 0))}async function g(l=r.value??void 0,c=!1){l&&(m.value=l,await t.nextTick(),c||await t.nextTick(),m.value=null)}async function O(l=!1){for(const c of n)await g(c.id,l)}async function F(l=i.defaultRoute){n.splice(0,n.length),r.value=null,d.value=void 0;for(const c of i.initialTabs){const u=R(e,c.to),h=K(u,c,i.keepAlive);n.push(h)}await e.replace(l)}async function E(){const l=r.value;l&&await g(l,!0)}function Y(l){return typeof l.matched=="object"?C(l):C(R(e,l))}function q(){const l=n.find(c=>c.id===r.value);return{tabs:n.map(ce),active:l?l.to:null}}async function S(l){f=!0,n.splice(0,n.length),r.value=null,d.value=void 0;const c=l?.tabs??[];for(const h of c)try{const w=R(e,h.to),a=ue(h),p=K(w,a,i.keepAlive);N(n,p,"last",null)}catch{}f=!1;const u=l?.active??c[c.length-1]?.to??i.defaultRoute;if(u)try{await e.replace(u)}catch{}}return t.watch(()=>e.currentRoute.value,l=>{if(f)return;const c=P(l);r.value=c.id,d.value=c,Q(n,i.maxAlive,r.value)},{immediate:!0}),i.initialTabs.length&&i.initialTabs.forEach(l=>{const c=R(e,l.to),u=K(c,l,i.keepAlive);N(n,u,"last",null)}),{options:i,tabs:n,activeId:r,current:d,includeKeys:s,refreshingKey:m,openTab:B,closeTab:v,removeTab:V,refreshTab:g,refreshAll:O,reset:F,reload:E,getRouteKey:Y,matchRoute:T,snapshot:q,hydrate:S}}function W(e){return e?typeof e=="string"?{name:e}:e:{}}const A=Symbol("RouterTabsContext"),x="router-tabs:snapshot";function j(e={}){const{optional:o=!1}=e,i=t.inject(A,null);if(i)return i;const n=t.inject("$tabs",null);if(n)return n;const d=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(d)return d;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const de=864e5;function pe(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,i=document.cookie?document.cookie.split("; "):[];for(const n of i)if(n.startsWith(o))return decodeURIComponent(n.slice(o.length));return null}function X(e,o,i){if(typeof document>"u")return;const{expiresInDays:n=7,path:r="/",domain:d,secure:m,sameSite:s="lax"}=i,f=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(n!==1/0){const T=new Date(Date.now()+n*de).toUTCString();f.push(`Expires=${T}`)}r&&f.push(`Path=${r}`),d&&f.push(`Domain=${d}`),m&&f.push("Secure"),s&&f.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=f.join("; ")}function Z(e,o){if(typeof document>"u")return;const{path:i="/",domain:n}=o,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),i&&r.push(`Path=${i}`),n&&r.push(`Domain=${n}`),document.cookie=r.join("; ")}const me=e=>JSON.stringify(e??null),be=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function D(e={}){const{cookieKey:o=x,serialize:i=me,deserialize:n=be}=e,r=j({optional:!0}),d=t.ref(!0),m=s=>{t.onMounted(async()=>{const f=n(pe(o));if(f&&f.tabs?.length)try{d.value=!0,await s.hydrate(f)}finally{d.value=!1}else try{d.value=!0;const P=e.fallbackRoute??s.options.defaultRoute;await s.reset(P)}finally{d.value=!1}const T=s.snapshot();T.tabs.length?X(o,i(T),e):Z(o,e),d.value=!1}),t.watch(()=>({tabs:s.tabs.map(f=>({to:f.to,title:f.title,tips:f.tips,icon:f.icon,tabClass:f.tabClass,closable:f.closable})),active:s.activeId.value}),()=>{if(d.value)return;const f=s.snapshot();f.tabs.length?X(o,i(f),e):Z(o,e)},{deep:!0})};r?m(r):t.onMounted(()=>{const s=j({optional:!0});s&&m(s)})}const he=t.defineComponent({name:"RouterTab",components:{RouterView:le.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:x},persistence:{type:Object,default:null}},setup(e){const o=t.getCurrentInstance();if(!o)throw new Error("[RouterTab] component must be used within a Vue application context.");const i=o.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 n=fe(i,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(A,n),o.appContext.config.globalProperties.$tabs=n;const r=t.computed(()=>!!o?.slots?.default);if(e.cookieKey!==null||e.persistence){const a={...e.persistence??{}};e.cookieKey!==null?a.cookieKey=e.cookieKey||x:a.cookieKey||(a.cookieKey=x),D(a)}const d=t.computed(()=>W(e.tabTransition)),m=t.computed(()=>W(e.pageTransition)),s=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),f=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function T(a){return n.tabs.findIndex(p=>p.id===a)}function P(a){const p=T(a.id);return p>0?n.tabs.slice(0,p):[]}function B(a){const p=T(a.id);return p>-1?n.tabs.slice(p+1):[]}function $(a){return n.tabs.filter(p=>p.id!==a.id)}async function v(a,p){const y=a.filter(k=>k.closable!==!1);if(y.length){for(const k of y)n.activeId.value===k.id?await n.closeTab(k.id,{redirect:p.to,force:!0}):await n.removeTab(k.id,{force:!0});n.activeId.value!==p.id&&await n.openTab(p.to,!0,!1)}}const V={refresh:{label:"Refresh",handler:async({target:a})=>{await n.refreshTab(a.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await n.refreshAll(!0)}},close:{label:"Close",handler:async({target:a})=>{await n.closeTab(a.id)},enable:({target:a})=>S(a)},closeLefts:{label:"Close to the Left",handler:async({target:a})=>{await v(P(a),a)},enable:({target:a})=>P(a).some(p=>p.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:a})=>{await v(B(a),a)},enable:({target:a})=>B(a).some(p=>p.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:a})=>{await v($(a),a)},enable:({target:a})=>$(a).some(p=>p.closable!==!1)}};function g(){s.visible=!1,s.target=null}function O(a,p){e.contextmenu&&(s.visible=!0,s.target=a,s.position.x=p.clientX,s.position.y=p.clientY,document.addEventListener("click",g,{once:!0}))}function F(a,p){const y=typeof a=="string"?{id:a}:a,k=V[y.id],Ve=y.label??k?.label??String(y.id),G=y.visible??k?.visible??!0;if(!(typeof G=="function"?G(p):G!==!1))return null;const J=y.enable??k?.enable??!0,Me=typeof J=="function"?J(p):J!==!1,ae=y.handler??k?.handler;if(!ae)return null;const Le=async()=>{await Promise.resolve(ae(p))};return{id:String(y.id),label:Ve,disabled:!Me,action:Le}}const E=t.computed(()=>{if(!s.visible||!s.target||e.contextmenu===!1)return[];const a=Array.isArray(e.contextmenu)?e.contextmenu:f,p={target:s.target,controller:n};return a.map(y=>F(y,p)).filter(y=>!!y)});async function Y(a){a.disabled||(g(),await a.action())}function q(a){return typeof a.title=="string"?a.title:Array.isArray(a.title)&&a.title.length?String(a.title[0]):a.fullPath}function S(a){return!(a.closable===!1||n.options.keepLastTab&&n.tabs.length<=1)}async function l(a){await n.closeTab(a.id)}function c(a){n.activeId.value!==a.id&&n.openTab(a.to,!1)}function u(a){return["router-tab__item",{"is-active":n.activeId.value===a.id,"is-closable":S(a)},a.tabClass]}function h(a){return n.refreshingKey.value===n.getRouteKey(a)}t.onMounted(()=>{document.addEventListener("keydown",g)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",g),o.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,a=>{n.options.keepAlive=a}),t.watch(()=>n.activeId.value,()=>g()),t.watch(()=>e.contextmenu,a=>{a||g()}),t.watch(()=>E.value.length,a=>{s.visible&&a===0&&g()});const w=n.includeKeys;return{controller:n,tabs:n.tabs,includeKeys:w,tabTransitionProps:d,pageTransitionProps:m,buildTabClass:u,activate:c,close:l,context:s,menuItems:E,handleMenuAction:Y,showContextMenu:O,hideContextMenu:g,tabTitle:q,isClosable:S,isRefreshing:h,hasCustomSlot:r}}}),ye=(e,o)=>{const i=e.__vccOpts||e;for(const[n,r]of o)i[n]=r;return i},ge={class:"router-tab"},ke={class:"router-tab__header"},Te={class:"router-tab__slot-start"},we={class:"router-tab__scroll"},Re=["onClick","onAuxclick","onContextmenu"],Ce=["title"],Pe=["onClick"],Ae={class:"router-tab__slot-end"},_e={class:"router-tab__container"},ve=["aria-disabled","onClick"];function Ee(e,o,i,n,r,d){const m=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",ge,[t.createElementVNode("header",ke,[t.createElementVNode("div",Te,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",we,[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=>(t.openBlock(),t.createElementBlock("li",{key:s.id,class:t.normalizeClass(e.buildTabClass(s)),onClick:f=>e.activate(s),onAuxclick:t.withModifiers(f=>e.close(s),["middle","prevent"]),onContextmenu:t.withModifiers(f=>e.showContextMenu(s,f),["prevent"])},[t.createElementVNode("span",{class:"router-tab__item-title",title:e.tabTitle(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.tabTitle(s)),1)],8,Ce),e.isClosable(s)?(t.openBlock(),t.createElementBlock("a",{key:0,class:"router-tab__item-close",type:"button",onClick:t.withModifiers(f=>e.close(s),["stop"])},null,8,Pe)):t.createCommentVNode("",!0)],42,Re))),128))]),_:1},16)]),t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",_e,[t.createVNode(m,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(f=>e.handleMenuAction(s),["prevent"])},t.toDisplayString(s.label),9,ve))),128))],4)):t.createCommentVNode("",!0)])}const U=ye(he,[["render",Ee]]),Se={class:"router-tabs","aria-hidden":"true"},I=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 D(e),(i,n)=>(t.openBlock(),t.createElementBlock("span",Se))}}),ee="theme-style",te="theme-primary-color",Ke="system",xe="#635bff",Ie="(prefers-color-scheme: dark)";let _=null;function ne(e){typeof document>"u"||(document.documentElement.style.setProperty("--theme-primary",e),document.documentElement.style.setProperty("--router-tab-primary",e))}function oe(e){if(typeof document>"u")return;const o=document.documentElement,i=window.matchMedia(Ie),n=()=>{o.dataset.theme=i.matches?"dark":"light"};_&&(i.removeEventListener("change",_),_=null),e==="system"?(n(),_=()=>n(),i.addEventListener("change",_)):o.dataset.theme=e}function ie(e={}){if(typeof window>"u")return;const{styleKey:o=ee,primaryKey:i=te,defaultStyle:n=Ke,defaultPrimary:r=xe}=e,d=window.localStorage.getItem(o)??n,m=window.localStorage.getItem(i)??r;oe(d),ne(m)}function Be(e,o){if(typeof window>"u")return;const i=o?.styleKey??ee;window.localStorage.setItem(i,e),oe(e)}function $e(e,o){if(typeof window>"u")return;const i=o?.primaryKey??te;window.localStorage.setItem(i,e),ne(e)}const z={install(e){if(z._installed)return;z._installed=!0,ie();const o=U.name||"RouterTab",i=I.name||"RouterTabs";e.component(o,U),e.component(i,I),i!=="router-tabs"&&e.component("router-tabs",I),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[A]},set(n){n&&e.provide(A,n)}})}};b.RouterTab=U,b.RouterTabs=I,b.default=z,b.initRouterTabsTheme=ie,b.routerTabsKey=A,b.setRouterTabsPrimary=$e,b.setRouterTabsTheme=Be,b.useRouterTabs=j,b.useRouterTabsPersistence=D,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(b,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):(b=typeof globalThis<"u"?globalThis:b||self,t(b["vue3-router-tab"]={},b.Vue,b.VueRouter))})(this,(function(b,t,le){"use strict";function se(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 R(e,o){const i=e.resolve(o);if(!i||!i.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return i}const re={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 C(e){const o=e.meta?.key;if(typeof o=="function"){const i=o(e);if(typeof i=="string"&&i.length)return i}else if(typeof o=="string"&&o.length){const i=re[o.toLowerCase()];return i?i(e):o}return e.fullPath}function M(e,o){const i=e.meta?.keepAlive;return typeof i=="boolean"?i:o}function L(e,o){const i=e.meta?.reuse;return typeof i=="boolean"?i:o}function H(e){const o=e.meta??{},i={};return"title"in o&&(i.title=o.title),"tips"in o&&(i.tips=o.tips),"icon"in o&&(i.icon=o.icon),"closable"in o&&(i.closable=o.closable),"tabClass"in o&&(i.tabClass=o.tabClass),"target"in o&&(i.target=o.target),"href"in o&&(i.href=o.href),i}function K(e,o,i){const n=H(e);return{id:C(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:M(e,i),reusable:L(e,!1),closable:n.closable??!0,...n,...o}}function N(e,o,i,n){if(!e.find(d=>d.id===o.id)){if(i==="next"&&n){const d=e.findIndex(m=>m.id===n);if(d>-1){e.splice(d+1,0,o);return}}e.push(o)}}function Q(e,o,i){if(!o||o<=0)return;const n=e.filter(r=>r.alive);for(;n.length>o;){const r=n.shift();if(!r||r.id===i)continue;const d=e.findIndex(m=>m.id===r.id);d>-1&&(e[d].alive=!1)}}function ce(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ue(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 fe(e,o={}){const i=se(o),n=t.reactive([]),r=t.ref(null),d=t.shallowRef(),m=t.ref(null),s=t.computed(()=>n.filter(l=>l.alive).map(l=>l.id));let f=!1;function T(l){const c=typeof l.matched=="object"?l:R(e,l);return{key:C(c),fullPath:c.fullPath,alive:M(c,i.keepAlive),reusable:L(c,!1),matched:c}}function P(l){const c=C(l);let u=n.find(h=>h.id===c);return u?(u.fullPath=l.fullPath,u.to=l.fullPath,u.matched=l,u.alive=M(l,i.keepAlive),u.reusable=L(l,u.reusable),Object.assign(u,H(l)),u):(u=K(l,{},i.keepAlive),N(n,u,i.appendPosition,r.value),Q(n,i.maxAlive,r.value),u)}async function B(l,c=!1,u=!0){const h=R(e,l),w=C(h),a=r.value===w;u==="sameTab"&&(u=a),u&&await g(w,!0),await e[c?"replace":"push"](h),a&&await E()}function $(l){const c=n.findIndex(h=>h.id===l),u=n[c]||n[c-1]||n[0];return u?u.to:i.defaultRoute}async function v(l=r.value,c={}){if(l){if(!c.force&&i.keepLastTab&&n.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");if(await V(l,{force:c.force}),c.redirect!==null)if(r.value===l){const u=c.redirect??$(l);u&&await e.replace(u)}else c.redirect&&await e.replace(c.redirect)}}async function V(l,c={}){const u=n.findIndex(h=>h.id===l);u!==-1&&(n.splice(u,1),m.value===l&&(m.value=null),r.value===l&&(r.value=null,d.value=void 0))}async function g(l=r.value??void 0,c=!1){l&&(m.value=l,await t.nextTick(),c||await t.nextTick(),m.value=null)}async function O(l=!1){for(const c of n)await g(c.id,l)}async function F(l=i.defaultRoute){n.splice(0,n.length),r.value=null,d.value=void 0;for(const c of i.initialTabs){const u=R(e,c.to),h=K(u,c,i.keepAlive);n.push(h)}await e.replace(l)}async function E(){const l=r.value;l&&await g(l,!0)}function Y(l){return typeof l.matched=="object"?C(l):C(R(e,l))}function q(){const l=n.find(c=>c.id===r.value);return{tabs:n.map(ce),active:l?l.to:null}}async function S(l){f=!0,n.splice(0,n.length),r.value=null,d.value=void 0;const c=l?.tabs??[];for(const h of c)try{const w=R(e,h.to),a=ue(h),p=K(w,a,i.keepAlive);N(n,p,"last",null)}catch{}f=!1;const u=l?.active??c[c.length-1]?.to??i.defaultRoute;if(u)try{await e.replace(u)}catch{}}return t.watch(()=>e.currentRoute.value,l=>{if(f)return;const c=P(l);r.value=c.id,d.value=c,Q(n,i.maxAlive,r.value)},{immediate:!0}),i.initialTabs.length&&i.initialTabs.forEach(l=>{const c=R(e,l.to),u=K(c,l,i.keepAlive);N(n,u,"last",null)}),{options:i,tabs:n,activeId:r,current:d,includeKeys:s,refreshingKey:m,openTab:B,closeTab:v,removeTab:V,refreshTab:g,refreshAll:O,reset:F,reload:E,getRouteKey:Y,matchRoute:T,snapshot:q,hydrate:S}}function W(e){return e?typeof e=="string"?{name:e}:e:{}}const A=Symbol("RouterTabsContext"),x="router-tabs:snapshot";function j(e={}){const{optional:o=!1}=e,i=t.inject(A,null);if(i)return i;const n=t.inject("$tabs",null);if(n)return n;const d=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(d)return d;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const de=864e5;function pe(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,i=document.cookie?document.cookie.split("; "):[];for(const n of i)if(n.startsWith(o))return decodeURIComponent(n.slice(o.length));return null}function X(e,o,i){if(typeof document>"u")return;const{expiresInDays:n=7,path:r="/",domain:d,secure:m,sameSite:s="lax"}=i,f=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(n!==1/0){const T=new Date(Date.now()+n*de).toUTCString();f.push(`Expires=${T}`)}r&&f.push(`Path=${r}`),d&&f.push(`Domain=${d}`),m&&f.push("Secure"),s&&f.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=f.join("; ")}function Z(e,o){if(typeof document>"u")return;const{path:i="/",domain:n}=o,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),i&&r.push(`Path=${i}`),n&&r.push(`Domain=${n}`),document.cookie=r.join("; ")}const me=e=>JSON.stringify(e??null),be=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function D(e={}){const{cookieKey:o=x,serialize:i=me,deserialize:n=be}=e,r=j({optional:!0}),d=t.ref(!0),m=s=>{t.onMounted(async()=>{const f=n(pe(o));if(f&&f.tabs?.length)try{d.value=!0,await s.hydrate(f)}finally{d.value=!1}else try{d.value=!0;const P=e.fallbackRoute??s.options.defaultRoute;await s.reset(P)}finally{d.value=!1}const T=s.snapshot();T.tabs.length?X(o,i(T),e):Z(o,e),d.value=!1}),t.watch(()=>({tabs:s.tabs.map(f=>({to:f.to,title:f.title,tips:f.tips,icon:f.icon,tabClass:f.tabClass,closable:f.closable})),active:s.activeId.value}),()=>{if(d.value)return;const f=s.snapshot();f.tabs.length?X(o,i(f),e):Z(o,e)},{deep:!0})};r?m(r):t.onMounted(()=>{const s=j({optional:!0});s&&m(s)})}const he=t.defineComponent({name:"RouterTab",components:{RouterView:le.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:x},persistence:{type:Object,default:null}},setup(e){const o=t.getCurrentInstance();if(!o)throw new Error("[RouterTab] component must be used within a Vue application context.");const i=o.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 n=fe(i,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(A,n),o.appContext.config.globalProperties.$tabs=n;const r=t.computed(()=>!!o?.slots?.default);if(e.cookieKey!==null||e.persistence){const a={...e.persistence??{}};e.cookieKey!==null?a.cookieKey=e.cookieKey||x:a.cookieKey||(a.cookieKey=x),D(a)}const d=t.computed(()=>W(e.tabTransition)),m=t.computed(()=>W(e.pageTransition)),s=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),f=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function T(a){return n.tabs.findIndex(p=>p.id===a)}function P(a){const p=T(a.id);return p>0?n.tabs.slice(0,p):[]}function B(a){const p=T(a.id);return p>-1?n.tabs.slice(p+1):[]}function $(a){return n.tabs.filter(p=>p.id!==a.id)}async function v(a,p){const y=a.filter(k=>k.closable!==!1);if(y.length){for(const k of y)n.activeId.value===k.id?await n.closeTab(k.id,{redirect:p.to,force:!0}):await n.removeTab(k.id,{force:!0});n.activeId.value!==p.id&&await n.openTab(p.to,!0,!1)}}const V={refresh:{label:"Refresh",handler:async({target:a})=>{await n.refreshTab(a.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await n.refreshAll(!0)}},close:{label:"Close",handler:async({target:a})=>{await n.closeTab(a.id)},enable:({target:a})=>S(a)},closeLefts:{label:"Close to the Left",handler:async({target:a})=>{await v(P(a),a)},enable:({target:a})=>P(a).some(p=>p.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:a})=>{await v(B(a),a)},enable:({target:a})=>B(a).some(p=>p.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:a})=>{await v($(a),a)},enable:({target:a})=>$(a).some(p=>p.closable!==!1)}};function g(){s.visible=!1,s.target=null}function O(a,p){e.contextmenu&&(s.visible=!0,s.target=a,s.position.x=p.clientX,s.position.y=p.clientY,document.addEventListener("click",g,{once:!0}))}function F(a,p){const y=typeof a=="string"?{id:a}:a,k=V[y.id],Ve=y.label??k?.label??String(y.id),G=y.visible??k?.visible??!0;if(!(typeof G=="function"?G(p):G!==!1))return null;const J=y.enable??k?.enable??!0,Me=typeof J=="function"?J(p):J!==!1,ae=y.handler??k?.handler;if(!ae)return null;const Le=async()=>{await Promise.resolve(ae(p))};return{id:String(y.id),label:Ve,disabled:!Me,action:Le}}const E=t.computed(()=>{if(!s.visible||!s.target||e.contextmenu===!1)return[];const a=Array.isArray(e.contextmenu)?e.contextmenu:f,p={target:s.target,controller:n};return a.map(y=>F(y,p)).filter(y=>!!y)});async function Y(a){a.disabled||(g(),await a.action())}function q(a){return typeof a.title=="string"?a.title:Array.isArray(a.title)&&a.title.length?String(a.title[0]):a.fullPath}function S(a){return!(a.closable===!1||n.options.keepLastTab&&n.tabs.length<=1)}async function l(a){await n.closeTab(a.id)}function c(a){n.activeId.value!==a.id&&n.openTab(a.to,!1)}function u(a){return["router-tab__item",{"is-active":n.activeId.value===a.id,"is-closable":S(a)},a.tabClass]}function h(a){return n.refreshingKey.value===n.getRouteKey(a)}t.onMounted(()=>{document.addEventListener("keydown",g)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",g),o.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,a=>{n.options.keepAlive=a}),t.watch(()=>n.activeId.value,()=>g()),t.watch(()=>e.contextmenu,a=>{a||g()}),t.watch(()=>E.value.length,a=>{s.visible&&a===0&&g()});const w=n.includeKeys;return{controller:n,tabs:n.tabs,includeKeys:w,tabTransitionProps:d,pageTransitionProps:m,buildTabClass:u,activate:c,close:l,context:s,menuItems:E,handleMenuAction:Y,showContextMenu:O,hideContextMenu:g,tabTitle:q,isClosable:S,isRefreshing:h,hasCustomSlot:r}}}),ye=(e,o)=>{const i=e.__vccOpts||e;for(const[n,r]of o)i[n]=r;return i},ge={class:"router-tab"},ke={class:"router-tab__header"},Te={class:"router-tab__slot-start"},we={class:"router-tab__scroll"},Re=["onClick","onAuxclick","onContextmenu"],Ce=["title"],Pe=["onClick"],Ae={class:"router-tab__slot-end"},_e={class:"router-tab__container"},ve=["aria-disabled","onClick"];function Ee(e,o,i,n,r,d){const m=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",ge,[t.createElementVNode("header",ke,[t.createElementVNode("div",Te,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",we,[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=>(t.openBlock(),t.createElementBlock("li",{key:s.id,class:t.normalizeClass(e.buildTabClass(s)),onClick:f=>e.activate(s),onAuxclick:t.withModifiers(f=>e.close(s),["middle","prevent"]),onContextmenu:t.withModifiers(f=>e.showContextMenu(s,f),["prevent"])},[t.createElementVNode("span",{class:"router-tab__item-title",title:e.tabTitle(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.tabTitle(s)),1)],8,Ce),e.isClosable(s)?(t.openBlock(),t.createElementBlock("a",{key:0,class:"router-tab__item-close",type:"button",onClick:t.withModifiers(f=>e.close(s),["stop"])},null,8,Pe)):t.createCommentVNode("",!0)],42,Re))),128))]),_:1},16)]),t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",_e,[t.createVNode(m,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(f=>e.handleMenuAction(s),["prevent"])},t.toDisplayString(s.label),9,ve))),128))],4)):t.createCommentVNode("",!0)])}const U=ye(he,[["render",Ee]]),Se={class:"router-tabs","aria-hidden":"true"},I=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 D(e),(i,n)=>(t.openBlock(),t.createElementBlock("span",Se))}}),ee="tab-theme-style",te="tab-theme-primary-color",Ke="system",xe="#635bff",Ie="(prefers-color-scheme: dark)";let _=null;function ne(e){typeof document>"u"||(document.documentElement.style.setProperty("--theme-primary",e),document.documentElement.style.setProperty("--router-tab-primary",e))}function oe(e){if(typeof document>"u")return;const o=document.documentElement,i=window.matchMedia(Ie),n=()=>{o.dataset.theme=i.matches?"dark":"light"};_&&(i.removeEventListener("change",_),_=null),e==="system"?(n(),_=()=>n(),i.addEventListener("change",_)):o.dataset.theme=e}function ie(e={}){if(typeof window>"u")return;const{styleKey:o=ee,primaryKey:i=te,defaultStyle:n=Ke,defaultPrimary:r=xe}=e,d=window.localStorage.getItem(o)??n,m=window.localStorage.getItem(i)??r;oe(d),ne(m)}function Be(e,o){if(typeof window>"u")return;const i=o?.styleKey??ee;window.localStorage.setItem(i,e),oe(e)}function $e(e,o){if(typeof window>"u")return;const i=o?.primaryKey??te;window.localStorage.setItem(i,e),ne(e)}const z={install(e){if(z._installed)return;z._installed=!0,ie();const o=U.name||"RouterTab",i=I.name||"RouterTabs";e.component(o,U),e.component(i,I),i!=="router-tabs"&&e.component("router-tabs",I),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[A]},set(n){n&&e.provide(A,n)}})}};b.RouterTab=U,b.RouterTabs=I,b.default=z,b.initRouterTabsTheme=ie,b.routerTabsKey=A,b.setRouterTabsPrimary=$e,b.setRouterTabsTheme=Be,b.useRouterTabs=j,b.useRouterTabsPersistence=D,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -15,6 +15,7 @@
15
15
  v-for="tab in tabs"
16
16
  :key="tab.id"
17
17
  :class="buildTabClass(tab)"
18
+ :data-title="tabTitle(tab)"
18
19
  @click="activate(tab)"
19
20
  @auxclick.middle.prevent="close(tab)"
20
21
  @contextmenu.prevent="showContextMenu(tab, $event)"
@@ -1,13 +1,16 @@
1
1
  @use "sass:math";
2
- @use "sass:color";
2
+ @use "sass:string";
3
3
 
4
- $default-primary: #635bff;
5
- $default-border: #e2e8f0;
6
- $default-text: #1e293b;
7
- $default-bg: #ffffff;
8
- $default-dark-bg: #0f172a;
9
- $default-dark-text: #e2e8f0;
10
- $default-dark-border: rgba(148, 163, 184, 0.35);
4
+ // Fallback palette (overridden by CSS vars when present)
5
+ $primary-fallback: #635bff;
6
+ $light-bg: #ffffff;
7
+ $light-text: #1e293b;
8
+ $light-border: rgba(15, 23, 42, 0.08);
9
+
10
+ // Dark mode fallbacks
11
+ $dark-bg: #0f172a;
12
+ $dark-text: #e2e8f0;
13
+ $dark-border: rgba(226, 232, 240, 0.12);
11
14
 
12
15
  $font-size: 14px;
13
16
  $tab-trans: all 0.3s ease-in-out;
@@ -16,48 +19,50 @@ $tab-padding: 20px;
16
19
  $close-icon-margin: 4px;
17
20
  $close-icon-size: 13px;
18
21
 
19
- :root {
20
- --theme-primary: #{$default-primary};
21
- --router-tab-primary: var(--theme-primary);
22
- --router-tab-background: #{$default-bg};
23
- --router-tab-foreground: #{$default-text};
24
- --router-tab-border: #{$default-border};
25
- --router-tab-header-bg: #{$default-bg};
26
- color-scheme: light;
27
- }
28
-
29
- :root[data-theme='light'] {
30
- color-scheme: light;
31
- --router-tab-background: #{$default-bg};
32
- --router-tab-foreground: #{$default-text};
33
- --router-tab-border: #{$default-border};
34
- --router-tab-header-bg: #{$default-bg};
22
+ /// Utility to fetch a CSS variable with a graceful fallback.
23
+ @function css-var($name, $fallback) {
24
+ @return string.unquote("var(#{$name}, #{$fallback})");
35
25
  }
36
26
 
37
- :root[data-theme='dark'] {
38
- color-scheme: dark;
39
- --router-tab-background: #{$default-dark-bg};
40
- --router-tab-foreground: #{$default-dark-text};
41
- --router-tab-border: #{$default-dark-border};
42
- --router-tab-header-bg: #{color.adjust($default-dark-bg, $lightness: 5%)};
27
+ /// Mixin for reduced motion preferences
28
+ @mixin reduced-motion {
29
+ @media (prefers-reduced-motion: reduce) {
30
+ @content;
31
+ }
43
32
  }
44
33
 
45
- @media (prefers-color-scheme: dark) {
46
- :root:not([data-theme]) {
47
- color-scheme: dark;
48
- --router-tab-background: #{$default-dark-bg};
49
- --router-tab-foreground: #{$default-dark-text};
50
- --router-tab-border: #{$default-dark-border};
51
- --router-tab-header-bg: #{color.adjust($default-dark-bg, $lightness: 5%)};
34
+ /// Mixin for dark theme styles - works with data-theme="dark"
35
+ @mixin dark-theme {
36
+ :root[data-theme="dark"] & {
37
+ @content;
52
38
  }
53
39
  }
54
40
 
55
41
  .router-tab {
42
+ $bg: css-var(--router-tab-background, css-var(--theme-background, $light-bg));
43
+ $fg: css-var(--router-tab-foreground, css-var(--theme-foreground, $light-text));
44
+ $border: css-var(--router-tab-border, css-var(--theme-border, $light-border));
45
+ $primary: css-var(--router-tab-primary, css-var(--theme-primary, $primary-fallback));
46
+ $header-bg: css-var(--router-tab-header-bg, $bg);
47
+ $tooltip-bg: css-var(--router-tab-tooltip-background, rgba(15, 23, 42, 0.88));
48
+ $tooltip-fg: css-var(--router-tab-tooltip-foreground, #ffffff);
49
+ $tooltip-shadow: css-var(--router-tab-tooltip-shadow, 0 8px 24px rgba(15, 23, 42, 0.18));
50
+
56
51
  display: flex;
57
52
  flex-direction: column;
58
53
  min-height: 300px;
59
- background-color: var(--router-tab-background);
60
- color: var(--router-tab-foreground);
54
+ background-color: transparent;
55
+ color: inherit;
56
+
57
+ // Dark theme default values
58
+ @include dark-theme {
59
+ --router-tab-background: #{$dark-bg};
60
+ --router-tab-foreground: #{$dark-text};
61
+ --router-tab-border: #{$dark-border};
62
+ --router-tab-tooltip-background: rgba(226, 232, 240, 0.95);
63
+ --router-tab-tooltip-foreground: #{$dark-bg};
64
+ --router-tab-tooltip-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
65
+ }
61
66
 
62
67
  &__header {
63
68
  position: relative;
@@ -66,9 +71,13 @@ $close-icon-size: 13px;
66
71
  flex: none;
67
72
  box-sizing: border-box;
68
73
  height: $hd-height;
69
- border-bottom: 1px solid var(--router-tab-border);
70
- background-color: var(--router-tab-header-bg);
74
+ border-bottom: 1px solid $border;
75
+ background-color: $header-bg;
71
76
  transition: all 0.2s ease-in-out;
77
+
78
+ @include reduced-motion {
79
+ transition: none;
80
+ }
72
81
  }
73
82
 
74
83
  &__scroll {
@@ -85,6 +94,7 @@ $close-icon-size: 13px;
85
94
  &.is-mobile {
86
95
  overflow-x: auto;
87
96
  overflow-y: hidden;
97
+ -webkit-overflow-scrolling: touch;
88
98
  }
89
99
  }
90
100
  }
@@ -102,6 +112,10 @@ $close-icon-size: 13px;
102
112
  opacity: 0;
103
113
  transition: opacity 0.3s ease-in-out;
104
114
 
115
+ @include reduced-motion {
116
+ transition: none;
117
+ }
118
+
105
119
  .router-tab__scroll:hover &,
106
120
  &.is-dragging {
107
121
  opacity: 1;
@@ -116,11 +130,29 @@ $close-icon-size: 13px;
116
130
  border-radius: $h;
117
131
  transition: background-color 0.3s ease-in-out;
118
132
 
133
+ @include reduced-motion {
134
+ transition: none;
135
+ }
136
+
119
137
  &:hover,
120
138
  .router-tab__scrollbar.is-dragging & {
121
139
  background-color: rgba(0, 0, 0, 0.2);
122
140
  }
123
141
  }
142
+
143
+ // Dark theme scrollbar
144
+ @include dark-theme {
145
+ background-color: rgba(255, 255, 255, 0.15);
146
+
147
+ .router-tab__scrollbar-thumb {
148
+ background-color: rgba(255, 255, 255, 0.2);
149
+
150
+ &:hover,
151
+ &.is-dragging {
152
+ background-color: rgba(255, 255, 255, 0.35);
153
+ }
154
+ }
155
+ }
124
156
  }
125
157
 
126
158
  &__nav {
@@ -141,7 +173,7 @@ $close-icon-size: 13px;
141
173
  padding: 0 $tab-padding;
142
174
  color: inherit;
143
175
  font-size: $font-size;
144
- border: 1px solid var(--router-tab-border);
176
+ border: 1px solid $border;
145
177
  border-left: none;
146
178
  transform-origin: left bottom;
147
179
  cursor: pointer;
@@ -149,35 +181,64 @@ $close-icon-size: 13px;
149
181
  user-select: none;
150
182
  background-color: transparent;
151
183
 
184
+ @include reduced-motion {
185
+ transition: none;
186
+ }
187
+
152
188
  &:first-child {
153
- border-left: 1px solid var(--router-tab-border);
189
+ border-left: 1px solid $border;
190
+ }
191
+
192
+ // Keyboard focus styles
193
+ &:focus-visible {
194
+ outline: 2px solid $primary;
195
+ outline-offset: -2px;
196
+ z-index: 1;
154
197
  }
155
198
 
156
199
  &.is-contextmenu {
157
- color: var(--router-tab-primary);
200
+ color: $primary;
158
201
  }
159
202
 
160
203
  &.is-drag-over {
161
204
  background: rgba(0, 0, 0, 0.05);
162
205
  transition: background 0.15s ease;
206
+
207
+ @include dark-theme {
208
+ background: rgba(255, 255, 255, 0.08);
209
+ }
163
210
  }
164
211
 
165
212
  &-title {
166
- min-width: 30px;
167
- max-width: 120px;
213
+ position: relative;
214
+ display: inline-flex;
215
+ align-items: center;
216
+ gap: var(--router-tab-title-gap, 0);
217
+ min-width: var(--router-tab-title-min-width, 30px);
218
+ max-width: var(--router-tab-title-max-width, 120px);
219
+ color: var(--router-tab-title-color, inherit);
220
+ font-weight: var(--router-tab-title-font-weight, inherit);
221
+ letter-spacing: var(--router-tab-title-letter-spacing, normal);
222
+ text-transform: var(--router-tab-title-transform, none);
168
223
  overflow: hidden;
169
224
  white-space: nowrap;
170
225
  text-overflow: ellipsis;
226
+ transition: max-width 0.3s ease-in-out;
227
+
228
+ @include reduced-motion {
229
+ transition: none;
230
+ }
171
231
  }
172
232
 
173
233
  &-icon {
174
234
  margin-right: 5px;
175
235
  font-size: 16px;
236
+ flex-shrink: 0;
176
237
  }
177
238
 
178
239
  &:hover,
179
240
  &.is-active {
180
- color: var(--router-tab-primary);
241
+ color: $primary;
181
242
 
182
243
  &.is-closable {
183
244
  padding: 0 ($tab-padding - math.div($close-icon-size + $close-icon-margin, 2));
@@ -194,8 +255,94 @@ $close-icon-size: 13px;
194
255
  }
195
256
  }
196
257
 
258
+ &:hover {
259
+ .router-tab__item-title {
260
+ max-width: var(--router-tab-title-hover-max-width, 220px);
261
+ }
262
+ }
263
+
197
264
  &.is-active {
198
- border-bottom-color: var(--router-tab-background);
265
+ border-bottom-color: $bg;
266
+ // Fallback for browsers without color-mix support
267
+ background: rgba(99, 91, 255, 0.12);
268
+ background: color-mix(in srgb, $primary 12%, $bg 88%);
269
+ box-shadow: inset 0 -2px 0 rgba(99, 91, 255, 0.65);
270
+ box-shadow: inset 0 -2px 0 color-mix(in srgb, $primary 65%, transparent);
271
+ color: color-mix(in srgb, $primary 80%, $fg 20%);
272
+
273
+ &::after {
274
+ content: '';
275
+ position: absolute;
276
+ left: 16px;
277
+ right: 16px;
278
+ bottom: 6px;
279
+ height: 2px;
280
+ border-radius: 999px;
281
+ background: $primary;
282
+ opacity: 0.6;
283
+ pointer-events: none;
284
+ }
285
+ }
286
+
287
+ &[data-title] {
288
+ &::after {
289
+ content: attr(data-title);
290
+ position: absolute;
291
+ left: 50%;
292
+ bottom: calc(100% + 8px);
293
+ display: inline-block;
294
+ max-width: 320px;
295
+ padding: 4px 10px;
296
+ border-radius: 6px;
297
+ background: $tooltip-bg;
298
+ color: $tooltip-fg;
299
+ font-size: 12px;
300
+ line-height: 1.3;
301
+ text-align: center;
302
+ white-space: normal;
303
+ word-break: break-word;
304
+ transform: translate(-50%, 4px);
305
+ box-shadow: $tooltip-shadow;
306
+ opacity: 0;
307
+ pointer-events: none;
308
+ transition: opacity 0.2s ease, transform 0.2s ease;
309
+ z-index: 10;
310
+
311
+ @include reduced-motion {
312
+ transition: opacity 0.2s ease;
313
+ transform: translate(-50%, 0);
314
+ }
315
+ }
316
+
317
+ &::before {
318
+ content: '';
319
+ position: absolute;
320
+ left: 50%;
321
+ bottom: calc(100% + 4px);
322
+ width: 8px;
323
+ height: 8px;
324
+ background: $tooltip-bg;
325
+ transform: translate(-50%, 6px) rotate(45deg);
326
+ opacity: 0;
327
+ pointer-events: none;
328
+ transition: opacity 0.2s ease, transform 0.2s ease;
329
+ z-index: 9;
330
+
331
+ @include reduced-motion {
332
+ transition: opacity 0.2s ease;
333
+ transform: translate(-50%, 0) rotate(45deg);
334
+ }
335
+ }
336
+
337
+ &:hover::after {
338
+ opacity: 1;
339
+ transform: translate(-50%, 0);
340
+ }
341
+
342
+ &:hover::before {
343
+ opacity: 1;
344
+ transform: translate(-50%, 0) rotate(45deg);
345
+ }
199
346
  }
200
347
 
201
348
  &-close {
@@ -212,6 +359,17 @@ $close-icon-size: 13px;
212
359
  transition: $tab-trans;
213
360
  background: transparent;
214
361
  border: none;
362
+ flex-shrink: 0;
363
+
364
+ @include reduced-motion {
365
+ transition: none;
366
+ }
367
+
368
+ // Keyboard focus
369
+ &:focus-visible {
370
+ outline: 2px solid $primary;
371
+ outline-offset: 2px;
372
+ }
215
373
 
216
374
  &::before,
217
375
  &::after {
@@ -225,6 +383,10 @@ $close-icon-size: 13px;
225
383
  background-color: currentColor;
226
384
  transition: background-color 0.2s ease-in-out;
227
385
  content: '';
386
+
387
+ @include reduced-motion {
388
+ transition: none;
389
+ }
228
390
  }
229
391
 
230
392
  &::before {
@@ -236,13 +398,23 @@ $close-icon-size: 13px;
236
398
  }
237
399
 
238
400
  &:hover {
239
- background-color: color-mix(in srgb, var(--router-tab-primary) 40%, #ffffff 60%);
401
+ // Fallback for browsers without color-mix
402
+ background-color: rgba(99, 91, 255, 0.4);
403
+ background-color: color-mix(in srgb, $primary 40%, #ffffff 60%);
240
404
 
241
405
  &::before,
242
406
  &::after {
243
407
  background-color: #fff;
244
408
  }
245
409
  }
410
+
411
+ // Dark theme close button
412
+ @include dark-theme {
413
+ &:hover {
414
+ background-color: rgba(99, 91, 255, 0.6);
415
+ background-color: color-mix(in srgb, $primary 60%, transparent 40%);
416
+ }
417
+ }
246
418
  }
247
419
  }
248
420
 
@@ -252,11 +424,16 @@ $close-icon-size: 13px;
252
424
  min-width: 140px;
253
425
  padding: 8px 0;
254
426
  font-size: $font-size;
255
- background: var(--router-tab-background);
256
- border: 1px solid var(--router-tab-border);
427
+ background: $bg;
428
+ border: 1px solid $border;
257
429
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
258
430
  border-radius: 8px;
259
431
 
432
+ // Dark theme context menu
433
+ @include dark-theme {
434
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
435
+ }
436
+
260
437
  a,
261
438
  button {
262
439
  display: block;
@@ -271,26 +448,87 @@ $close-icon-size: 13px;
271
448
  font: inherit;
272
449
  transition: background-color 0.2s ease-in-out;
273
450
 
451
+ @include reduced-motion {
452
+ transition: none;
453
+ }
454
+
455
+ // Keyboard focus
456
+ &:focus-visible {
457
+ outline: 2px solid $primary;
458
+ outline-offset: -2px;
459
+ }
460
+
274
461
  &[aria-disabled='true'] {
275
462
  color: rgba(148, 163, 184, 0.6);
276
463
  pointer-events: none;
464
+ cursor: not-allowed;
465
+
466
+ @include dark-theme {
467
+ color: rgba(226, 232, 240, 0.3);
468
+ }
277
469
  }
278
470
 
279
- &:hover,
280
- &:focus-visible {
281
- background: color-mix(in srgb, var(--router-tab-primary) 20%, #ffffff 80%);
282
- color: var(--router-tab-primary);
471
+ &:hover:not([aria-disabled='true']),
472
+ &:focus-visible:not([aria-disabled='true']) {
473
+ // Fallback for browsers without color-mix
474
+ background: rgba(99, 91, 255, 0.2);
475
+ background: color-mix(in srgb, $primary 20%, #ffffff 80%);
476
+ color: $primary;
477
+
478
+ @include dark-theme {
479
+ background: color-mix(in srgb, $primary 25%, transparent 75%);
480
+ }
283
481
  }
284
482
  }
285
483
  }
286
484
  }
287
485
 
288
486
  .router-tab__container {
487
+ padding: 1rem;
289
488
  position: relative;
290
489
  flex: 1 1 auto;
291
- background-color: var(--router-tab-background);
490
+ background-color: transparent;
292
491
  }
293
492
 
294
493
  .router-tab__item.is-active + .router-tab__item {
295
- border-left-color: var(--router-tab-border);
494
+ border-left-color: css-var(--router-tab-border, css-var(--theme-border, $light-border));
495
+ }
496
+
497
+ // Container query support (progressive enhancement)
498
+ @supports (container-type: inline-size) {
499
+ .router-tab {
500
+ container-type: inline-size;
501
+ container-name: router-tab;
502
+ }
503
+
504
+ @container router-tab (max-width: 600px) {
505
+ .router-tab__item {
506
+ padding: 0 12px;
507
+
508
+ &-title {
509
+ max-width: var(--router-tab-title-max-width, 80px);
510
+ }
511
+
512
+ &:hover .router-tab__item-title {
513
+ max-width: var(--router-tab-title-hover-max-width, 150px);
514
+ }
515
+ }
516
+ }
296
517
  }
518
+
519
+ // High contrast mode support
520
+ @media (prefers-contrast: high) {
521
+ .router-tab {
522
+ &__item {
523
+ border-width: 2px;
524
+
525
+ &.is-active {
526
+ border-bottom-width: 3px;
527
+ }
528
+
529
+ &:focus-visible {
530
+ outline-width: 3px;
531
+ }
532
+ }
533
+ }
534
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",