vue3-router-tab 1.1.2 → 1.1.3

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="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"}})}));
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,63 @@
1
1
  @use "sass:math";
2
2
  @use "sass:color";
3
3
 
4
- // ----------------------
5
- // Variables
6
- // ----------------------
7
- $color-primary: #42b983;
8
- $border-color: #eaecef;
9
- $text-color: #4d4d4d;
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);
10
11
 
11
12
  $font-size: 14px;
12
-
13
- $border: 1px solid $border-color;
14
13
  $tab-trans: all 0.3s ease-in-out;
15
-
16
14
  $hd-height: 40px;
17
-
18
15
  $tab-padding: 20px;
19
16
  $close-icon-margin: 4px;
20
17
  $close-icon-size: 13px;
21
18
 
22
- // ----------------------
23
- // Router Tab Component
24
- // ----------------------
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};
35
+ }
36
+
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%)};
43
+ }
44
+
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%)};
52
+ }
53
+ }
54
+
25
55
  .router-tab {
26
56
  display: flex;
27
57
  flex-direction: column;
28
58
  min-height: 300px;
59
+ background-color: var(--router-tab-background);
60
+ color: var(--router-tab-foreground);
29
61
 
30
62
  &__header {
31
63
  position: relative;
@@ -34,7 +66,8 @@ $close-icon-size: 13px;
34
66
  flex: none;
35
67
  box-sizing: border-box;
36
68
  height: $hd-height;
37
- border-bottom: 1px solid $border-color;
69
+ border-bottom: 1px solid var(--router-tab-border);
70
+ background-color: var(--router-tab-header-bg);
38
71
  transition: all 0.2s ease-in-out;
39
72
  }
40
73
 
@@ -85,7 +118,7 @@ $close-icon-size: 13px;
85
118
 
86
119
  &:hover,
87
120
  .router-tab__scrollbar.is-dragging & {
88
- background-color: rgba($color-primary, 0.8);
121
+ background-color: rgba(0, 0, 0, 0.2);
89
122
  }
90
123
  }
91
124
  }
@@ -106,31 +139,48 @@ $close-icon-size: 13px;
106
139
  flex: none;
107
140
  align-items: center;
108
141
  padding: 0 $tab-padding;
109
- color: $text-color;
142
+ color: inherit;
110
143
  font-size: $font-size;
111
- border: $border;
144
+ border: 1px solid var(--router-tab-border);
112
145
  border-left: none;
113
146
  transform-origin: left bottom;
114
147
  cursor: pointer;
115
148
  transition: $tab-trans;
116
149
  user-select: none;
150
+ background-color: transparent;
117
151
 
118
152
  &:first-child {
119
- border-left: $border;
153
+ border-left: 1px solid var(--router-tab-border);
120
154
  }
121
155
 
122
156
  &.is-contextmenu {
123
- color: #000;
157
+ color: var(--router-tab-primary);
158
+ }
159
+
160
+ &.is-drag-over {
161
+ background: rgba(0, 0, 0, 0.05);
162
+ transition: background 0.15s ease;
163
+ }
164
+
165
+ &-title {
166
+ min-width: 30px;
167
+ max-width: 120px;
168
+ overflow: hidden;
169
+ white-space: nowrap;
170
+ text-overflow: ellipsis;
171
+ }
172
+
173
+ &-icon {
174
+ margin-right: 5px;
175
+ font-size: 16px;
124
176
  }
125
177
 
126
178
  &:hover,
127
179
  &.is-active {
128
- color: $color-primary;
180
+ color: var(--router-tab-primary);
129
181
 
130
182
  &.is-closable {
131
- padding: 0 (
132
- $tab-padding - math.div($close-icon-size + $close-icon-margin, 2)
133
- );
183
+ padding: 0 ($tab-padding - math.div($close-icon-size + $close-icon-margin, 2));
134
184
  }
135
185
 
136
186
  .router-tab__item-close {
@@ -139,31 +189,13 @@ $close-icon-size: 13px;
139
189
 
140
190
  &::before,
141
191
  &::after {
142
- border-color: $color-primary;
192
+ background-color: #fff;
143
193
  }
144
194
  }
145
195
  }
146
196
 
147
197
  &.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;
198
+ border-bottom-color: var(--router-tab-background);
167
199
  }
168
200
 
169
201
  &-close {
@@ -178,6 +210,8 @@ $close-icon-size: 13px;
178
210
  border-radius: 50%;
179
211
  cursor: pointer;
180
212
  transition: $tab-trans;
213
+ background: transparent;
214
+ border: none;
181
215
 
182
216
  &::before,
183
217
  &::after {
@@ -188,9 +222,9 @@ $close-icon-size: 13px;
188
222
  width: $inner;
189
223
  height: 1px;
190
224
  margin-left: math.div(-$inner, 2);
191
- background-color: $text-color;
225
+ background-color: currentColor;
192
226
  transition: background-color 0.2s ease-in-out;
193
- content: "";
227
+ content: '';
194
228
  }
195
229
 
196
230
  &::before {
@@ -202,7 +236,8 @@ $close-icon-size: 13px;
202
236
  }
203
237
 
204
238
  &:hover {
205
- background-color: color.mix($text-color, #fff, 50%);
239
+ background-color: color-mix(in srgb, var(--router-tab-primary) 40%, #ffffff 60%);
240
+
206
241
  &::before,
207
242
  &::after {
208
243
  background-color: #fff;
@@ -211,115 +246,51 @@ $close-icon-size: 13px;
211
246
  }
212
247
  }
213
248
 
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
249
  &__contextmenu {
236
250
  position: fixed;
237
- z-index: 999;
238
- min-width: 120px;
251
+ z-index: 1000;
252
+ min-width: 140px;
239
253
  padding: 8px 0;
240
254
  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;
255
+ background: var(--router-tab-background);
256
+ border: 1px solid var(--router-tab-border);
257
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
258
+ border-radius: 8px;
246
259
 
247
- &-item {
248
- position: relative;
260
+ a,
261
+ button {
249
262
  display: block;
250
- padding: 0 20px;
251
- color: $text-color;
263
+ width: 100%;
264
+ padding: 0 16px;
252
265
  line-height: 30px;
266
+ text-align: left;
267
+ background: transparent;
268
+ border: none;
269
+ color: inherit;
253
270
  cursor: pointer;
254
- transition: all 0.2s ease-in-out;
255
- user-select: none;
271
+ font: inherit;
272
+ transition: background-color 0.2s ease-in-out;
256
273
 
257
- &:hover,
258
- &:active {
259
- color: $color-primary;
260
- }
261
-
262
- &[disabled],
263
274
  &[aria-disabled='true'] {
264
- color: #aaa;
265
- background: none;
266
- cursor: default;
275
+ color: rgba(148, 163, 184, 0.6);
267
276
  pointer-events: none;
268
277
  }
269
278
 
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;
279
+ &:hover,
280
+ &:focus-visible {
281
+ background: color-mix(in srgb, var(--router-tab-primary) 20%, #ffffff 80%);
282
+ color: var(--router-tab-primary);
284
283
  }
285
284
  }
286
285
  }
287
286
  }
288
287
 
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
- }
288
+ .router-tab__container {
289
+ position: relative;
290
+ flex: 1 1 auto;
291
+ background-color: var(--router-tab-background);
303
292
  }
304
293
 
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
- }
294
+ .router-tab__item.is-active + .router-tab__item {
295
+ border-left-color: var(--router-tab-border);
325
296
  }
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.3",
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
  }