Skip to content

Commit 1ec9838

Browse files
fix(hierarchical-grid): keep child row edit overlay inside grid
1 parent 95904c8 commit 1ec9838

3 files changed

Lines changed: 89 additions & 88 deletions

File tree

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,64 @@ export class RowEditPositionStrategy extends ConnectedPositioningStrategy {
6363

6464
super.position(contentElement, { width: targetElement.clientWidth, height: targetElement.clientHeight },
6565
document, initialCall, targetElement);
66+
67+
this.updateContentClip(contentElement);
68+
}
69+
70+
private updateContentClip(contentElement: HTMLElement): void {
71+
const container = this.settings.container;
72+
73+
if (!container) {
74+
return;
75+
}
76+
77+
const clippingRect = this.getClippingRect(container);
78+
const contentRect = contentElement.getBoundingClientRect();
79+
80+
const top = Math.round(Math.max(clippingRect.top - contentRect.top, 0));
81+
const right = Math.round(Math.max(contentRect.right - clippingRect.right, 0));
82+
const bottom = Math.round(Math.max(contentRect.bottom - clippingRect.bottom, 0));
83+
const left = Math.round(Math.max(clippingRect.left - contentRect.left, 0));
84+
85+
const fullyClipped = top >= contentRect.height || bottom >= contentRect.height ||
86+
left >= contentRect.width || right >= contentRect.width;
87+
88+
contentElement.style.clipPath = fullyClipped ? 'inset(100%)' :
89+
(top || right || bottom || left ? `inset(${top}px ${right}px ${bottom}px ${left}px)` : '');
90+
91+
contentElement.style.pointerEvents = fullyClipped ? 'none' : '';
92+
contentElement.style.visibility = '';
93+
}
94+
95+
private getClippingRect(element: HTMLElement): Pick<DOMRect, 'top' | 'right' | 'bottom' | 'left'> {
96+
const document = element.ownerDocument;
97+
const rect = element.getBoundingClientRect();
98+
const clippingRect = { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left };
99+
100+
let parent = element.parentElement;
101+
102+
while (parent && parent !== document.body && parent !== document.documentElement) {
103+
const style = getComputedStyle(parent);
104+
const overflow = `${style.overflow}${style.overflowX}${style.overflowY}`;
105+
106+
if (/(auto|scroll|hidden|clip)/.test(overflow)) {
107+
const parentRect = parent.getBoundingClientRect();
108+
109+
clippingRect.top = Math.max(clippingRect.top, parentRect.top);
110+
clippingRect.right = Math.min(clippingRect.right, parentRect.right);
111+
clippingRect.bottom = Math.min(clippingRect.bottom, parentRect.bottom);
112+
clippingRect.left = Math.max(clippingRect.left, parentRect.left);
113+
}
114+
115+
parent = parent.parentElement;
116+
}
117+
118+
return {
119+
top: Math.max(clippingRect.top, 0),
120+
right: Math.min(clippingRect.right, document.documentElement.clientWidth),
121+
bottom: Math.min(clippingRect.bottom, document.documentElement.clientHeight),
122+
left: Math.max(clippingRect.left, 0)
123+
};
66124
}
67125

68126
/**

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

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IgxColumnComponent, } from 'igniteui-angular/grids/core';
88
import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service';
99
import { IgxGridSummaryService } from 'igniteui-angular/grids/core';
1010
import { IgxHierarchicalGridBaseDirective } from './hierarchical-grid-base.directive';
11-
import { first, takeUntil } from 'rxjs/operators';
11+
import { takeUntil } from 'rxjs/operators';
1212
import { CellType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from 'igniteui-angular/grids/core';
1313
import { IgxRowIslandAPIService } from './row-island-api.service';
1414
import { IgxGridCRUDService } from 'igniteui-angular/grids/core';
@@ -1115,7 +1115,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
11151115
/** @hidden @internal **/
11161116
public onContainerScroll() {
11171117
this.hideOverlays();
1118-
this.updateChildRowEditingOverlayStateOnScroll();
11191118
}
11201119

