vue3-router-tab 1.2.6 → 1.2.7
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/dist/vue3-router-tab.css +1 -1
- package/dist/vue3-router-tab.js +694 -522
- package/dist/vue3-router-tab.umd.cjs +1 -1
- package/lib/components/RouterTab.vue +189 -8
- package/lib/core/createRouterTabs.ts +18 -0
- package/lib/index.ts +40 -9
- package/lib/persistence.ts +3 -1
- package/lib/scss/index.scss +62 -10
- package/lib/scss/variables.scss +14 -2
- package/lib/theme.ts +39 -4
- package/lib/useReactiveTab.ts +49 -49
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(T,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):(T=typeof globalThis<"u"?globalThis:T||self,t(T["vue3-router-tab"]={},T.Vue,T.VueRouter))})(this,(function(T,t,me){"use strict";function pe(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 $(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 ge={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 x(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=ge[o.toLowerCase()];return a?a(e):o}return e.fullPath}function J(e,o){const a=e.meta?.keepAlive;return typeof a=="boolean"?a:o}function q(e,o){const a=e.meta?.reuse;return typeof a=="boolean"?a:o}function oe(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 _(e,o,a){const r=oe(e);return{id:x(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:J(e,a),reusable:q(e,!1),closable:r.closable??!0,...r,...o}}function G(e,o,a,r){e.find(p=>p.id===o.id)||e.push(o)}function ae(e,o,a){if(!o||o<=0)return;const r=e.filter(i=>i.alive);for(;r.length>o;){const i=r.shift();if(!i||i.id===a)continue;const p=e.findIndex(k=>k.id===i.id);p>-1&&(e[p].alive=!1)}}function he(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function ye(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 Te(e,o={}){const a=pe(o),r=t.reactive([]),i=t.ref(null),p=t.shallowRef(),k=t.ref(null),c=t.computed(()=>r.filter(s=>s.alive).map(s=>s.id));let u=!1;function g(s){const f=typeof s.matched=="object"?s:$(e,s);return{key:x(f),fullPath:f.fullPath,alive:J(f,a.keepAlive),reusable:q(f,!1),matched:f}}function R(s){const f=x(s);let b=r.find(h=>h.id===f);return b?(b.fullPath=s.fullPath,b.to=s.fullPath,b.matched=s,b.alive=J(s,a.keepAlive),b.reusable=q(s,b.reusable),Object.assign(b,oe(s)),b):(b=_(s,{},a.keepAlive),G(r,b,a.appendPosition,i.value),ae(r,a.maxAlive,i.value),b)}async function I(s,f=!1,b=!0){const h=$(e,s),B=x(h),A=i.value===B;b==="sameTab"&&(b=A),b&&await M(B,!0),await e[f?"replace":"push"](h),A&&await z()}function K(s){const f=r.findIndex(D=>D.id===s);if(f===-1)return a.defaultRoute;const b=r[f+1],h=r[f-1],B=r.find(D=>D.id!==s),A=b||h||B;return A?A.to:a.defaultRoute}async function E(s=i.value,f={}){if(!s)return;if(!f.force&&a.keepLastTab&&r.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const h=i.value===s&&f.redirect!==null,B=h?f.redirect??K(s):null;await U(s,{force:f.force}),f.redirect!==null&&h&&B&&await e.replace(B)}async function U(s,f={}){const b=r.findIndex(h=>h.id===s);b!==-1&&(r.splice(b,1),k.value===s&&(k.value=null),i.value===s&&(i.value=null,p.value=void 0))}async function M(s=i.value??void 0,f=!1){s&&(k.value=s,await t.nextTick(),f||await t.nextTick(),k.value=null)}async function w(s=!1){for(const f of r)await M(f.id,s)}async function y(s=a.defaultRoute){r.splice(0,r.length),i.value=null,p.value=void 0;for(const f of a.initialTabs){const b=$(e,f.to),h=_(b,f,a.keepAlive);r.push(h)}await e.replace(s)}async function z(){const s=i.value;s&&await M(s,!0)}function V(s){return typeof s.matched=="object"?x(s):x($(e,s))}function F(){const s=r.find(f=>f.id===i.value);return{tabs:r.map(he),active:s?s.to:null}}async function Y(s){u=!0,r.splice(0,r.length),i.value=null,p.value=void 0;const f=s?.tabs??[];for(const h of f)try{const B=$(e,h.to),A=ye(h),D=_(B,A,a.keepAlive);G(r,D,"last",null)}catch{}u=!1;const b=s?.active??f[f.length-1]?.to??a.defaultRoute;if(b)try{await e.replace(b)}catch{}}return t.watch(()=>e.currentRoute.value,s=>{if(u)return;const f=R(s);i.value=f.id,p.value=f,ae(r,a.maxAlive,i.value)},{immediate:!0}),a.initialTabs.length&&a.initialTabs.forEach(s=>{const f=$(e,s.to),b=_(f,s,a.keepAlive);G(r,b)}),{options:a,tabs:r,activeId:i,current:p,includeKeys:c,refreshingKey:k,openTab:I,closeTab:E,removeTab:U,refreshTab:M,refreshAll:w,reset:y,reload:z,getRouteKey:V,matchRoute:g,snapshot:F,hydrate:Y}}function ie(e){return e?typeof e=="string"?{name:e}:e:{}}const S=Symbol("RouterTabsContext"),N="router-tabs:snapshot";function W(e={}){const{optional:o=!1}=e,a=t.inject(S,null);if(a)return a;const r=t.inject("$tabs",null);if(r)return r;const p=t.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!o)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const ke=864e5;function ve(e){if(typeof document>"u")return null;const o=`${encodeURIComponent(e)}=`,a=document.cookie?document.cookie.split("; "):[];for(const r of a)if(r.startsWith(o))return decodeURIComponent(r.slice(o.length));return null}function re(e,o,a){if(typeof document>"u")return;const{expiresInDays:r=7,path:i="/",domain:p,secure:k,sameSite:c="lax"}=a,u=[`${encodeURIComponent(e)}=${encodeURIComponent(o)}`];if(r!==1/0){const g=new Date(Date.now()+r*ke).toUTCString();u.push(`Expires=${g}`)}i&&u.push(`Path=${i}`),p&&u.push(`Domain=${p}`),k&&u.push("Secure"),c&&u.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=u.join("; ")}function le(e,o){if(typeof document>"u")return;const{path:a="/",domain:r}=o,i=[`${encodeURIComponent(e)}=`];i.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),a&&i.push(`Path=${a}`),r&&i.push(`Domain=${r}`),document.cookie=i.join("; ")}const Ce=e=>JSON.stringify(e??null),Re=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function H(e={}){const{cookieKey:o=N,serialize:a=Ce,deserialize:r=Re}=e,i=W({optional:!0}),p=t.ref(!0),k=c=>{t.onMounted(async()=>{const u=r(ve(o));if(u&&u.tabs?.length)try{if(p.value=!0,await c.hydrate(u),u.active){await t.nextTick();const R=c.tabs.find(I=>I.to===u.active);R&&(c.activeId.value=R.id,c.current.value=R)}}finally{p.value=!1}else try{p.value=!0;const R=e.fallbackRoute??c.options.defaultRoute;await c.reset(R)}finally{p.value=!1}const g=c.snapshot();g.tabs.length?re(o,a(g),e):le(o,e),p.value=!1}),t.watch(()=>({tabs:c.tabs.map(u=>({to:u.to,title:u.title,tips:u.tips,icon:u.icon,tabClass:u.tabClass,closable:u.closable})),active:c.activeId.value}),()=>{if(p.value)return;const u=c.snapshot();u.tabs.length?re(o,a(u),e):le(o,e)},{deep:!0})};i?k(i):t.onMounted(()=>{const c=W({optional:!0});c&&k(c)})}const we=t.defineComponent({name:"RouterTab",components:{RouterView:me.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:N},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:o}){const a=t.getCurrentInstance();if(!a)throw new Error("[RouterTab] component must be used within a Vue application context.");const r=a.appContext.app.config.globalProperties.$router;if(!r)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const i=Te(r,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});t.provide(S,i),a.appContext.config.globalProperties.$tabs=i;const p=t.computed(()=>!!a?.slots?.default),k=t.ref(0),c=t.computed(()=>{k.value;const n={};return i.tabs.forEach(l=>{const m=typeof l.title=="string"?l.title:String(l.title||ee(l));n[l.id]=m}),n});function u(){k.value++}const g=new Map,R=new Map;function I(n,l){if(!l||g.has(n))return;g.set(n,l);const m=i.tabs.find(P=>i.getRouteKey(P.to)===n);if(!m)return;const v=[];if(l.routeTabTitle!==void 0){const P=t.watch(()=>{const d=l.routeTabTitle;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{if(d!=null){const fe=String(d);m.title=fe,u()}},{immediate:!0});v.push(P)}if(l.routeTabIcon!==void 0){const P=t.watch(()=>{const d=l.routeTabIcon;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.icon=String(d),u())},{immediate:!0});v.push(P)}if(l.routeTabClosable!==void 0){const P=t.watch(()=>{const d=l.routeTabClosable;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d!=null&&(m.closable=!!d,u())},{immediate:!0});v.push(P)}if(l.routeTabMeta!==void 0){const P=t.watch(()=>{const d=l.routeTabMeta;return d&&typeof d=="object"&&"value"in d?d.value:d},d=>{d&&typeof d=="object"&&(Object.assign(m,d),u())},{immediate:!0,deep:!0});v.push(P)}R.set(n,v)}function K(n){const l=R.get(n);l&&(l.forEach(m=>m()),R.delete(n)),g.delete(n)}function E(n,l){n?n.routeTabTitle!==void 0||n.routeTabIcon!==void 0||n.routeTabClosable!==void 0?I(l,n):n.$&&(n.$.routeTabTitle!==void 0||n.$.routeTabIcon!==void 0||n.$.routeTabClosable!==void 0)&&I(l,n.$):n===null&&K(l)}if(e.cookieKey!==null||e.persistence){const n={...e.persistence??{}};e.cookieKey!==null?n.cookieKey=e.cookieKey||N:n.cookieKey||(n.cookieKey=N),H(n)}const U=t.computed(()=>ie(e.tabTransition)),M=t.computed(()=>ie(e.pageTransition)),w=t.reactive({visible:!1,target:null,position:{x:0,y:0}}),y=t.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),z=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function V(n){return i.tabs.findIndex(l=>l.id===n)}function F(n){const l=V(n.id);return l>0?i.tabs.slice(0,l):[]}function Y(n){const l=V(n.id);return l>-1?i.tabs.slice(l+1):[]}function s(n){return i.tabs.filter(l=>l.id!==n.id)}async function f(n,l){const m=n.filter(v=>v.closable!==!1);if(m.length){for(const v of m)i.activeId.value===v.id?await i.closeTab(v.id,{redirect:l.to,force:!0}):await i.removeTab(v.id,{force:!0});i.activeId.value!==l.id&&await i.openTab(l.to,!0,!1)}}const b={refresh:{label:"Refresh",handler:async({target:n})=>{await i.refreshTab(n.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await i.refreshAll(!0)}},close:{label:"Close",handler:async({target:n})=>{await i.closeTab(n.id)},enable:({target:n})=>te(n)},closeLefts:{label:"Close to the Left",handler:async({target:n})=>{await f(F(n),n)},enable:({target:n})=>F(n).some(l=>l.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:n})=>{await f(Y(n),n)},enable:({target:n})=>Y(n).some(l=>l.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:n})=>{await f(s(n),n)},enable:({target:n})=>s(n).some(l=>l.closable!==!1)}};function h(){w.visible=!1,w.target=null}function B(n,l){e.contextmenu&&(w.visible=!0,w.target=n,w.position.x=l.clientX,w.position.y=l.clientY,document.addEventListener("click",h,{once:!0}))}function A(n,l){const m=typeof n=="string"?{id:n}:n,v=b[m.id],P=m.label??v?.label??String(m.id),d=m.visible??v?.visible??!0;if(!(typeof d=="function"?d(l):d!==!1))return null;const ne=m.enable??v?.enable??!0,it=typeof ne=="function"?ne(l):ne!==!1,be=m.handler??v?.handler;if(!be)return null;const rt=async()=>{await Promise.resolve(be(l))};return{id:String(m.id),label:P,disabled:!it,action:rt}}const D=t.computed(()=>{if(!w.visible||!w.target||e.contextmenu===!1)return[];const n=Array.isArray(e.contextmenu)?e.contextmenu:z,l={target:w.target,controller:i};return n.map(m=>A(m,l)).filter(m=>!!m)});async function qe(n){n.disabled||(h(),await n.action())}function ee(n){return typeof n.title=="string"&&n.title.trim()?n.title:Array.isArray(n.title)&&n.title.length&&String(n.title[0]).trim()?String(n.title[0]):"Untitled"}function Ge(n){return c.value[n.id]||ee(n)}function te(n){return!(n.closable===!1||i.options.keepLastTab&&i.tabs.length<=1)}async function We(n){await i.closeTab(n.id)}function He(n){i.activeId.value!==n.id&&i.openTab(n.to,!1)}function Qe(n){return["router-tab__item",{"is-active":i.activeId.value===n.id,"is-closable":te(n),"is-dragging":y.dragging&&y.dragTab?.id===n.id,"is-drag-over":y.dropIndex===V(n.id)},n.tabClass]}function Xe(n){return i.refreshingKey.value===i.getRouteKey(n)}function Ze(n,l,m){e.sortable&&(y.dragging=!0,y.dragIndex=l,y.dragTab=n,m.dataTransfer&&(m.dataTransfer.effectAllowed="move",m.dataTransfer.setData("text/plain",n.id)),o("tab-sort",{tab:n,index:l}))}function et(n,l){!e.sortable||!y.dragging||(l.preventDefault(),l.dataTransfer&&(l.dataTransfer.dropEffect="move"))}function tt(n){!e.sortable||!y.dragging||(y.dropIndex=n)}function nt(){!e.sortable||y.dragging}function ot(n,l){if(!(!e.sortable||!y.dragging)){if(l.preventDefault(),y.dragIndex!==-1&&y.dragIndex!==n){const m=i.tabs.splice(y.dragIndex,1)[0];i.tabs.splice(n,0,m),o("tab-sorted",{tab:m,fromIndex:y.dragIndex,toIndex:n})}de()}}function de(){y.dragging=!1,y.dragIndex=-1,y.dropIndex=-1,y.dragTab=null}t.onMounted(()=>{document.addEventListener("keydown",h)}),t.onBeforeUnmount(()=>{document.removeEventListener("keydown",h),a.appContext.config.globalProperties.$tabs=null,R.forEach(n=>{n.forEach(l=>l())}),R.clear(),g.clear()}),t.watch(()=>e.keepAlive,n=>{i.options.keepAlive=n}),t.watch(()=>i.activeId.value,()=>h()),t.watch(()=>e.contextmenu,n=>{n||h()}),t.watch(()=>D.value.length,n=>{w.visible&&n===0&&h()});const at=i.includeKeys;return{controller:i,tabs:i.tabs,includeKeys:at,tabTransitionProps:U,pageTransitionProps:M,buildTabClass:Qe,activate:He,close:We,context:w,menuItems:D,handleMenuAction:qe,showContextMenu:B,hideContextMenu:h,getTabTitle:ee,isClosable:te,isRefreshing:Xe,hasCustomSlot:p,onDragStart:Ze,onDragOver:et,onDragEnter:tt,onDragLeave:nt,onDrop:ot,onDragEnd:de,setupComponentWatching:I,cleanupComponentWatching:K,handleComponentRef:E,getReactiveTabTitle:Ge,triggerTabUpdate:u}}}),Be=(e,o)=>{const a=e.__vccOpts||e;for(const[r,i]of o)a[r]=i;return a},Ee={class:"router-tab"},Pe={class:"router-tab__header"},Ae={class:"router-tab__slot-start"},Ie={class:"router-tab__scroll"},De=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],$e=["title"],xe=["onClick"],Me={class:"router-tab__slot-end"},Se={class:"router-tab__container"},Le=["aria-disabled","onClick"];function Ke(e,o,a,r,i,p){const k=t.resolveComponent("RouterView");return t.openBlock(),t.createElementBlock("div",Ee,[t.createElementVNode("header",Pe,[t.createElementVNode("div",Ae,[t.renderSlot(e.$slots,"start")]),t.createElementVNode("div",Ie,[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,(c,u)=>(t.openBlock(),t.createElementBlock("li",{key:c.id,class:t.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:g=>e.activate(c),onAuxclick:t.withModifiers(g=>e.close(c),["middle","prevent"]),onContextmenu:t.withModifiers(g=>e.showContextMenu(c,g),["prevent"]),onDragstart:g=>e.onDragStart(c,u,g),onDragover:g=>e.onDragOver(u,g),onDragenter:g=>e.onDragEnter(u),onDragleave:o[0]||(o[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(u,g),onDragend:o[1]||(o[1]=(...g)=>e.onDragEnd&&e.onDragEnd(...g))},[c.icon?(t.openBlock(),t.createElementBlock("i",{key:0,class:t.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):t.createCommentVNode("",!0),t.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},t.toDisplayString(e.getReactiveTabTitle(c)),9,$e),e.isClosable(c)?(t.openBlock(),t.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:t.withModifiers(g=>e.close(c),["stop"])},null,8,xe)):t.createCommentVNode("",!0)],42,De))),128))]),_:1},16)]),t.createElementVNode("div",Me,[t.renderSlot(e.$slots,"end")])]),t.createElementVNode("div",Se,[t.createVNode(k,null,{default:t.withCtx(c=>[e.hasCustomSlot?t.renderSlot(e.$slots,"default",t.normalizeProps(t.mergeProps({key:0},{...c,controller:e.controller,pageRef:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route))}))):(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(c.route)?t.createCommentVNode("",!0):(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.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(c.route)?(t.openBlock(),t.createBlock(t.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route)+(e.isRefreshing(c.route)?"-refresh":""),ref:u=>e.handleComponentRef(u,e.controller.getRouteKey(c.route)),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,c=>(t.openBlock(),t.createElementBlock("a",{key:c.id,class:"router-tab__contextmenu-item","aria-disabled":c.disabled,onClick:t.withModifiers(u=>e.handleMenuAction(c),["prevent"])},t.toDisplayString(c.label),9,Le))),128))],4)):t.createCommentVNode("",!0)])}const Q=Be(we,[["render",Ke]]),Ve={class:"router-tabs","aria-hidden":"true"},j=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 H(e),(a,r)=>(t.openBlock(),t.createElementBlock("span",Ve))}}),se="tab-theme-style",_e="tab-theme-primary-color",Ne="system",je="(prefers-color-scheme: dark)";let L=null;const C={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},Oe={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function X(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??C.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??C.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??C.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??C.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??C.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??C.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??C.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??C.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??C.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??C.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??C.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??C.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??C.iconColor))}function ce(e){if(typeof document>"u")return;const o=document.documentElement,a=window.matchMedia(je),r=()=>{o.dataset.theme=a.matches?"dark":"light"};L&&(a.removeEventListener("change",L),L=null),e==="system"?(r(),L=()=>r(),a.addEventListener("change",L)):o.dataset.theme=e}function ue(e={}){if(typeof window>"u")return;const{styleKey:o=se,defaultStyle:a=Ne}=e,r=window.localStorage.getItem(o)??a;ce(r),X(r==="dark"?Oe:C)}function Ue(e,o){if(typeof window>"u")return;const a=o?.styleKey??se;window.localStorage.setItem(a,e),ce(e)}function ze(e,o){if(typeof window>"u")return;const a=o?.primaryKey??_e;window.localStorage.setItem(a,JSON.stringify(e)),X(e)}function O(e={}){const o=t.ref(e.title||"Untitled"),a=t.ref(e.icon||"mdi-tab"),r=t.ref(e.closable!==!1),i=t.ref(e.meta||{}),p=typeof e.title=="function"?t.computed(e.title):o,k=typeof e.icon=="function"?t.computed(e.icon):a,c=typeof e.closable=="function"?t.computed(e.closable):r,u=typeof e.meta=="function"?t.computed(e.meta):i;return{routeTabTitle:p,routeTabIcon:k,routeTabClosable:c,routeTabMeta:u,updateTitle:E=>{"value"in o&&(o.value=E)},updateIcon:E=>{"value"in a&&(a.value=E)},updateClosable:E=>{"value"in r&&(r.value=E)},updateMeta:E=>{"value"in i&&(i.value=E)}}}function Fe(e,o="Page"){return O({title:t.computed(()=>e.value?"Loading...":o),icon:t.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:t.computed(()=>!e.value)})}function Ye(e,o="Page",a="mdi-page"){return O({title:t.computed(()=>e.value>0?`${o} (${e.value})`:o),icon:t.computed(()=>e.value>0?"mdi-bell-badge":a)})}function Je(e,o="Page"){const a={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return O({title:t.computed(()=>o+a[e.value].suffix),icon:t.computed(()=>a[e.value].icon),closable:t.computed(()=>e.value!=="loading")})}const Z={install(e){if(Z._installed)return;Z._installed=!0,ue();const o=Q.name||"RouterTab",a=j.name||"RouterTabs";e.component(o,Q),e.component(a,j),a!=="router-tabs"&&e.component("router-tabs",j),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[S]},set(r){r&&e.provide(S,r)}})}};T.RouterTab=Q,T.RouterTabs=j,T.default=Z,T.initRouterTabsTheme=ue,T.routerTabsKey=S,T.setRouterTabsPrimary=ze,T.setRouterTabsTheme=Ue,T.useLoadingTab=Fe,T.useNotificationTab=Ye,T.useReactiveTab=O,T.useRouterTabs=W,T.useRouterTabsPersistence=H,T.useStatusTab=Je,Object.defineProperties(T,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
1
|
+
(function(w,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],n):(w=typeof globalThis<"u"?globalThis:w||self,n(w["vue3-router-tab"]={},w.Vue,w.VueRouter))})(this,(function(w,n,Ce){"use strict";function Re(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 M(e,a){const o=e.resolve(a);if(!o||!o.matched.length)throw new Error(`[RouterTabs] Unable to resolve route: ${String(a)}`);return o}const Ee={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 $(e){const a=e.meta?.key;if(typeof a=="function"){const o=a(e);if(typeof o=="string"&&o.length)return o}else if(typeof a=="string"&&a.length){const o=Ee[a.toLowerCase()];return o?o(e):a}return e.fullPath}function G(e,a){const o=e.meta?.keepAlive;return typeof o=="boolean"?o:a}function X(e,a){const o=e.meta?.reuse;return typeof o=="boolean"?o:a}function se(e){const a=e.meta??{},o={};return"title"in a&&(o.title=a.title),"tips"in a&&(o.tips=a.tips),"icon"in a&&(o.icon=a.icon),"closable"in a&&(o.closable=a.closable),"tabClass"in a&&(o.tabClass=a.tabClass),"target"in a&&(o.target=a.target),"href"in a&&(o.href=a.href),o}function _(e,a,o){const s=se(e);return{id:$(e),to:e.fullPath,fullPath:e.fullPath,matched:e,alive:G(e,o),reusable:X(e,!1),closable:s.closable??!0,...s,...a}}function Q(e,a,o,s){if(!e.find(p=>p.id===a.id)){if(o==="next"&&s){const p=e.findIndex(k=>k.id===s);if(p!==-1){e.splice(p+1,0,a);return}}e.push(a)}}function ce(e,a,o){if(!a||a<=0)return;const s=e.filter(r=>r.alive);for(;s.length>a;){const r=s.shift();if(!r||r.id===o)continue;const p=e.findIndex(k=>k.id===r.id);p>-1&&(e[p].alive=!1)}}function Pe(e){return{to:e.to,title:e.title,tips:e.tips,icon:e.icon,tabClass:e.tabClass,closable:e.closable}}function Be(e){const a={};return"title"in e&&(a.title=e.title),"tips"in e&&(a.tips=e.tips),"icon"in e&&(a.icon=e.icon),"tabClass"in e&&(a.tabClass=e.tabClass),"closable"in e&&(a.closable=e.closable),a}function Ae(e,a={}){const o=Re(a),s=n.reactive([]),r=n.ref(null),p=n.shallowRef(),k=n.ref(null),c=n.computed(()=>s.filter(l=>l.alive).map(l=>l.id));let d=!1;function g(l){const b=typeof l.matched=="object"?l:M(e,l);return{key:$(b),fullPath:b.fullPath,alive:G(b,o.keepAlive),reusable:X(b,!1),matched:b}}function R(l){const b=$(l);let m=s.find(v=>v.id===b);return m?(m.fullPath=l.fullPath,m.to=l.fullPath,m.matched=l,m.alive=G(l,o.keepAlive),m.reusable=X(l,m.reusable),Object.assign(m,se(l)),m):(m=_(l,{},o.keepAlive),Q(s,m,o.appendPosition,r.value),ce(s,o.maxAlive,r.value),m)}async function I(l,b=!1,m=!0){const v=M(e,l),E=$(v),P=r.value===E;m==="sameTab"&&(m=P),m&&await K(E,!0),await e[b?"replace":"push"](v),P&&await T()}function x(l){const b=s.findIndex(A=>A.id===l);if(b===-1)return o.defaultRoute;const m=s[b+1],v=s[b-1],E=s.find(A=>A.id!==l),P=m||v||E;return P?P.to:o.defaultRoute}async function j(l=r.value,b={}){if(!l)return;if(!b.force&&o.keepLastTab&&s.length===1)throw new Error("[RouterTabs] Unable to close the final tab when keepLastTab is true.");const v=r.value===l&&b.redirect!==null,E=v?b.redirect??x(l):null;await O(l,{force:b.force}),b.redirect!==null&&v&&E&&await e.replace(E)}async function O(l,b={}){const m=s.findIndex(v=>v.id===l);m!==-1&&(s.splice(m,1),k.value===l&&(k.value=null),r.value===l&&(r.value=null,p.value=void 0))}async function K(l=r.value??void 0,b=!1){if(!l)return;const m=s.find(v=>v.id===l);m&&(o.keepAlive&&m.alive&&(m.alive=!1,await n.nextTick(),m.alive=!0,await n.nextTick()),k.value=l,await n.nextTick(),b||await n.nextTick(),k.value=null)}async function oe(l=!1){for(const b of s)await K(b.id,l)}async function ae(l=o.defaultRoute){s.splice(0,s.length),r.value=null,p.value=void 0;for(const b of o.initialTabs){const m=M(e,b.to),v=_(m,b,o.keepAlive);s.push(v)}await e.replace(l)}async function T(){const l=r.value;l&&await K(l,!0)}function W(l){return typeof l.matched=="object"?$(l):$(M(e,l))}function D(){const l=s.find(b=>b.id===r.value);return{tabs:s.map(Pe),active:l?l.to:null}}async function S(l){d=!0,s.splice(0,s.length),r.value=null,p.value=void 0;const b=l?.tabs??[];for(const v of b)try{const E=M(e,v.to),P=Be(v),A=_(E,P,o.keepAlive);Q(s,A,"last",null)}catch{}d=!1;const m=l?.active??b[b.length-1]?.to??o.defaultRoute;if(m)try{await e.replace(m)}catch{}}return n.watch(()=>e.currentRoute.value,l=>{if(d)return;const b=R(l);r.value=b.id,p.value=b,ce(s,o.maxAlive,r.value)},{immediate:!0}),o.initialTabs.length&&o.initialTabs.forEach(l=>{const b=M(e,l.to),m=_(b,l,o.keepAlive);Q(s,m,"last",null)}),{options:o,tabs:s,activeId:r,current:p,includeKeys:c,refreshingKey:k,openTab:I,closeTab:j,removeTab:O,refreshTab:K,refreshAll:oe,reset:ae,reload:T,getRouteKey:W,matchRoute:g,snapshot:D,hydrate:S}}function ue(e){return e?typeof e=="string"?{name:e}:e:{}}const V=Symbol("RouterTabsContext"),Y="router-tabs:snapshot";function Z(e={}){const{optional:a=!1}=e,o=n.inject(V,null);if(o)return o;const s=n.inject("$tabs",null);if(s)return s;const p=n.getCurrentInstance()?.appContext.config.globalProperties.$tabs;if(p)return p;if(!a)throw new Error("[RouterTabs] useRouterTabs must be used within <router-tab>.");return null}const Ie=864e5;function Se(e){if(typeof document>"u")return null;const a=`${encodeURIComponent(e)}=`,o=document.cookie?document.cookie.split("; "):[];for(const s of o)if(s.startsWith(a))return decodeURIComponent(s.slice(a.length));return null}function de(e,a,o){if(typeof document>"u")return;const{expiresInDays:s=7,path:r="/",domain:p,secure:k,sameSite:c="lax"}=o,d=[`${encodeURIComponent(e)}=${encodeURIComponent(a)}`];if(s!==1/0){const g=new Date(Date.now()+s*Ie).toUTCString();d.push(`Expires=${g}`)}r&&d.push(`Path=${r}`),p&&d.push(`Domain=${p}`),k&&d.push("Secure"),c&&d.push(`SameSite=${c.charAt(0).toUpperCase()}${c.slice(1)}`),document.cookie=d.join("; ")}function fe(e,a){if(typeof document>"u")return;const{path:o="/",domain:s}=a,r=[`${encodeURIComponent(e)}=`];r.push("Expires=Thu, 01 Jan 1970 00:00:01 GMT"),o&&r.push(`Path=${o}`),s&&r.push(`Domain=${s}`),document.cookie=r.join("; ")}const xe=e=>JSON.stringify(e??null),De=e=>{if(!e)return null;try{return JSON.parse(e)}catch{return null}};function ee(e={}){const{cookieKey:a=Y,serialize:o=xe,deserialize:s=De}=e,r=Z({optional:!0}),p=n.ref(!0),k=c=>{n.onMounted(async()=>{const d=s(Se(a));if(d&&d.tabs?.length)try{if(p.value=!0,await c.hydrate(d),d.active){await n.nextTick();const R=c.tabs.find(I=>I.to===d.active);R&&(c.activeId.value=R.id,c.current.value=R)}}finally{p.value=!1}else if(Object.prototype.hasOwnProperty.call(e,"fallbackRoute"))try{p.value=!0;const R=e.fallbackRoute??c.options.defaultRoute;await c.reset(R)}finally{p.value=!1}else p.value=!1;const g=c.snapshot();g.tabs.length?de(a,o(g),e):fe(a,e),p.value=!1}),n.watch(()=>({tabs:c.tabs.map(d=>({to:d.to,title:d.title,tips:d.tips,icon:d.icon,tabClass:d.tabClass,closable:d.closable})),active:c.activeId.value}),()=>{if(p.value)return;const d=c.snapshot();d.tabs.length?de(a,o(d),e):fe(a,e)},{deep:!0})};r?k(r):n.onMounted(()=>{const c=Z({optional:!0});c&&k(c)})}const Me=n.defineComponent({name:"RouterTab",components:{RouterView:Ce.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:Y},persistence:{type:Object,default:null},sortable:{type:Boolean,default:!0}},emits:["tab-sort","tab-sorted"],setup(e,{emit:a}){const o=n.getCurrentInstance();if(!o)throw new Error("[RouterTab] component must be used within a Vue application context.");const s=o.appContext.app.config.globalProperties.$router;if(!s)throw new Error("[RouterTab] Vue Router is required. Make sure to call app.use(router) before RouterTab.");const r=Ae(s,{initialTabs:e.tabs,keepAlive:e.keepAlive,maxAlive:e.maxAlive,keepLastTab:e.keepLastTab,appendPosition:e.append,defaultRoute:e.defaultPage});n.provide(V,r),o.appContext.config.globalProperties.$tabs=r;const p=n.computed(()=>!!o?.slots?.default),k=n.computed(()=>!!o?.slots?.start),c=n.computed(()=>!!o?.slots?.end),d=n.ref(0),g=n.computed(()=>{d.value;const t={};return r.tabs.forEach(i=>{const f=typeof i.title=="string"?i.title:String(i.title||ie(i));t[i.id]=f}),t});function R(){d.value++}const I=new Map,x=new Map;function j(t,i){if(!i||I.has(t))return;I.set(t,i);const f=r.tabs.find(y=>r.getRouteKey(y.to)===t);if(!f)return;const h=[];if(i.routeTabTitle!==void 0){const y=n.watch(()=>{const u=i.routeTabTitle;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{if(u!=null){const L=String(u);f.title=L,R()}},{immediate:!0});h.push(y)}if(i.routeTabIcon!==void 0){const y=n.watch(()=>{const u=i.routeTabIcon;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.icon=String(u),R())},{immediate:!0});h.push(y)}if(i.routeTabClosable!==void 0){const y=n.watch(()=>{const u=i.routeTabClosable;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u!=null&&(f.closable=!!u,R())},{immediate:!0});h.push(y)}if(i.routeTabMeta!==void 0){const y=n.watch(()=>{const u=i.routeTabMeta;return u&&typeof u=="object"&&"value"in u?u.value:u},u=>{u&&typeof u=="object"&&(Object.assign(f,u),R())},{immediate:!0,deep:!0});h.push(y)}x.set(t,h)}function O(t){const i=x.get(t);i&&(i.forEach(f=>f()),x.delete(t)),I.delete(t)}function K(t,i){t?t.routeTabTitle!==void 0||t.routeTabIcon!==void 0||t.routeTabClosable!==void 0?j(i,t):t.$&&(t.$.routeTabTitle!==void 0||t.$.routeTabIcon!==void 0||t.$.routeTabClosable!==void 0)&&j(i,t.$):t===null&&O(i)}if(e.cookieKey!==null||e.persistence){const t={...e.persistence??{}};e.cookieKey!==null?t.cookieKey=e.cookieKey||Y:t.cookieKey||(t.cookieKey=Y),ee(t)}const oe=n.computed(()=>ue(e.tabTransition)),ae=n.computed(()=>ue(e.pageTransition)),T=n.reactive({visible:!1,target:null,position:{x:0,y:0}}),W=n.ref(null),D=n.ref([]),S=n.ref(-1),l=n.reactive({dragging:!1,dragIndex:-1,dropIndex:-1,dragTab:null}),b=["refresh","refreshAll","close","closeLefts","closeRights","closeOthers"];function m(t){return r.tabs.findIndex(i=>i.id===t)}function v(t){const i=m(t.id);return i>0?r.tabs.slice(0,i):[]}function E(t){const i=m(t.id);return i>-1?r.tabs.slice(i+1):[]}function P(t){return r.tabs.filter(i=>i.id!==t.id)}async function A(t,i){const f=t.filter(h=>h.closable!==!1);if(f.length){for(const h of f)r.activeId.value===h.id?await r.closeTab(h.id,{redirect:i.to,force:!0}):await r.removeTab(h.id,{force:!0});r.activeId.value!==i.id&&await r.openTab(i.to,!0,!1)}}const et={refresh:{label:"Refresh",handler:async({target:t})=>{await r.refreshTab(t.id,!0)}},refreshAll:{label:"Refresh All",handler:async()=>{await r.refreshAll(!0)}},close:{label:"Close",handler:async({target:t})=>{await r.closeTab(t.id)},enable:({target:t})=>re(t)},closeLefts:{label:"Close to the Left",handler:async({target:t})=>{await A(v(t),t)},enable:({target:t})=>v(t).some(i=>i.closable!==!1)},closeRights:{label:"Close to the Right",handler:async({target:t})=>{await A(E(t),t)},enable:({target:t})=>E(t).some(i=>i.closable!==!1)},closeOthers:{label:"Close Others",handler:async({target:t})=>{await A(P(t),t)},enable:({target:t})=>P(t).some(i=>i.closable!==!1)}};function B(){T.visible=!1,T.target=null,S.value=-1,D.value=[]}function tt(t,i){e.contextmenu&&(T.visible=!0,T.target=t,T.position.x=i.clientX,T.position.y=i.clientY,document.addEventListener("click",B,{once:!0}),n.nextTick(()=>{ot()}))}function nt(t,i){const f=typeof t=="string"?{id:t}:t,h=et[f.id],y=f.label??h?.label??String(f.id),u=f.visible??h?.visible??!0;if(!(typeof u=="function"?u(i):u!==!1))return null;const le=f.enable??h?.enable??!0,yt=typeof le=="function"?le(i):le!==!1,we=f.handler??h?.handler;if(!we)return null;const Tt=async()=>{await Promise.resolve(we(i))};return{id:String(f.id),label:y,disabled:!yt,action:Tt}}const U=n.computed(()=>{if(!T.visible||!T.target||e.contextmenu===!1)return[];const t=Array.isArray(e.contextmenu)?e.contextmenu:b,i={target:T.target,controller:r};return t.map(f=>nt(f,i)).filter(f=>!!f)});function ot(){const t=W.value;if(!t)return;const i=8,{innerWidth:f,innerHeight:h}=window,y=t.getBoundingClientRect();let u=T.position.x,L=T.position.y;y.right>f-i&&(u=Math.max(i,f-y.width-i)),y.bottom>h-i&&(L=Math.max(i,h-y.height-i)),(u!==T.position.x||L!==T.position.y)&&(T.position.x=u,T.position.y=L)}function at(t,i){D.value[i]=t??null}function it(t){if(t<0)return;D.value[t]?.focus({preventScroll:!0})}function q(t,i,f=U.value){if(!f.length)return-1;const h=f.length;let y=t;for(let u=0;u<h;u++)if(y=(y+i+h)%h,!f[y].disabled)return y;return-1}function z(t){S.value=t,!(t<0)&&n.nextTick(()=>it(t))}function Te(t){const i=q(S.value,t);i!==-1&&z(i)}function rt(t){if(!T.visible)return;const i=t.key,f=U.value;if(!f.length)return;if(i==="Tab"){B();return}if(["ArrowDown","ArrowUp","ArrowRight","ArrowLeft","Home","End","Enter"," ","Spacebar","Escape"].includes(i))switch(t.preventDefault(),i){case"ArrowDown":case"ArrowRight":Te(1);break;case"ArrowUp":case"ArrowLeft":Te(-1);break;case"Home":z(q(-1,1));break;case"End":z(q(f.length,-1));break;case"Enter":case" ":case"Spacebar":{const y=S.value;if(y>-1){const u=f[y];u.disabled||ke(u)}break}case"Escape":B();break}}async function ke(t){t.disabled||(B(),await t.action())}function ie(t){return typeof t.title=="string"&&t.title.trim()?t.title:Array.isArray(t.title)&&t.title.length&&String(t.title[0]).trim()?String(t.title[0]):"Untitled"}function lt(t){return g.value[t.id]||ie(t)}function re(t){return!(t.closable===!1||r.options.keepLastTab&&r.tabs.length<=1)}async function st(t){await r.closeTab(t.id)}function ct(t){if(t.href&&typeof window<"u"){t.target&&t.target!=="_self"?window.open(t.href,t.target):window.location.assign(t.href);return}r.activeId.value!==t.id&&r.openTab(t.to,!1)}function ut(t){return["router-tab__item",{"is-active":r.activeId.value===t.id,"is-closable":re(t),"is-dragging":l.dragging&&l.dragTab?.id===t.id,"is-drag-over":l.dropIndex===m(t.id)},t.tabClass]}function dt(t){return r.refreshingKey.value===r.getRouteKey(t)}function ft(t,i,f){e.sortable&&(l.dragging=!0,l.dragIndex=i,l.dragTab=t,f.dataTransfer&&(f.dataTransfer.effectAllowed="move",f.dataTransfer.setData("text/plain",t.id)),a("tab-sort",{tab:t,index:i}))}function bt(t,i){!e.sortable||!l.dragging||(i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="move"))}function mt(t){!e.sortable||!l.dragging||(l.dropIndex=t)}function pt(){!e.sortable||l.dragging}function gt(t,i){if(!(!e.sortable||!l.dragging)){if(i.preventDefault(),l.dragIndex!==-1&&l.dragIndex!==t){const f=r.tabs.splice(l.dragIndex,1)[0];r.tabs.splice(t,0,f),a("tab-sorted",{tab:f,fromIndex:l.dragIndex,toIndex:t})}ve()}}function ve(){l.dragging=!1,l.dragIndex=-1,l.dropIndex=-1,l.dragTab=null}n.onMounted(()=>{document.addEventListener("keydown",B)}),n.onBeforeUnmount(()=>{document.removeEventListener("keydown",B),o.appContext.config.globalProperties.$tabs=null,x.forEach(t=>{t.forEach(i=>i())}),x.clear(),I.clear()}),n.watch(()=>e.keepAlive,t=>{r.options.keepAlive=t}),n.watch(()=>r.activeId.value,()=>B()),n.watch(()=>e.contextmenu,t=>{t||B()}),n.watch(()=>U.value.length,t=>{T.visible&&t===0&&B()}),n.watch(U,t=>{if(D.value=new Array(t.length).fill(null),!T.visible)return;const i=q(-1,1,t);z(i)}),n.watch(()=>T.visible,t=>{t||(S.value=-1,D.value=[])});const ht=r.includeKeys;return{controller:r,tabs:r.tabs,includeKeys:ht,tabTransitionProps:oe,pageTransitionProps:ae,buildTabClass:ut,activate:ct,close:st,context:T,menuItems:U,handleMenuAction:ke,showContextMenu:tt,hideContextMenu:B,getTabTitle:ie,isClosable:re,isRefreshing:dt,hasCustomSlot:p,hasStartSlot:k,hasEndSlot:c,onDragStart:ft,onDragOver:bt,onDragEnter:mt,onDragLeave:pt,onDrop:gt,onDragEnd:ve,setupComponentWatching:j,cleanupComponentWatching:O,handleComponentRef:K,getReactiveTabTitle:lt,triggerTabUpdate:R,menuRef:W,highlightedIndex:S,setMenuItemRef:at,onMenuKeydown:rt,highlightMenuIndex:z}}}),$e=(e,a)=>{const o=e.__vccOpts||e;for(const[s,r]of a)o[s]=r;return o},Ke={class:"router-tab"},Le={class:"router-tab__header"},Ve={class:"router-tab__scroll"},Ne=["data-title","draggable","onClick","onAuxclick","onContextmenu","onDragstart","onDragover","onDragenter","onDrop"],je=["title"],Oe=["onClick"],Ue={class:"router-tab__container"},ze=["aria-disabled","disabled","tabindex","onMouseenter","onClick"];function _e(e,a,o,s,r,p){const k=n.resolveComponent("RouterView");return n.openBlock(),n.createElementBlock("div",Ke,[n.createElementVNode("header",Le,[n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-start",{"has-content":e.hasStartSlot}])},[n.renderSlot(e.$slots,"start")],2),n.createElementVNode("div",Ve,[n.createVNode(n.TransitionGroup,n.mergeProps({tag:"ul",class:"router-tab__nav"},e.tabTransitionProps),{default:n.withCtx(()=>[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.tabs,(c,d)=>(n.openBlock(),n.createElementBlock("li",{key:c.id,class:n.normalizeClass(e.buildTabClass(c)),"data-title":e.getTabTitle(c),draggable:e.sortable,onClick:g=>e.activate(c),onAuxclick:n.withModifiers(g=>e.close(c),["middle","prevent"]),onContextmenu:n.withModifiers(g=>e.showContextMenu(c,g),["prevent"]),onDragstart:g=>e.onDragStart(c,d,g),onDragover:g=>e.onDragOver(d,g),onDragenter:g=>e.onDragEnter(d),onDragleave:a[0]||(a[0]=(...g)=>e.onDragLeave&&e.onDragLeave(...g)),onDrop:g=>e.onDrop(d,g),onDragend:a[1]||(a[1]=(...g)=>e.onDragEnd&&e.onDragEnd(...g))},[c.icon?(n.openBlock(),n.createElementBlock("i",{key:0,class:n.normalizeClass(["router-tab__item-icon",c.icon])},null,2)):n.createCommentVNode("",!0),n.createElementVNode("span",{class:"router-tab__item-title",title:e.getReactiveTabTitle(c)},n.toDisplayString(e.getReactiveTabTitle(c)),9,je),e.isClosable(c)?(n.openBlock(),n.createElementBlock("a",{key:1,class:"router-tab__item-close",onClick:n.withModifiers(g=>e.close(c),["stop"])},null,8,Oe)):n.createCommentVNode("",!0)],42,Ne))),128))]),_:1},16)]),n.createElementVNode("div",{class:n.normalizeClass(["router-tab__slot-end",{"has-content":e.hasEndSlot}])},[n.renderSlot(e.$slots,"end")],2)]),n.createElementVNode("div",Ue,[n.createVNode(k,null,{default:n.withCtx(c=>[e.hasCustomSlot?n.renderSlot(e.$slots,"default",n.normalizeProps(n.mergeProps({key:0},{...c,controller:e.controller,pageRef:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route))}))):(n.openBlock(),n.createElementBlock(n.Fragment,{key:1},[n.createVNode(n.Transition,n.mergeProps(e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[e.controller.options.keepAlive?(n.openBlock(),n.createBlock(n.KeepAlive,{key:0,include:e.includeKeys,max:e.controller.options.maxAlive||void 0},[e.isRefreshing(c.route)?n.createCommentVNode("",!0):(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"}))],1032,["include","max"])):n.createCommentVNode("",!0)]),_:2},1040),n.createVNode(n.Transition,n.mergeProps(e.pageTransitionProps,{appear:""}),{default:n.withCtx(()=>[!e.controller.options.keepAlive||e.isRefreshing(c.route)?(n.openBlock(),n.createBlock(n.resolveDynamicComponent(c.Component),{key:e.controller.getRouteKey(c.route)+(e.isRefreshing(c.route)?"-refresh":""),ref:d=>e.handleComponentRef(d,e.controller.getRouteKey(c.route)),class:"router-tab-page"})):n.createCommentVNode("",!0)]),_:2},1040)],64))]),_:3})]),e.context.visible&&e.context.target?(n.openBlock(),n.createElementBlock("div",{key:0,ref:"menuRef",class:"router-tab__contextmenu",role:"menu",onKeydown:a[2]||(a[2]=(...c)=>e.onMenuKeydown&&e.onMenuKeydown(...c)),style:n.normalizeStyle({left:e.context.position.x+"px",top:e.context.position.y+"px"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList(e.menuItems,(c,d)=>(n.openBlock(),n.createElementBlock("a",{key:c.id,role:"menuitem",class:n.normalizeClass(["router-tab__contextmenu-item",{"is-focused":d===e.highlightedIndex}]),"aria-disabled":c.disabled,disabled:c.disabled,tabindex:c.disabled?-1:d===e.highlightedIndex?0:-1,ref_for:!0,ref:g=>e.setMenuItemRef(g,d),onMouseenter:g=>!c.disabled&&e.highlightMenuIndex(d),onClick:g=>e.handleMenuAction(c)},n.toDisplayString(c.label),43,ze))),128))],36)):n.createCommentVNode("",!0)])}const te=$e(Me,[["render",_e]]),Ye={class:"router-tabs","aria-hidden":"true"},F=n.defineComponent({name:"RouterTabs",__name:"RouterTabs",props:{cookieKey:{},expiresInDays:{},path:{},domain:{},secure:{type:Boolean},sameSite:{},serialize:{type:Function},deserialize:{type:Function},fallbackRoute:{}},setup(e){return ee(e),(o,s)=>(n.openBlock(),n.createElementBlock("span",Ye))}}),be="tab-theme-style",me="tab-theme-primary-color",Fe="system",pe="(prefers-color-scheme: dark)";let N=null;const C={primary:"#034960",background:"#ffffff",text:"#1e293b",border:"#e2e8f0",activeBackground:"#034960",activeText:"#ffffff",activeBorder:"#034960",headerBackground:"#ffffff",buttonBackground:"#f8fafc",buttonColor:"#034960",activeButtonBackground:"#034960",activeButtonColor:"#ffffff",iconColor:"#475569"},He={primary:"#38bdf8",background:"#0f172a",text:"#f1f5f9",border:"#334155",activeBackground:"#1e293b",activeText:"#38bdf8",activeBorder:"#38bdf8",headerBackground:"#0c4a6e",buttonBackground:"#1e293b",buttonColor:"#f1f5f9",activeButtonBackground:"#38bdf8",activeButtonColor:"#0f172a",iconColor:"#cbd5e1"};function Je(e){if(typeof window>"u")return null;const a=window.localStorage.getItem(e);if(!a)return null;try{const o=JSON.parse(a);return o&&typeof o=="object"?o:null}catch{return null}}function ne(e){typeof document>"u"||(document.documentElement.style.setProperty("--router-tab-primary",e.primary??C.primary),document.documentElement.style.setProperty("--router-tab-header-bg",e.headerBackground??C.headerBackground),document.documentElement.style.setProperty("--router-tab-background",e.background??C.background),document.documentElement.style.setProperty("--router-tab-active-background",e.activeBackground??C.activeBackground),document.documentElement.style.setProperty("--router-tab-text",e.text??C.text),document.documentElement.style.setProperty("--router-tab-active-text",e.activeText??C.activeText),document.documentElement.style.setProperty("--router-tab-border",e.border??C.border),document.documentElement.style.setProperty("--router-tab-active-border",e.activeBorder??C.activeBorder),document.documentElement.style.setProperty("--router-tab-button-color",e.buttonColor??C.buttonColor),document.documentElement.style.setProperty("--router-tab-active-button-color",e.activeButtonColor??C.activeButtonColor),document.documentElement.style.setProperty("--router-tab-button-background",e.buttonBackground??C.buttonBackground),document.documentElement.style.setProperty("--router-tab-active-button-background",e.activeButtonBackground??C.activeButtonBackground),document.documentElement.style.setProperty("--router-tab-icon-color",e.iconColor??C.iconColor))}function ge(e){if(typeof document>"u")return;const a=document.documentElement,o=window.matchMedia(pe),s=()=>{a.dataset.theme=o.matches?"dark":"light"};N&&(o.removeEventListener("change",N),N=null),e==="system"?(s(),N=()=>s(),o.addEventListener("change",N)):a.dataset.theme=e}function he(e={}){if(typeof window>"u")return;const{styleKey:a=be,primaryKey:o=me,defaultStyle:s=Fe,defaultPrimary:r}=e,p=window.localStorage.getItem(a)??s;ge(p);const c=p==="dark"||p==="system"&&window.matchMedia(pe).matches?{...He}:{...C};r&&(c.primary=r);const d=Je(o);ne(d?{...c,...d}:c)}function We(e,a){if(typeof window>"u")return;const o=a?.styleKey??be;window.localStorage.setItem(o,e),ge(e)}function qe(e,a){if(typeof window>"u")return;const o=a?.primaryKey??me;window.localStorage.setItem(o,JSON.stringify(e)),ne(e)}function H(e,a){if(n.isRef(e)){const s=!n.isReadonly(e);return{value:e,update:s?r=>{e.value=r}:()=>{}}}if(typeof e=="function"){const s=e;return{value:n.computed(s),update:()=>{}}}const o=n.ref(e===void 0?a:e);return{value:o,update:s=>{o.value=s}}}function J(e={}){const a=H(e.title,"Untitled"),o=H(e.icon,""),s=H(e.closable,!0),r=H(e.meta,{});return{routeTabTitle:a.value,routeTabIcon:o.value,routeTabClosable:s.value,routeTabMeta:r.value,updateTitle:a.update,updateIcon:o.update,updateClosable:s.update,updateMeta:r.update}}function Ge(e,a="Page"){return J({title:n.computed(()=>e.value?"Loading...":a),icon:n.computed(()=>e.value?"mdi-loading mdi-spin":"mdi-page"),closable:n.computed(()=>!e.value)})}function Xe(e,a="Page",o="mdi-page"){return J({title:n.computed(()=>e.value>0?`${a} (${e.value})`:a),icon:n.computed(()=>e.value>0?"mdi-bell-badge":o)})}function Qe(e,a="Page"){const o={normal:{suffix:"",icon:"mdi-page"},loading:{suffix:" - Loading",icon:"mdi-loading mdi-spin"},error:{suffix:" - Error",icon:"mdi-alert"},success:{suffix:" - Success",icon:"mdi-check-circle"}};return J({title:n.computed(()=>a+o[e.value].suffix),icon:n.computed(()=>o[e.value].icon),closable:n.computed(()=>e.value!=="loading")})}let ye=!1;const Ze={install(e,a){if(ye)return;ye=!0;const{initTheme:o=!0,themeOptions:s,componentName:r=te.name||"RouterTab",tabsComponentName:p=F.name||"RouterTabs"}=a??{};o&&he(s??{}),e.component(r,te),e.component(p,F),p.toLowerCase()!=="router-tabs"&&e.component("router-tabs",F),Object.defineProperty(e.config.globalProperties,"$tabs",{configurable:!0,enumerable:!1,get(){return e._context.provides[V]},set(k){k&&e.provide(V,k)}})}};w.RouterTab=te,w.RouterTabs=F,w.default=Ze,w.initRouterTabsTheme=he,w.routerTabsKey=V,w.setRouterTabsPrimary=qe,w.setRouterTabsTheme=We,w.useLoadingTab=Ge,w.useNotificationTab=Xe,w.useReactiveTab=J,w.useRouterTabs=Z,w.useRouterTabsPersistence=ee,w.useStatusTab=Qe,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="router-tab">
|
|
3
3
|
<header class="router-tab__header">
|
|
4
|
-
<div
|
|
4
|
+
<div
|
|
5
|
+
class="router-tab__slot-start"
|
|
6
|
+
:class="{ 'has-content': hasStartSlot }"
|
|
7
|
+
>
|
|
5
8
|
<slot name="start" />
|
|
6
9
|
</div>
|
|
7
10
|
|
|
@@ -40,7 +43,10 @@
|
|
|
40
43
|
</transition-group>
|
|
41
44
|
</div>
|
|
42
45
|
|
|
43
|
-
<div
|
|
46
|
+
<div
|
|
47
|
+
class="router-tab__slot-end"
|
|
48
|
+
:class="{ 'has-content': hasEndSlot }"
|
|
49
|
+
>
|
|
44
50
|
<slot name="end" />
|
|
45
51
|
</div>
|
|
46
52
|
</header>
|
|
@@ -95,18 +101,26 @@
|
|
|
95
101
|
|
|
96
102
|
<div
|
|
97
103
|
v-if="context.visible && context.target"
|
|
104
|
+
ref="menuRef"
|
|
98
105
|
class="router-tab__contextmenu"
|
|
106
|
+
role="menu"
|
|
107
|
+
@keydown="onMenuKeydown"
|
|
99
108
|
:style="{ left: context.position.x + 'px', top: context.position.y + 'px' }"
|
|
100
109
|
>
|
|
101
110
|
<a
|
|
102
|
-
v-for="item in menuItems"
|
|
111
|
+
v-for="(item, index) in menuItems"
|
|
103
112
|
:key="item.id"
|
|
104
|
-
|
|
113
|
+
role="menuitem"
|
|
114
|
+
:class="['router-tab__contextmenu-item', { 'is-focused': index === highlightedIndex }]"
|
|
105
115
|
:aria-disabled="item.disabled"
|
|
106
|
-
|
|
116
|
+
:disabled="item.disabled"
|
|
117
|
+
:tabindex="item.disabled ? -1 : index === highlightedIndex ? 0 : -1"
|
|
118
|
+
:ref="el => setMenuItemRef(el, index)"
|
|
119
|
+
@mouseenter="!item.disabled && highlightMenuIndex(index)"
|
|
120
|
+
@click="handleMenuAction(item)"
|
|
107
121
|
>
|
|
108
122
|
{{ item.label }}
|
|
109
|
-
|
|
123
|
+
</a>
|
|
110
124
|
</div>
|
|
111
125
|
</div>
|
|
112
126
|
</template>
|
|
@@ -116,6 +130,7 @@ import {
|
|
|
116
130
|
computed,
|
|
117
131
|
defineComponent,
|
|
118
132
|
getCurrentInstance,
|
|
133
|
+
nextTick,
|
|
119
134
|
onBeforeUnmount,
|
|
120
135
|
onMounted,
|
|
121
136
|
provide,
|
|
@@ -226,6 +241,8 @@ export default defineComponent({
|
|
|
226
241
|
instance.appContext.config.globalProperties.$tabs = controller
|
|
227
242
|
|
|
228
243
|
const hasCustomSlot = computed(() => Boolean(instance?.slots?.default))
|
|
244
|
+
const hasStartSlot = computed(() => Boolean(instance?.slots?.start))
|
|
245
|
+
const hasEndSlot = computed(() => Boolean(instance?.slots?.end))
|
|
229
246
|
|
|
230
247
|
// Force reactivity by creating a trigger ref
|
|
231
248
|
const tabUpdateTrigger = ref(0)
|
|
@@ -390,6 +407,10 @@ export default defineComponent({
|
|
|
390
407
|
position: { x: 0, y: 0 }
|
|
391
408
|
})
|
|
392
409
|
|
|
410
|
+
const menuRef = ref<HTMLElement | null>(null)
|
|
411
|
+
const menuItemRefs = ref<(HTMLElement | null)[]>([])
|
|
412
|
+
const highlightedIndex = ref(-1)
|
|
413
|
+
|
|
393
414
|
// Drag and drop state
|
|
394
415
|
const dragState = reactive({
|
|
395
416
|
dragging: false,
|
|
@@ -500,6 +521,8 @@ export default defineComponent({
|
|
|
500
521
|
function hideContextMenu() {
|
|
501
522
|
context.visible = false
|
|
502
523
|
context.target = null
|
|
524
|
+
highlightedIndex.value = -1
|
|
525
|
+
menuItemRefs.value = []
|
|
503
526
|
}
|
|
504
527
|
|
|
505
528
|
function showContextMenu(tab: TabRecord, event: MouseEvent) {
|
|
@@ -510,6 +533,9 @@ export default defineComponent({
|
|
|
510
533
|
context.position.y = event.clientY
|
|
511
534
|
|
|
512
535
|
document.addEventListener('click', hideContextMenu, { once: true })
|
|
536
|
+
nextTick(() => {
|
|
537
|
+
adjustMenuPosition()
|
|
538
|
+
})
|
|
513
539
|
}
|
|
514
540
|
|
|
515
541
|
function normalizeMenuItem(raw: MenuConfig, ctx: MenuActionContext): ResolvedMenuItem | null {
|
|
@@ -549,6 +575,128 @@ export default defineComponent({
|
|
|
549
575
|
.filter((item): item is ResolvedMenuItem => !!item)
|
|
550
576
|
})
|
|
551
577
|
|
|
578
|
+
function adjustMenuPosition() {
|
|
579
|
+
const menuEl = menuRef.value
|
|
580
|
+
if (!menuEl) return
|
|
581
|
+
const margin = 8
|
|
582
|
+
const { innerWidth, innerHeight } = window
|
|
583
|
+
const rect = menuEl.getBoundingClientRect()
|
|
584
|
+
|
|
585
|
+
let nextX = context.position.x
|
|
586
|
+
let nextY = context.position.y
|
|
587
|
+
|
|
588
|
+
if (rect.right > innerWidth - margin) {
|
|
589
|
+
nextX = Math.max(margin, innerWidth - rect.width - margin)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (rect.bottom > innerHeight - margin) {
|
|
593
|
+
nextY = Math.max(margin, innerHeight - rect.height - margin)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (nextX !== context.position.x || nextY !== context.position.y) {
|
|
597
|
+
context.position.x = nextX
|
|
598
|
+
context.position.y = nextY
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function setMenuItemRef(el: Element | null, index: number) {
|
|
603
|
+
menuItemRefs.value[index] = (el as HTMLElement) ?? null
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function focusMenuItem(index: number) {
|
|
607
|
+
if (index < 0) return
|
|
608
|
+
const el = menuItemRefs.value[index]
|
|
609
|
+
el?.focus({ preventScroll: true })
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function findNextEnabledIndex(start: number, step: 1 | -1, items = menuItems.value): number {
|
|
613
|
+
if (!items.length) return -1
|
|
614
|
+
const total = items.length
|
|
615
|
+
let idx = start
|
|
616
|
+
|
|
617
|
+
for (let i = 0; i < total; i++) {
|
|
618
|
+
idx = (idx + step + total) % total
|
|
619
|
+
if (!items[idx].disabled) return idx
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return -1
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function highlightMenuIndex(index: number) {
|
|
626
|
+
highlightedIndex.value = index
|
|
627
|
+
if (index < 0) return
|
|
628
|
+
nextTick(() => focusMenuItem(index))
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function moveHighlight(step: 1 | -1) {
|
|
632
|
+
const nextIndex = findNextEnabledIndex(highlightedIndex.value, step)
|
|
633
|
+
if (nextIndex !== -1) {
|
|
634
|
+
highlightMenuIndex(nextIndex)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function onMenuKeydown(event: KeyboardEvent) {
|
|
639
|
+
if (!context.visible) return
|
|
640
|
+
|
|
641
|
+
const key = event.key
|
|
642
|
+
const items = menuItems.value
|
|
643
|
+
if (!items.length) return
|
|
644
|
+
|
|
645
|
+
if (key === 'Tab') {
|
|
646
|
+
hideContextMenu()
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const handledKeys = [
|
|
651
|
+
'ArrowDown',
|
|
652
|
+
'ArrowUp',
|
|
653
|
+
'ArrowRight',
|
|
654
|
+
'ArrowLeft',
|
|
655
|
+
'Home',
|
|
656
|
+
'End',
|
|
657
|
+
'Enter',
|
|
658
|
+
' ',
|
|
659
|
+
'Spacebar',
|
|
660
|
+
'Escape'
|
|
661
|
+
]
|
|
662
|
+
|
|
663
|
+
if (!handledKeys.includes(key)) return
|
|
664
|
+
|
|
665
|
+
event.preventDefault()
|
|
666
|
+
|
|
667
|
+
switch (key) {
|
|
668
|
+
case 'ArrowDown':
|
|
669
|
+
case 'ArrowRight':
|
|
670
|
+
moveHighlight(1)
|
|
671
|
+
break
|
|
672
|
+
case 'ArrowUp':
|
|
673
|
+
case 'ArrowLeft':
|
|
674
|
+
moveHighlight(-1)
|
|
675
|
+
break
|
|
676
|
+
case 'Home':
|
|
677
|
+
highlightMenuIndex(findNextEnabledIndex(-1, 1))
|
|
678
|
+
break
|
|
679
|
+
case 'End':
|
|
680
|
+
highlightMenuIndex(findNextEnabledIndex(items.length, -1))
|
|
681
|
+
break
|
|
682
|
+
case 'Enter':
|
|
683
|
+
case ' ':
|
|
684
|
+
case 'Spacebar': {
|
|
685
|
+
const index = highlightedIndex.value
|
|
686
|
+
if (index > -1) {
|
|
687
|
+
const item = items[index]
|
|
688
|
+
if (!item.disabled) {
|
|
689
|
+
handleMenuAction(item)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
break
|
|
693
|
+
}
|
|
694
|
+
case 'Escape':
|
|
695
|
+
hideContextMenu()
|
|
696
|
+
break
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
552
700
|
async function handleMenuAction(item: ResolvedMenuItem) {
|
|
553
701
|
if (item.disabled) return
|
|
554
702
|
hideContextMenu()
|
|
@@ -579,6 +727,15 @@ export default defineComponent({
|
|
|
579
727
|
}
|
|
580
728
|
|
|
581
729
|
function activate(tab: TabRecord) {
|
|
730
|
+
if (tab.href && typeof window !== 'undefined') {
|
|
731
|
+
if (tab.target && tab.target !== '_self') {
|
|
732
|
+
window.open(tab.href as string, tab.target)
|
|
733
|
+
} else {
|
|
734
|
+
window.location.assign(tab.href as string)
|
|
735
|
+
}
|
|
736
|
+
return
|
|
737
|
+
}
|
|
738
|
+
|
|
582
739
|
if (controller.activeId.value === tab.id) return
|
|
583
740
|
controller.openTab(tab.to, false)
|
|
584
741
|
}
|
|
@@ -704,6 +861,23 @@ export default defineComponent({
|
|
|
704
861
|
}
|
|
705
862
|
)
|
|
706
863
|
|
|
864
|
+
watch(menuItems, items => {
|
|
865
|
+
menuItemRefs.value = new Array(items.length).fill(null)
|
|
866
|
+
if (!context.visible) return
|
|
867
|
+
const first = findNextEnabledIndex(-1, 1, items)
|
|
868
|
+
highlightMenuIndex(first)
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
watch(
|
|
872
|
+
() => context.visible,
|
|
873
|
+
visible => {
|
|
874
|
+
if (!visible) {
|
|
875
|
+
highlightedIndex.value = -1
|
|
876
|
+
menuItemRefs.value = []
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
|
|
707
881
|
const includeKeys = controller.includeKeys
|
|
708
882
|
|
|
709
883
|
return {
|
|
@@ -724,6 +898,8 @@ export default defineComponent({
|
|
|
724
898
|
isClosable,
|
|
725
899
|
isRefreshing,
|
|
726
900
|
hasCustomSlot,
|
|
901
|
+
hasStartSlot,
|
|
902
|
+
hasEndSlot,
|
|
727
903
|
onDragStart,
|
|
728
904
|
onDragOver,
|
|
729
905
|
onDragEnter,
|
|
@@ -734,8 +910,13 @@ export default defineComponent({
|
|
|
734
910
|
cleanupComponentWatching,
|
|
735
911
|
handleComponentRef,
|
|
736
912
|
getReactiveTabTitle,
|
|
737
|
-
triggerTabUpdate
|
|
913
|
+
triggerTabUpdate,
|
|
914
|
+
menuRef,
|
|
915
|
+
highlightedIndex,
|
|
916
|
+
setMenuItemRef,
|
|
917
|
+
onMenuKeydown,
|
|
918
|
+
highlightMenuIndex
|
|
738
919
|
}
|
|
739
920
|
}
|
|
740
921
|
})
|
|
741
|
-
</script>
|
|
922
|
+
</script>
|
|
@@ -105,6 +105,14 @@ function insertTab(tabs: TabRecord[], tab: TabRecord, position: 'last' | 'next',
|
|
|
105
105
|
const exists = tabs.find(item => item.id === tab.id)
|
|
106
106
|
if (exists) return
|
|
107
107
|
|
|
108
|
+
if (position === 'next' && referenceId) {
|
|
109
|
+
const referenceIndex = tabs.findIndex(item => item.id === referenceId)
|
|
110
|
+
if (referenceIndex !== -1) {
|
|
111
|
+
tabs.splice(referenceIndex + 1, 0, tab)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
tabs.push(tab)
|
|
109
117
|
}
|
|
110
118
|
|
|
@@ -259,6 +267,16 @@ export function createRouterTabs(
|
|
|
259
267
|
|
|
260
268
|
async function refreshTab(id: string | undefined = activeId.value ?? undefined, force = false) {
|
|
261
269
|
if (!id) return
|
|
270
|
+
const tab = tabs.find(item => item.id === id)
|
|
271
|
+
if (!tab) return
|
|
272
|
+
|
|
273
|
+
if (options.keepAlive && tab.alive) {
|
|
274
|
+
tab.alive = false
|
|
275
|
+
await nextTick()
|
|
276
|
+
tab.alive = true
|
|
277
|
+
await nextTick()
|
|
278
|
+
}
|
|
279
|
+
|
|
262
280
|
refreshingKey.value = id
|
|
263
281
|
await nextTick()
|
|
264
282
|
if (!force) await nextTick()
|
package/lib/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from './theme'
|
|
12
12
|
|
|
13
13
|
import type { RouterTabsContext } from './core/types'
|
|
14
|
+
import type { RouterTabsThemeOptions } from './theme'
|
|
14
15
|
|
|
15
16
|
export type {
|
|
16
17
|
TabRecord,
|
|
@@ -22,6 +23,28 @@ export type {
|
|
|
22
23
|
|
|
23
24
|
export type { RouterTabsThemeOptions } from './theme'
|
|
24
25
|
|
|
26
|
+
export interface RouterTabsPluginOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Whether to initialise the theme system automatically during install.
|
|
29
|
+
* Defaults to `true`.
|
|
30
|
+
*/
|
|
31
|
+
initTheme?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* Theme options passed to `initRouterTabsTheme` when `initTheme` is enabled.
|
|
34
|
+
*/
|
|
35
|
+
themeOptions?: RouterTabsThemeOptions
|
|
36
|
+
/**
|
|
37
|
+
* Global component name used when registering `RouterTab`.
|
|
38
|
+
* Defaults to the component's `name` option or `"RouterTab"`.
|
|
39
|
+
*/
|
|
40
|
+
componentName?: string
|
|
41
|
+
/**
|
|
42
|
+
* Global component name used when registering `RouterTabs`.
|
|
43
|
+
* Defaults to the component's `name` option or `"RouterTabs"`.
|
|
44
|
+
*/
|
|
45
|
+
tabsComponentName?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
25
48
|
export {
|
|
26
49
|
routerTabsKey,
|
|
27
50
|
useRouterTabs,
|
|
@@ -45,22 +68,30 @@ export type {
|
|
|
45
68
|
ReactiveTabReturn
|
|
46
69
|
} from './useReactiveTab'
|
|
47
70
|
|
|
48
|
-
import
|
|
71
|
+
import './scss/index.scss'
|
|
72
|
+
|
|
73
|
+
let installed = false
|
|
49
74
|
|
|
50
75
|
const plugin: Plugin = {
|
|
51
|
-
install(app: App) {
|
|
52
|
-
if (
|
|
53
|
-
|
|
76
|
+
install(app: App, options?: RouterTabsPluginOptions) {
|
|
77
|
+
if (installed) return
|
|
78
|
+
installed = true
|
|
54
79
|
|
|
55
|
-
|
|
80
|
+
const {
|
|
81
|
+
initTheme = true,
|
|
82
|
+
themeOptions,
|
|
83
|
+
componentName = RouterTab.name || 'RouterTab',
|
|
84
|
+
tabsComponentName = RouterTabsComponent.name || 'RouterTabs'
|
|
85
|
+
} = options ?? {}
|
|
56
86
|
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
if (initTheme) {
|
|
88
|
+
initRouterTabsTheme(themeOptions ?? {})
|
|
89
|
+
}
|
|
59
90
|
|
|
60
91
|
app.component(componentName, RouterTab)
|
|
61
|
-
app.component(
|
|
92
|
+
app.component(tabsComponentName, RouterTabsComponent)
|
|
62
93
|
|
|
63
|
-
if (
|
|
94
|
+
if (tabsComponentName.toLowerCase() !== 'router-tabs') {
|
|
64
95
|
app.component('router-tabs', RouterTabsComponent)
|
|
65
96
|
}
|
|
66
97
|
|
package/lib/persistence.ts
CHANGED
|
@@ -119,7 +119,7 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
119
119
|
} finally {
|
|
120
120
|
hydrating.value = false
|
|
121
121
|
}
|
|
122
|
-
} else {
|
|
122
|
+
} else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
|
|
123
123
|
try {
|
|
124
124
|
hydrating.value = true
|
|
125
125
|
const fallback = options.fallbackRoute ?? ctrl.options.defaultRoute
|
|
@@ -127,6 +127,8 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
127
127
|
} finally {
|
|
128
128
|
hydrating.value = false
|
|
129
129
|
}
|
|
130
|
+
} else {
|
|
131
|
+
hydrating.value = false
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
const snapshot = ctrl.snapshot()
|