UNPKG

vue3-tabor

Version:

Vue 3 routing tabs with keepAlive support, browser-like tab navigation for Vue applications

1,022 lines (1,021 loc) 32.3 kB
import { reactive, computed, nextTick, markRaw, inject, defineComponent, createVNode, withDirectives, mergeProps, vShow, onMounted, resolveComponent, createElementBlock, openBlock, normalizeClass, withCtx, createBlock, KeepAlive, createCommentVNode, resolveDynamicComponent, normalizeStyle, createElementVNode, toDisplayString, ref, h, provide } from "vue"; class VNodeWrapper { constructor(vnode, lastAccessed) { this.vnode = vnode; this.lastAccessed = lastAccessed; } } const useCache = (options) => { const { max = 10 } = options; const state = reactive({ keySet: /* @__PURE__ */ new Set(), keyToWrapper: /* @__PURE__ */ new Map(), componentMap: /* @__PURE__ */ new WeakMap(), refreshing: false, activeKey: void 0 }); const keys = computed(() => Array.from(state.keySet)); const setActiveKey = (key) => { state.activeKey = key; if (key) { const wrapper = state.keyToWrapper.get(key); if (wrapper) wrapper.lastAccessed = Date.now(); } }; const add = (key) => { state.keySet.add(key); }; const has = (key) => { return state.keySet.has(key); }; const hasComponent = (key) => { const wrapper = state.keyToWrapper.get(key); return Boolean(wrapper && state.componentMap.has(wrapper)); }; const getComponent = (key) => { const wrapper = state.keyToWrapper.get(key); return wrapper ? state.componentMap.get(wrapper) : void 0; }; const removeOldestEntry = () => { if (state.keyToWrapper.size === 0) return void 0; const entries = Array.from(state.keyToWrapper.entries()); if (entries.length === 0) return void 0; const oldestEntry = entries.reduce( (acc, curr) => acc[1].lastAccessed < curr[1].lastAccessed ? acc : curr, entries[0] ); const oldestKey = oldestEntry == null ? void 0 : oldestEntry[0]; if (oldestKey) { remove(oldestKey); return oldestKey; } return void 0; }; const addComponent = (key, vNode) => { add(key); if (state.keySet.size > max) { removeOldestEntry(); } const wrapper = new VNodeWrapper(markRaw(vNode), Date.now()); state.keyToWrapper.set(key, wrapper); state.componentMap.set(wrapper, vNode); }; const remove = (key) => { if (!key) return; state.keySet.delete(key); state.keyToWrapper.delete(key); }; const reset = () => { state.keySet.clear(); state.keyToWrapper.clear(); }; const refresh = async (key) => { if (!key) return; const wrapper = state.keyToWrapper.get(key); const component = wrapper ? state.componentMap.get(wrapper) : void 0; if (!wrapper || !component) return; state.keyToWrapper.delete(key); const isActiveKey = state.activeKey === key; if (isActiveKey) { state.refreshing = true; state.keySet.delete(key); } await nextTick(); const newWrapper = new VNodeWrapper(markRaw(component), Date.now()); state.keyToWrapper.set(key, newWrapper); state.componentMap.set(newWrapper, component); if (isActiveKey) { state.refreshing = false; state.keySet.add(key); } }; const cleanup = () => { if (state.keySet.size <= max) { return; } const sortedEntries = Array.from(state.keyToWrapper.entries()).sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed); const entriesToRemove = sortedEntries.slice(0, sortedEntries.length - max); for (const [key] of entriesToRemove) { remove(key); } }; return { state, keys, setActiveKey, add, has, hasComponent, getComponent, addComponent, remove, reset, refresh, cleanup }; }; const isType = (type) => { return (value) => Object.prototype.toString.call(value) === `[object ${type}]`; }; const isFunction = (value) => isType("Function")(value); const isString = (value) => isType("String")(value); const isNonEmptyString = (value) => isString(value) && value !== ""; const throwError = (message) => { console.error(`[vue3-tabor]: ${message}`); return void 0; }; function withPostAction(originalFn, postActionFn) { return function(...args) { const res = originalFn.apply(this, args); postActionFn.apply(this, args); return res; }; } function renameComponentType(component, newName) { return { ...component, type: { ...component.type, name: newName } }; } const INITIAL_TAB_CONFIG = { key: "fullPath", keepAlive: true, hideClose: false }; const INITIAL_TAB_TYPE = "line"; const createTabId = (tabKey, route) => { const _tabKey = tabKey ?? INITIAL_TAB_CONFIG.key; const tabId = isFunction(_tabKey) ? _tabKey(route) : route[_tabKey]; if (isNonEmptyString(tabId)) return tabId; return throwError( "tabKey is not 'path','fullPath' or a function, or the return value of the function is not a non-empty string" ); }; const createTab = (router) => { const { key = INITIAL_TAB_CONFIG.key, name, keepAlive = INITIAL_TAB_CONFIG.keepAlive, iframeAttributes, hideClose = INITIAL_TAB_CONFIG.hideClose } = router.meta.tabConfig ?? INITIAL_TAB_CONFIG; if (!key) return throwError("tabKey is required"); const tabId = createTabId(key, router); if (!tabId) return throwError(`TabId is not found, please check the tab key: ${key}`); const tab = { name: name ?? router.name ?? router.path, id: tabId, keepAlive: keepAlive ?? INITIAL_TAB_CONFIG.keepAlive, fullPath: router.fullPath, hideClose: hideClose ?? false }; if (iframeAttributes) tab.iframeAttributes = iframeAttributes; if (router.meta.routeName) tab.routeName = router.meta.routeName; return tab; }; const TABOR_STORE_KEY = Symbol("tabor-store"); const IFRAME_ROUTE_STORAGE_KEY = "tabor-iframe-route"; const createTaborStore = (router, options = {}) => { const { maxCache = 10 } = options; const state = reactive({ tabs: [], activeTab: void 0, shouldClose: true }); const currentTab = computed(() => state.activeTab); const currentTabId = computed(() => { var _a; return (_a = state.activeTab) == null ? void 0 : _a.id; }); const iframeTabs = computed(() => state.tabs.filter((tab) => tab.iframeAttributes)); const cache = useCache({ max: maxCache }); const indexOf = (tabId) => { const index = state.tabs.findIndex(({ id }) => id === tabId); if (index < 0) throwError(`Tab not found, please check the tab id: ${tabId}`); return index; }; const has = (tabId) => state.tabs.some(({ id }) => id === tabId); const find = (tabId) => state.tabs.find(({ id }) => id === tabId); const modify = (tabId, tab) => { const index = indexOf(tabId); if (index < 0) return throwError(`Tab not found, please check the tab id: ${tabId}`); state.tabs[index] = tab; return tab; }; const getTabByFullpath = (fullPath) => state.tabs.find((tab) => tab.fullPath === fullPath); const setActive = (tab) => { state.activeTab = tab; if (tab) cache.setActiveKey(tab.id); return tab; }; const addTab = (tab, options2) => { const index = state.tabs.push(tab); cache.add(tab.id); if (options2 == null ? void 0 : options2.setActive) setActive(tab); return index; }; const removeTabByIndex = (index) => { if (index < 0) return throwError( `Index is less than 0, please check the index: ${index}` ); return state.tabs.splice(index, 1)[0]; }; const removeTabById = (tabId) => { const index = indexOf(tabId); if (index < 0) return void 0; const removedTab = removeTabByIndex(index); cache.remove(tabId); if ((removedTab == null ? void 0 : removedTab.iframeAttributes) && removedTab.routeName) { router.removeRoute(removedTab.routeName); } return removedTab ? { ...removedTab, index } : void 0; }; const routerPush = (to) => router.push(to); const routerReplace = (to) => router.replace(to); const refresh = (tabId) => { if (!tabId) return; const tab = find(tabId); if (!tab) return throwError(`Tab not found, please check the tab id: ${tabId}`); cache.refresh(tabId); }; const saveIframeRoute = (route) => { try { localStorage.setItem(IFRAME_ROUTE_STORAGE_KEY, JSON.stringify(route)); } catch (error) { console.error("[vue3-tabor]: Failed to save iframe route to localStorage:", error); } }; const doesRouteExist = (to) => { const resolvedRoute = router.resolve(to); return resolvedRoute.matched.length > 0; }; const restoreIframeRoute = () => { try { const storedRoute = localStorage.getItem(IFRAME_ROUTE_STORAGE_KEY); if (!storedRoute) return; const route = JSON.parse(storedRoute); const doesExist = doesRouteExist({ path: route.path }); if (!doesExist) { router.addRoute({ path: route.path, name: route.name, meta: route.meta, component: Page }); } } catch (error) { console.error("[vue3-tabor]: Failed to restore iframe route from localStorage:", error); } }; let routeRestored = false; router.beforeEach((to, _, next) => { if (!routeRestored) { restoreIframeRoute(); routeRestored = true; const storedRoute = localStorage.getItem(IFRAME_ROUTE_STORAGE_KEY); if (storedRoute) { try { const route = JSON.parse(storedRoute); if (to.path === route.path) { next({ path: to.fullPath, replace: true }); return; } } catch (e) { console.error("[vue3-tabor]: Error parsing stored route:", e); } } } next(); }); const open = async (to, options2 = { replace: false, refresh: false }) => { const { replace, tabConfig } = options2; const routeExist = doesRouteExist(to); if (!routeExist && (tabConfig == null ? void 0 : tabConfig.iframeAttributes)) { const path = typeof to === "string" ? to : to.path; if (!path) return throwError(`Path not found, please check the path: ${to}`); const name = `rt-iframe-${path.replace(/\//g, "-")}`; const route2 = { path, name, meta: { tabConfig, routeName: name }, component: Page }; router.addRoute(route2); saveIframeRoute({ path, name, meta: { tabConfig, routeName: name } }); } if (replace) return routerReplace(to); const route = await routerPush(to); if (options2.refresh && route) { const tabId = getTabIdByRoute(route.to); if (tabId) refresh(tabId); } return route; }; const openTabById = (tabId) => { const tab = find(tabId); if (!tab) return throwError(`Tab not found, please check the tab id: ${tabId}`); return routerPush(tab.fullPath); }; const openNearTab = async (removedTab) => { const { index: afterIndex } = removedTab; const afterTab = state.tabs[afterIndex] ?? state.tabs[afterIndex - 1] ?? void 0; if (afterTab) await open(afterTab.fullPath); }; const getTabIdByRoute = (route) => { var _a, _b; const key = ((_b = (_a = route.meta) == null ? void 0 : _a.tabConfig) == null ? void 0 : _b.key) ?? INITIAL_TAB_CONFIG.key; const tabId = createTabId(key, route); return tabId; }; const getTabIdByRemoveItem = (item) => { var _a; let tabId; const tabGetter = typeof item === "string" ? { fullPath: item } : item; if ("id" in tabGetter) tabId = tabGetter.id; if ("fullPath" in tabGetter) tabId = tabGetter.fullPath ? (_a = getTabByFullpath(tabGetter.fullPath)) == null ? void 0 : _a.id : void 0; if (!tabId) return throwError(`Tab not found, please check the param: ${item}`); return tabId; }; const remove = (item) => { const tabId = getTabIdByRemoveItem(item); if (!tabId) return void 0; if (state.tabs.length === 1 && state.tabs[0].id === tabId) { return throwError(`The last tab cannot be closed:${item}`); } return removeTabById(tabId); }; const clear = withPostAction( () => { state.tabs = []; }, () => { cache.reset(); } ); const getRemoveItem = (item) => { var _a, _b; if (!item && !((_a = state.activeTab) == null ? void 0 : _a.id)) return void 0; const _item = isString(item) ? { fullPath: item } : item; return _item ? _item : { id: (_b = state.activeTab) == null ? void 0 : _b.id }; }; const setShouldClose = (val) => { state.shouldClose = val; }; const close = async (item, toOptions) => { var _a, _b, _c; if (!state.shouldClose) return; const _item = getRemoveItem(item); if (!_item) return void 0; const closedTabIsActive = _item.id ? _item.id === ((_a = state.activeTab) == null ? void 0 : _a.id) : _item.fullPath === ((_b = state.activeTab) == null ? void 0 : _b.fullPath); if (closedTabIsActive) setActive(void 0); const removedTab = remove(_item); if (removedTab && closedTabIsActive) await openNearTab(removedTab); if (toOptions && (toOptions.id || toOptions.fullPath)) { const { id, fullPath } = toOptions; if (id === item) return throwError( "The id of the tab to be closed cannot be the same as the id of the tab to be opened,if you want to open the tab, please use the fullPath parameter." ); const _fullPath = id ? (_c = find(id)) == null ? void 0 : _c.fullPath : fullPath; if (!_fullPath) return throwError( `The fullPath of the tab to be opened is not found, please check ${id ? id : fullPath}.` ); await routerPush(_fullPath); return removedTab; } return removedTab; }; const closeOthers = (tabId) => { var _a, _b; if (!tabId && !((_a = state.activeTab) == null ? void 0 : _a.id)) return; const _tabId = tabId ?? ((_b = state.activeTab) == null ? void 0 : _b.id); if (!has(_tabId)) return; for (const item of [...state.tabs]) { if (item.id !== _tabId) { const tab = find(item.id); if (tab) { const hideClose = typeof tab.hideClose === "function" ? tab.hideClose(tab) : tab.hideClose; if (!hideClose) { removeTabById(item.id); } } } } if (!_tabId) return; const afterActiveTab = find(_tabId); if (afterActiveTab) setActive(afterActiveTab); }; const retrieveOrCacheComponent = (Component) => { var _a, _b; const key = currentTabId.value; if (!Component || !key) return Component; if (cache.hasComponent(key)) return cache.getComponent(key); if ((_a = currentTab.value) == null ? void 0 : _a.iframeAttributes) return void 0; if ((_b = currentTab.value) == null ? void 0 : _b.keepAlive) { const renamedComponent = renameComponentType(Component, key); cache.addComponent(key, renamedComponent); cache.add(key); return cache.getComponent(key); } return Component; }; router.afterEach((to) => { const tabId = getTabIdByRoute(to); if (!tabId || !has(tabId)) { const tab2 = createTab(to); if (tab2) addTab(tab2, { setActive: true }); return; } const tab = find(tabId); if (!tab) return throwError(`Tab not found, please check the tab id: ${tabId}`); const fullPathIsSame = to.fullPath === tab.fullPath; const newTab = fullPathIsSame ? tab : modify(tabId, { ...tab, fullPath: to.fullPath }) ?? tab; setActive(newTab); if (!fullPathIsSame) refresh(tabId); }); return { $router: router, state, createTab, indexOf, find, getTabByFullpath, getTabIdByRoute, setActive, addTab, removeTabByIndex, removeTabById, routerPush, routerReplace, openTabById, openNearTab, remove, refresh, clear, getRemoveItem, setShouldClose, currentTab, currentTabId, open, close, closeOthers, has, retrieveOrCacheComponent, cache, iframeTabs }; }; const initTaborStore = (router, options = {}) => { const store = createTaborStore(router, options); return store; }; const useTabor = () => { const store = inject(TABOR_STORE_KEY); if (!store) throwError("Tabor store not found"); return store; }; const Iframe = /* @__PURE__ */ defineComponent({ name: "Iframe", setup() { const taborStore = inject(TABOR_STORE_KEY); const iframes = computed(() => taborStore == null ? void 0 : taborStore.iframeTabs.value); return () => { var _a; return createVNode("div", { "class": "tabor-iframe-container" }, [(_a = iframes.value) == null ? void 0 : _a.map((iframe) => { var _a2; const activeTabId = (_a2 = taborStore == null ? void 0 : taborStore.state.activeTab) == null ? void 0 : _a2.id; const shouldKeep = iframe.id === activeTabId || iframe.keepAlive; const shouldShow = iframe.id === activeTabId; return shouldKeep ? withDirectives(createVNode("iframe", mergeProps({ "key": iframe.id, "width": "100%", "height": "100%" }, iframe.iframeAttributes), null), [[vShow, shouldShow]]) : null; })]); }; } }); const _sfc_main$1 = defineComponent({ name: "RtPages", components: { Iframe }, setup() { const taborStore = inject(TABOR_STORE_KEY); const pageClass = inject("pageClass"); onMounted(() => { if (!taborStore) { console.error("[vue3-tabor]: taborStore not provided. Did you install the plugin correctly?"); } }); const activeTab = computed(() => taborStore == null ? void 0 : taborStore.state.activeTab); const activeTabKey = computed(() => { var _a; return (_a = activeTab.value) == null ? void 0 : _a.id; }); const refreshing = computed(() => taborStore == null ? void 0 : taborStore.cache.state.refreshing); const cachedKeys = computed(() => { var _a; const keys = taborStore == null ? void 0 : taborStore.cache.keys.value; return ((_a = activeTab.value) == null ? void 0 : _a.keepAlive) ? keys : keys == null ? void 0 : keys.filter((k) => k !== activeTabKey.value); }); return { activeTabKey, cachedKeys, refreshing, pageClass, retrieveOrCacheComponent: taborStore == null ? void 0 : taborStore.retrieveOrCacheComponent }; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { const _component_router_view = resolveComponent("router-view"); const _component_Iframe = resolveComponent("Iframe"); return openBlock(), createElementBlock("div", { class: normalizeClass(["tabor-pages", _ctx.pageClass]) }, [ createVNode(_component_router_view, null, { default: withCtx(({ Component }) => { var _a; return [ (openBlock(), createBlock(KeepAlive, { include: _ctx.cachedKeys }, [ !_ctx.refreshing ? (openBlock(), createBlock(resolveDynamicComponent((_a = _ctx.retrieveOrCacheComponent) == null ? void 0 : _a.call(_ctx, Component)), { key: _ctx.activeTabKey })) : createCommentVNode("", true) ], 1032, ["include"])) ]; }), _: 1 }), createVNode(_component_Iframe) ], 2); } const Page = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render$1]]); const translations = { zh: { refresh: "刷新", close: "关闭", closeOthers: "关闭其他" }, en: { refresh: "Refresh", close: "Close", closeOthers: "Close Others" } }; let currentLanguage = "zh"; function setLanguage(lang) { currentLanguage = lang; } function getLanguage() { return currentLanguage; } function t(key) { return translations[currentLanguage][key]; } const _sfc_main = defineComponent({ name: "DropdownMenu", props: { visible: { type: Boolean, required: true }, position: { type: Object, required: true }, disabledActions: { type: Array, default: () => [] }, hideActions: { type: Array, default: () => [] }, language: { type: String, default: "zh" } }, emits: ["action"], setup(props, { emit }) { if (props.language && props.language !== getLanguage()) { setLanguage(props.language); } const translations2 = { refresh: t("refresh"), close: t("close"), closeOthers: t("closeOthers") }; const handleAction = (action) => { if (props.disabledActions.includes(action)) { return; } emit("action", action); }; const handleRightClick = (event) => { event.preventDefault(); event.stopPropagation(); }; return { translations: translations2, handleAction, handleRightClick, hideActions: props.hideActions }; } }); const _hoisted_1 = { key: 1, class: "tabor-dropdown-divider" }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return _ctx.visible ? (openBlock(), createElementBlock("div", { key: 0, class: "tabor-dropdown-menu", style: normalizeStyle({ top: _ctx.position.y + "px", left: _ctx.position.x + "px" }), onContextmenu: _cache[3] || (_cache[3] = (...args) => _ctx.handleRightClick && _ctx.handleRightClick(...args)) }, [ createElementVNode("ul", null, [ createElementVNode("li", { onClick: _cache[0] || (_cache[0] = ($event) => _ctx.handleAction("refresh")) }, [ createElementVNode("span", null, toDisplayString(_ctx.translations.refresh), 1) ]), !_ctx.hideActions.includes("close") ? (openBlock(), createElementBlock("li", { key: 0, onClick: _cache[1] || (_cache[1] = ($event) => !_ctx.disabledActions.includes("close") && _ctx.handleAction("close")), class: normalizeClass({ "tabor-dropdown-item-disabled": _ctx.disabledActions.includes("close") }) }, [ createElementVNode("span", null, toDisplayString(_ctx.translations.close), 1) ], 2)) : createCommentVNode("", true), !_ctx.hideActions.includes("closeOthers") ? (openBlock(), createElementBlock("li", _hoisted_1)) : createCommentVNode("", true), !_ctx.hideActions.includes("closeOthers") ? (openBlock(), createElementBlock("li", { key: 2, onClick: _cache[2] || (_cache[2] = ($event) => !_ctx.disabledActions.includes("closeOthers") && _ctx.handleAction("closeOthers")), class: normalizeClass({ "tabor-dropdown-item-disabled": _ctx.disabledActions.includes("closeOthers") }) }, [ createElementVNode("span", null, toDisplayString(_ctx.translations.closeOthers), 1) ], 2)) : createCommentVNode("", true) ]) ], 36)) : createCommentVNode("", true); } const DropdownMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-54f47c12"]]); const InitialClose = /* @__PURE__ */ defineComponent({ name: "ElementClose", props: { style: { type: Object, required: false } }, setup(props) { return () => createVNode("div", { "style": { width: "1em", height: "1em", ...props.style } }, [createVNode("svg", { "viewBox": "0 0 24 24" }, [createVNode("path", { "fill": "currentColor", "d": "m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275q-.275-.275-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7q.275-.275.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275q.275.275.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7q-.275.275-.7.275t-.7-.275L12 13.4Z" }, null)])]); } }); const Close = /* @__PURE__ */ defineComponent({ name: "TaborTabClose", props: { id: { type: String, required: true } }, setup(props) { const store = inject(TABOR_STORE_KEY); const close = (e) => { store == null ? void 0 : store.close({ id: props.id }); e.stopPropagation(); }; return () => createVNode("div", { "class": "tabor-remove-icon", "onClick": close }, [createVNode(InitialClose, null, null)]); } }); const Tablabel = /* @__PURE__ */ defineComponent({ name: "TaborTabLabel", props: { name: { type: [String, Symbol], required: false, default: void 0 } }, setup(props) { return () => createVNode("div", { "class": "tabor-tab-label" }, [createVNode("span", null, [props.name])]); } }); const clickOutside = { beforeMount(el, binding) { const handler = (event) => { const target = event.target; if (el === target || el.contains(target)) return; binding.value(event); }; el._clickOutsideHandler = handler; document.addEventListener("click", handler, true); }, unmounted(el) { if (el._clickOutsideHandler) { document.removeEventListener("click", el._clickOutsideHandler, true); } } }; const activeDropdownId = ref(null); const Tab = /* @__PURE__ */ defineComponent({ name: "TaborTab", directives: { clickOutside // Register the directive }, props: { name: { type: [String, Symbol, Function], required: true }, id: { type: String, required: true }, hideClose: { type: Boolean, default: false }, fullPath: { type: String, required: true }, prefix: { type: [Object, Function] } }, setup(props) { const store = inject(TABOR_STORE_KEY); const tabClass = inject("tabClass"); const tabType = inject("tabType"); const language = inject("language"); const tabsLength = computed(() => (store == null ? void 0 : store.state.tabs.length) ?? 0); const isActive = computed(() => { var _a; return ((_a = store == null ? void 0 : store.state.activeTab) == null ? void 0 : _a.id) === props.id; }); const tab = computed(() => store == null ? void 0 : store.find(props.id)); const showClose = computed(() => { var _a; return tabsLength.value > 1 && !((_a = tab.value) == null ? void 0 : _a.hideClose) && !props.hideClose; }); const name = computed(() => { if (typeof props.name === "function") { const route = store == null ? void 0 : store.$router.resolve(props.fullPath); if (route) { return props.name(route); } else { console.warn(`Route not found for fullPath: ${props.fullPath}`); return String(props.name); } } return props.name; }); const dropdownVisible = ref(false); const dropdownPosition = ref({ x: 0, y: 0 }); computed(() => { if (activeDropdownId.value !== props.id) { dropdownVisible.value = false; } }); const classNames = computed(() => ["tabor-tab", `tabor-tab--${tabType}`, isActive.value && "tabor-tab-active"]); const click = () => { activeDropdownId.value = null; if (isActive.value) return; const tab2 = store == null ? void 0 : store.find(props.id); if (tab2) store == null ? void 0 : store.open(tab2.fullPath); }; const handleClickOutside = () => { dropdownVisible.value = false; activeDropdownId.value = null; }; const handleRightClick = (event) => { event.preventDefault(); event.stopPropagation(); let x = event.clientX; let y = event.clientY; const menuWidth = 120; if (x + menuWidth > window.innerWidth) { x = window.innerWidth - menuWidth - 5; } const menuHeight = 110; if (y + menuHeight > window.innerHeight) { y = window.innerHeight - menuHeight - 5; } dropdownPosition.value = { x, y }; dropdownVisible.value = true; activeDropdownId.value = props.id; }; const handleDropdownAction = (action) => { dropdownVisible.value = false; activeDropdownId.value = null; const tab2 = store == null ? void 0 : store.find(props.id); if (!tab2) return; switch (action) { case "refresh": store == null ? void 0 : store.refresh(props.id); break; case "close": store == null ? void 0 : store.close(props.id); break; case "closeOthers": { store == null ? void 0 : store.closeOthers(props.id); break; } } }; const renderPrefix = () => { if (!props.prefix) return null; const tab2 = store == null ? void 0 : store.find(props.id); if (!tab2) return null; return h(props.prefix, { tab: tab2 }); }; return () => { let dropdownMenu = null; if (dropdownVisible.value && activeDropdownId.value === props.id) { const disabledActions = []; const hideActions = []; if (tabsLength.value <= 1) { disabledActions.push("close"); } if (tabsLength.value <= 1) { disabledActions.push("closeOthers"); } if (props.hideClose) { hideActions.push("close"); hideActions.push("closeOthers"); } dropdownMenu = withDirectives(h(DropdownMenu, { visible: dropdownVisible.value, position: dropdownPosition.value, disabledActions, hideActions, language, onAction: handleDropdownAction }), [[clickOutside, handleClickOutside]]); } return createVNode("div", { "class": [...classNames.value, tabClass], "onClick": click, "onContextmenu": handleRightClick }, [props.prefix && createVNode("div", { "class": "tabor-tab--prefix" }, [renderPrefix()]), createVNode(Tablabel, { "name": name.value }, null), showClose.value && createVNode(Close, { "id": props.id }, null), dropdownMenu]); }; } }); const Tabs = /* @__PURE__ */ defineComponent({ name: "Tabs", props: { tabPrefix: { type: Object }, hideClose: { type: Boolean, default: false } }, setup(props) { const store = inject(TABOR_STORE_KEY); const tabType = inject("tabType"); const tabs = computed(() => (store == null ? void 0 : store.state.tabs) ?? []); const classNames = computed(() => ["tabor-tabs", `tabor-tabs--${tabType}`]); return () => createVNode("div", { "class": classNames.value }, [tabs.value.map((tab) => createVNode(Tab, mergeProps({ "prefix": props.tabPrefix }, tab, { "key": tab.id, "hideClose": props.hideClose }), null))]); } }); const Tabor = /* @__PURE__ */ defineComponent({ name: "vue-tabor", components: { Tabs, Page }, props: { maxAlive: { type: Number, required: false, default: 10 }, hideClose: { type: Boolean, required: false, default: false }, tabClass: { type: String }, pageClass: { type: String }, dropdownClass: { type: String }, tabType: { type: String, default: INITIAL_TAB_TYPE }, tabPrefix: { type: Object }, language: { type: String, default: "zh" }, showLanguageSwitch: { type: Boolean, default: true } }, setup(props) { if (props.language) { setLanguage(props.language); } provide("dropdownClass", props.dropdownClass); provide("tabClass", props.tabClass); provide("pageClass", props.pageClass); provide("tabType", props.tabType ?? INITIAL_TAB_TYPE); provide("language", props.language ?? "zh"); return () => createVNode("div", { "class": "rt-container" }, [createVNode(Tabs, { "tabPrefix": props.tabPrefix, "hideClose": props.hideClose }, null), createVNode(Page, null, null)]); } }); const init = (app, options) => { const { router } = options; const taborStore = initTaborStore(router, options); app.provide(TABOR_STORE_KEY, taborStore); app.config.globalProperties.$taborStore = taborStore; }; const TaborPlugin = { install(app, options) { init(app, options); app.component("vue-tabor", Tabor); } }; export { Tabor, TaborPlugin as default, useTabor }; //# sourceMappingURL=index.es.js.map