feat: simplify tab layout to CSS-driven sizing and throttle terminal state broadcasts

This commit is contained in:
Mohamed Boudra
2026-03-03 23:29:48 +07:00
parent cb92f39e73
commit e9b57a5ac6
7 changed files with 197 additions and 114 deletions
@@ -8,18 +8,17 @@ export type WorkspaceTabLayoutInput = {
actionsReservedWidth: number;
rowPaddingHorizontal: number;
tabGap: number;
minTabWidth: number;
maxTabWidth: number;
iconOnlyTabWidth: number;
tabBaseWidthWithClose: number;
minLabelChars: number;
charWidth: number;
tabIconWidth: number;
tabHorizontalPadding: number;
estimatedCharWidth: number;
closeButtonWidth: number;
};
};
export type WorkspaceTabLayoutResult = {
mode: WorkspaceTabLayoutMode;
tabWidths: number[];
shouldScroll: boolean;
showLabels: boolean;
showCloseButtons: boolean;
};
@@ -42,18 +41,14 @@ function sum(values: number[]): number {
return total;
}
function computeTotalRowWidth(input: {
tabWidths: number[];
tabGap: number;
rowPaddingHorizontal: number;
}): number {
if (input.tabWidths.length === 0) {
function computeTotalRowWidth(input: { itemWidths: number[]; tabGap: number; rowPaddingHorizontal: number }): number {
if (input.itemWidths.length === 0) {
return 0;
}
return (
input.rowPaddingHorizontal * 2 +
sum(input.tabWidths) +
Math.max(input.tabWidths.length - 1, 0) * input.tabGap
sum(input.itemWidths) +
Math.max(input.itemWidths.length - 1, 0) * input.tabGap
);
}
@@ -64,8 +59,6 @@ export function computeWorkspaceTabLayout(
if (tabCount === 0) {
return {
mode: "full",
tabWidths: [],
shouldScroll: false,
showLabels: true,
showCloseButtons: true,
};
@@ -75,68 +68,36 @@ export function computeWorkspaceTabLayout(
0,
input.viewportWidth - input.metrics.rowHorizontalInset * 2 - input.metrics.actionsReservedWidth
);
const minLabelWidth =
input.metrics.tabBaseWidthWithClose + input.metrics.charWidth * input.metrics.minLabelChars;
const baseTabWidth = input.metrics.tabIconWidth + input.metrics.tabHorizontalPadding * 2;
const estimateLabelWidth = (labelLength: number) => labelLength * input.metrics.estimatedCharWidth;
const preferredTabWidths = input.tabLabelLengths.map((rawLength) => {
const fullTabWidths = input.tabLabelLengths.map((rawLength) => {
const labelLength = Math.max(rawLength, 1);
const preferred = input.metrics.tabBaseWidthWithClose + labelLength * input.metrics.charWidth;
return clamp(preferred, minLabelWidth, input.metrics.maxTabWidth);
const estimatedWidth = baseTabWidth + estimateLabelWidth(labelLength) + input.metrics.closeButtonWidth;
return clamp(estimatedWidth, input.metrics.minTabWidth, input.metrics.maxTabWidth);
});
const preferredTotal = computeTotalRowWidth({
tabWidths: preferredTabWidths,
const fullTotal = computeTotalRowWidth({
itemWidths: fullTabWidths,
tabGap: input.metrics.tabGap,
rowPaddingHorizontal: input.metrics.rowPaddingHorizontal,
});
if (preferredTotal <= availableWidth) {
return {
mode: "full",
tabWidths: preferredTabWidths,
shouldScroll: false,
showLabels: true,
showCloseButtons: true,
};
if (fullTotal <= availableWidth) {
return { mode: "full", showLabels: true, showCloseButtons: true };
}
const compactMinTabWidths = preferredTabWidths.map(() => minLabelWidth);
const compactTabWidths = input.tabLabelLengths.map((rawLength) => {
const labelLength = Math.max(rawLength, 1);
const estimatedWidth = baseTabWidth + estimateLabelWidth(labelLength);
return clamp(estimatedWidth, input.metrics.minTabWidth, input.metrics.maxTabWidth);
});
const compactTotal = computeTotalRowWidth({
tabWidths: compactMinTabWidths,
itemWidths: compactTabWidths,
tabGap: input.metrics.tabGap,
rowPaddingHorizontal: input.metrics.rowPaddingHorizontal,
});
if (compactTotal <= availableWidth) {
const totalShrinkCapacity = sum(preferredTabWidths.map((width) => width - minLabelWidth));
const targetShrink = preferredTotal - availableWidth;
const shrinkRatio = totalShrinkCapacity > 0 ? targetShrink / totalShrinkCapacity : 1;
const tabWidths = preferredTabWidths.map((preferred) => {
const shrinkCapacity = preferred - minLabelWidth;
return preferred - shrinkCapacity * shrinkRatio;
});
return {
mode: "compact",
tabWidths,
shouldScroll: false,
showLabels: true,
showCloseButtons: true,
};
return { mode: "compact", showLabels: true, showCloseButtons: false };
}
const iconTabWidths = preferredTabWidths.map(() => input.metrics.iconOnlyTabWidth);
const iconTotal = computeTotalRowWidth({
tabWidths: iconTabWidths,
tabGap: input.metrics.tabGap,
rowPaddingHorizontal: input.metrics.rowPaddingHorizontal,
});
return {
mode: "icon",
tabWidths: iconTabWidths,
shouldScroll: iconTotal > availableWidth,
showLabels: false,
showCloseButtons: false,
};
return { mode: "icon", showLabels: false, showCloseButtons: false };
}