UNPKG

@qin_sunrise/tab

Version:

A lightweight tab management package based on zustand state management for React applications

521 lines (518 loc) 15.7 kB
// src/store.ts import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; // src/utils.ts function getRouteIcons(route) { const { icon, localIcon } = route.handle || {}; return { icon, localIcon }; } function getTabByRoute(route, homePath = "/home") { const { fullPath, handle, id, pathname } = route; const { fixedIndexInTab, i18nKey, keepAlive = false, title } = handle || {}; let fixedIndex = fixedIndexInTab; if (pathname === homePath) { fixedIndex = 0; } const { icon, localIcon } = getRouteIcons(route); const tab = { fixedIndex, fullPath, i18nKey, icon, id: handle?.multiTab ? fullPath : pathname, keepAlive, label: title || "", localIcon, newLabel: "", oldLabel: i18nKey || title || "", routeKey: id, routePath: pathname }; return tab; } function isTabInTabs(tabId, tabs) { return tabs.some((tab) => tab.id === tabId); } function getFixedTabs(tabs) { return tabs.filter((tab) => tab.fixedIndex !== void 0 && tab.fixedIndex !== null); } function getTabById(tabId, tabs) { return tabs.find((tab) => tab.id === tabId); } function getTabIndexById(tabId, tabs) { return tabs.findIndex((tab) => tab.id === tabId); } function isTabRetain(tabId, tabs) { const tab = getTabById(tabId, tabs); return tab ? tab.fixedIndex !== void 0 && tab.fixedIndex !== null : false; } function getActiveFirstLevelMenuKey(route) { const pathSegments = route.pathname.split("/").filter(Boolean); return pathSegments.length > 0 ? pathSegments[0] : ""; } var storage = { get: (key) => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch { return null; } }, set: (key, value) => { try { localStorage.setItem(key, JSON.stringify(value)); } catch { } }, remove: (key) => { try { localStorage.removeItem(key); } catch { } } }; // src/store.ts var initialState = { activeTabId: "", activeFirstLevelMenuKey: "", tabs: [], removeCacheKey: null }; function createTabStore(config = {}) { const { cache = false, storageKey = "globalTabs", homePath = "/home" } = config; return create()( devtools( persist( (set, get) => ({ ...initialState, addTab: (tab) => { set( (state) => { const { fixedIndex } = tab; let newTabs; if (fixedIndex !== void 0 && fixedIndex !== null) { newTabs = [...state.tabs]; newTabs.splice(fixedIndex, 0, tab); } else { newTabs = [...state.tabs, tab]; } return { ...state, tabs: newTabs }; }, false, "addTab" ); }, updateTab: (index, tab) => { set( (state) => { const newTabs = [...state.tabs]; newTabs[index] = tab; return { ...state, tabs: newTabs }; }, false, "updateTab" ); }, setActiveTabId: (tabId) => { set( (state) => ({ ...state, activeTabId: tabId }), false, "setActiveTabId" ); }, setActiveFirstLevelMenuKey: (key) => { set( (state) => ({ ...state, activeFirstLevelMenuKey: key }), false, "setActiveFirstLevelMenuKey" ); }, setTabs: (tabs) => { set( (state) => ({ ...state, tabs }), false, "setTabs" ); }, changeTabLabel: (index, label) => { set( (state) => { const newTabs = [...state.tabs]; if (label) { newTabs[index].i18nKey = label; } else { newTabs[index].i18nKey = newTabs[index].oldLabel; } return { ...state, tabs: newTabs }; }, false, "changeTabLabel" ); }, removeTabById: (tabId) => { set( (state) => { const tabIndex = getTabIndexById(tabId, state.tabs); if (tabIndex === -1) return state; const newTabs = [...state.tabs]; newTabs.splice(tabIndex, 1); let newActiveTabId = state.activeTabId; if (state.activeTabId === tabId) { const nextTab = newTabs[tabIndex] || newTabs[tabIndex - 1] || newTabs[newTabs.length - 1]; newActiveTabId = nextTab?.id || ""; } return { ...state, tabs: newTabs, activeTabId: newActiveTabId }; }, false, "removeTabById" ); }, clearAllTabs: () => { set( (state) => { const fixedTabs = getFixedTabs(state.tabs); const lastFixedTab = fixedTabs[fixedTabs.length - 1]; return { ...state, tabs: fixedTabs, activeTabId: lastFixedTab?.id || "" }; }, false, "clearAllTabs" ); }, clearLeftTabs: (tabId) => { set( (state) => { const tabIndex = getTabIndexById(tabId, state.tabs); if (tabIndex === -1) return state; const fixedTabs = getFixedTabs(state.tabs); const fixedTabIds = fixedTabs.map((tab) => tab.id); const newTabs = state.tabs.filter((tab, index) => { if (index < tabIndex) { return fixedTabIds.includes(tab.id); } return true; }); return { ...state, tabs: newTabs }; }, false, "clearLeftTabs" ); }, clearRightTabs: (tabId) => { set( (state) => { const tabIndex = getTabIndexById(tabId, state.tabs); if (tabIndex === -1) return state; const newTabs = state.tabs.filter((_, index) => index <= tabIndex); return { ...state, tabs: newTabs }; }, false, "clearRightTabs" ); }, clearOtherTabs: (tabId) => { set( (state) => { const targetTab = getTabById(tabId, state.tabs); if (!targetTab) return state; const fixedTabs = getFixedTabs(state.tabs); const newTabs = [...fixedTabs, targetTab]; return { ...state, tabs: newTabs, activeTabId: tabId }; }, false, "clearOtherTabs" ); }, setRemoveCacheKey: (keys) => { set( (state) => ({ ...state, removeCacheKey: keys }), false, "setRemoveCacheKey" ); } }), { name: storageKey, storage: { getItem: (name) => { const value = storage.get(name); return { state: value, version: 0 }; }, setItem: (name, value) => { storage.set(name, value.state); }, removeItem: (name) => { storage.remove(name); } } } ), { name: "tab-store" } ) ); } // src/hooks.ts import { useEffect, useRef } from "react"; var globalTabStore = null; function getTabStore(config = {}) { if (!globalTabStore) { globalTabStore = createTabStore(config); } return globalTabStore; } function useTabStore(config) { return getTabStore(config); } function useTabs() { const store = useTabStore(); return store((state) => state.tabs); } function useActiveTabId() { const store = useTabStore(); return store((state) => state.activeTabId); } function useActiveFirstLevelMenuKey() { const store = useTabStore(); return store((state) => state.activeFirstLevelMenuKey); } function useRemoveCacheKey() { const store = useTabStore(); return store((state) => state.removeCacheKey); } function useTabActions() { const store = useTabStore(); return { addTab: store.getState().addTab, updateTab: store.getState().updateTab, setActiveTabId: store.getState().setActiveTabId, setActiveFirstLevelMenuKey: store.getState().setActiveFirstLevelMenuKey, setTabs: store.getState().setTabs, changeTabLabel: store.getState().changeTabLabel, removeTabById: store.getState().removeTabById, clearAllTabs: store.getState().clearAllTabs, clearLeftTabs: store.getState().clearLeftTabs, clearRightTabs: store.getState().clearRightTabs, clearOtherTabs: store.getState().clearOtherTabs, setRemoveCacheKey: store.getState().setRemoveCacheKey }; } function useTabManager(navigate) { const store = useTabStore(); const isInit = useRef(false); const addTab = (route) => { const tab = getTabByRoute(route); if (!isInit.current) { isInit.current = true; const cachedTabs = storage.get("globalTabs"); if (cachedTabs && cachedTabs.tabs && cachedTabs.tabs.length > 0) { store.getState().setTabs(cachedTabs.tabs); store.getState().setActiveTabId(cachedTabs.activeTabId || tab.id); store.getState().setActiveFirstLevelMenuKey(cachedTabs.activeFirstLevelMenuKey || getActiveFirstLevelMenuKey(route)); } else { store.getState().addTab(tab); store.getState().setActiveTabId(tab.id); store.getState().setActiveFirstLevelMenuKey(getActiveFirstLevelMenuKey(route)); } } else if (!isTabInTabs(tab.id, store.getState().tabs)) { store.getState().addTab(tab); } else { const index = store.getState().tabs.findIndex((item) => item.id === tab.id); store.getState().updateTab(index, tab); } store.getState().setActiveTabId(tab.id); store.getState().setActiveFirstLevelMenuKey(getActiveFirstLevelMenuKey(route)); }; const switchRouteByTab = (tab) => { if (navigate) { navigate(tab.fullPath); } store.getState().setActiveTabId(tab.id); }; const removeTabById = (tabId) => { const tab = store.getState().tabs.find((t) => t.id === tabId); if (tab) { store.getState().removeTabById(tabId); store.getState().setRemoveCacheKey([tab.routePath]); } }; const clearTabs = (excludes = []) => { const fixedTabs = store.getState().tabs.filter((tab) => tab.fixedIndex !== void 0 && tab.fixedIndex !== null); const remainTabIds = [...fixedTabs.map((tab) => tab.id), ...excludes]; const removeKeepKeys = []; const updatedTabs = []; for (const tab of store.getState().tabs) { if (remainTabIds.includes(tab.id)) { updatedTabs.push(tab); } else if (tab.keepAlive) { removeKeepKeys.push(tab.routePath); } } if (updatedTabs.length === store.getState().tabs.length) return; if (!remainTabIds.includes(store.getState().activeTabId)) { const currentIndex = store.getState().tabs.findIndex((tab) => tab.id === store.getState().activeTabId); const newActive = store.getState().tabs[currentIndex + 1] || store.getState().tabs[currentIndex - 1] || updatedTabs[updatedTabs.length - 1]; if (newActive) { switchRouteByTab({ fullPath: newActive.fullPath, pathname: newActive.routePath, id: newActive.routeKey, handle: { title: newActive.label, keepAlive: newActive.keepAlive } }); } } store.getState().setTabs(updatedTabs); if (removeKeepKeys.length > 0) { store.getState().setRemoveCacheKey(removeKeepKeys); } }; const clearLeftTabs = (tabId) => { const index = store.getState().tabs.findIndex((tab) => tab.id === tabId); if (index === -1) return; const excludes = store.getState().tabs.slice(index).map((tab) => tab.id); clearTabs(excludes); }; const clearRightTabs = (tabId) => { const index = store.getState().tabs.findIndex((tab) => tab.id === tabId); if (index === -1) return; const excludes = store.getState().tabs.slice(0, index + 1).map((tab) => tab.id); clearTabs(excludes); }; const clearOtherTabs = (tabId) => { const targetTab = store.getState().tabs.find((tab) => tab.id === tabId); if (!targetTab) return; const fixedTabs = store.getState().tabs.filter((tab) => tab.fixedIndex !== void 0 && tab.fixedIndex !== null); const newTabs = [...fixedTabs, targetTab]; store.getState().setTabs(newTabs); store.getState().setActiveTabId(tabId); }; const clearAllTabs = () => { const fixedTabs = store.getState().tabs.filter((tab) => tab.fixedIndex !== void 0 && tab.fixedIndex !== null); const lastFixedTab = fixedTabs[fixedTabs.length - 1]; store.getState().setTabs(fixedTabs); store.getState().setActiveTabId(lastFixedTab?.id || ""); }; const isTabRetain2 = (tabId) => { const tab = store.getState().tabs.find((t) => t.id === tabId); return tab ? tab.fixedIndex !== void 0 && tab.fixedIndex !== null : false; }; return { addTab, switchRouteByTab, removeTabById, clearTabs, clearLeftTabs, clearRightTabs, clearOtherTabs, clearAllTabs, isTabRetain: isTabRetain2, tabs: store.getState().tabs, activeTabId: store.getState().activeTabId, navigate }; } function useCacheTabs() { const store = useTabStore(); const cacheTabs = () => { const state = store.getState(); storage.set("globalTabs", { tabs: state.tabs, activeTabId: state.activeTabId, activeFirstLevelMenuKey: state.activeFirstLevelMenuKey }); }; useEffect(() => { const handleBeforeUnload = () => { cacheTabs(); }; window.addEventListener("beforeunload", handleBeforeUnload); return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; }, []); return cacheTabs; } function useTabScroll() { const tabRef = useRef(null); const bsWrapper = useRef(null); const setBsScroll = (scrollLeft) => { if (bsWrapper.current) { bsWrapper.current.scrollLeft = scrollLeft; } }; return { tabRef, bsWrapper, setBsScroll }; } export { createTabStore, createTabStore as default, getActiveFirstLevelMenuKey, getFixedTabs, getRouteIcons, getTabById, getTabByRoute, getTabIndexById, getTabStore, isTabInTabs, isTabRetain, storage, useActiveFirstLevelMenuKey, useActiveTabId, useCacheTabs, useRemoveCacheKey, useTabActions, useTabManager, useTabScroll, useTabStore, useTabs }; //# sourceMappingURL=index.mjs.map