vue3-router-tab 1.1.1 → 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,Z){"use strict";function ee(e={}){return{initialTabs:e.initialTabs??[],keepAlive:e.keepAlive??!0,maxAlive:e.maxAlive??0,keepLastTab:e.keepLastTab??!0,appendPosition:e.appendPosition??"last",defaultRoute:e.defaultRoute??"/"}}function w(e,o){const a=e.resolve(o);if(!a||!a.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(o)}`);return a}const te={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 o=e.meta?.key;if(typeof o=="function"){const a=o(e);if(typeof a=="string"&&a.length)return a}else if(typeof o=="string"&&o.length){const a=te[o.toLowerCase()];return a?a(e):o}return e.fullPath}function I(e,o){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:o}function K(e,o){const a=e.meta?.reuse;return typeof a=="boolean"?a:o}function J(e){const o=e.meta??{},a={};return"title"in o&&(a.title=o.title),"tips"in o&&(a.tips=o.tips),"icon"in o&&(a.icon=o.icon),"closable"in o&&(a.closable=o.closable),"tabClass"in o&&(a.tabClass=o.tabClass),"target"in o&&(a.target=o.target),"href"in o&&(a.href=o.href),a}function B(e,o,a){const n=J(e);return{id:R(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:I(e,a),reusable:K(e,!1),closable:n.closable??!0,...n,...o}}function N(e,o,a,n){if(!e.find(b=>b.id===o.id)){if(a==="next"&&n){const b=e.findIndex(p=>p.id===n);if(b>-1){e.splice(b+1,0,o);return}}e.push(o)}}function H(e,o,a){if(!o||o<=0)return;const n=e.filter(c=>c.alive);for(;n.length>o;){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 ne(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function oe(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 ie(e,o={}){const a=ee(o),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:K(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=K(l,u.reusable),Object.assign(u,J(l)),u):(u=B(l,{},a.keepAlive),N(n,u,a.appendPosition,c.value),H(n,a.maxAlive,c.value),u)}async function E(l,r=!1,u=!0){const m=w(e,l),C=R(m),i=c.value===C;u==="sameTab"&&(u=i),u&&await g(C,!0),await e[r?"replace":"push"](m),i&&await v()}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 P(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 g(l=c.value??void 0,r=!1){l&&(p.value=l,await t.nextTick(),r||await t.nextTick(),p.value=null)}async function D(l=!1){for(const r of n)await g(r.id,l)}async function O(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 v(){const l=c.value;l&&await g(l,!0)}function U(l){return typeof l.matched=="object"?R(l):R(w(e,l))}function F(){const l=n.find(r=>r.id===c.value);return{tabs:n.map(ne),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),i=oe(m),d=B(C,i,a.keepAlive);N(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,H(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);N(n,u,"last",null)}),{options:a,tabs:n,activeId:c,current:b,includeKeys:s,refreshingKey:p,openTab:E,closeTab:P,removeTab:V,refreshTab:g,refreshAll:D,reset:O,reload:v,getRouteKey:U,matchRoute:T,snapshot:F,hydrate:x}}function Y(e){return e?typeof e=="string"?{name:e}:e:{}}const _=Symbol("RouterTabsContext"),ae="router-tabs:snapshot";function M(e={}){const{optional:o=!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(!o)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 o=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const n of a)if(n.startsWith(o))return decodeURIComponent(n.slice(o.length));return null}function W(e,o,a){if(typeof document>"u")return;const{expiresInDays:n=7,path:c="/",domain:b,secure:p,sameSite:s="lax"}=a,f=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];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 X(e,o){if(typeof document>"u")return;const{path:a="/",domain:n}=o,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 L(e={}){const{cookieKey:o=ae,serialize:a=re,deserialize:n=ce}=e,c=M({optional:!0}),b=t.ref(!1),p=s=>{t.onMounted(async()=>{const f=n(se(o));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?W(o,a(T),e):X(o,e)}),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?W(o,a(f),e):X(o,e)},{deep:!0})};c?p(c):t.onMounted(()=>{const s=M({optional:!0});s&&p(s)})}const ue=t.defineComponent({name:"RouterTab",components:{RouterView:Z.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:null},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 a=o.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=ie(a,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(_,n),o.appContext.config.globalProperties.$tabs=n;const c=t.computed(()=>!!o?.slots?.default);if(e.cookieKey||e.persistence){const i={...e.persistence??{}};e.cookieKey&&(i.cookieKey=e.cookieKey),L(i)}const b=t.computed(()=>Y(e.tabTransition)),p=t.computed(()=>Y(e.pageTransition)),s=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),f=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function T(i){return n.tabs.findIndex(d=>d.id===i)}function A(i){const d=T(i.id);return d>0?n.tabs.slice(0,d):[]}function E(i){const d=T(i.id);return d>-1?n.tabs.slice(d+1):[]}function S(i){return n.tabs.filter(d=>d.id!==i.id)}async function P(i,d){const h=i.filter(k=>k.closable!==!1);if(h.length){for(const k of h)n.activeId.value===k.id?await n.closeTab(k.id,{redirect:d.to,force:!0}):await n.removeTab(k.id,{force:!0});n.activeId.value!==d.id&&await n.openTab(d.to,!0,!1)}}const V={refresh:{label:"Refresh",handler:async({target:i})=>{await n.refreshTab(i.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await n.refreshAll(!0)}},close:{label:"Close",handler:async({target:i})=>{await n.closeTab(i.id)},enable:({target:i})=>x(i)},closeLefts:{label:"Close to the Left",handler:async({target:i})=>{await P(A(i),i)},enable:({target:i})=>A(i).some(d=>d.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:i})=>{await P(E(i),i)},enable:({target:i})=>E(i).some(d=>d.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:i})=>{await P(S(i),i)},enable:({target:i})=>S(i).some(d=>d.closable!==!1)}};function g(){s.visible=!1,s.target=null}function D(i,d){e.contextmenu&&(s.visible=!0,s.target=i,s.position.x=d.clientX,s.position.y=d.clientY,document.addEventListener("click",g,{once:!0}))}function O(i,d){const h=typeof i=="string"?{id:i}:i,k=V[h.id],Ae=h.label??k?.label??String(h.id),q=h.visible??k?.visible??!0;if(!(typeof q=="function"?q(d):q!==!1))return null;const G=h.enable??k?.enable??!0,_e=typeof G=="function"?G(d):G!==!1,Q=h.handler??k?.handler;if(!Q)return null;const Pe=async()=>{await Promise.resolve(Q(d))};return{id:String(h.id),label:Ae,disabled:!_e,action:Pe}}const v=t.computed(()=>{if(!s.visible||!s.target||e.contextmenu===!1)return[];const i=Array.isArray(e.contextmenu)?e.contextmenu:f,d={target:s.target,controller:n};return i.map(h=>O(h,d)).filter(h=>!!h)});async function U(i){i.disabled||(g(),await i.action())}function F(i){return typeof i.title=="string"?i.title:Array.isArray(i.title)&&i.title.length?String(i.title[0]):i.fullPath}function x(i){return!(i.closable===!1||n.options.keepLastTab&&n.tabs.length<=1)}async function l(i){await n.closeTab(i.id)}function r(i){n.activeId.value!==i.id&&n.openTab(i.to,!1)}function u(i){return["router-tab__item",{"is-active":n.activeId.value===i.id,"is-closable":x(i)},i.tabClass]}function m(i){return n.refreshingKey.value===n.getRouteKey(i)}t.onMounted(()=>{document.addEventListener("keydown",g)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",g),o.appContext.config.globalProperties.$tabs=null}),t.watch(()=>e.keepAlive,i=>{n.options.keepAlive=i}),t.watch(()=>n.activeId.value,()=>g()),t.watch(()=>e.contextmenu,i=>{i||g()}),t.watch(()=>v.value.length,i=>{s.visible&&i===0&&g()});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:v,handleMenuAction:U,showContextMenu:D,hideContextMenu:g,tabTitle:F,isClosable:x,isRefreshing:m,hasCustomSlot:c}}}),fe=(e,o)=>{const a=e.__vccOpts||e;for(const[n,c]of o)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"],ge=["onClick"],ke={class:"router-tab__slot-end"},Te={class:"router-tab__container"},Ce=["aria-disabled","onClick"];function we(e,o,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,ge)):t.createCommentVNode("",!0)],42,he))),128))]),_:1},16)]),t.createElementVNode("div",ke,[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 j=fe(ue,[["render",we]]),Re={class:"router-tabs","aria-hidden":"true"},$=t.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return L(e),(a,n)=>(t.openBlock(),t.createElementBlock("span",Re))}}),z={install(e){if(z._installed)return;z._installed=!0;const o=j.name||"RouterTab",a=$.name||"RouterTabs";e.component(o,j),e.component(a,$),a!=="router-tabs"&&e.component("router-tabs",$),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[_]},set(n){n&&e.provide(_,n)}})}};y.RouterTab=j,y.RouterTabs=$,y.default=z,y.routerTabsKey=_,y.useRouterTabs=M,y.useRouterTabsPersistence=L,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 ADDED
@@ -0,0 +1,102 @@
1
+ import type { App, Plugin, DefineComponent, PropType } from 'vue'
2
+ import type { RouteLocationRaw } from 'vue-router'
3
+ import type {
4
+ TabRecord,
5
+ TabInput,
6
+ RouterTabsOptions,
7
+ CloseTabOptions,
8
+ RouterTabsContext,
9
+ RouterTabsMenuConfig,
10
+ RouterTabsMenuItem,
11
+ RouterTabsMenuPreset,
12
+ RouterTabsSnapshot,
13
+ RouterTabsSnapshotTab,
14
+ RouterTabsPersistenceOptions
15
+ } from './lib/core/types'
16
+ import type { RouterTabsThemeOptions } from './lib/theme'
17
+
18
+ export type {
19
+ TabRecord,
20
+ TabInput,
21
+ RouterTabsOptions,
22
+ CloseTabOptions,
23
+ RouterTabsContext,
24
+ RouterTabsMenuConfig,
25
+ RouterTabsMenuItem,
26
+ RouterTabsMenuPreset,
27
+ RouterTabsSnapshot,
28
+ RouterTabsSnapshotTab,
29
+ RouterTabsPersistenceOptions,
30
+ RouterTabsThemeOptions
31
+ }
32
+
33
+ export declare const routerTabsKey: import('vue').InjectionKey<RouterTabsContext>
34
+
35
+ export declare function useRouterTabs(options?: { optional?: boolean }): RouterTabsContext | null
36
+
37
+ export declare function useRouterTabsPersistence(options?: RouterTabsPersistenceOptions): void
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
+
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>, {}>
44
+
45
+ export declare const RouterTab: DefineComponent<{
46
+ tabs: {
47
+ type: PropType<TabInput[]>
48
+ default: () => TabInput[]
49
+ }
50
+ keepAlive: BooleanConstructor
51
+ maxAlive: NumberConstructor
52
+ keepLastTab: BooleanConstructor
53
+ append: PropType<'last' | 'next'>
54
+ defaultPage: PropType<RouteLocationRaw>
55
+ tabTransition: PropType<import('./lib/core/types').TransitionLike>
56
+ pageTransition: PropType<import('./lib/core/types').TransitionLike>
57
+ contextmenu: PropType<boolean | RouterTabsMenuConfig[]>
58
+ cookieKey: StringConstructor
59
+ persistence: PropType<RouterTabsPersistenceOptions | null>
60
+ }, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, Record<string, any>, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<{
61
+ tabs?: TabInput[] | undefined
62
+ keepAlive?: boolean | undefined
63
+ maxAlive?: number | undefined
64
+ keepLastTab?: boolean | undefined
65
+ append?: 'last' | 'next' | undefined
66
+ defaultPage?: RouteLocationRaw | undefined
67
+ tabTransition?: import('./lib/core/types').TransitionLike | undefined
68
+ pageTransition?: import('./lib/core/types').TransitionLike | undefined
69
+ contextmenu?: boolean | RouterTabsMenuConfig[] | undefined
70
+ cookieKey?: string | undefined
71
+ persistence?: RouterTabsPersistenceOptions | null | undefined
72
+ }> & {
73
+ tabs?: TabInput[] | undefined
74
+ keepAlive?: boolean | undefined
75
+ maxAlive?: number | undefined
76
+ keepLastTab?: boolean | undefined
77
+ append?: 'last' | 'next' | undefined
78
+ defaultPage?: RouteLocationRaw | undefined
79
+ tabTransition?: import('./lib/core/types').TransitionLike | undefined
80
+ pageTransition?: import('./lib/core/types').TransitionLike | undefined
81
+ contextmenu?: boolean | RouterTabsMenuConfig[] | undefined
82
+ cookieKey?: string | undefined
83
+ persistence?: RouterTabsPersistenceOptions | null | undefined
84
+ }, {
85
+ tabs: TabInput[]
86
+ keepAlive: boolean
87
+ maxAlive: number
88
+ keepLastTab: boolean
89
+ append: 'last' | 'next'
90
+ defaultPage: RouteLocationRaw
91
+ tabTransition: import('./lib/core/types').TransitionLike
92
+ pageTransition: import('./lib/core/types').TransitionLike
93
+ contextmenu: boolean | RouterTabsMenuConfig[]
94
+ cookieKey: string
95
+ persistence: RouterTabsPersistenceOptions | null
96
+ }>
97
+
98
+ export interface RouterTabPlugin extends Plugin {}
99
+
100
+ declare const plugin: RouterTabPlugin
101
+
102
+ export default plugin
@@ -121,7 +121,7 @@ import type {
121
121
  TransitionLike
122
122
  } from '../core/types'
123
123
  import { getTransOpt } from '../util/index'
124
- import { routerTabsKey } from '../constants'
124
+ import { routerTabsKey, routerTabsCookie } from '../constants'
125
125
  import { useRouterTabsPersistence } from '../persistence'
126
126
 
127
127
 
@@ -174,7 +174,7 @@ export default defineComponent({
174
174
  },
175
175
  cookieKey: {
176
176
  type: String,
177
- default: null
177
+ default: routerTabsCookie
178
178
  },
179
179
  persistence: {
180
180
  type: Object as PropType<RouterTabsPersistenceOptions | null>,
@@ -206,11 +206,15 @@ export default defineComponent({
206
206
 
207
207
  const hasCustomSlot = computed(() => Boolean(instance?.slots?.default))
208
208
 
209
- if (props.cookieKey || props.persistence) {
209
+ if (props.cookieKey !== null || props.persistence) {
210
210
  const options: RouterTabsPersistenceOptions = {
211
211
  ...(props.persistence ?? {})
212
212
  }
213
- if (props.cookieKey) options.cookieKey = props.cookieKey
213
+ if (props.cookieKey !== null) {
214
+ options.cookieKey = props.cookieKey || routerTabsCookie
215
+ } else if (!options.cookieKey) {
216
+ options.cookieKey = routerTabsCookie
217
+ }
214
218
  useRouterTabsPersistence(options)
215
219
  }
216
220
 
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'
@@ -98,7 +98,7 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
98
98
  } = options
99
99
 
100
100
  const controller = useRouterTabs({ optional: true })
101
- const hydrating = ref(false)
101
+ const hydrating = ref(true)
102
102
 
103
103
  const setup = (ctrl: NonNullable<typeof controller>) => {
104
104
  onMounted(async () => {
@@ -127,6 +127,8 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
127
127
  } else {
128
128
  writeCookie(cookieKey, serialize(snapshot), options)
129
129
  }
130
+
131
+ hydrating.value = false
130
132
  })
131
133
 
132
134
  watch(
@@ -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
  }