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