@qin_sunrise/tab
Version:
A lightweight tab management package based on zustand state management for React applications
521 lines (518 loc) • 15.7 kB
JavaScript
// 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