Skip to content

Commit 0531129

Browse files
committed
fix grow mode
1 parent b412040 commit 0531129

File tree

2 files changed

+95
-121
lines changed

2 files changed

+95
-121
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
}
325325

326326
.hiddenSmartColMeasure {
327+
box-sizing: border-box;
327328
visibility: hidden;
328329
position: fixed;
329330
white-space: nowrap;

packages/main/src/components/AnalyticalTable/hooks/useDynamicColumnWidths.ts

Lines changed: 94 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
11
import { useMemo } from 'react';
22
import { AnalyticalTableScaleWidthMode } from '../../../enums/AnalyticalTableScaleWidthMode.js';
3-
import { DEFAULT_COLUMN_WIDTH } from '../defaults/Column/index.js';
4-
import type { AnalyticalTableColumnDefinition, ReactTableHooks, TableInstance } from '../types/index.js';
3+
import type {
4+
AnalyticalTableColumnDefinition,
5+
ColumnType,
6+
ReactTableHooks,
7+
TableInstance,
8+
RowType,
9+
} from '../types/index.js';
510

611
interface IColumnMeta {
712
contentPxAvg: number;
813
headerPx: number;
914
headerDefinesWidth?: boolean;
10-
//todo: not optional?
1115
maxWidth?: number;
1216
}
1317

1418
const ROW_SAMPLE_SIZE = 20;
1519
const MAX_WIDTH = 700;
1620
export const CELL_PADDING_PX = 18; /* padding left and right 0.5rem each (16px) + borders (1px) + buffer (1px) */
1721

18-
function findLongestString(str1, str2) {
19-
if (typeof str1 !== 'string' || typeof str2 !== 'string') {
20-
return str1 || str2 || undefined;
21-
}
22+
function getContentPxLongest(rowSample: RowType[], columnIdOrAccessor: string, uniqueId) {
23+
return rowSample.reduce((max, item) => {
24+
const dataPoint = item.values?.[columnIdOrAccessor];
25+
26+
if (dataPoint) {
27+
const val = stringToPx(dataPoint, uniqueId) + CELL_PADDING_PX;
28+
return Math.max(max, val);
29+
}
2230

23-
return str1.length > str2.length ? str1 : str2;
31+
return max;
32+
}, 0);
2433
}
2534

2635
function getContentPxAvg(rowSample, columnIdOrAccessor, uniqueId) {
@@ -193,7 +202,12 @@ function calculateDefaultColumnWidths(tableWidth: number, columns: AnalyticalTab
193202
return result;
194203
}
195204

196-
const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], instance, hiddenColumns) => {
205+
const calculateSmartAndGrowColumns = (
206+
columns: AnalyticalTableColumnDefinition[],
207+
instance: TableInstance,
208+
hiddenColumns: ColumnType,
209+
isGrow = false,
210+
) => {
197211
const { rows, state, webComponentsReactProperties } = instance;
198212
const rowSample = rows.slice(0, ROW_SAMPLE_SIZE);
199213
const { tableClientWidth: totalWidth } = state;
@@ -217,13 +231,15 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
217231
return metadata;
218232
}
219233

220-
let headerPx: number, contentPxAvg: number;
234+
let headerPx: number, contentPxLength: number;
221235

222236
if (column.scaleWidthModeOptions?.cellString) {
223-
contentPxAvg =
237+
contentPxLength =
224238
stringToPx(column.scaleWidthModeOptions.cellString, webComponentsReactProperties.uniqueId) + CELL_PADDING_PX;
225239
} else {
226-
contentPxAvg = getContentPxAvg(rowSample, columnIdOrAccessor, webComponentsReactProperties.uniqueId);
240+
contentPxLength = isGrow
241+
? getContentPxLongest(rowSample, columnIdOrAccessor, webComponentsReactProperties.uniqueId)
242+
: getContentPxAvg(rowSample, columnIdOrAccessor, webComponentsReactProperties.uniqueId);
227243
}
228244

229245
if (column.scaleWidthModeOptions?.headerString) {
@@ -241,8 +257,9 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
241257

242258
metadata[columnIdOrAccessor] = {
243259
headerPx,
244-
contentPxAvg,
245-
maxWidth: column.maxWidth,
260+
contentPxAvg: contentPxLength,
261+
// When Grow mode is active, static max width should be applied
262+
maxWidth: column.maxWidth ?? (isGrow ? MAX_WIDTH : undefined),
246263
};
247264
return metadata;
248265
},
@@ -252,11 +269,25 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
252269
let totalContentPxAvgPrio1 = 0;
253270
let totalNumberColPrio2 = 0;
254271

255-
// width reserved by predefined widths or columns defined by header
272+
/**
273+
* Width reserved by predefined widths or columns defined by header
274+
* Grow: full content width or header width (if wider) if not restricted by maxWidth
275+
*/
256276
const reservedWidth: number = visibleColumns.reduce((acc, column) => {
257277
const columnIdOrAccessor = (column.id ?? column.accessor) as string;
258278
const { contentPxAvg, headerPx } = columnMeta[columnIdOrAccessor];
259-
279+
if (isGrow) {
280+
totalContentPxAvgPrio1 += columnMeta[columnIdOrAccessor].contentPxAvg;
281+
const targetWidth = Math.min(
282+
Math.max(column.minWidth ?? 0, column.width ?? 0, contentPxAvg, headerPx),
283+
column.maxWidth ?? MAX_WIDTH,
284+
);
285+
286+
if (targetWidth !== column.maxWidth) {
287+
totalNumberColPrio2++;
288+
}
289+
return acc + targetWidth;
290+
}
260291
if (contentPxAvg > headerPx) {
261292
if (!column.minWidth && !column.width) {
262293
totalContentPxAvgPrio1 += columnMeta[columnIdOrAccessor].contentPxAvg;
@@ -278,22 +309,29 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
278309
const availableWidthPrio1 = totalWidth - reservedWidth;
279310
let availableWidthPrio2 = availableWidthPrio1;
280311

281-
// Step 1: Give columns defined by content more space (priority 1)
312+
/**
313+
* Step 1: Give columns defined by content more space (priority 1)
314+
* Grow: Give all columns the required space necessary to display the full content (up to the maxWidth)
315+
*/
282316
const visibleColumnsAdaptedPrio1 = visibleColumns.map((column) => {
283317
const columnIdOrAccessor = (column.id ?? column.accessor) as string;
284318
const meta = columnMeta[columnIdOrAccessor];
285319
if (meta && !column.minWidth && !column.width && !meta.headerDefinesWidth) {
286-
let targetWidth;
320+
let targetWidth: number;
287321
const { contentPxAvg, headerPx } = meta;
288322

289-
if (availableWidthPrio1 > 0) {
323+
if (isGrow) {
324+
targetWidth = Math.min(Math.max(contentPxAvg, headerPx), meta.maxWidth);
325+
} else if (availableWidthPrio1 > 0) {
290326
const factor = contentPxAvg / totalContentPxAvgPrio1;
291327
targetWidth = Math.max(Math.min(availableWidthPrio1 * factor, contentPxAvg), headerPx);
292328
availableWidthPrio2 -= targetWidth;
293329
}
330+
294331
return {
295332
...column,
296333
nextWidth: targetWidth || headerPx,
334+
maxWidth: isGrow ? (column?.maxWidth ?? MAX_WIDTH) : Infinity,
297335
};
298336
}
299337
return column;
@@ -302,19 +340,29 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
302340
const columnWithMaxWidthIndex: number[] = [];
303341
let maxWidthDifference = 0;
304342
let fullWidthOfAllColumns = 0;
305-
// Step 2: Give all columns more space (priority 2)
343+
let lessThanMaxWidthCount = 0;
344+
345+
/**
346+
* Step 2: Give all columns more space (priority 2)
347+
*/
306348
const visibleColumnsAdaptedPrio2 = visibleColumnsAdaptedPrio1.map((column, index) => {
307349
const columnIdOrAccessor = (column.id ?? column.accessor) as string;
308350
const meta = columnMeta[columnIdOrAccessor];
309351
const { headerPx } = meta;
310352

311-
if (column.maxWidth) {
353+
if (column.maxWidth && column.maxWidth !== Infinity) {
312354
columnWithMaxWidthIndex.push(index);
313355
}
314356
if (meta && !column.minWidth && !column.width) {
315357
let targetWidth = column.nextWidth || headerPx;
316358
if (availableWidthPrio2 > 0) {
317-
targetWidth = targetWidth + availableWidthPrio2 * (1 / totalNumberColPrio2);
359+
targetWidth = Math.min(
360+
targetWidth + availableWidthPrio2 * (1 / totalNumberColPrio2),
361+
column.maxWidth ?? Infinity,
362+
);
363+
if (targetWidth < (column.maxWidth ?? Infinity)) {
364+
lessThanMaxWidthCount++;
365+
}
318366
}
319367
fullWidthOfAllColumns += targetWidth;
320368
if (targetWidth >= column.maxWidth) {
@@ -334,7 +382,22 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
334382
}
335383
});
336384

337-
// Step 3: Only if any column has maxWidth defined and there is still space to distribute, distribute the exceeding width to the other columns (priority 3)
385+
// In Grow mode, all columns implement a maxWidth
386+
if (isGrow) {
387+
const remainingWidth = totalWidth - fullWidthOfAllColumns;
388+
if (remainingWidth > 0) {
389+
//Step 3 (Grow): Distribute remaining width to all columns that are not at their maxWidth
390+
return visibleColumnsAdaptedPrio2.map((column) => {
391+
if (column.width !== column.maxWidth) {
392+
return { ...column, width: column.width + remainingWidth / lessThanMaxWidthCount };
393+
}
394+
return column;
395+
});
396+
}
397+
return visibleColumnsAdaptedPrio2;
398+
}
399+
400+
// Step 3 (Smart): Only if any column has maxWidth defined and there is still space to distribute, distribute the exceeding width to the other columns (priority 3)
338401
if (columnWithMaxWidthIndex.length && totalWidth >= fullWidthOfAllColumns) {
339402
return visibleColumnsAdaptedPrio2.map((column, index, arr) => {
340403
if (!columnWithMaxWidthIndex.includes(index)) {
@@ -390,9 +453,9 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
390453
if (!instance.state || !instance.rows) {
391454
return columns;
392455
}
393-
const { rows, state } = instance;
456+
const { state } = instance;
394457
const { hiddenColumns, tableClientWidth: totalWidth } = state;
395-
const { scaleWidthMode, loading, uniqueId } = instance.webComponentsReactProperties;
458+
const { scaleWidthMode, loading } = instance.webComponentsReactProperties;
396459

397460
if (columns.length === 0 || !totalWidth || !AnalyticalTableScaleWidthMode[scaleWidthMode]) {
398461
return columns;
@@ -412,8 +475,13 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
412475
return column ?? false;
413476
})
414477
.filter(Boolean) as TableInstance['columns'];
478+
415479
if (scaleWidthMode === AnalyticalTableScaleWidthMode.Smart) {
416-
return calculateSmartColumns(columns, instance, hiddenColumns);
480+
return calculateSmartAndGrowColumns(columns, instance, hiddenColumns);
481+
}
482+
483+
if (scaleWidthMode === AnalyticalTableScaleWidthMode.Grow) {
484+
return calculateSmartAndGrowColumns(columns, instance, hiddenColumns, true);
417485
}
418486

419487
const hasData = instance.data.length > 0;
@@ -431,101 +499,6 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
431499
return { ...column, width: calculatedWidth };
432500
});
433501
}
434-
435-
// AnalyticalTableScaleWidthMode.Grow
436-
437-
const rowSample = rows.slice(0, ROW_SAMPLE_SIZE);
438-
439-
const columnMeta = visibleColumns.reduce((acc, column) => {
440-
const columnIdOrAccessor = (column.id ?? column.accessor) as string;
441-
if (
442-
column.id === '__ui5wcr__internal_selection_column' ||
443-
column.id === '__ui5wcr__internal_highlight_column' ||
444-
column.id === '__ui5wcr__internal_navigation_column'
445-
) {
446-
acc[columnIdOrAccessor] = {
447-
minHeaderWidth: column.width,
448-
fullWidth: column.width,
449-
};
450-
return acc;
451-
}
452-
453-
const smartWidth = findLongestString(
454-
column.scaleWidthModeOptions?.headerString,
455-
column.scaleWidthModeOptions?.cellString,
456-
);
457-
458-
if (smartWidth) {
459-
const width = Math.max(stringToPx(smartWidth, uniqueId) + CELL_PADDING_PX, 60);
460-
acc[columnIdOrAccessor] = {
461-
minHeaderWidth: width,
462-
fullWidth: width,
463-
};
464-
return acc;
465-
}
466-
467-
const minHeaderWidth =
468-
typeof column.Header === 'string'
469-
? stringToPx(column.Header, uniqueId, true) + CELL_PADDING_PX
470-
: DEFAULT_COLUMN_WIDTH;
471-
472-
acc[columnIdOrAccessor] = {
473-
minHeaderWidth,
474-
fullWidth: Math.max(minHeaderWidth, getContentPxAvg(rowSample, columnIdOrAccessor, uniqueId)),
475-
};
476-
return acc;
477-
}, {});
478-
479-
let reservedWidth = visibleColumns.reduce((acc, column) => {
480-
const { minHeaderWidth, fullWidth } = columnMeta[column.id ?? column.accessor];
481-
return acc + Math.max(column.minWidth || 0, column.width || 0, minHeaderWidth || 0, fullWidth) || 0;
482-
}, 0);
483-
484-
let availableWidth = totalWidth - reservedWidth;
485-
486-
if (availableWidth > 0) {
487-
let notReservedCount = 0;
488-
reservedWidth = visibleColumns.reduce((acc, column) => {
489-
const reserved = Math.max(column.minWidth || 0, column.width || 0) || 0;
490-
if (!reserved) {
491-
notReservedCount++;
492-
}
493-
return acc + reserved;
494-
}, 0);
495-
availableWidth = totalWidth - reservedWidth;
496-
497-
return columns.map((column) => {
498-
const isColumnVisible = (column.isVisible ?? true) && !hiddenColumns.includes(column.id ?? column.accessor);
499-
const meta = columnMeta[column.id ?? (column.accessor as string)];
500-
501-
if (isColumnVisible && meta) {
502-
const { minHeaderWidth } = meta;
503-
504-
const targetWidth = availableWidth / notReservedCount;
505-
506-
return {
507-
...column,
508-
width: column.width ?? Math.min(targetWidth, MAX_WIDTH),
509-
minWidth: column.minWidth ?? minHeaderWidth,
510-
};
511-
}
512-
return column;
513-
});
514-
}
515-
516-
return columns.map((column) => {
517-
const isColumnVisible = (column.isVisible ?? true) && !hiddenColumns.includes(column.id ?? column.accessor);
518-
const meta = columnMeta[column.id ?? (column.accessor as string)];
519-
if (isColumnVisible && meta) {
520-
const { fullWidth } = meta;
521-
return {
522-
...column,
523-
width: column.width ?? fullWidth,
524-
maxWidth: column.maxWidth ?? MAX_WIDTH,
525-
};
526-
}
527-
return column;
528-
});
529502
};
530503

531504
export const useDynamicColumnWidths = (hooks: ReactTableHooks) => {

0 commit comments

Comments
 (0)