Skip to content

Commit 233ed40

Browse files
authored
Merge branch 'master' into kdinev/unused-vars-lint
2 parents 971af3f + 7608b49 commit 233ed40

6 files changed

Lines changed: 158 additions & 5 deletions

File tree

projects/igniteui-angular/grids/core/src/api.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,8 @@ export class GridBaseAPIService<T extends GridType> implements GridServiceType {
320320
const transaction: Transaction = { id: rowId, type: TransactionType.ADD, newValue: rowData };
321321
grid.transactions.add(transaction);
322322
} else {
323-
grid.data.push(rowData);
324-
grid.data = cloneArray(grid.data);
323+
(grid.data ?? (grid.data = [])).push(rowData);
324+
grid.summaryService.clearSummaryCache();
325325
}
326326
grid.validation.markAsTouched(rowId);
327327
grid.validation.update(rowId, rowData);
@@ -336,8 +336,8 @@ export class GridBaseAPIService<T extends GridType> implements GridServiceType {
336336
const transaction: Transaction = { id: rowID, type: TransactionType.DELETE, newValue: null };
337337
grid.transactions.add(transaction, grid.data[index]);
338338
} else {
339-
grid.data.splice(index, 1);
340-
grid.data = cloneArray(grid.data);
339+
(grid.data ?? (grid.data = [])).splice(index, 1);
340+
grid.summaryService.clearSummaryCache();
341341
}
342342
} else {
343343
const state: State = grid.transactions.getState(rowID);

projects/igniteui-angular/grids/core/src/common/pipes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,10 @@ export class IgxGridTransactionPipe implements PipeTransform {
248248
this.grid.dataCloneStrategy);
249249
return result;
250250
}
251-
return collection;
251+
// Return a shallow copy so downstream pipes and igxGridForOf always
252+
// receive a new array reference when pipeTrigger changes, regardless
253+
// of whether the source array was mutated in place.
254+
return cloneArray(collection);
252255
}
253256
}
254257

projects/igniteui-angular/grids/core/src/grid.common.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class IgxGridBodyDirective { }
1818
*/
1919
export interface RowEditPositionSettings extends PositionSettings {
2020
container?: HTMLElement;
21+
clipToVisibleArea?: boolean;
2122
}
2223