11211120
/**
@@ -1138,15 +1137,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
11381137
return this.gridAPI.getChildGrids(inDeph);
11391138
}
11401139

1141-
/** @hidden @internal **/
1142-
protected override verticalScrollHandler(event) {
1143-
super.verticalScrollHandler(event);
1144-
1145-
this.zone.onStable.pipe(first()).subscribe(() => {
1146-
this.updateChildRowEditingOverlayStateOnScroll();
1147-
});
1148-
}
1149-
11501140
protected override generateDataFields(data: any[]): string[] {
11511141
return super.generateDataFields(data).filter((field) => {
11521142
const layoutsList = this.parentIsland ? this.parentIsland.children : this.childLayoutList;
@@ -1305,29 +1295,4 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
13051295
childEntities: childEntities
13061296
}
13071297
}
1308-
1309-
private updateChildRowEditingOverlayStateOnScroll() {
1310-
const visibleArea = this.tbodyContainer.nativeElement.getBoundingClientRect();
1311-
const childGrids = this.gridAPI.getChildGrids(true) as IgxHierarchicalGridComponent[];
1312-
1313-
childGrids.forEach((grid) => {
1314-
if (!grid.rowEditable || !grid.rowEditingOverlay || grid.rowEditingOverlay.collapsed) {
1315-
return;
1316-
}
1317-
1318-
const row = grid.crudService.rowInEditMode;
1319-
1320-
if (!row || !this.isElementInVisibleArea(row.nativeElement, visibleArea)) {
1321-
grid.toggleRowEditingOverlay(false);
1322-
} else {
1323-
grid.toggleRowEditingOverlay(true);
1324-
grid.repositionRowEditingOverlay(row);
1325-
}
1326-
});
1327-
}
1328-
1329-
private isElementInVisibleArea(element: HTMLElement, visibleArea: DOMRect) {
1330-
const rect = element.getBoundingClientRect();
1331-
return rect.bottom > visibleArea.top && rect.top < visibleArea.bottom;
1332-
}
13331298
}

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

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -794,60 +794,38 @@ describe('Basic IgxHierarchicalGrid #hGrid', () => {
794794

795795
const row = childGrid.gridAPI.get_row_by_index(0);
796796
spyOnProperty(childGrid.crudService, 'rowInEditMode', 'get').and.returnValue(row);
797-
spyOnProperty(childGrid.rowEditingOverlay, 'collapsed', 'get').and.returnValue(false);
798-
childGrid.rowEditingOverlay.element.style.display = 'block';
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;
799805

800-
spyOn((hierarchicalGrid as any).tbodyContainer.nativeElement, 'getBoundingClientRect').and.returnValue({
801-
top: 0,
802-
bottom: 200
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
803816
} as DOMRect);
804-
let childRowRect = {
805-
top: 40,
806-
bottom: 80
807-
} as DOMRect;
808-
spyOn(row.nativeElement, 'getBoundingClientRect').and.callFake(() => childRowRect);
809-
const repositionOverlaySpy = spyOn(childGrid, 'repositionRowEditingOverlay');
810-
const toggleOverlaySpy = spyOn(childGrid, 'toggleRowEditingOverlay').and.callThrough();
811-
812-
const scroll = hierarchicalGrid.verticalScrollContainer.getScroll();
813-
scroll.scrollTop = 10;
814-
(hierarchicalGrid as any).verticalScrollHandler({ target: scroll });
815-
(hierarchicalGrid as any).zone.onStable.emit(null);
816-
fixture.detectChanges();
817-
818-
expect(repositionOverlaySpy).toHaveBeenCalledWith(row);
819-
expect(toggleOverlaySpy).not.toHaveBeenCalledWith(false);
820-
expect(childGrid.rowEditingOverlay.element.style.display).not.toBe('none');
821-
822-
repositionOverlaySpy.calls.reset();
823-
toggleOverlaySpy.calls.reset();
824-
childRowRect = {
825-
top: -80,
826-
bottom: -40
827-
} as DOMRect;
828-
scroll.scrollTop = 1000;
829-
(hierarchicalGrid as any).verticalScrollHandler({ target: scroll });
830-
(hierarchicalGrid as any).zone.onStable.emit(null);
831-
fixture.detectChanges();
832-
833-
expect(repositionOverlaySpy).not.toHaveBeenCalled();
834-
expect(toggleOverlaySpy).toHaveBeenCalledWith(false);
835-
expect(childGrid.rowEditingOverlay.element.style.display).toBe('none');
836-
837-
repositionOverlaySpy.calls.reset();
838-
toggleOverlaySpy.calls.reset();
839-
childRowRect = {
840-
top: 40,
841-
bottom: 80
842-
} as DOMRect;
843-
scroll.scrollTop = 10;
844-
(hierarchicalGrid as any).verticalScrollHandler({ target: scroll });
845-
(hierarchicalGrid as any).zone.onStable.emit(null);
846-
fixture.detectChanges();
847-
848-
expect(toggleOverlaySpy).toHaveBeenCalledWith(true);
849-
expect(repositionOverlaySpy).toHaveBeenCalledWith(row);
850-
expect(childGrid.rowEditingOverlay.element.style.display).not.toBe('none');
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');
851829
});
852830

853831
it('Should apply runtime option changes to all related child grids (both existing and not yet initialized).', () => {

0 commit comments

Comments
 (0)