From 669e32887ddfa4b91a371f491115f7a1b5c8da1f Mon Sep 17 00:00:00 2001 From: Viktor Kombov Date: Fri, 23 Oct 2026 17:15:23 +0300 Subject: [PATCH 1/2] fix(pivot-grid): span collapsed and summary row headers across columns In the horizontal row layout, size collapsed and summary row headers to the combined width of the columns they span instead of the first one, and do not measure auto-sized dimensions again from them. Closes #17222 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 ++ .../pivot-grid/src/pivot-grid.component.ts | 3 +- .../grids/pivot-grid/src/pivot-grid.spec.ts | 117 ++++++++++++++++++ ...ot-row-dimension-header-group.component.ts | 3 +- 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f1182c6f4..dfc6c564e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes for each version of this project will be documented in this file. +## 21.2.23 + +### Bug Fixes + +- `IgxPivotGridComponent` + - In the horizontal row layout, collapsed and summary row headers now fill all the columns they span (#17222). + ## 21.2.0 ### New Features diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts index cee54935f9a..46693d0dc55 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.ts @@ -2197,7 +2197,8 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni protected autoSizeDimensionsInView() { if (!this.hasDimensionsToAutosize) return; for (const dim of this.visibleRowDimensions) { - if (dim.width === 'auto') { + // Skip sized dimensions, their merged headers span several columns. + if (dim.width === 'auto' && !dim.autoWidth) { const contentWidths = []; const relatedDims = PivotUtil.flatten([dim]).map(x => x.memberName); const contentCollection = this.getContentCollection(dim); diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts index 9330d3472a9..1661800cc20 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts @@ -3157,6 +3157,123 @@ describe('IgxPivotGrid #pivotGrid', () => { expect(rowDimensionsCol4.length).toBe(0); }); + it("should size the collapsed and summary row headers to all the columns they span.", () => { + pivotGrid.pivotConfiguration.rows = [ + { + memberName: 'All cities', + memberFunction: () => 'All Cities', + enabled: true, + horizontalSummary: true, + childLevel: { + memberName: 'City', + enabled: true + } + }, { + memberFunction: () => 'AllProducts', + memberName: 'AllProducts', + enabled: true, + horizontalSummary: true, + childLevel: { + memberFunction: (data) => data.ProductCategory, + memberName: 'ProductCategory', + enabled: true, + width: '120px' + } + } + ]; + pivotGrid.pipeTrigger++; + fixture.detectChanges(); + + const getContentCells = (header: string) => fixture.debugElement + .queryAll(By.directive(IgxPivotRowDimensionContentComponent)) + .filter(x => x.componentInstance.rowDimensionColumn.header === header); + const expectHeadersToSpanTheirColumns = () => { + const contentCells = fixture.debugElement.queryAll(By.directive(IgxPivotRowDimensionContentComponent)); + for (const cell of contentCells) { + const content = cell.componentInstance as IgxPivotRowDimensionContentComponent; + const spannedWidth = pivotGrid.visibleRowDimensions + .slice(content.layout.colStart - 1, content.layout.colEnd - 1) + .reduce((width, dim) => width + pivotGrid.rowDimensionWidthToPixels(dim), 0); + const header = cell.query(By.directive(IgxPivotRowDimensionHeaderComponent)).nativeElement as HTMLElement; + expect(header.getBoundingClientRect().width).withContext(content.rowDimensionColumn.header).toBe(spannedWidth); + } + }; + + // the summary headers span all columns to their right + const citiesSummary = getContentCells('All Cities Total'); + expect(citiesSummary.length).toBe(1); + expect(citiesSummary[0].componentInstance.layout.colStart).toBe(1); + expect(citiesSummary[0].componentInstance.layout.colEnd).toBe(5); + const productsSummaries = getContentCells('AllProducts Total'); + expect(productsSummaries.length).toBe(6); + productsSummaries.forEach(x => { + expect(x.componentInstance.layout.colStart).toBe(3); + expect(x.componentInstance.layout.colEnd).toBe(5); + }); + expectHeadersToSpanTheirColumns(); + + // the collapsed header spans the empty ProductCategory column + getContentCells('AllProducts')[0].query(By.directive(IgxIconComponent)).nativeElement.click(); + fixture.detectChanges(); + const collapsedProducts = getContentCells('AllProducts')[0].componentInstance.layout; + expect(collapsedProducts.colStart).toBe(3); + expect(collapsedProducts.colEnd).toBe(5); + expectHeadersToSpanTheirColumns(); + + // the same applies when the summaries are on top + pivotGrid.pivotUI = { horizontalSummariesPosition: PivotSummaryPosition.Top }; + fixture.detectChanges(); + expect(getContentCells('All Cities Total')[0].componentInstance.layout.colEnd).toBe(5); + expectHeadersToSpanTheirColumns(); + }); + + it("should not widen an auto-sized row dimension to the columns its summary headers span.", async () => { + fixture.componentInstance.pivotConfigHierarchy = { + columns: pivotGrid.pivotConfiguration.columns, + values: pivotGrid.pivotConfiguration.values, + rows: [ + { + memberName: 'All cities', + memberFunction: () => 'All Cities', + enabled: false, + width: 'auto', + childLevel: { + memberName: 'City', + enabled: true + } + }, { + memberFunction: () => 'AllProducts', + memberName: 'AllProducts', + enabled: true, + width: 'auto', + horizontalSummary: true, + childLevel: { + memberFunction: (data) => data.ProductCategory, + memberName: 'ProductCategory', + enabled: true + } + } + ] + }; + fixture.detectChanges(); + await wait(50); + fixture.detectChanges(); + + const [allCities, allProducts] = pivotGrid.pivotConfiguration.rows; + const productsWidth = allProducts.autoWidth; + expect(productsWidth).toBeGreaterThan(0); + + // Enabling another auto dimension reruns sizing. + pivotGrid.toggleDimension(allCities); + await wait(100); + fixture.detectChanges(); + await wait(50); + fixture.detectChanges(); + + expect(allCities.autoWidth).toBeGreaterThan(0); + expect(allProducts.autoWidth).toBe(productsWidth); + }); + it("should render summary rows when enabled.", () => { pivotGrid.pivotConfiguration.rows = [ { diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-header-group.component.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-header-group.component.ts index fda18cb8240..6dac6011803 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-header-group.component.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-row-dimension-header-group.component.ts @@ -145,7 +145,8 @@ export class IgxPivotRowDimensionHeaderGroupComponent extends IgxGridHeaderGroup protected getHeaderWidthFromDimension() { if (this.grid.hasHorizontalLayout) { - return this.parent.width === -1 ? 'fit-content' : this.width; + // Collapsed and summary headers can span several columns. + return this.parent.width === -1 ? 'fit-content' : `${this.parent.width}px`; } return this.grid.rowDimensionWidth(this.parent.rootDimension); } From 6b9d63be4a43b8db78b677d53431bfc5aaf952a0 Mon Sep 17 00:00:00 2001 From: Viktor Kombov Date: Fri, 23 Oct 2026 19:08:25 +0300 Subject: [PATCH 2/2] fix(pivot-grid): stop merged row header resizing at the first column Collapsed and summary row headers span several dimensions, but resizing changes only the first one, so measure that dimension for the resize line limits instead of the merged header. Closes #17222 Co-Authored-By: Claude Opus 5.5 --- .../pivot-grid/pivot-resizing.service.ts | 5 +- .../grids/pivot-grid/src/pivot-grid.spec.ts | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/grids/core/src/resizing/pivot-grid/pivot-resizing.service.ts b/projects/igniteui-angular/grids/core/src/resizing/pivot-grid/pivot-resizing.service.ts index 2fbaca7072d..cde676d0eec 100644 --- a/projects/igniteui-angular/grids/core/src/resizing/pivot-grid/pivot-resizing.service.ts +++ b/projects/igniteui-angular/grids/core/src/resizing/pivot-grid/pivot-resizing.service.ts @@ -19,7 +19,10 @@ export class IgxPivotColumnResizingService extends IgxColumnResizingService { * @hidden */ public override getColumnHeaderRenderedWidth() { - return this.rowHeaderGroup.header.nativeElement.getBoundingClientRect().width; + const headerWidth = this.rowHeaderGroup.header.nativeElement.getBoundingClientRect().width; + // A collapsed or summary header spans several dimensions, but only the first one is resized. + const rowDimWidth = this.rowHeaderGroup.grid.rowDimensionWidthToPixels(this.rowHeaderGroup.parent.rootDimension); + return rowDimWidth < headerWidth ? rowDimWidth : headerWidth; } protected override _handlePixelResize(diff: number, column: ColumnType) { diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts index 1661800cc20..202becfcc13 100644 --- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts +++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.spec.ts @@ -3520,6 +3520,55 @@ describe('IgxPivotGrid #pivotGrid', () => { expect(rowHeaders[0].componentInstance.column.width).toEqual('400px'); })); + it("should stop the resizer of a summary row header at the minimum width of its first dimension.", fakeAsync(() => { + pivotGrid.pivotConfiguration.rows = [ + { + memberName: 'All cities', + memberFunction: () => 'All Cities', + enabled: true, + childLevel: { + memberName: 'City', + enabled: true + } + }, { + memberFunction: () => 'AllProducts', + memberName: 'AllProducts', + enabled: true, + horizontalSummary: true, + childLevel: { + memberFunction: (data) => data.ProductCategory, + memberName: 'ProductCategory', + enabled: true, + width: '120px' + } + } + ]; + pivotGrid.pipeTrigger++; + fixture.detectChanges(); + + const summaryHeader = fixture.debugElement.queryAll(By.directive(IgxPivotRowDimensionHeaderGroupComponent)) + .find(x => x.componentInstance.column.header === 'AllProducts Total'); + const allProducts = pivotGrid.pivotConfiguration.rows[1]; + const rootWidth = pivotGrid.rowDimensionWidthToPixels(allProducts); + const minWidth = summaryHeader.componentInstance.column.minWidthPx; + // the summary header spans the ProductCategory column as well, but only AllProducts is resized + expect(summaryHeader.componentInstance.header.nativeElement.getBoundingClientRect().width).toBe(rootWidth + 120); + UIInteractions.simulateMouseEvent('mousedown', GridFunctions.getHeaderResizeArea(summaryHeader).nativeElement, 500, 0); + tick(200); + fixture.detectChanges(); + tick(16); + const resizer = GridFunctions.getResizer(fixture).nativeElement; + const startLeft = parseFloat(resizer.style.left); + + UIInteractions.simulateMouseEvent('mousemove', resizer, -500, 5); + tick(16); + expect(startLeft - parseFloat(resizer.style.left)).toBe(rootWidth - minWidth); + + UIInteractions.simulateMouseEvent('mouseup', resizer, -500, 5); + fixture.detectChanges(); + expect(allProducts.width).toBe(`${minWidth}px`); + })); + it("should allow sorting each dimension column from the hierarchy.", () => { pivotGrid.pivotConfiguration.rows = [ {