2324
/**
@@ -63,6 +64,73 @@ export class RowEditPositionStrategy extends ConnectedPositioningStrategy {
6364

6465
super.position(contentElement, { width: targetElement.clientWidth, height: targetElement.clientHeight },
6566
document, initialCall, targetElement);
67+
68+
if (this.settings.clipToVisibleArea) {
69+
// After positioning in the top layer, keep the overlay clipped to the visible grid body.
70+
this.updateContentClip(contentElement);
71+
}
72+
}
73+
74+
private updateContentClip(contentElement: HTMLElement): void {
75+
const container = this.settings.container;
76+
77+
if (!container) {
78+
return;
79+
}
80+
81+
const clippingRect = this.getClippingRect(container);
82+
const contentRect = contentElement.getBoundingClientRect();
83+
84+
// Convert the clipped overflow on each side to CSS inset values.
85+
const top = Math.round(Math.max(clippingRect.top - contentRect.top, 0));
86+
const right = Math.round(Math.max(contentRect.right - clippingRect.right, 0));
87+
const bottom = Math.round(Math.max(contentRect.bottom - clippingRect.bottom, 0));
88+
const left = Math.round(Math.max(clippingRect.left - contentRect.left, 0));
89+
90+
// When the overlay is fully outside the clipping rect, hide it and block its action buttons.
91+
const fullyClipped = top >= contentRect.height || bottom >= contentRect.height ||
92+
left >= contentRect.width || right >= contentRect.width;
93+
94+
// Row-edit overlays are rendered in the top layer, so clip the content explicitly to the grid's visible area.
95+
contentElement.style.clipPath = fullyClipped ? 'inset(100%)' :
96+
(top || right || bottom || left ? `inset(${top}px ${right}px ${bottom}px ${left}px)` : '');
97+
98+
contentElement.style.pointerEvents = fullyClipped ? 'none' : '';
99+
contentElement.style.visibility = fullyClipped ? 'hidden' : '';
100+
}
101+
102+
private getClippingRect(element: HTMLElement): Pick<DOMRect, 'top' | 'right' | 'bottom' | 'left'> {
103+
const document = element.ownerDocument;
104+
const gridBody = element.closest('[igxgridbody]') as HTMLElement || element;
105+
const rect = gridBody.getBoundingClientRect();
106+
// Start with the current grid body, then narrow it by parent grid bodies.
107+
const clippingRect = { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left };
108+
109+
let parent = gridBody.parentElement?.closest('[igxgridbody]') as HTMLElement;
110+
111+
// Intersect with parent grid bodies so nested grids respect their parent scroll bounds.
112+
while (parent) {
113+
const parentRect = parent.getBoundingClientRect();
114+
115+
clippingRect.top = Math.max(clippingRect.top, parentRect.top);
116+
clippingRect.right = Math.min(clippingRect.right, parentRect.right);
117+
clippingRect.bottom = Math.min(clippingRect.bottom, parentRect.bottom);
118+
clippingRect.left = Math.max(clippingRect.left, parentRect.left);
119+
120+
if (clippingRect.top >= clippingRect.bottom || clippingRect.left >= clippingRect.right) {
121+
break;
122+
}
123+
124+
parent = parent.parentElement?.closest('[igxgridbody]') as HTMLElement;
125+
}
126+
127+
// Keep the clipping area inside the viewport because popover content is viewport-positioned.
128+
return {
129+
top: Math.max(clippingRect.top, 0),
130+
right: Math.min(clippingRect.right, document.documentElement.clientWidth),
131+
bottom: Math.min(clippingRect.bottom, document.documentElement.clientHeight),
132+
left: Math.max(clippingRect.left, 0)
133+
};
66134
}
67135

68136
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8143,6 +8143,8 @@ export abstract class IgxGridBaseDirective implements GridType,
81438143
settings = overlay.settings;
81448144
}
81458145
this.rowEditPositioningStrategy.settings.container = this.tbody.nativeElement;
8146+
this.rowEditPositioningStrategy.settings.clipToVisibleArea =
8147+
this.type === 'hierarchical' && (this as GridType).rootGrid !== this;
81468148
const pinned = this._pinnedRecordIDs.indexOf(rowID) !== -1;
81478149
const targetRow = !pinned ?
81488150
this.gridAPI.get_row_by_key(rowID) as IgxRowDirective

projects/igniteui-angular/grids/grid/src/grid.crud.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,41 @@ describe('IgxGrid - CRUD operations #grid', () => {
4343
expect(grid.rowList.length).toEqual(expectedLength);
4444
});
4545

46+
it('should keep the external data array reference in sync after repeated addRow() calls', () => {
47+
const originalRef = fix.componentInstance.data;
48+
49+
for (let i = 2; i <= 6; i++) {
50+
grid.addRow({ index: i, value: i * 10 });
51+
}
52+
fix.detectChanges();
53+
54+
expect(grid.data).toBe(originalRef);
55+
expect(data.length).toEqual(6);
56+
expect(data.find(r => r.index === 6)).toBeDefined();
57+
expect(data).toBe(grid.data);
58+
});
59+
60+
it('should keep the external data array reference in sync after repeated deleteRow() calls', () => {
61+
const originalRef = fix.componentInstance.data;
62+
63+
for (let i = 2; i <= 5; i++) {
64+
grid.addRow({ index: i, value: i * 10 });
65+
}
66+
fix.detectChanges();
67+
68+
expect(data.length).toEqual(5);
69+
70+
grid.deleteRow(1);
71+
grid.deleteRow(3);
72+
fix.detectChanges();
73+
74+
expect(grid.data).toBe(originalRef);
75+
expect(data.length).toEqual(3);
76+
expect(data.find(r => r.index === 1)).toBeUndefined();
77+
expect(data.find(r => r.index === 3)).toBeUndefined();
78+
expect(data).toBe(grid.data);
79+
});
80+
4681
// No longer supported - array mutations are not detected automatically, need ref change.
4782
xit('should support adding rows by manipulating the `data` @Input of the grid', () => {
4883
// Add to the data array without changing the reference

projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,51 @@ describe('Basic IgxHierarchicalGrid #hGrid', () => {
783783
expect(childGrids[1].height).toBe('200px');
784784
});
785785

786+
it('should hide child row editing overlay when parent scroll moves child row out of view', () => {
787+
hierarchicalGrid.getRowByIndex(0).expanded = true;
788+
fixture.detectChanges();
789+
790+
const childGrid = hierarchicalGrid.gridAPI.getChildGrids()[0] as IgxHierarchicalGridComponent;
791+
childGrid.primaryKey = 'ID';
792+
childGrid.rowEditable = true;
793+
fixture.detectChanges();
794+
795+
const row = childGrid.gridAPI.get_row_by_index(0);
796+
spyOnProperty(childGrid.crudService, 'rowInEditMode', 'get').and.returnValue(row);
797+
childGrid.openRowOverlay(row.key);
798+
fixture.detectChanges();
799+
800+
expect(childGrid.rowEditingOverlay.collapsed).toBeFalse();
801+
802+
const parentTbody = hierarchicalGrid.tbody.nativeElement.parentElement;
803+
const childTbody = childGrid.tbody.nativeElement.parentElement;
804+
const overlayContent = childGrid.rowEditingOverlay.element.parentElement;
805+
806+
parentTbody.style.overflow = 'hidden';
807+
childTbody.style.overflow = 'hidden';
808+
spyOn(parentTbody, 'getBoundingClientRect').and.returnValue({
809+
top: 0, right: 500, bottom: 200, left: 0
810+
} as DOMRect);
811+
spyOn(childTbody, 'getBoundingClientRect').and.returnValue({
812+
top: 0, right: 500, bottom: 200, left: 0
813+
} as DOMRect);
814+
spyOn(childGrid.tbody.nativeElement, 'getBoundingClientRect').and.returnValue({
815+
top: -100, right: 500, bottom: 500, left: 0
816+
} as DOMRect);
817+
spyOn(row.nativeElement, 'getBoundingClientRect').and.returnValue({
818+
top: -120, right: 500, bottom: -80, left: 0
819+
} as DOMRect);
820+
spyOn(overlayContent, 'getBoundingClientRect').and.returnValue({
821+
top: -80, right: 500, bottom: -32, left: 0, width: 500, height: 48
822+
} as DOMRect);
823+
824+
childGrid.rowEditingOverlay.reposition();
825+
fixture.detectChanges();
826+
827+
expect(overlayContent.style.clipPath).toBe('inset(100%)');
828+
expect(overlayContent.style.pointerEvents).toBe('none');
829+
});
830+
786831
it('Should apply runtime option changes to all related child grids (both existing and not yet initialized).', () => {
787832
const row = hierarchicalGrid.gridAPI.get_row_by_index(0) as IgxHierarchicalRowComponent;
788833
UIInteractions.simulateClickAndSelectEvent(row.expander);

0 commit comments

Comments
 (0)