Add shared automation and WebUI editing primitives
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import {
|
||||
dataGridLayoutSignature,
|
||||
dataGridWidthsForLayout,
|
||||
distributeDataGridResizeAmount,
|
||||
effectiveDataGridColumnMinWidth,
|
||||
fitDataGridColumns,
|
||||
type DataGridSizingColumn
|
||||
} from "../src/components/table/dataGridSizing";
|
||||
|
||||
function assertEqual<T>(actual: T, expected: T, message: string): void {
|
||||
if (actual !== expected) throw new Error(`${message}: expected ${String(expected)}, got ${String(actual)}`);
|
||||
}
|
||||
|
||||
function assertWidths(
|
||||
actual: Record<string, number>,
|
||||
expected: Record<string, number>,
|
||||
message: string
|
||||
): void {
|
||||
for (const [columnId, width] of Object.entries(expected)) {
|
||||
assertEqual(actual[columnId], width, `${message} (${columnId})`);
|
||||
}
|
||||
}
|
||||
|
||||
const minmaxColumn: DataGridSizingColumn = {
|
||||
id: "recipients",
|
||||
width: "minmax(320px, 1.4fr)",
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true
|
||||
};
|
||||
assertEqual(
|
||||
effectiveDataGridColumnMinWidth(minmaxColumn),
|
||||
320,
|
||||
"a minmax lower bound remains the resize minimum"
|
||||
);
|
||||
|
||||
const weightedColumns: DataGridSizingColumn[] = [
|
||||
{ id: "fixed", width: 100 },
|
||||
{ id: "primary", width: "minmax(100px, 1fr)", minWidth: 100, maxWidth: 500 },
|
||||
{ id: "secondary", width: "minmax(100px, 2fr)", minWidth: 100, maxWidth: 500 }
|
||||
];
|
||||
const weightedLayout = fitDataGridColumns(weightedColumns, 600);
|
||||
assertWidths(
|
||||
weightedLayout.widths,
|
||||
{ fixed: 100, primary: 200, secondary: 300 },
|
||||
"fraction tracks receive residual width by declared weight"
|
||||
);
|
||||
assertEqual(weightedLayout.bufferWidth, 0, "fraction tracks cover the available container");
|
||||
|
||||
const boundedLayout = fitDataGridColumns([
|
||||
{ id: "fixed", width: 100 },
|
||||
{ id: "primary", width: "minmax(100px, 1fr)", minWidth: 100, maxWidth: 150 },
|
||||
{ id: "secondary", width: "minmax(100px, 2fr)", minWidth: 100, maxWidth: 200 }
|
||||
], 600);
|
||||
assertWidths(
|
||||
boundedLayout.widths,
|
||||
{ fixed: 100, primary: 150, secondary: 200 },
|
||||
"fraction tracks stop at their declared maxima"
|
||||
);
|
||||
assertEqual(boundedLayout.bufferWidth, 150, "space beyond column maxima becomes the synthetic buffer");
|
||||
|
||||
const overflowLayout = fitDataGridColumns([
|
||||
{ id: "fixed", width: 200 },
|
||||
{ id: "primary", width: "minmax(320px, 1fr)", resizable: true },
|
||||
{ id: "secondary", width: "minmax(260px, 1fr)", resizable: true }
|
||||
], 600);
|
||||
assertWidths(
|
||||
overflowLayout.widths,
|
||||
{ fixed: 200, primary: 320, secondary: 260 },
|
||||
"columns never shrink below declared minima"
|
||||
);
|
||||
assertEqual(overflowLayout.overflowWidth, 180, "minimum-width excess remains horizontal overflow");
|
||||
|
||||
const percentageLayout = fitDataGridColumns([
|
||||
{ id: "fixed", width: 100 },
|
||||
{ id: "primary", width: "30%", minWidth: 100 },
|
||||
{ id: "secondary", width: "20%", minWidth: 100 }
|
||||
], 1000);
|
||||
assertWidths(
|
||||
percentageLayout.widths,
|
||||
{ fixed: 100, primary: 300, secondary: 200 },
|
||||
"percentage tracks retain their preferred container share"
|
||||
);
|
||||
assertEqual(percentageLayout.bufferWidth, 400, "unused percentage space remains a neutral buffer");
|
||||
|
||||
const overriddenLayout = fitDataGridColumns(weightedColumns, 800, {}, { primary: 450 });
|
||||
assertWidths(
|
||||
overriddenLayout.widths,
|
||||
{ fixed: 100, primary: 450, secondary: 250 },
|
||||
"a user-resized track stays fixed while responsive peers absorb container changes"
|
||||
);
|
||||
|
||||
const overriddenOverflowLayout = fitDataGridColumns(weightedColumns, 500, {}, { primary: 450 });
|
||||
assertWidths(
|
||||
overriddenOverflowLayout.widths,
|
||||
{ fixed: 100, primary: 450, secondary: 100 },
|
||||
"a narrow viewport preserves the user override without squashing peers below their minima"
|
||||
);
|
||||
assertEqual(
|
||||
overriddenOverflowLayout.overflowWidth,
|
||||
150,
|
||||
"a user override that cannot fit becomes horizontal overflow"
|
||||
);
|
||||
|
||||
const clampedOverrideLayout = fitDataGridColumns(weightedColumns, 800, {}, { primary: 900 });
|
||||
assertEqual(
|
||||
clampedOverrideLayout.widths.primary,
|
||||
500,
|
||||
"user overrides remain bounded by the column maximum"
|
||||
);
|
||||
|
||||
const redistributedGrowth = distributeDataGridResizeAmount(300, [
|
||||
{ columnId: "bounded", startWidth: 100, minWidth: 100, maxWidth: 150, weight: 1 },
|
||||
{ columnId: "remaining", startWidth: 100, minWidth: 100, maxWidth: 500, weight: 1 }
|
||||
]);
|
||||
assertWidths(
|
||||
redistributedGrowth.amounts,
|
||||
{ bounded: 50, remaining: 250 },
|
||||
"unused growth is redistributed after a peer reaches its maximum"
|
||||
);
|
||||
assertEqual(redistributedGrowth.applied, 300, "bounded weighted growth applies the complete available amount");
|
||||
|
||||
const boundedShrink = distributeDataGridResizeAmount(-300, [
|
||||
{ columnId: "primary", startWidth: 300, minWidth: 200, maxWidth: 500, weight: 1 },
|
||||
{ columnId: "secondary", startWidth: 300, minWidth: 250, maxWidth: 500, weight: 1 }
|
||||
]);
|
||||
assertWidths(
|
||||
boundedShrink.amounts,
|
||||
{ primary: -100, secondary: -50 },
|
||||
"shrink distribution stops every peer at its minimum"
|
||||
);
|
||||
assertEqual(boundedShrink.applied, -150, "unavailable shrink remains unapplied instead of squashing columns");
|
||||
|
||||
const legacyFillLayout = fitDataGridColumns([
|
||||
{ id: "fixed", width: 100 },
|
||||
{ id: "fill", width: 200, minWidth: 120, maxWidth: 450, fill: true }
|
||||
], 500);
|
||||
assertWidths(
|
||||
legacyFillLayout.widths,
|
||||
{ fixed: 100, fill: 400 },
|
||||
"legacy fill columns remain stable under the explicit sizing contract"
|
||||
);
|
||||
|
||||
const originalSignature = dataGridLayoutSignature(weightedColumns, "container", "cover");
|
||||
const changedSignature = dataGridLayoutSignature(
|
||||
weightedColumns.map((column) => column.id === "primary" ? { ...column, maxWidth: 420 } : column),
|
||||
"container",
|
||||
"cover"
|
||||
);
|
||||
assertEqual(
|
||||
dataGridWidthsForLayout(originalSignature, originalSignature, { primary: 450 })?.primary,
|
||||
450,
|
||||
"matching layout signatures restore user widths"
|
||||
);
|
||||
assertEqual(
|
||||
dataGridWidthsForLayout(originalSignature, changedSignature, { primary: 450 }),
|
||||
undefined,
|
||||
"stale layout signatures discard user widths"
|
||||
);
|
||||
assertEqual(
|
||||
dataGridLayoutSignature(weightedColumns, "container", "free") === originalSignature,
|
||||
false,
|
||||
"free and cover layouts keep independent user overrides"
|
||||
);
|
||||
Reference in New Issue
Block a user