Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,41 @@ function getContentPxAvg(rowSample: RowType[], columnIdOrAccessor: string, compu
);
}

// Absorbs `deficit` px of overflow by shrinking `items` toward each one's floor (react-table's distribute-and-floor).
// Mutates `item.width` in place.
function shrinkColumnsToFit<T extends { width: number }>(items: T[], deficit: number, getFloor: (item: T) => number) {
while (deficit > 0) {
let shrinkableCount = 0;
for (const item of items) {
if (item.width > getFloor(item)) {
shrinkableCount++;
}
}
if (shrinkableCount === 0) {
break;
}
const reduction = deficit / shrinkableCount;
let used = 0;
for (const item of items) {
const floor = getFloor(item);
if (item.width > floor) {
const potential = item.width - reduction;
if (potential < floor) {
used += item.width - floor;
item.width = floor;
} else {
item.width = potential;
used += reduction;
}
}
}
deficit -= used;
if (used === 0) {
break;
}
}
}

function calculateDefaultColumnWidths(tableWidth: number, columns: AnalyticalTableColumnDefinition[]) {
// Columns w/ external width property
const fixed = [];
Expand Down Expand Up @@ -211,37 +246,7 @@ function calculateDefaultColumnWidths(tableWidth: number, columns: AnalyticalTab
}

// Shrink columns
while (remainingSpace < 0) {
let shrinkableCount = 0;
for (const { col, width } of dynamic) {
const min = col.minWidth ?? 0;
if (width > min) {
shrinkableCount++;
}
}
if (shrinkableCount === 0) {
break;
}
const reduction = Math.abs(remainingSpace) / shrinkableCount;
let used = 0;
for (const dc of dynamic) {
const min = dc.col.minWidth ?? 0;
if (dc.width > min) {
const potential = dc.width - reduction;
if (potential < min) {
used += dc.width - min;
dc.width = min;
} else {
dc.width = potential;
used += reduction;
}
}
}
remainingSpace += used;
if (used === 0) {
break;
}
}
shrinkColumnsToFit(dynamic, -remainingSpace, ({ col }) => col.minWidth ?? 0);
}

const result = {};
Expand Down Expand Up @@ -452,6 +457,21 @@ const calculateSmartAndGrowColumns = (
return column;
});
}

// Step 3 (Smart only): shrink flexible columns to fit when they overflow, floored at each header width so header
// text is never clipped. Grow sizes to full content and overflows by design, so it is excluded.
if (remainingWidth < 0 && !isGrow) {
// Flexible columns: no external `width`, so not fixed/internal columns.
const shrinkableColumns = visibleColumnsAdaptedPrio2.filter((column) => {
const meta = columnMeta[(column.id ?? column.accessor) as string];
const isInternalColumn = typeof column.id === 'string' && column.id.startsWith('__ui5wcr__internal_');
return !isInternalColumn && !meta?.width;
});
shrinkColumnsToFit(shrinkableColumns, -remainingWidth, (column) =>
Math.max(columnMeta[(column.id ?? column.accessor) as string].headerPx, column.minWidth ?? 0),
);
}

return visibleColumnsAdaptedPrio2;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,67 @@ test.describe('AnalyticalTable — last header cell end border', () => {
expect(await borderInlineEnd(lastHeader)).toBe(await borderInlineEnd(lastCell));
});
});

test.describe('AnalyticalTable Smart scaleWidthMode — shrink to fit', () => {
// Total width of all rendered column headers (horizontal virtualization is off in Smart mode, so all are in DOM).
const sumColumnWidths = (page: Page) =>
page
.locator('[role="columnheader"]')
.evaluateAll((els) => els.reduce((acc, el) => acc + el.getBoundingClientRect().width, 0));
const tableClientWidth = (page: Page) =>
page.locator('[data-component-name="AnalyticalTableContainer"]').evaluate((el) => el.clientWidth);

// One wide content column ('name') plus three columns whose fair share is narrower than their header (so Smart
// bumps them up to their header width and the row overflows), and a fixed 100px column — all in a 540px container.
// The shrink pass must take the whole overflow out of the content column; the header-bumped and fixed columns stay.
const columns: AnalyticalTableColumnDefinition[] = [
{ Header: 'Name', accessor: 'name', scaleWidthModeOptions: { headerString: 'Name', cellString: 'x'.repeat(70) } },
{
Header: 'Age',
accessor: 'age',
scaleWidthModeOptions: { headerString: 'Wide Header1', cellString: 'content valueX' },
},
{
Header: 'Friend Name',
accessor: 'friend.name',
scaleWidthModeOptions: { headerString: 'Wide Header2', cellString: 'content valueX' },
},
{
Header: 'Friend Age',
accessor: 'friend.age',
scaleWidthModeOptions: { headerString: 'Wide Header3', cellString: 'content valueX' },
},
{ Header: 'Fixed', accessor: 'status', width: 100 },
];
const smartTableProps = {
columns,
withHook: false,
scaleWidthMode: AnalyticalTableScaleWidthMode.Smart,
containerWidth: '540px',
} as const;

// Resolves once Smart has settled the widths so the columns fit the container (the width calc runs async, after
// fonts are ready, so the pre-shrink overflow is briefly visible).
const expectColumnsToFit = async (page: Page) =>
expect.poll(async () => (await sumColumnWidths(page)) - (await tableClientWidth(page))).toBeLessThanOrEqual(5);

test('shrinks overflowing columns to fit', async ({ mount, page }) => {
await mount<typeof StickyHarness>(STORY, smartTableProps);
await expect(columnHeader(page, 'name')).toBeVisible();
// Columns fit the table → no horizontal overflow (without the shrink pass the sum stays well above the width).
await expectColumnsToFit(page);
// The fixed-width column keeps its width.
expect(Math.abs((await boxWidth(columnHeader(page, 'status'))) - 100)).toBeLessThanOrEqual(5);
});

test('does not shrink below header width', async ({ mount, page }) => {
await mount<typeof StickyHarness>(STORY, smartTableProps);
await expect(columnHeader(page, 'friend.name')).toBeVisible();
await expectColumnsToFit(page);
// The Smart calc keeps adjusting after the first fit, so let the widths fully settle before reading them.
await page.waitForTimeout(1500);
// 'Friend Name' must stay at its header width: the shrink floors there, not at the 60px default, so only the wide
// 'name' column absorbs the overflow instead of every column collapsing toward 60px.
expect(await boxWidth(columnHeader(page, 'friend.name'))).toBeGreaterThanOrEqual(100);
});
});
Loading