- Externalize all @tamagui/* and tamagui subpaths so dist no longer vendors Tamagui. - Emit TypeScript declarations with vite-plugin-dts; fix package exports types for ui/*. - Align initEnv with profiles: displayName, brandLogo, api.baseURL, themeColor, uiShell. - Stabilize tests with Node localStorage file; env tests pass. - Update README and component docs for services, menus, API client, and development.
59 lines
1.4 KiB
JavaScript
59 lines
1.4 KiB
JavaScript
import { useSyncExternalStore } from 'react';
|
|
|
|
const accountTabs = [];
|
|
const listeners = new Set();
|
|
let cachedTabs = [];
|
|
|
|
function rebuildSnapshot() {
|
|
cachedTabs = [...accountTabs].sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
}
|
|
|
|
function emit() {
|
|
listeners.forEach((listener) => {
|
|
try {
|
|
listener();
|
|
} catch (error) {
|
|
console.warn('[Security] Account tab listener failed:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function publishAccountTab(tab) {
|
|
if (!tab || !tab.id || !tab.label || !tab.component) {
|
|
console.warn('[Security] publishAccountTab() requires id, label, and component');
|
|
return;
|
|
}
|
|
|
|
const existingIndex = accountTabs.findIndex((item) => item.id === tab.id);
|
|
if (existingIndex >= 0) {
|
|
accountTabs[existingIndex] = tab;
|
|
} else {
|
|
accountTabs.push(tab);
|
|
}
|
|
rebuildSnapshot();
|
|
emit();
|
|
}
|
|
|
|
export function retractAccountTab(tabId) {
|
|
const nextTabs = accountTabs.filter((tab) => tab.id !== tabId);
|
|
if (nextTabs.length !== accountTabs.length) {
|
|
accountTabs.length = 0;
|
|
accountTabs.push(...nextTabs);
|
|
rebuildSnapshot();
|
|
emit();
|
|
}
|
|
}
|
|
|
|
export function getAccountTabs() {
|
|
return cachedTabs;
|
|
}
|
|
|
|
export function subscribeToAccountTabs(listener) {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
|
|
export function useAccountTabs() {
|
|
return useSyncExternalStore(subscribeToAccountTabs, getAccountTabs, getAccountTabs);
|
|
}
|