From a6dbc95f09c20e79465562d6c886a3503568fb6b Mon Sep 17 00:00:00 2001 From: Petar Stoyanov Date: Thu, 8 Feb 2024 17:39:58 +0200 Subject: [PATCH] fix(scaleColumns): adjust for overflow when scaling columns in flex mode (#43) * fix(scaleColumns): adjust for overflow when scaling columns in flex mode * fix(scaleColumns): handle null pointer for biggest column ref * fix(scaleColumns): respect min widths when applying fixes --- .../ngx-datatable/src/lib/utils/math.spec.ts | 49 ++++++++++++++++++- projects/ngx-datatable/src/lib/utils/math.ts | 34 +++++++++++-- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/projects/ngx-datatable/src/lib/utils/math.spec.ts b/projects/ngx-datatable/src/lib/utils/math.spec.ts index 21124c1bf..748f59515 100644 --- a/projects/ngx-datatable/src/lib/utils/math.spec.ts +++ b/projects/ngx-datatable/src/lib/utils/math.spec.ts @@ -1,4 +1,4 @@ -import { forceFillColumnWidths } from './math'; +import { adjustColumnWidths, forceFillColumnWidths } from './math'; describe('Math function', () => { describe('forceFillColumnWidths', () => { @@ -34,4 +34,51 @@ describe('Math function', () => { }); }); }); + + describe('adjustColumnWidths', () => { + describe('flex mode', () => { + it('should not go over/under compared to given max width', () => { + const cols = [ + { prop: 'id1', width: 287, maxWidth: undefined, minWidth: 175, flexGrow: 2, canAutoResize: true }, + { prop: 'id2', width: 215, maxWidth: undefined, minWidth: 200, flexGrow: 1.5, canAutoResize: true }, + { prop: 'id3', width: 287, maxWidth: undefined, minWidth: 150, flexGrow: 2, canAutoResize: true }, + { prop: 'id4', width: 175, maxWidth: undefined, minWidth: 175, flexGrow: 1, canAutoResize: true }, + { prop: 'id5', width: 143, maxWidth: undefined, minWidth: 120, flexGrow: 1, canAutoResize: true } + ]; + + const givenTableWidth = 1180; + + adjustColumnWidths(cols, givenTableWidth); + + const totalAdjustedColumnWidths = cols.map(c => c.width).reduce((p, c) => p + c, 0); + expect(totalAdjustedColumnWidths).toBeCloseTo(givenTableWidth, 0.001); + }); + + it('should overflow if the total of given min widths is bigger than given max width', () => { + const cols = [ + { prop: 'id1', width: 100, maxWidth: undefined, minWidth: 100, flexGrow: 1, canAutoResize: true }, + { prop: 'id2', width: 100, maxWidth: undefined, minWidth: 100, flexGrow: 1, canAutoResize: true } + ]; + const maxWidth = 199; + + adjustColumnWidths(cols, maxWidth); + + const totalAdjustedColumnWidths = cols.map(c => c.width).reduce((p, c) => p + c, 0); + expect(totalAdjustedColumnWidths).toBeGreaterThan(maxWidth); + }); + + it('should respect min widths', () => { + const cols = [ + { prop: 'id1', width: 0, maxWidth: undefined, minWidth: 10, flexGrow: 3.0000000000000075, canAutoResize: true }, + { prop: 'id2', width: 0, maxWidth: undefined, minWidth: 10, flexGrow: 1, canAutoResize: true } + ]; + + adjustColumnWidths(cols, 40); + + for (const col of cols) { + expect(col.width - col.minWidth).toBeGreaterThanOrEqual(0); + } + }); + }); + }); }); diff --git a/projects/ngx-datatable/src/lib/utils/math.ts b/projects/ngx-datatable/src/lib/utils/math.ts index 548e9afcb..5b6303acf 100644 --- a/projects/ngx-datatable/src/lib/utils/math.ts +++ b/projects/ngx-datatable/src/lib/utils/math.ts @@ -31,9 +31,9 @@ export function adjustColumnWidths(allColumns: any, expectedWidth: any) { * Resizes columns based on the flexGrow property, while respecting manually set widths */ function scaleColumns(colsByGroup: any, maxWidth: any, totalFlexGrow: any) { - // calculate total width and flexgrow points for coulumns that can be resized + // calculate total width and flexgrow points for columns that can be resized for (const attr in colsByGroup) { - if (colsByGroup.hasOwnProperty(attr)){ + if (colsByGroup.hasOwnProperty(attr)) { for (const column of colsByGroup[attr]) { if (column.$$oldWidth) { // when manually resized, switch off auto-resize @@ -58,9 +58,9 @@ function scaleColumns(colsByGroup: any, maxWidth: any, totalFlexGrow: any) { remainingWidth = 0; for (const attr in colsByGroup) { - if (colsByGroup.hasOwnProperty(attr)){ + if (colsByGroup.hasOwnProperty(attr)) { for (const column of colsByGroup[attr]) { - // if the column can be resize and it hasn't reached its minimum width yet + // if the column can be resize and it hasn't reached its minimum width yet if (column.canAutoResize && !hasMinWidth[column.prop]) { const newWidth = column.width + column.flexGrow * widthPerFlexPoint; if (column.minWidth !== undefined && newWidth < column.minWidth) { @@ -75,6 +75,32 @@ function scaleColumns(colsByGroup: any, maxWidth: any, totalFlexGrow: any) { } } } while (remainingWidth !== 0); + + // Adjust for any remaining offset in computed widths vs maxWidth + const columns = Object.values<{ + width: number, + canAutoResize: boolean, + minWidth: number, + maxWidth: number + }>(colsByGroup).reduce((acc, col) => acc.concat(col), []); + + const totalWidthAchieved = columns.reduce((acc, col) => acc + col.width, 0); + const delta = maxWidth - totalWidthAchieved; + + if (delta === 0) { + return + } + + // adjust the first column that can be auto-resized respecting the min/max widths + for (const col of columns.filter(c => c.canAutoResize).sort((a, b) => a.width - b.width)) { + if ( + (delta > 0 && (!col.maxWidth || col.width + delta <= col.maxWidth)) || + (delta < 0 && (!col.minWidth || col.width + delta >= col.minWidth)) + ) { + col.width += delta; + break; + } + } } /**