Skip to content

Commit b61024d

Browse files
authored
Merge pull request #10794 from IgniteUI/mkirova/pivot-cell-merging-POC
Pivot cell merging
2 parents 9f9c0c5 + 30f1bbb commit b61024d

22 files changed

+554
-233
lines changed

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,10 @@
577577
@extend %igx-grid__tr-pivot-group !optional
578578
}
579579

580+
@include e(tr-header-row) {
581+
@extend %igx-grid__tr-header-row !optional;
582+
}
583+
580584
@include e(tr-pivot-toggle-icons) {
581585
@extend %igx-grid__tr-pivot-toggle-icons !optional;
582586
}

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2977,6 +2977,14 @@
29772977
%igx-grid__tr-pivot-toggle-icons {
29782978
display: inline-flex !important;
29792979
}
2980+
2981+
%igx-grid__tr-header-row {
2982+
igx-pivot-row-dimension-header-group {
2983+
igx-pivot-row-dimension-header {
2984+
align-items: center;
2985+
}
2986+
}
2987+
}
29802988
// Pivot grid END
29812989
}
29822990

projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,10 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
414414

415415
if (this.igxForScrollOrientation === 'vertical') {
416416
this.dc.instance._viewContainer.element.nativeElement.style.top = '0px';
417-
this.scrollComponent = vc.createComponent(VirtualHelperComponent).instance;
417+
this.scrollComponent = this.syncScrollService.getScrollMaster(this.igxForScrollOrientation);
418+
if (!this.scrollComponent || !this.document.contains(this.scrollComponent.elementRef.nativeElement)) {
419+
this.scrollComponent = vc.createComponent(VirtualHelperComponent).instance
420+
}
418421
this._maxHeight = this._calcMaxBrowserHeight();
419422
this.scrollComponent.size = this.igxForOf ? this._calcHeight() : 0;
420423
this.syncScrollService.setScrollMaster(this.igxForScrollOrientation, this.scrollComponent);
@@ -721,6 +724,34 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
721724
return scroll;
722725
}
723726

