133 lines
4.0 KiB
TypeScript
133 lines
4.0 KiB
TypeScript
import type { LayoutColumn, LayoutGroup } from "./types";
|
|
import { LAYOUT_SIDEBAR_RATIO } from "./constants";
|
|
|
|
/**
|
|
* Get the effective pinned width for a column,
|
|
* considering user overrides, maxWidth cap, and ratio-based default.
|
|
*/
|
|
export function getPinnedWidth(
|
|
column: LayoutColumn,
|
|
totalWidth: number,
|
|
override?: number,
|
|
): number {
|
|
if (override !== undefined) {
|
|
const max = column.maxWidth ?? Infinity;
|
|
const min = column.minWidth ?? 50;
|
|
return Math.max(min, Math.min(override, max));
|
|
}
|
|
const ratioWidth = Math.round(totalWidth * LAYOUT_SIDEBAR_RATIO);
|
|
const max = column.maxWidth ?? Infinity;
|
|
const min = column.minWidth ?? 50;
|
|
return Math.max(min, Math.min(ratioWidth, max));
|
|
}
|
|
|
|
type ColumnBucket = {
|
|
pinnedTotal: number;
|
|
pinnedWidths: number[];
|
|
explicitIndices: number[];
|
|
explicitTotal: number;
|
|
flexIndices: number[];
|
|
};
|
|
|
|
/** First pass: classify columns and compute pinned widths. */
|
|
function classifyColumns(
|
|
columns: LayoutColumn[],
|
|
totalWidth: number,
|
|
pinnedOverrides: Map<string, number>,
|
|
): ColumnBucket {
|
|
const bucket: ColumnBucket = {
|
|
pinnedTotal: 0,
|
|
pinnedWidths: new Array(columns.length).fill(0),
|
|
explicitIndices: [],
|
|
explicitTotal: 0,
|
|
flexIndices: [],
|
|
};
|
|
|
|
for (let i = 0; i < columns.length; i++) {
|
|
const col = columns[i];
|
|
if (col.pinned) {
|
|
const w = getPinnedWidth(col, totalWidth, pinnedOverrides.get(col.id));
|
|
bucket.pinnedWidths[i] = w;
|
|
bucket.pinnedTotal += w;
|
|
} else if (col.width !== undefined && col.width > 0) {
|
|
bucket.explicitIndices.push(i);
|
|
bucket.explicitTotal += col.width;
|
|
} else {
|
|
bucket.flexIndices.push(i);
|
|
}
|
|
}
|
|
return bucket;
|
|
}
|
|
|
|
/** Second pass: compute non-pinned column widths. */
|
|
function computeFlexWidths(
|
|
columns: LayoutColumn[],
|
|
bucket: ColumnBucket,
|
|
remainingSpace: number,
|
|
): number[] {
|
|
const widths = [...bucket.pinnedWidths];
|
|
|
|
if (bucket.explicitIndices.length > 0 && bucket.flexIndices.length === 0) {
|
|
// All non-pinned have explicit widths → scale proportionally
|
|
const scale = bucket.explicitTotal > 0 ? remainingSpace / bucket.explicitTotal : 1;
|
|
for (const i of bucket.explicitIndices) {
|
|
widths[i] = Math.round((columns[i].width ?? 0) * scale);
|
|
}
|
|
return widths;
|
|
}
|
|
|
|
if (bucket.explicitIndices.length > 0) {
|
|
// Mix of explicit and flex — use explicit as-is (capped), flex splits remainder
|
|
let explicitUsed = 0;
|
|
for (const i of bucket.explicitIndices) {
|
|
const w = Math.min(columns[i].width ?? 0, remainingSpace);
|
|
widths[i] = w;
|
|
explicitUsed += w;
|
|
}
|
|
const flexSpace = Math.max(0, remainingSpace - explicitUsed);
|
|
const perFlex =
|
|
bucket.flexIndices.length > 0 ? Math.floor(flexSpace / bucket.flexIndices.length) : 0;
|
|
for (const i of bucket.flexIndices) {
|
|
widths[i] = perFlex;
|
|
}
|
|
return widths;
|
|
}
|
|
|
|
// All non-pinned are flex → equal split
|
|
const perFlex =
|
|
bucket.flexIndices.length > 0 ? Math.floor(remainingSpace / bucket.flexIndices.length) : 0;
|
|
for (const i of bucket.flexIndices) {
|
|
widths[i] = perFlex;
|
|
}
|
|
return widths;
|
|
}
|
|
|
|
/**
|
|
* Compute absolute pixel widths for each column.
|
|
*
|
|
* Strategy:
|
|
* 1. Pinned columns: use getPinnedWidth (ratio-based default or user override)
|
|
* 2. Non-pinned columns with explicit width (from captured layouts): scale
|
|
* proportionally to fill remaining space
|
|
* 3. Non-pinned columns without width: split remaining space equally
|
|
*/
|
|
export function computeColumnWidths(
|
|
columns: LayoutColumn[],
|
|
totalWidth: number,
|
|
pinnedWidths: Map<string, number>,
|
|
): number[] {
|
|
const bucket = classifyColumns(columns, totalWidth, pinnedWidths);
|
|
const remainingSpace = Math.max(0, totalWidth - bucket.pinnedTotal);
|
|
return computeFlexWidths(columns, bucket, remainingSpace);
|
|
}
|
|
|
|
/**
|
|
* Compute absolute pixel heights for groups within a column.
|
|
* Equal distribution among groups.
|
|
*/
|
|
export function computeGroupHeights(groups: LayoutGroup[], totalHeight: number): number[] {
|
|
if (groups.length === 0) return [];
|
|
const h = Math.floor(totalHeight / groups.length);
|
|
return groups.map(() => h);
|
|
}
|