vue3-router-tab 1.1.2 → 1.1.4

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,ee){"use strict";function te(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 w(e,i){const a=e.resolve(i);if(!a||!a.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(i)}`);return a}const ne={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 R(e){const i=e.meta?.key;if(typeof i=="function"){const a=i(e);if(typeof a=="string"&&a.length)return a}else if(typeof i=="string"&&i.length){const a=ne[i.toLowerCase()];return a?a(e):i}return e.fullPath}function I(e,i){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:i}function N(e,i){const a=e.meta?.reuse;return typeof a=="boolean"?a:i}function H(e){const i=e.meta??{},a={};return"title"in i&&(a.title=i.title),"tips"in i&&(a.tips=i.tips),"icon"in i&&(a.icon=i.icon),"closable"in i&&(a.closable=i.closable),"tabClass"in i&&(a.tabClass=i.tabClass),"target"in i&&(a.target=i.target),"href"in i&&(a.href=i.href),a}function B(e,i,a){const n=H(e);return{id:R(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:I(e,a),reusable:N(e,!1),closable:n.closable??!0,...n,...i}}function M(e,i,a,n){if(!e.find(b=>b.id===i.id)){if(a==="next"&&n){const b=e.findIndex(p=>p.id===n);if(b>-1){e.splice(b+1,0,i);return}}e.push(i)}}function Y(e,i,a){if(!i||i<=0)return;const n=e.filter(c=>c.alive);for(;n.length>i;){const c=n.shift();if(!c||c.id===a)continue;const b=e.findIndex(p=>p.id===c.id);b>-1&&(e[b].alive=!1)}}function oe(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ie(e){const i={};return"title"in e&&(i.title=e.title),"tips"in e&&(i.tips=e.tips),"icon"in e&&(i.icon=e.icon),"tabClass"in e&&(i.tabClass=e.tabClass),"closable"in e&&(i.closable=e.closable),i}function ae(e,i={}){const a=te(i),n=t.reactive([]),c=t.ref(null),b=t.shallowRef(),p=t.ref(null),s=t.computed(()=>n.filter(l=>l.alive).map(l=>l.id));let f=!1;function T(l){const r=typeof l.matched=="object"?l:w(e,l);return{key:R(r),fullPath:r.fullPath,alive:I(r,a.keepAlive),reusable:N(r,!1),matched:r}}function A(l){const r=R(l);let u=n.find(m=>m.id===r);return u?(u.fullPath=l.fullPath,u.to=l.fullPath,u.matched=l,u.alive=I(l,a.keepAlive),u.reusable=N(l,u.reusable),Object.assign(u,H(l)),u):(u=B(l,{},a.keepAlive),M(n,u,a.appendPosition,c.value),Y(n,a.maxAlive,c.value),u)}async function K(l,r=!1,u=!0){const m=w(e,l),C=R(m),o=c.value===C;u==="sameTab"&&(u=o),u&&await k(C,!0),await e[r?"replace":"push"](m),o&&await P()}function S(l){const r=n.findIndex(m=>m.id===l),u=n[r]||n[r-1]||n[0];return u?u.to:a.defaultRoute}async function v(l=c.value,r={}){if(l){if(!r.force&&a.keepLastTab&&n.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");if(await V(l,{force:r.force}),r.redirect!==null)if(c.value===l){const u=r.redirect??S(l);u&&await e.replace(u)}else r.redirect&&await e.replace(r.redirect)}}async function V(l,r={}){const u=n.findIndex(m=>m.id===l);u!==-1&&(n.splice(u,1),p.value===l&&(p.value=null),c.value===l&&(c.value=null,b.value=void 0))}async function k(l=c.value??void 0,r=!1){l&&(p.value=l,await t.nextTick(),r||await t.nextTick(),p.value=null)}async function O(l=!1){for(const r of n)await k(r.id,l)}async function U(l=a.defaultRoute){n.splice(0,n.length),c.value=null,b.value=void 0;for(const r of a.initialTabs){const u=w(e,r.to),m=B(u,r,a.keepAlive);n.push(m)}await e.replace(l)}async function P(){const l=c.value;l&&await k(l,!0)}function F(l){return typeof l.matched=="object"?R(l):R(w(e,l))}function q(){const l=n.find(r=>r.id===c.value);return{tabs:n.map(oe),active:l?l.to:null}}async function x(l){f=!0,n.splice(0,n.length),c.value=null,b.value=void 0;const r=l?.tabs??[];for(const m of r)try{const C=w(e,m.to),o=ie(m),d=B(C,o,a.keepAlive);M(n,d,"last",null)}catch{}f=!1;const u=l?.active??r[r.length-1]?.to??a.defaultRoute;if(u)try{await e.replace(u)}catch{}}return t.watch(()=>e.currentRoute.value,l=>{if(f)return;const r=A(l);c.value=r.id,b.value=r,Y(n,a.maxAlive,c.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(l=>{const r=w(e,l.to),u=B(r,l,a.keepAlive);M(n,u,"last",null)}),{options:a,tabs:n,activeId:c,current:b,includeKeys:s,refreshingKey:p,openTab:K,closeTab:v,removeTab:V,refreshTab:k,refreshAll:O,reset:U,reload:P,getRouteKey:F,matchRoute:T,snapshot:q,hydrate:x}}function W(e){return e?typeof e=="string"?{name:e}:e:{}}const _=Symbol("RouterTabsContext"),$="router-tabs:snapshot";function L(e={}){const{optional:i=!1}=e,a=t.inject(_,null);if(a)return a;const n=t.inject("$tabs",null);if(n)return n;const b=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(b)return b;if(!i)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const le=864e5;function se(e){if(typeof document>"u")return null;const i=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const n of a)if(n.startsWith(i))return decodeURIComponent(n.slice(i.length));return null}function X(e,i,a){if(typeof document>"u")return;const{expiresInDays:n=7,path:c="/",domain:b,secure:p,sameSite:s="lax"}=a,f=[`${encodeURIComponent(e)}=${encodeURIComponent(i)}`];if(n!==1/0){const T=new Date(Date.now()+n*le).toUTCString();f.push(`Expires=${T}`)}c&&f.push(`Path=${c}`),b&&f.push(`Domain=${b}`),p&&f.push("Secure"),s&&f.push(`SameSite=${s.charAt(0).toUpperCase()}${s.slice(1)}`),document.cookie=f.join("; ")}function Q(e,i){if(typeof document>"u")return;const{path:a="/",domain:n}=i,c=[`${encodeURIComponent(e)}=`];c.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),a&&c.push(`Path=${a}`),n&&c.push(`Domain=${n}`),document.cookie=c.join("; ")}const re=e=>JSON.stringify(e??null),ce=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function j(e={}){const{cookieKey:i=$,serialize:a=re,deserialize:n=ce}=e,c=L({optional:!0}),b=t.ref(!0),p=s=>{t.onMounted(async()=>{const f=n(se(i));if(f&&f.tabs?.length)try{b.value=!0,await s.hydrate(f)}finally{b.value=!1}else try{b.value=!0;const A=e.fallbackRoute??s.options.defaultRoute;await s.reset(A)}finally{b.value=!1}const T=s.snapshot();T.tabs.length?X(i,a(T),e):Q(i,e),b.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(b.value)return;const f=s.snapshot();f.tabs.length?X(i,a(f),e):Q(i,e)},{deep:!0})};c?p(c):t.onMounted(()=>{const s=L({optional:!0});s&&p(s)})}const ue=t.defineComponent({name:"RouterTab",components:{RouterView:ee.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:$},persistence:{type:Object,default:null}},setup(e){const i=t.getCurrentInstance();if(!i)throw new Error("[RouterTab] component must be used within a Vue application context.");const a=i.appContext.app.config.globalProperties.$router;if(!a)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const n=ae(a,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(_,n),i.appContext.config.globalProperties.$tabs=n;const c=t.computed(()=>!!i?.slots?.default);if(e.cookieKey!==null||e.persistence){const o={...e.persistence??{}};e.cookieKey!==null?o.cookieKey=e.cookieKey||$:o.cookieKey||(o.cookieKey=$),j(o)}const b=t.computed(()=>W(e.tabTransition)),p=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(o){return n.tabs.findIndex(d=>d.id===o)}function A(o){const d=T(o.id);return d>0?n.tabs.slice(0,d):[]}function K(o){const d=T(o.id);return d>-1?n.tabs.slice(d+1):[]}function S(o){return n.tabs.filter(d=>d.id!==o.id)}async function v(o,d){const h=o.filter(g=>g.closable!==!1);if(h.length){for(const g of h)n.activeId.value===g.id?await n.closeTab(g.id,{redirect:d.to,force:!0}):await n.removeTab(g.id,{force:!0});n.activeId.value!==d.id&&await n.openTab(d.to,!0,!1)}}const V={refresh:{label:"Refresh",handler:async({target:o})=>{await n.refreshTab(o.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await n.refreshAll(!0)}},close:{label:"Close",handler:async({target:o})=>{await n.closeTab(o.id)},enable:({target:o})=>x(o)},closeLefts:{label:"Close to the Left",handler:async({target:o})=>{await v(A(o),o)},enable:({target:o})=>A(o).some(d=>d.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:o})=>{await v(K(o),o)},enable:({target:o})=>K(o).some(d=>d.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:o})=>{await v(S(o),o)},enable:({target:o})=>S(o).some(d=>d.closable!==!1)}};function k(){s.visible=!1,s.target=null}function O(o,d){e.contextmenu&&(s.visible=!0,s.target=o,s.position.x=d.clientX,s.position.y=d.clientY,document.addEventListener("click",k,{once:!0}))}function U(o,d){const h=typeof o=="string"?{id:o}:o,g=V[h.id],Ae=h.label??g?.label??String(h.id),G=h.visible??g?.visible??!0;if(!(typeof G=="function"?G(d):G!==!1))return null;const J=h.enable??g?.enable??!0,_e=typeof J=="function"?J(d):J!==!1,Z=h.handler??g?.handler;if(!Z)return null;const ve=async()=>{await Promise.resolve(Z(d))};return{id:String(h.id),label:Ae,disabled:!_e,action:ve}}const P=t.computed(()=>{if(!s.visible||!s.target||e.contextmenu===!1)return[];const o=Array.isArray(e.contextmenu)?e.contextmenu:f,d={target:s.target,controller:n};return o.map(h=>U(h,d)).filter(h=>!!h)});async function F(o){o.disabled||(k(),await o.action())}function q(o){return typeof o.title=="string"?o.title:Array.isArray(o.title)&&o.title.length?String(o.title[0]):o.fullPath}function x(o){return!(o.closable===!1||n.options.keepLastTab&&n.tabs.length<=1)}async function l(o){await n.closeTab(o.id)}function r(o){n.activeId.value!==o.id&&n.openTab(o.to,!1)}function u(o){return["router-tab__item",{"is-active":n.activeId.value===o.id,"is-closable":x(o)},o.tabClass]}function m(o){return n.refreshingKey.value===n.getRouteKey(o)}t.onMounted(()=>{document.addEventListener("keydown",k)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",k),i.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,o=>{n.options.keepAlive=o}),t.watch(()=>n.activeId.value,()=>k()),t.watch(()=>e.contextmenu,o=>{o||k()}),t.watch(()=>P.value.length,o=>{s.visible&&o===0&&k()});const C=n.includeKeys;return{controller:n,tabs:n.tabs,includeKeys:C,tabTransitionProps:b,pageTransitionProps:p,buildTabClass:u,activate:r,close:l,context:s,menuItems:P,handleMenuAction:F,showContextMenu:O,hideContextMenu:k,tabTitle:q,isClosable:x,isRefreshing:m,hasCustomSlot:c}}}),fe=(e,i)=>{const a=e.__vccOpts||e;for(const[n,c]of i)a[n]=c;return a},de={class:"router-tab"},be={class:"router-tab__header"},pe={class:"router-tab__slot-start"},me={class:"router-tab__scroll"},he=["onClick","onAuxclick","onContextmenu"],ye=["title"],ke=["onClick"],ge={class:"router-tab__slot-end"},Te={class:"router-tab__container"},Ce=["aria-disabled","onClick"];function we(e,i,a,n,c,b){const p=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",de,[t.createElementVNode("header",be,[t.createElementVNode("div",pe,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",me,[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,ye),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,ke)):t.createCommentVNode("",!0)],42,he))),128))]),_:1},16)]),t.createElementVNode("div",ge,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Te,[t.createVNode(p,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,Ce))),128))],4)):t.createCommentVNode("",!0)])}const z=fe(ue,[["render",we]]),Re={class:"router-tabs","aria-hidden":"true"},E=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 j(e),(a,n)=>(t.openBlock(),t.createElementBlock("span",Re))}}),D={install(e){if(D._installed)return;D._installed=!0;const i=z.name||"RouterTab",a=E.name||"RouterTabs";e.component(i,z),e.component(a,E),a!=="router-tabs"&&e.component("router-tabs",E),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[_]},set(n){n&&e.provide(_,n)}})}};y.RouterTab=z,y.RouterTabs=E,y.default=D,y.routerTabsKey=_,y.useRouterTabs=L,y.useRouterTabsPersistence=j,Object.defineProperties(y,{__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"}})}));
package/index.d.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  RouterTabsSnapshotTab,
14
14
  RouterTabsPersistenceOptions
15
15
  } from './lib/core/types'
16
+ import type { RouterTabsThemeOptions } from './lib/theme'
16
17
 
17
18
  export type {
18
19
  TabRecord,
@@ -25,7 +26,8 @@ export type {
25
26
  RouterTabsMenuPreset,
26
27
  RouterTabsSnapshot,
27
28
  RouterTabsSnapshotTab,
28
- RouterTabsPersistenceOptions
29
+ RouterTabsPersistenceOptions,
30
+ RouterTabsThemeOptions
29
31
  }
30
32
 
31
33
  export declare const routerTabsKey: import('vue').InjectionKey<RouterTabsContext>
@@ -34,6 +36,10 @@ export declare function useRouterTabs(options?: { optional?: boolean }): RouterT
34
36
 
35
37
  export declare function useRouterTabsPersistence(options?: RouterTabsPersistenceOptions): void
36
38
 
39
+ export declare function initRouterTabsTheme(options?: RouterTabsThemeOptions): void
40
+ export declare function setRouterTabsTheme(style: 'light' | 'dark' | 'system', options?: RouterTabsThemeOptions): void
41
+ export declare function setRouterTabsPrimary(color: string, options?: RouterTabsThemeOptions): void
42
+
37
43
  export declare const RouterTabs: DefineComponent<RouterTabsPersistenceOptions, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<RouterTabsPersistenceOptions>, {}>
38
44
 
39
45
  export declare const RouterTab: DefineComponent<{
package/lib/index.ts CHANGED
@@ -4,19 +4,43 @@ import RouterTabsComponent from './components/RouterTabs.vue'
4
4
  import { routerTabsKey } from './constants'
5
5
  import useRouterTabs from './useRouterTabs'
6
6
  import { useRouterTabsPersistence } from './persistence'
7
+ import {
8
+ initRouterTabsTheme,
9
+ setRouterTabsPrimary,
10
+ setRouterTabsTheme
11
+ } from './theme'
7
12
 
8
13
  import type { RouterTabsContext } from './core/types'
9
14
 
10
- export type { TabRecord, TabInput, RouterTabsOptions, CloseTabOptions, RouterTabsPersistenceOptions } from './core/types'
15
+ export type {
16
+ TabRecord,
17
+ TabInput,
18
+ RouterTabsOptions,
19
+ CloseTabOptions,
20
+ RouterTabsPersistenceOptions
21
+ } from './core/types'
11
22
 
12
- export { routerTabsKey, useRouterTabs, useRouterTabsPersistence, RouterTab, RouterTabsComponent as RouterTabs }
23
+ export type { RouterTabsThemeOptions } from './theme'
24
+
25
+ export {
26
+ routerTabsKey,
27
+ useRouterTabs,
28
+ useRouterTabsPersistence,
29
+ RouterTab,
30
+ RouterTabsComponent as RouterTabs,
31
+ initRouterTabsTheme,
32
+ setRouterTabsTheme,
33
+ setRouterTabsPrimary
34
+ }
13
35
 
14
36
  import "./scss/index.scss";
15
37
 
16
38
  const plugin: Plugin = {
17
39
  install(app: App) {
18
40
  if ((plugin as any)._installed) return
19
- ;(plugin as any)._installed = true
41
+ ; (plugin as any)._installed = true
42
+
43
+ initRouterTabsTheme()
20
44
 
21
45
  const componentName = RouterTab.name || 'RouterTab'
22
46
  const persistenceComponentName = RouterTabsComponent.name || 'RouterTabs'
@@ -1,31 +1,35 @@
1
1
  @use "sass:math";
2
- @use "sass:color";
3
2
 
4
- // ----------------------
5
- // Variables
6
- // ----------------------
7
- $color-primary: #42b983;
8
- $border-color: #eaecef;
9
- $text-color: #4d4d4d;
3
+ // Fallback palette (overridden by CSS vars when present)
4
+ $primary-fallback: #635bff;
5
+ $light-bg: #ffffff;
6
+ $light-text: #1e293b;
7
+ $light-border: rgba(15, 23, 42, 0.08);
10
8
 
11
9
  $font-size: 14px;
12
-
13
- $border: 1px solid $border-color;
14
10
  $tab-trans: all 0.3s ease-in-out;
15
-
16
11
  $hd-height: 40px;
17
-
18
12
  $tab-padding: 20px;
19
13
  $close-icon-margin: 4px;
20
14
  $close-icon-size: 13px;
21
15
 
22
- // ----------------------
23
- // Router Tab Component
24
- // ----------------------
16
+ /// Utility to fetch a CSS variable with a graceful fallback.
17
+ @function css-var($name, $fallback) {
18
+ @return unquote("var(#{$name}, #{$fallback})");
19
+ }
20
+
25
21
  .router-tab {
22
+ $bg: css-var(--router-tab-background, css-var(--theme-background, $light-bg));
23
+ $fg: css-var(--router-tab-foreground, css-var(--theme-foreground, $light-text));
24
+ $border: css-var(--router-tab-border, css-var(--theme-border, $light-border));
25
+ $primary: css-var(--router-tab-primary, css-var(--theme-primary, $primary-fallback));
26
+ $header-bg: css-var(--router-tab-header-bg, $bg);
27
+
26
28
  display: flex;
27
29
  flex-direction: column;
28
30
  min-height: 300px;
31
+ background-color: $bg;
32
+ color: $fg;
29
33
 
30
34
  &__header {
31
35
  position: relative;
@@ -34,7 +38,8 @@ $close-icon-size: 13px;
34
38
  flex: none;
35
39
  box-sizing: border-box;
36
40
  height: $hd-height;
37
- border-bottom: 1px solid $border-color;
41
+ border-bottom: 1px solid $border;
42
+ background-color: $header-bg;
38
43
  transition: all 0.2s ease-in-out;
39
44
  }
40
45
 
@@ -85,7 +90,7 @@ $close-icon-size: 13px;
85
90
 
86
91
  &:hover,
87
92
  .router-tab__scrollbar.is-dragging & {
88
- background-color: rgba($color-primary, 0.8);
93
+ background-color: rgba(0, 0, 0, 0.2);
89
94
  }
90
95
  }
91
96
  }
@@ -106,31 +111,48 @@ $close-icon-size: 13px;
106
111
  flex: none;
107
112
  align-items: center;
108
113
  padding: 0 $tab-padding;
109
- color: $text-color;
114
+ color: inherit;
110
115
  font-size: $font-size;
111
- border: $border;
116
+ border: 1px solid $border;
112
117
  border-left: none;
113
118
  transform-origin: left bottom;
114
119
  cursor: pointer;
115
120
  transition: $tab-trans;
116
121
  user-select: none;
122
+ background-color: transparent;
117
123
 
118
124
  &:first-child {
119
- border-left: $border;
125
+ border-left: 1px solid $border;
120
126
  }
121
127
 
122
128
  &.is-contextmenu {
123
- color: #000;
129
+ color: $primary;
130
+ }
131
+
132
+ &.is-drag-over {
133
+ background: rgba(0, 0, 0, 0.05);
134
+ transition: background 0.15s ease;
135
+ }
136
+
137
+ &-title {
138
+ min-width: 30px;
139
+ max-width: 120px;
140
+ overflow: hidden;
141
+ white-space: nowrap;
142
+ text-overflow: ellipsis;
143
+ }
144
+
145
+ &-icon {
146
+ margin-right: 5px;
147
+ font-size: 16px;
124
148
  }
125
149
 
126
150
  &:hover,
127
151
  &.is-active {
128
- color: $color-primary;
152
+ color: $primary;
129
153
 
130
154
  &.is-closable {
131
- padding: 0 (
132
- $tab-padding - math.div($close-icon-size + $close-icon-margin, 2)
133
- );
155
+ padding: 0 ($tab-padding - math.div($close-icon-size + $close-icon-margin, 2));
134
156
  }
135
157
 
136
158
  .router-tab__item-close {
@@ -139,31 +161,13 @@ $close-icon-size: 13px;
139
161
 
140
162
  &::before,
141
163
  &::after {
142
- border-color: $color-primary;
164
+ background-color: #fff;
143
165
  }
144
166
  }
145
167
  }
146
168
 
147
169
  &.is-active {
148
- border-bottom-color: #fff;
149
- }
150
-
151
- &.is-drag-over {
152
- background: rgba(0, 0, 0, 0.05);
153
- transition: background 0.15s ease;
154
- }
155
-
156
- &-title {
157
- min-width: 30px;
158
- max-width: 100px;
159
- overflow: hidden;
160
- white-space: nowrap;
161
- text-overflow: ellipsis;
162
- }
163
-
164
- &-icon {
165
- margin-right: 5px;
166
- font-size: 16px;
170
+ border-bottom-color: $bg;
167
171
  }
168
172
 
169
173
  &-close {
@@ -178,6 +182,8 @@ $close-icon-size: 13px;
178
182
  border-radius: 50%;
179
183
  cursor: pointer;
180
184
  transition: $tab-trans;
185
+ background: transparent;
186
+ border: none;
181
187
 
182
188
  &::before,
183
189
  &::after {
@@ -188,9 +194,9 @@ $close-icon-size: 13px;
188
194
  width: $inner;
189
195
  height: 1px;
190
196
  margin-left: math.div(-$inner, 2);
191
- background-color: $text-color;
197
+ background-color: currentColor;
192
198
  transition: background-color 0.2s ease-in-out;
193
- content: "";
199
+ content: '';
194
200
  }
195
201
 
196
202
  &::before {
@@ -202,7 +208,8 @@ $close-icon-size: 13px;
202
208
  }
203
209
 
204
210
  &:hover {
205
- background-color: color.mix($text-color, #fff, 50%);
211
+ background-color: color-mix(in srgb, $primary 40%, #ffffff 60%);
212
+
206
213
  &::before,
207
214
  &::after {
208
215
  background-color: #fff;
@@ -211,115 +218,52 @@ $close-icon-size: 13px;
211
218
  }
212
219
  }
213
220
 
214
- &__container {
215
- position: relative;
216
- flex: 1;
217
- overflow-x: hidden;
218
- overflow-y: auto;
219
- background: #fff;
220
- transition: all 0.4s ease-in-out;
221
-
222
- > .router-alive {
223
- height: 100%;
224
- }
225
- }
226
-
227
- &__iframe {
228
- position: absolute;
229
- top: 0;
230
- left: 0;
231
- width: 100%;
232
- height: 100%;
233
- }
234
-
235
221
  &__contextmenu {
236
222
  position: fixed;
237
- z-index: 999;
238
- min-width: 120px;
223
+ z-index: 1000;
224
+ min-width: 140px;
239
225
  padding: 8px 0;
240
226
  font-size: $font-size;
241
- background: #fff;
242
- border: $border;
243
- box-shadow: 1px 1px 4px 0 rgba(0, 0, 0, 0.1);
244
- transform-origin: left top;
245
- transition: all 0.25s ease-in;
227
+ background: $bg;
228
+ border: 1px solid $border;
229
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
230
+ border-radius: 8px;
246
231
 
247
- &-item {
248
- position: relative;
232
+ a,
233
+ button {
249
234
  display: block;
250
- padding: 0 20px;
251
- color: $text-color;
235
+ width: 100%;
236
+ padding: 0 16px;
252
237
  line-height: 30px;
238
+ text-align: left;
239
+ background: transparent;
240
+ border: none;
241
+ color: inherit;
253
242
  cursor: pointer;
254
- transition: all 0.2s ease-in-out;
255
- user-select: none;
256
-
257
- &:hover,
258
- &:active {
259
- color: $color-primary;
260
- }
243
+ font: inherit;
244
+ transition: background-color 0.2s ease-in-out;
261
245
 
262
- &[disabled],
263
246
  &[aria-disabled='true'] {
264
- color: #aaa;
265
- background: none;
266
- cursor: default;
247
+ color: rgba(148, 163, 184, 0.6);
267
248
  pointer-events: none;
268
249
  }
269
250
 
270
- .has-icon & {
271
- padding-left: 30px;
272
- }
273
- }
274
-
275
- &-icon {
276
- position: absolute;
277
- top: 0;
278
- left: 8px;
279
- display: none;
280
- line-height: 30px;
281
-
282
- .has-icon & {
283
- display: block;
251
+ &:hover,
252
+ &:focus-visible {
253
+ background: color-mix(in srgb, $primary 20%, #ffffff 80%);
254
+ color: $primary;
284
255
  }
285
256
  }
286
257
  }
287
258
  }
288
259
 
289
- // ----------------------
290
- // Transitions
291
- // ----------------------
292
- .router-tab-zoom {
293
- &-enter-active,
294
- &-leave-active {
295
- transition: all 0.4s;
296
- }
297
-
298
- &-enter,
299
- &-leave-to {
300
- transform: scale(0);
301
- opacity: 0;
302
- }
260
+ .router-tab__container {
261
+ padding: 1rem;
262
+ position: relative;
263
+ flex: 1 1 auto;
264
+ background-color: css-var(--router-tab-background, css-var(--theme-background, $light-bg));
303
265
  }
304
266
 
305
- .router-tab-swap {
306
- $trans: 30px;
307
-
308
- &-enter-active,
309
- &-leave-active {
310
- transition: all 0.5s;
311
- }
312
-
313
- &-enter,
314
- &-leave-to {
315
- opacity: 0;
316
- }
317
-
318
- &-enter {
319
- transform: translateX(-#{$trans});
320
- }
321
-
322
- &-leave-to {
323
- transform: translateX(#{$trans});
324
- }
267
+ .router-tab__item.is-active + .router-tab__item {
268
+ border-left-color: css-var(--router-tab-border, css-var(--theme-border, $light-border));
325
269
  }
package/lib/theme.ts ADDED
@@ -0,0 +1,75 @@
1
+ const STYLE_KEY = 'tab-theme-style'
2
+ const PRIMARY_KEY = 'tab-theme-primary-color'
3
+ const DEFAULT_STYLE: 'light' | 'dark' | 'system' = 'system'
4
+ const DEFAULT_PRIMARY = '#635bff'
5
+ const MEDIA_QUERY = '(prefers-color-scheme: dark)'
6
+
7
+ let mediaListener: ((event: MediaQueryListEvent) => void) | null = null
8
+
9
+ export interface RouterTabsThemeOptions {
10
+ styleKey?: string
11
+ primaryKey?: string
12
+ defaultStyle?: 'light' | 'dark' | 'system'
13
+ defaultPrimary?: string
14
+ }
15
+
16
+ function applyPrimary(color: string) {
17
+ if (typeof document === 'undefined') return
18
+ document.documentElement.style.setProperty('--theme-primary', color)
19
+ document.documentElement.style.setProperty('--router-tab-primary', color)
20
+ }
21
+
22
+ function applyStyle(style: 'light' | 'dark' | 'system') {
23
+ if (typeof document === 'undefined') return
24
+
25
+ const root = document.documentElement
26
+ const media = window.matchMedia(MEDIA_QUERY)
27
+
28
+ const updateFromSystem = () => {
29
+ root.dataset.theme = media.matches ? 'dark' : 'light'
30
+ }
31
+
32
+ if (mediaListener) {
33
+ media.removeEventListener('change', mediaListener)
34
+ mediaListener = null
35
+ }
36
+
37
+ if (style === 'system') {
38
+ updateFromSystem()
39
+ mediaListener = () => updateFromSystem()
40
+ media.addEventListener('change', mediaListener)
41
+ } else {
42
+ root.dataset.theme = style
43
+ }
44
+ }
45
+
46
+ export function initRouterTabsTheme(options: RouterTabsThemeOptions = {}) {
47
+ if (typeof window === 'undefined') return
48
+
49
+ const {
50
+ styleKey = STYLE_KEY,
51
+ primaryKey = PRIMARY_KEY,
52
+ defaultStyle = DEFAULT_STYLE,
53
+ defaultPrimary = DEFAULT_PRIMARY
54
+ } = options
55
+
56
+ const storedStyle = (window.localStorage.getItem(styleKey) as 'light' | 'dark' | 'system' | null) ?? defaultStyle
57
+ const storedPrimary = window.localStorage.getItem(primaryKey) ?? defaultPrimary
58
+
59
+ applyStyle(storedStyle)
60
+ applyPrimary(storedPrimary)
61
+ }
62
+
63
+ export function setRouterTabsTheme(style: 'light' | 'dark' | 'system', options?: RouterTabsThemeOptions) {
64
+ if (typeof window === 'undefined') return
65
+ const key = options?.styleKey ?? STYLE_KEY
66
+ window.localStorage.setItem(key, style)
67
+ applyStyle(style)
68
+ }
69
+
70
+ export function setRouterTabsPrimary(color: string, options?: RouterTabsThemeOptions) {
71
+ if (typeof window === 'undefined') return
72
+ const key = options?.primaryKey ?? PRIMARY_KEY
73
+ window.localStorage.setItem(key, color)
74
+ applyPrimary(color)
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -24,7 +24,6 @@
24
24
  },
25
25
  "devDependencies": {
26
26
  "@vitejs/plugin-vue": "^6.0.1",
27
- "pinia": "^3.0.3",
28
27
  "sass": "^1.93.2",
29
28
  "sass-loader": "^16.0.5",
30
29
  "typescript": "^5.9.2",
@@ -65,7 +64,6 @@
65
64
  ]
66
65
  },
67
66
  "peerDependencies": {
68
- "pinia": "^2.1.7",
69
67
  "vue": "^3.3.0",
70
68
  "vue-router": "^4.2.0"
71
69
  }