Files
feuerwerk/src/editor/tabRow.ts
T

51 lines
1.8 KiB
TypeScript

export interface TabWidth<T extends string> {
key: T;
width: number;
}
export interface TabRowInput<T extends string> {
tabs: TabWidth<T>[];
/** Width the row may occupy, padding already taken off. */
available: number;
gap: number;
/** Width of the button that opens the hidden ones; only claimed when something is hidden. */
overflowWidth: number;
active: T | null;
}
export interface TabRowLayout<T extends string> {
shown: T[];
hidden: T[];
}
function rowWidth<T extends string>(tabs: TabWidth<T>[], gap: number): number {
if (tabs.length === 0) return 0;
return tabs.reduce((sum, tab) => sum + tab.width, 0) + gap * (tabs.length - 1);
}
/**
* Which tabs fit in the row and which go behind the overflow button. Tabs keep their order; the
* active one is always shown, even if the row can hold nothing else.
*/
export function tabRowLayout<T extends string>(input: TabRowInput<T>): TabRowLayout<T> {
const { tabs, available, gap, overflowWidth, active } = input;
if (rowWidth(tabs, gap) <= available) return { shown: tabs.map((tab) => tab.key), hidden: [] };
const budget = available - overflowWidth - gap;
const shown: TabWidth<T>[] = [];
for (const tab of tabs) {
if (rowWidth([...shown, tab], gap) > budget) break;
shown.push(tab);
}
if (!shown.some((tab) => tab.key === active)) {
const activeTab = tabs.find((tab) => tab.key === active);
if (activeTab) {
// The prefix stops before the active tab, so appending it keeps the row in order.
while (shown.length > 0 && rowWidth([...shown, activeTab], gap) > budget) shown.pop();
shown.push(activeTab);
}
}
const kept = new Set(shown.map((tab) => tab.key));
return { shown: [...kept], hidden: tabs.map((tab) => tab.key).filter((key) => !kept.has(key)) };
}