727+
/**
728+
* Returns the index of the element at the specified offset.
729+
* ```typescript
730+
* this.parentVirtDir.getIndexAtScroll(100);
731+
* ```
732+
*/
733+
public getIndexAtScroll(scrollOffset: number) {
734+
return this.getIndexAt(scrollOffset, this.sizesCache);
735+
}
736+
/**
737+
* Returns whether the target index is outside the view.
738+
* ```typescript
739+
* this.parentVirtDir.isIndexOutsideView(10);
740+
* ```
741+
*/
742+
public isIndexOutsideView(index: number) {
743+
const targetNode = index >= this.state.startIndex && index <= this.state.startIndex + this.state.chunkSize ?
744+
this._embeddedViews.map(view =>
745+
view.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE) || view.rootNodes[0].nextElementSibling)[index - this.state.startIndex] : null;
746+
const rowHeight = this.getSizeAt(index);
747+
const containerSize = parseInt(this.igxForContainerSize, 10);
748+
const containerOffset = -(this.scrollPosition - this.sizesCache[this.state.startIndex]);
749+
const endTopOffset = targetNode ? targetNode.offsetTop + rowHeight + containerOffset : containerSize + rowHeight;
750+
return !targetNode || targetNode.offsetTop < Math.abs(containerOffset)
751+
|| containerSize && endTopOffset - containerSize > 5;
752+
}
753+
754+
724755
/**
725756
* @hidden
726757
* Function that recalculates and updates cache sizes.
@@ -1115,20 +1146,16 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
11151146
protected initSizesCache(items: any[]): number {
11161147
let totalSize = 0;
11171148
let size = 0;
1118-
const dimension = this.igxForScrollOrientation === 'horizontal' ?
1119-
this.igxForSizePropName : 'height';
1149+
const dimension = this.igxForSizePropName || 'height';
11201150
let i = 0;
11211151
this.sizesCache = [];
11221152
this.heightCache = [];
11231153
this.sizesCache.push(0);
11241154
const count = this.isRemote ? this.totalItemCount : items.length;
11251155
for (i; i < count; i++) {
1126-
if (dimension === 'height') {
1127-
// cols[i][dimension] = parseInt(this.igxForItemSize, 10) || 0;
1128-
size = parseInt(this.igxForItemSize, 10) || 0;
1156+
size = this._getItemSize(items[i], dimension);
1157+
if (this.igxForScrollOrientation === 'vertical') {
11291158
this.heightCache.push(size);
1130-
} else {
1131-
size = this._getItemSize(items[i], dimension);
11321159
}
11331160
totalSize += size;
11341161
this.sizesCache.push(totalSize);
@@ -1359,6 +1386,11 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
13591386
this._virtScrollTop = realPercentScrolled * maxVirtScrollTop;
13601387
}
13611388

1389+
protected _getItemSize(item, dimension: string): number {
1390+
const dim = item ? item[dimension] : null;
1391+
return typeof dim === 'number' ? dim : parseInt(this.igxForItemSize, 10) || 0;
1392+
}
1393+
13621394
private _updateVScrollOffset() {
13631395
let scrollOffset = 0;
13641396
let currentScrollTop = this.scrollPosition;
@@ -1380,10 +1412,6 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
13801412
this.dc.instance._viewContainer.element.nativeElement.style.left = -scrollOffset + 'px';
13811413
}
13821414

1383-
private _getItemSize(item, dimension: string): number {
1384-
const dim = item[dimension];
1385-
return typeof dim === 'number' ? dim : parseInt(this.igxForItemSize, 10) || 0;
1386-
}
13871415

13881416
private _adjustScrollPositionAfterSizeChange(sizeDiff) {
13891417
// if data has been changed while container is scrolled
@@ -1436,10 +1464,14 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
14361464
* @internal
14371465
*/
14381466
public get sizesCache(): number[] {
1439-
if (this.syncService.isMaster(this)) {
1467+
if (this.igxForScrollOrientation === 'horizontal') {
1468+
if (this.syncService.isMaster(this)) {
1469+
return this._sizesCache;
1470+
}
1471+
return this.syncService.sizesCache(this.igxForScrollOrientation);
1472+
} else {
14401473
return this._sizesCache;
14411474
}
1442-
return this.syncService.sizesCache(this.igxForScrollOrientation);
14431475
}
14441476
/**
14451477
* @hidden
@@ -1450,7 +1482,7 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
14501482
}
14511483

14521484
protected get itemsDimension() {
1453-
return this.igxForScrollOrientation === 'horizontal' ? this.igxForSizePropName : 'height';
1485+
return this.igxForSizePropName || 'height';
14541486
}
14551487

14561488
/**
@@ -1578,10 +1610,9 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
15781610

15791611
protected getItemSize(item) {
15801612
let size = 0;
1581-
const dimension = this.igxForScrollOrientation === 'horizontal' ?
1582-
this.igxForSizePropName : 'height';
1583-
if (dimension === 'height') {
1584-
size = parseInt(this.igxForItemSize, 10) || 0;
1613+
const dimension = this.igxForSizePropName || 'height';
1614+
if (this.igxForScrollOrientation === 'vertical') {
1615+
size = this._getItemSize(item, dimension);
15851616
if (item && item.summaries) {
15861617
size = item.max;
15871618
} else if (item && item.groups && item.height) {
@@ -1594,7 +1625,7 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
15941625
}
15951626

15961627
protected initSizesCache(items: any[]): number {
1597-
if (!this.syncService.isMaster(this)) {
1628+
if (!this.syncService.isMaster(this) && this.igxForScrollOrientation === 'horizontal') {
15981629
const masterSizesCache = this.syncService.sizesCache(this.igxForScrollOrientation);
15991630
return masterSizesCache[masterSizesCache.length - 1];
16001631
}
@@ -1607,7 +1638,7 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
16071638
const count = this.isRemote ? this.totalItemCount : items.length;
16081639
for (i; i < count; i++) {
16091640
size = this.getItemSize(items[i]);
1610-
if (this.itemsDimension === 'height') {
1641+
if (this.igxForScrollOrientation === 'vertical') {
16111642
this.heightCache.push(size);
16121643
}
16131644
totalSize += size;
@@ -1744,10 +1775,15 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
17441775
* @hidden
17451776
*/
17461777
protected _calcMaxChunkSize(): number {
1747-
if (this.syncService.isMaster(this)) {
1778+
if (this.igxForScrollOrientation === 'horizontal') {
1779+
if (this.syncService.isMaster(this)) {
1780+
return super._calcMaxChunkSize();
1781+
}
1782+
return this.syncService.chunkSize(this.igxForScrollOrientation);
1783+
} else {
17481784
return super._calcMaxChunkSize();
17491785
}
1750-
return this.syncService.chunkSize(this.igxForScrollOrientation);
1786+
17511787
}
17521788
}
17531789

projects/igniteui-angular/src/lib/grids/columns/column.component.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,7 +1790,7 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy
17901790
*/
17911791
public getGridTemplate(isRow: boolean): string {
17921792
if (isRow) {
1793-
const rowsCount = this.grid.multiRowLayoutRowSize;
1793+
const rowsCount = !this.grid.isPivot ? this.grid.multiRowLayoutRowSize : this.children.length - 1;
17941794
return `repeat(${rowsCount},1fr)`;
17951795
} else {
17961796
return this.getColumnSizesString(this.children);
@@ -2312,23 +2312,7 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy
23122312
* Returns the width and padding of a header cell.
23132313
*/
23142314
public getHeaderCellWidths() {
2315-
const range = this.grid.document.createRange();
2316-
2317-
// We do not cover cases where there are children with width 100% and etc,
2318-
// because then we try to get new column size, based on header content, which is sized based on column size...
2319-
const headerWidth = this.platform.getNodeSizeViaRange(range,
2320-
this.headerCell.nativeElement,
2321-
this.headerGroup.nativeElement);
2322-
2323-
const headerStyle = this.grid.document.defaultView.getComputedStyle(this.headerCell.nativeElement);
2324-
const headerPadding = parseFloat(headerStyle.paddingLeft) + parseFloat(headerStyle.paddingRight) +
2325-
parseFloat(headerStyle.borderRightWidth);
2326-
2327-
// Take into consideration the header group element, since column pinning applies borders to it if its not a columnGroup.
2328-
const headerGroupStyle = this.grid.document.defaultView.getComputedStyle(this.headerGroup.nativeElement);
2329-
const borderSize = !this.parent ? parseFloat(headerGroupStyle.borderRightWidth) + parseFloat(headerGroupStyle.borderLeftWidth) : 0;
2330-
2331-
return { width: Math.ceil(headerWidth), padding: Math.ceil(headerPadding + borderSize) };
2315+
return this.grid.getHeaderCellWidth(this.headerCell.nativeElement);
23322316
}
23332317

23342318
/**

projects/igniteui-angular/src/lib/grids/common/grid.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ export interface GridType extends IGridDataBindable {
404404
hasColumnGroups: boolean;
405405
hasEditableColumns: boolean;
406406
uniqueColumnValuesStrategy: (column: ColumnType, tree: FilteringExpressionsTree, done: (values: any[]) => void) => void;
407+
getHeaderCellWidth: (element: HTMLElement) => ISizeInfo;
407408

408409
cdr: ChangeDetectorRef;
409410
document: Document;
@@ -643,3 +644,8 @@ export interface GridSVGIcon {
643644
name: string;
644645
value: string;
645646
}
647+
648+
export interface ISizeInfo {
649+
width: number,
650+
padding: number
651+
}

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ import {
123123
IPinColumnCancellableEventArgs
124124
} from './common/events';
125125
import { IgxAdvancedFilteringDialogComponent } from './filtering/advanced-filtering/advanced-filtering-dialog.component';
126-
import { ColumnType, GridServiceType, GridType, IGX_GRID_SERVICE_BASE, RowType } from './common/grid.interface';
126+
import { ColumnType, GridServiceType, GridType, IGX_GRID_SERVICE_BASE, ISizeInfo, RowType } from './common/grid.interface';
127127
import { DropPosition } from './moving/moving.service';
128128
import { IgxHeadSelectorDirective, IgxRowSelectorDirective } from './selection/row-selectors';
129129
import { IgxColumnComponent } from './columns/column.component';
@@ -2960,7 +2960,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
29602960
@Inject(IGX_GRID_SERVICE_BASE) public gridAPI: GridServiceType,
29612961
protected transactionFactory: IgxFlatTransactionFactory,
29622962
private elementRef: ElementRef<HTMLElement>,
2963-
private zone: NgZone,
2963+
protected zone: NgZone,
29642964
@Inject(DOCUMENT) public document: any,
29652965
public cdr: ChangeDetectorRef,
29662966
protected resolver: ComponentFactoryResolver,
@@ -3883,6 +3883,26 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
38833883
return this.width === null || diff >= 0;
38843884
}
38853885

3886+
/**
3887+
* @hidden @internal
3888+
* Gets the header cell inner width for auto-sizing.
3889+
*/
3890+
public getHeaderCellWidth(element: HTMLElement): ISizeInfo {
3891+
const range = this.document.createRange();
3892+
const headerWidth = this.platform.getNodeSizeViaRange(range,
3893+
element,
3894+
element.parentElement);
3895+
3896+
const headerStyle = this.document.defaultView.getComputedStyle(element);
3897+
const headerPadding = parseFloat(headerStyle.paddingLeft) + parseFloat(headerStyle.paddingRight) +
3898+
parseFloat(headerStyle.borderRightWidth);
3899+
3900+
// Take into consideration the header group element, since column pinning applies borders to it if its not a columnGroup.
3901+
const headerGroupStyle = this.document.defaultView.getComputedStyle(element.parentElement);
3902+
const borderSize = parseFloat(headerGroupStyle.borderRightWidth) + parseFloat(headerGroupStyle.borderLeftWidth);
3903+
return { width: Math.ceil(headerWidth), padding: Math.ceil(headerPadding + borderSize) };
3904+
}
3905+
38863906
/**
38873907
* @hidden @internal
38883908
* Gets the combined width of the columns that are specific to the enabled grid features. They are fixed.
@@ -6980,7 +7000,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
69807000
this.cdr.markForCheck();
69817001
}
69827002

6983-
private verticalScrollHandler(event) {
7003+
protected verticalScrollHandler(event) {
69847004
this.verticalScrollContainer.onScroll(event);
69857005
this.disableTransitions = true;
69867006

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid-keyboard-nav.spec.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { configureTestSuite } from '../../test-utils/configure-suite';
66
import { GridFunctions } from '../../test-utils/grid-functions.spec';
77
import { IgxPivotGridMultipleRowComponent } from '../../test-utils/pivot-grid-samples.spec';
88
import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec';
9+
import { IgxPivotRowDimensionHeaderComponent } from './pivot-row-dimension-header.component';
910

1011
const DEBOUNCE_TIME = 250;
1112
const PIVOT_TBODY_CSS_CLASS = '.igx-grid__tbody';
@@ -35,8 +36,10 @@ describe('IgxPivotGrid - Keyboard navigation #pivotGrid', () => {
3536
}));
3637

3738
it('should allow navigating between row headers', () => {
38-
const [firstCell, secondCell] = fixture.debugElement.queryAll(
39-
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
39+
const allGroups = fixture.debugElement.queryAll(
40+
By.directive(IgxPivotRowDimensionHeaderComponent));
41+
const firstCell = allGroups[0];
42+
const secondCell = allGroups.filter(x => x.componentInstance.column.field === 'Country')[0];
4043
UIInteractions.simulateClickAndSelectEvent(firstCell);
4144
fixture.detectChanges();
4245

@@ -52,8 +55,10 @@ describe('IgxPivotGrid - Keyboard navigation #pivotGrid', () => {
5255
});
5356

5457
it('should not go outside of the boundaries of the row dimensions content', () => {
55-
const [firstCell, _, thirdCell] = fixture.debugElement.queryAll(
56-
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
58+
const allGroups = fixture.debugElement.queryAll(
59+
By.directive(IgxPivotRowDimensionHeaderComponent));
60+
const firstCell = allGroups[0];
61+
const thirdCell = allGroups.filter(x => x.componentInstance.column.field === 'Date')[0];
5762
UIInteractions.simulateClickAndSelectEvent(firstCell);
5863
fixture.detectChanges();
5964

@@ -76,8 +81,10 @@ describe('IgxPivotGrid - Keyboard navigation #pivotGrid', () => {
7681
});
7782

7883
it('should allow navigating from first to last row headers in a row(Home/End)', () => {
79-
const [firstCell, _, thirdCell] = fixture.debugElement.queryAll(
80-
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
84+
const allGroups = fixture.debugElement.queryAll(
85+
By.directive(IgxPivotRowDimensionHeaderComponent));
86+
const firstCell = allGroups[0];
87+
const thirdCell = allGroups.filter(x => x.componentInstance.column.field === 'Date')[0];
8188
UIInteractions.simulateClickAndSelectEvent(firstCell);
8289
fixture.detectChanges();
8390

@@ -95,17 +102,18 @@ describe('IgxPivotGrid - Keyboard navigation #pivotGrid', () => {
95102
});
96103

97104
it('should allow navigating from first to last row headers(Ctrl + ArrowDown)', () => {
98-
const [_firstCell, _secondCell, thirdCell] = fixture.debugElement.queryAll(
99-
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
105+
let allGroups = fixture.debugElement.queryAll(
106+
By.directive(IgxPivotRowDimensionHeaderComponent));
107+
const thirdCell = allGroups.filter(x => x.componentInstance.column.field === 'Date')[0];
100108
UIInteractions.simulateClickAndSelectEvent(thirdCell);
101109
fixture.detectChanges();
102110

103111
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', thirdCell.nativeElement, true, false, false, true);
104112
fixture.detectChanges();
105113

106-
const allCells = fixture.debugElement.queryAll(
107-
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
108-
const lastCell = allCells[allCells.length - 1];
114+
allGroups = fixture.debugElement.queryAll(
115+
By.directive(IgxPivotRowDimensionHeaderComponent));
116+
const lastCell = allGroups[allGroups.length - 1];
109117
GridFunctions.verifyHeaderIsFocused(lastCell.parent);
110118
const activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
111119
expect(activeCells.length).toBe(1);

0 commit comments

Comments
 (0)