Skip to content

Commit f8d58d6

Browse files
committed
prevent frequent reflow when measuring
1 parent a75a294 commit f8d58d6

File tree

5 files changed

+235
-212
lines changed

5 files changed

+235
-212
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,7 @@ describe('AnalyticalTable', () => {
12971297
);
12981298
};
12991299

1300+
cy.log('cols: initial');
13001301
// additional fonts need to be prefetched in Cypress, otherwise it leads to flakiness
13011302
cy.window()
13021303
.then((win) => {
@@ -1319,6 +1320,7 @@ describe('AnalyticalTable', () => {
13191320
cy.findByText('100').click();
13201321
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 100);
13211322

1323+
cy.log('cols: cols');
13221324
const cols = [...initialColumns, { Header: 'Short Width', accessor: 'age' }];
13231325
cy.mount(<TableComp columns={cols} data={data} />);
13241326
cy.get('[data-column-id="name"]')
@@ -1328,6 +1330,7 @@ describe('AnalyticalTable', () => {
13281330
.invoke('outerWidth')
13291331
.should('equal', isGrow ? 700 : 97);
13301332

1333+
cy.log('cols: cols2');
13311334
const cols2 = [
13321335
{ ...initialColumns[0], maxWidth: Infinity },
13331336
{ Header: 'Short Width', accessor: 'age' },
@@ -1336,6 +1339,7 @@ describe('AnalyticalTable', () => {
13361339
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 4120);
13371340
cy.get('[data-column-id="age"]').invoke('outerWidth').should('equal', 97);
13381341

1342+
cy.log('cols: cols3');
13391343
const cols3 = [
13401344
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
13411345
{ Header: 'Short Width', accessor: 'age' },
@@ -1346,6 +1350,7 @@ describe('AnalyticalTable', () => {
13461350
.invoke('outerWidth')
13471351
.should('equal', isGrow ? 700 : 1704);
13481352

1353+
cy.log('cols: cols4');
13491354
const cols4 = [
13501355
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
13511356
{ Header: 'Short Width', accessor: 'age' },
@@ -1360,6 +1365,7 @@ describe('AnalyticalTable', () => {
13601365
.invoke('outerWidth')
13611366
.should('equal', isGrow ? 1004 : 836);
13621367

1368+
cy.log('cols: cols5');
13631369
const cols5 = [
13641370
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
13651371
{ Header: 'Short Width', accessor: 'age' },
@@ -1372,6 +1378,7 @@ describe('AnalyticalTable', () => {
13721378
checkColumnWidthWithTolerance('[data-column-id="friend.name"]', 486, 324.0625, isGrow);
13731379
checkColumnWidthWithTolerance('[data-column-id="long"]', 700, 1023.8593139648438, isGrow);
13741380

1381+
cy.log('cols: cols6');
13751382
const cols6 = [
13761383
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
13771384
{ Header: 'Short Width', accessor: 'age' },
@@ -1384,6 +1391,7 @@ describe('AnalyticalTable', () => {
13841391
checkColumnWidthWithTolerance('[data-column-id="friend.name"]', 65, 324.0625, isGrow);
13851392
checkColumnWidthWithTolerance('[data-column-id="long"]', 3824, 1023.8593139648438, isGrow);
13861393

1394+
cy.log('cols: cols7');
13871395
const cols7 = [
13881396
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
13891397
{ Header: 'Short Width', accessor: 'age', minWidth: 400 },
@@ -1396,6 +1404,7 @@ describe('AnalyticalTable', () => {
13961404
.invoke('outerWidth')
13971405
.should('equal', isGrow ? 3824 : 1304);
13981406

1407+
cy.log('cols: cols8');
13991408
const cols8 = [
14001409
{ ...initialColumns[0], maxWidth: Infinity, width: 200 },
14011410
{ Header: 'Spread', accessor: 'friend.name' },

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,11 @@
352352
}
353353

354354
.hiddenA11yText {
355-
display: none;
355+
font-size: 0;
356+
left: 0;
357+
position: absolute;
358+
top: 0;
359+
user-select: none;
356360
}
357361

358362
.checkBox::part(root) {

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

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,79 @@ const ROW_SAMPLE_SIZE = 20;
2020
const MAX_WIDTH = 700;
2121
export const CELL_PADDING_PX = 18; /* padding left and right 0.5rem each (16px) + borders (1px) + buffer (1px) */
2222

23-
function getContentPxLongest(rowSample: RowType[], columnIdOrAccessor: string, uniqueId) {
23+
let measurementCanvas: HTMLCanvasElement | null = null;
24+
let measurementContext: CanvasRenderingContext2D | null = null;
25+
26+
function getComputedCSSVarValue(variableName: string, fallback: string): string {
27+
if (typeof document === 'undefined') {
28+
return fallback;
29+
}
30+
const value = getComputedStyle(document.documentElement).getPropertyValue(variableName);
31+
return value.trim() || fallback;
32+
}
33+
34+
/**
35+
* Convert fontSize string to number. Only handles `rem` and `px` values as `em` is not by design.
36+
*/
37+
function toPx(fontSize: string): number {
38+
if (fontSize.endsWith('rem')) {
39+
const rem = parseFloat(fontSize);
40+
const rootFont = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
41+
return rem * rootFont;
42+
}
43+
if (fontSize.endsWith('px')) {
44+
return parseFloat(fontSize);
45+
}
46+
return parseFloat(fontSize) || 16;
47+
}
48+
49+
function stringToPx(dataPoint: string, isHeader: boolean = false): number {
50+
if (!measurementCanvas) {
51+
measurementCanvas = document.createElement('canvas');
52+
measurementContext = measurementCanvas.getContext('2d');
53+
}
54+
if (!measurementContext) {
55+
return 14 * (isHeader ? 0.55 : 0.5) * dataPoint.length;
56+
}
57+
58+
const bodyFontFamily = getComputedCSSVarValue('--sapFontFamily', 'Arial, Helvetica, sans-serif');
59+
const bodyFontSize = getComputedCSSVarValue('--sapFontSize', '0.875rem');
60+
const headerFontFamily = getComputedCSSVarValue('--_ui5wcr-AnalyticalTable-HeaderFontFamily', bodyFontFamily);
61+
62+
const fontFamily = isHeader ? headerFontFamily : bodyFontFamily;
63+
const fontSizePx = toPx(bodyFontSize);
64+
65+
measurementContext.font = `${fontSizePx}px ${fontFamily}`;
66+
return Math.ceil(measurementContext.measureText(dataPoint).width);
67+
}
68+
69+
function getContentPxLongest(rowSample: RowType[], columnIdOrAccessor: string) {
2470
return rowSample.reduce((max, item) => {
2571
const dataPoint = item.values?.[columnIdOrAccessor];
2672

2773
if (dataPoint) {
28-
const val = stringToPx(dataPoint, uniqueId) + CELL_PADDING_PX;
74+
const val = stringToPx(dataPoint) + CELL_PADDING_PX;
2975
return Math.max(max, val);
3076
}
3177

3278
return max;
3379
}, 0);
3480
}
3581

36-
function getContentPxAvg(rowSample, columnIdOrAccessor, uniqueId) {
82+
function getContentPxAvg(rowSample, columnIdOrAccessor) {
3783
return (
3884
rowSample.reduce((acc, item) => {
3985
const dataPoint = item.values?.[columnIdOrAccessor];
4086

4187
let val = 0;
4288
if (dataPoint) {
43-
val = stringToPx(dataPoint, uniqueId) + CELL_PADDING_PX;
89+
val = stringToPx(dataPoint) + CELL_PADDING_PX;
4490
}
4591
return acc + val;
4692
}, 0) / (rowSample.length || 1)
4793
);
4894
}
4995

50-
function stringToPx(dataPoint, id, isHeader = false) {
51-
const elementId = isHeader ? 'scaleModeHelperHeader' : 'scaleModeHelper';
52-
const ruler = document.getElementById(`${elementId}-${id}`);
53-
if (ruler) {
54-
ruler.textContent = `${dataPoint}`;
55-
return ruler.scrollWidth;
56-
}
57-
return 0;
58-
}
59-
6096
function calculateDefaultColumnWidths(tableWidth: number, columns: AnalyticalTableColumnDefinition[]) {
6197
// Columns w/ external width property
6298
const fixed = [];
@@ -209,7 +245,7 @@ const calculateSmartAndGrowColumns = (
209245
hiddenColumns: ColumnType,
210246
isGrow = false,
211247
) => {
212-
const { rows, state, webComponentsReactProperties } = instance;
248+
const { rows, state } = instance;
213249
const rowSample = rows.slice(0, ROW_SAMPLE_SIZE);
214250
const { tableClientWidth: totalWidth } = state;
215251

@@ -235,25 +271,18 @@ const calculateSmartAndGrowColumns = (
235271
let headerPx: number, contentPxLength: number;
236272

237273
if (column.scaleWidthModeOptions?.cellString) {
238-
contentPxLength =
239-
stringToPx(column.scaleWidthModeOptions.cellString, webComponentsReactProperties.uniqueId) + CELL_PADDING_PX;
274+
contentPxLength = stringToPx(column.scaleWidthModeOptions.cellString) + CELL_PADDING_PX;
240275
} else {
241276
contentPxLength = isGrow
242-
? getContentPxLongest(rowSample, columnIdOrAccessor, webComponentsReactProperties.uniqueId)
243-
: getContentPxAvg(rowSample, columnIdOrAccessor, webComponentsReactProperties.uniqueId);
277+
? getContentPxLongest(rowSample, columnIdOrAccessor)
278+
: getContentPxAvg(rowSample, columnIdOrAccessor);
244279
}
245280

246281
if (column.scaleWidthModeOptions?.headerString) {
247-
headerPx = Math.max(
248-
stringToPx(column.scaleWidthModeOptions.headerString, webComponentsReactProperties.uniqueId, true) +
249-
CELL_PADDING_PX,
250-
60,
251-
);
282+
headerPx = Math.max(stringToPx(column.scaleWidthModeOptions.headerString, true) + CELL_PADDING_PX, 60);
252283
} else {
253284
headerPx =
254-
typeof column.Header === 'string'
255-
? Math.max(stringToPx(column.Header, webComponentsReactProperties.uniqueId, true) + CELL_PADDING_PX, 60)
256-
: 60;
285+
typeof column.Header === 'string' ? Math.max(stringToPx(column.Header, true) + CELL_PADDING_PX, 60) : 60;
257286
}
258287

259288
metadata[columnIdOrAccessor] = {
@@ -368,7 +397,7 @@ const calculateSmartAndGrowColumns = (
368397
width: targetWidth,
369398
};
370399
} else {
371-
const targetWidth = Math.max(Math.min(column.width ?? 0, headerPx), column.minWidth ?? 0);
400+
const targetWidth = Math.max(column.width > 0 ? column.width : headerPx, column.minWidth ?? 0);
372401
if (targetWidth < (column.maxWidth ?? Infinity) && !meta.width) {
373402
lessThanMaxWidthCount++;
374403
}
@@ -476,7 +505,6 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
476505
if (scaleWidthMode === AnalyticalTableScaleWidthMode.Grow) {
477506
return calculateSmartAndGrowColumns(columns, instance, hiddenColumns, true);
478507
}
479-
480508
const hasData = instance.data.length > 0;
481509

482510
if (scaleWidthMode === AnalyticalTableScaleWidthMode.Default || (!hasData && loading)) {

0 commit comments

Comments
 (0)