From 0c21c550c19c7fa8e963fc949eaff140e230e47b Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Thu, 29 Jan 2026 18:47:31 +0800 Subject: [PATCH 1/4] refactoring --- .../grids/pivot_grid/area_item/m_area_item.ts | 141 +++++++----------- 1 file changed, 57 insertions(+), 84 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts index 78d3033a96c7..25ee104dd168 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts @@ -90,11 +90,12 @@ abstract class AreaItem { return this.component.option.apply(this.component, arguments); } - _getRowElement(index) { - const that = this; - if (that._tableElement && that._tableElement.length > 0) { - return that._tableElement[0].rows[index]; + _getRowElement(index: number) : HTMLTableRowElement | null { + if (this._tableElement?.length > 0) { + const tableElement = this._tableElement.get(0) as HTMLTableElement; + return tableElement.rows[index]; } + return null; } @@ -139,49 +140,40 @@ abstract class AreaItem { } _renderTableContent(tableElement, data) { - const that = this; const rowsCount = data.length; - let row; - let cell; - let i; - let j; - let rowElement; - let cellElement; - let cellText; - const rtlEnabled = that.option('rtlEnabled'); - const encodeHtml = that.option('encodeHtml'); - let rowClassNames; + const rtlEnabled = this.option('rtlEnabled'); + const encodeHtml = this.option('encodeHtml'); - tableElement.data('area', that._getAreaName()); + tableElement.data('area', this._getAreaName()); tableElement.data('data', data); - tableElement.css('width', ''); const tbody = this._getMainElementMarkup(); - for (i = 0; i < rowsCount; i += 1) { - row = data[i]; - rowClassNames = []; + for (let i = 0; i < rowsCount; i += 1) { + const row = data[i]; + const rowClassNames = []; const tr = domAdapter.createElement('tr'); - for (j = 0; j < row.length; j += 1) { - cell = row[j]; + for (let j = 0; j < row.length; j += 1) { + const cell = row[j]; + const td = domAdapter.createElement('td'); this._getRowClassNames(i, cell, rowClassNames); - const td = domAdapter.createElement('td'); + let cellText = ''; if (cell) { cell.rowspan && td.setAttribute('rowspan', cell.rowspan || 1); cell.colspan && td.setAttribute('colspan', cell.colspan || 1); const styleOptions = { - cellElement, + cellElement: undefined, cell, cellsCount: row.length, cellIndex: j, - rowElement, + rowElement: undefined, rowIndex: i, rowsCount, rtlEnabled, @@ -189,7 +181,7 @@ abstract class AreaItem { cssArray: [], }; - that._applyCustomStyles(styleOptions); + this._applyCustomStyles(styleOptions); if (styleOptions.cssArray.length) { setStyle(td, styleOptions.cssArray.join(';')); @@ -209,8 +201,6 @@ abstract class AreaItem { } cellText = this._getCellText(cell, encodeHtml); - } else { - cellText = ''; } const span = domAdapter.createElement('span'); @@ -284,24 +274,15 @@ abstract class AreaItem { } } - _getRowHeight(index) { + _getRowHeight(index: number): number { const row = this._getRowElement(index); - let height = 0; - - const { offsetHeight } = row; - if (row && row.lastChild) { - if (row.getBoundingClientRect) { - const clientRect = getBoundingRect(row); - height = clientRect.height; - - if (height <= offsetHeight - 1) { - height = offsetHeight; - } - } + if (row?.lastChild) { + const { height } = row.getBoundingClientRect(); - return height > 0 ? height : offsetHeight; + return height <= row.offsetHeight - 1 ? row.offsetHeight : height; } + return 0; } @@ -345,69 +326,61 @@ abstract class AreaItem { this._tableElement[0].style.height = `${totalHeight}px`; } - getColumnsWidth() { + getColumnsWidth(): number[] { const rowsLength = this.getRowsLength(); - let rowIndex; - let row; - let i; - let columnIndex; - const processedCells = []; - const result = []; - const fillCells = function (cells, rowIndex, columnIndex, rowSpan, colSpan) { - let rowOffset; - let columnOffset; - for (rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) { - for (columnOffset = 0; columnOffset < colSpan; columnOffset += 1) { - cells[rowIndex + rowOffset] = cells[rowIndex + rowOffset] || []; - cells[rowIndex + rowOffset][columnIndex + columnOffset] = true; - } - } - }; + const processedCells: boolean[][] = []; + const result: number[] = []; - if (rowsLength) { - for (rowIndex = 0; rowIndex < rowsLength; rowIndex += 1) { - processedCells[rowIndex] = processedCells[rowIndex] || []; - row = this._getRowElement(rowIndex); - for (i = 0; i < row.cells.length; i += 1) { - for (columnIndex = 0; processedCells[rowIndex][columnIndex]; columnIndex += 1); - fillCells( - processedCells, - rowIndex, - columnIndex, - row.cells[i].rowSpan, - row.cells[i].colSpan, - ); - if (row.cells[i].colSpan === 1) { - result[columnIndex] = result[columnIndex] || getRealElementWidth(row.cells[i]); + if (!rowsLength) { + return []; + } + + for (let rowIndex = 0; rowIndex < rowsLength; rowIndex += 1) { + processedCells[rowIndex] ||= []; + const row = this._getRowElement(rowIndex) as HTMLTableRowElement; + + Array.from(row.cells).forEach((cell) => { + let columnIndex = 0; + for (; processedCells[rowIndex][columnIndex]; columnIndex += 1); + + for (let rowOffset = 0; rowOffset < cell.rowSpan; rowOffset += 1) { + for (let columnOffset = 0; columnOffset < cell.colSpan; columnOffset += 1) { + processedCells[rowIndex + rowOffset] ||= []; + processedCells[rowIndex + rowOffset][columnIndex + columnOffset] = true; } } - } + + if (cell.colSpan === 1) { + result[columnIndex] ||= getRealElementWidth(cell); + } + }); } + return result; } - setColumnsWidth(values) { - let i; - const tableElement = this._tableElement[0]; - this._colgroupElement.html(''); + setColumnsWidth(values: number[]): void { + const tableElement = this._tableElement.get(0); const columnsCount = this.getColumnsCount(); - const columnWidth: any = []; + const columnWidth: number[] = []; - for (i = 0; i < columnsCount; i += 1) { + for (let i = 0; i < columnsCount; i += 1) { columnWidth.push(values[i] || 0); } - for (i = columnsCount; i < values.length && values; i += 1) { + for (let i = columnsCount; i < values.length && values; i += 1) { columnWidth[columnsCount - 1] += values[i]; } - for (i = 0; i < columnsCount; i += 1) { + this._colgroupElement.html(''); + + for (let i = 0; i < columnsCount; i += 1) { const col = domAdapter.createElement('col'); col.style.width = `${columnWidth[i]}px`; this._colgroupElement.append(col); } - this._tableWidth = columnWidth.reduce((sum, width) => sum + width, 0); + this._tableWidth = columnWidth.reduce((sum, width) => sum + width, 0); tableElement.style.width = `${this._tableWidth}px`; tableElement.style.tableLayout = 'fixed'; } From efdaa94160661f351e08d6ffd74a45697091c730 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Thu, 29 Jan 2026 20:36:09 +0800 Subject: [PATCH 2/4] fix --- .../js/__internal/grids/pivot_grid/area_item/m_area_item.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts index 25ee104dd168..a8dd6125adf6 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts @@ -330,6 +330,7 @@ abstract class AreaItem { const rowsLength = this.getRowsLength(); const processedCells: boolean[][] = []; const result: number[] = []; + const colSpans: number[] = []; if (!rowsLength) { return []; @@ -350,8 +351,9 @@ abstract class AreaItem { } } - if (cell.colSpan === 1) { - result[columnIndex] ||= getRealElementWidth(cell); + if (colSpans[columnIndex] === undefined || cell.colSpan < colSpans[columnIndex]) { + result[columnIndex] = getRealElementWidth(cell); + colSpans[columnIndex] = cell.colSpan; } }); } From 16ce6b665e6e9fe50c92d048fc647c94352bdc92 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 30 Jan 2026 14:18:34 +0800 Subject: [PATCH 3/4] add test --- .../pivotGrid.tests.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js index 47575050dd08..118592b39d60 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js @@ -2897,6 +2897,58 @@ QUnit.module('dxPivotGrid', { assert.ok(pivotGrid._rowsArea.hasScroll(), 'rows area scroll'); }); + QUnit.test('All columns should have width > 0 in virtual scrolling mode with many fields', function(assert) { + const fields = [ + { dataField: 'row1', area: 'row', dataType: 'string', index: 0, }, + { dataField: 'row2', area: 'row', dataType: 'string', index: 1 }, + ]; + const dataFieldsNum = 40; + + for(let i = 0; i < dataFieldsNum; i++) { + fields.push({ dataField: 'data' + i, area: 'data', dataType: 'number', index: i }); + } + + const data = []; + const dataItemsNum = 10; + + for(let i = 0; i < dataItemsNum; i++) { + const item = { + row1: i % 2 === 0 ? 'Category A' : 'Category B', + row2: i % 3 === 0 ? 'Subcategory X' : 'Subcategory Y' + }; + + data.push(item); + } + + $('#pivotGrid').empty(); + $('#pivotGrid').width(800); + $('#pivotGrid').height(400); + + const pivotGrid = createPivotGrid({ + dataFieldArea: 'row', + rowHeaderLayout: 'tree', + scrolling: { + mode: 'virtual', + timeout: 0 + }, + dataSource: { + fields: fields, + store: data + } + }); + this.clock.tick(10); + + pivotGrid.getDataSource().expandHeaderItem('rowsArea', ['Category A']); + this.clock.tick(10); + + const $cols = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers table:not(.dx-pivot-grid-fake-table) colgroup col'); + + assert.strictEqual($cols.length, 3, 'colgroup has 3 elements'); + assert.ok(getWidth($cols.eq(0)) > 0, 'first column width > 0'); + assert.strictEqual(getWidth($cols.eq(1)), 0, 'second column width == 0'); + assert.ok(getWidth($cols.eq(2)) > 0, 'third column width > 0'); + }); + // T518512; QUnit.test('render should be called once after expand item if virtual scrolling enabled', function(assert) { $('#pivotGrid').empty(); From 61695a38d7095ef78216f9a85fea89aa8610d388 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 30 Jan 2026 14:37:18 +0800 Subject: [PATCH 4/4] fix qunit test --- .../tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js index 118592b39d60..a70a1c2138e6 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js @@ -2897,7 +2897,7 @@ QUnit.module('dxPivotGrid', { assert.ok(pivotGrid._rowsArea.hasScroll(), 'rows area scroll'); }); - QUnit.test('All columns should have width > 0 in virtual scrolling mode with many fields', function(assert) { + QUnit.test('T1317673 - First and third columns should have width > 0 in virtual scrolling mode with many fields', function(assert) { const fields = [ { dataField: 'row1', area: 'row', dataType: 'string', index: 0, }, { dataField: 'row2', area: 'row', dataType: 'string', index: 1 }, @@ -2945,7 +2945,6 @@ QUnit.module('dxPivotGrid', { assert.strictEqual($cols.length, 3, 'colgroup has 3 elements'); assert.ok(getWidth($cols.eq(0)) > 0, 'first column width > 0'); - assert.strictEqual(getWidth($cols.eq(1)), 0, 'second column width == 0'); assert.ok(getWidth($cols.eq(2)) > 0, 'third column width > 0'); });