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.
- package/README.md +30 -0
- package/dist/vue3-router-tab.css +1 -1
- package/dist/vue3-router-tab.js +298 -263
- package/dist/vue3-router-tab.umd.cjs +1 -1
- package/index.d.ts +102 -0
- package/lib/components/RouterTab.vue +8 -4
- package/lib/index.ts +27 -3
- package/lib/persistence.ts +3 -1
- package/lib/scss/index.scss +108 -137
- 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,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:
|
|
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
|
|
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 {
|
|
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/persistence.ts
CHANGED
|
@@ -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(
|
|
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(
|
package/lib/scss/index.scss
CHANGED
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
@use "sass:math";
|
|
2
2
|
@use "sass:color";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
$
|
|
8
|
-
$
|
|
9
|
-
$text
|
|
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
|
-
|
|
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
|
|
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(
|
|
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:
|
|
142
|
+
color: inherit;
|
|
110
143
|
font-size: $font-size;
|
|
111
|
-
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:
|
|
153
|
+
border-left: 1px solid var(--router-tab-border);
|
|
120
154
|
}
|
|
121
155
|
|
|
122
156
|
&.is-contextmenu {
|
|
123
|
-
color:
|
|
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:
|
|
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
|
-
|
|
192
|
+
background-color: #fff;
|
|
143
193
|
}
|
|
144
194
|
}
|
|
145
195
|
}
|
|
146
196
|
|
|
147
197
|
&.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;
|
|
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:
|
|
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
|
|
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:
|
|
238
|
-
min-width:
|
|
251
|
+
z-index: 1000;
|
|
252
|
+
min-width: 140px;
|
|
239
253
|
padding: 8px 0;
|
|
240
254
|
font-size: $font-size;
|
|
241
|
-
background:
|
|
242
|
-
border:
|
|
243
|
-
box-shadow:
|
|
244
|
-
|
|
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
|
-
|
|
248
|
-
|
|
260
|
+
a,
|
|
261
|
+
button {
|
|
249
262
|
display: block;
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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:
|
|
265
|
-
background: none;
|
|
266
|
-
cursor: default;
|
|
275
|
+
color: rgba(148, 163, 184, 0.6);
|
|
267
276
|
pointer-events: none;
|
|
268
277
|
}
|
|
269
278
|
|
|
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;
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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-
|
|
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
|
-
}
|
|
294
|
+
.router-tab__item.is-active + .router-tab__item {
|
|
295
|
+
border-left-color: var(--router-tab-border);
|
|
325
296
|
}
|