From 71bb96bf27ad14cbcc4d2ce94cde272a1716589c Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Fri, 29 May 2026 14:24:07 +0300 Subject: [PATCH 1/2] fix(hierarchical-grid): hide child row editing overlay on scroll --- .../src/hierarchical-grid.component.ts | 37 +++++++++- .../src/hierarchical-grid.spec.ts | 67 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts index fb4c2f4aa85..f9b17f9a2e2 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts @@ -8,7 +8,7 @@ import { IgxColumnComponent, } from 'igniteui-angular/grids/core'; import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service'; import { IgxGridSummaryService } from 'igniteui-angular/grids/core'; import { IgxHierarchicalGridBaseDirective } from './hierarchical-grid-base.directive'; -import { takeUntil } from 'rxjs/operators'; +import { first, takeUntil } from 'rxjs/operators'; import { CellType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from 'igniteui-angular/grids/core'; import { IgxRowIslandAPIService } from './row-island-api.service'; import { IgxGridCRUDService } from 'igniteui-angular/grids/core'; @@ -1115,6 +1115,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti /** @hidden @internal **/ public onContainerScroll() { this.hideOverlays(); + this.updateChildRowEditingOverlayStateOnScroll(); } /** @@ -1137,6 +1138,15 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti return this.gridAPI.getChildGrids(inDeph); } + /** @hidden @internal **/ + protected override verticalScrollHandler(event) { + super.verticalScrollHandler(event); + + this.zone.onStable.pipe(first()).subscribe(() => { + this.updateChildRowEditingOverlayStateOnScroll(); + }); + } + protected override generateDataFields(data: any[]): string[] { return super.generateDataFields(data).filter((field) => { const layoutsList = this.parentIsland ? this.parentIsland.children : this.childLayoutList; @@ -1295,4 +1305,29 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti childEntities: childEntities } } + + private updateChildRowEditingOverlayStateOnScroll() { + const visibleArea = this.tbodyContainer.nativeElement.getBoundingClientRect(); + const childGrids = this.gridAPI.getChildGrids(true) as IgxHierarchicalGridComponent[]; + + childGrids.forEach((grid) => { + const row = grid.crudService.rowInEditMode; + + if (!grid.rowEditable || !grid.rowEditingOverlay || grid.rowEditingOverlay.collapsed) { + return; + } + + if (!row || !this.isElementInVisibleArea(row.nativeElement, visibleArea)) { + grid.toggleRowEditingOverlay(false); + } else { + grid.toggleRowEditingOverlay(true); + grid.repositionRowEditingOverlay(row); + } + }); + } + + private isElementInVisibleArea(element: HTMLElement, visibleArea: DOMRect) { + const rect = element.getBoundingClientRect(); + return rect.bottom > visibleArea.top && rect.top < visibleArea.bottom; + } } diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.spec.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.spec.ts index a2ac5d7c87f..a81ce7c141d 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.spec.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.spec.ts @@ -783,6 +783,73 @@ describe('Basic IgxHierarchicalGrid #hGrid', () => { expect(childGrids[1].height).toBe('200px'); }); + it('should hide child row editing overlay when parent scroll moves child row out of view', () => { + hierarchicalGrid.getRowByIndex(0).expanded = true; + fixture.detectChanges(); + + const childGrid = hierarchicalGrid.gridAPI.getChildGrids()[0] as IgxHierarchicalGridComponent; + childGrid.primaryKey = 'ID'; + childGrid.rowEditable = true; + fixture.detectChanges(); + + const row = childGrid.gridAPI.get_row_by_index(0); + spyOnProperty(childGrid.crudService, 'rowInEditMode', 'get').and.returnValue(row); + spyOnProperty(childGrid.rowEditingOverlay, 'collapsed', 'get').and.returnValue(false); + childGrid.rowEditingOverlay.element.style.display = 'block'; + + spyOn((hierarchicalGrid as any).tbodyContainer.nativeElement, 'getBoundingClientRect').and.returnValue({ + top: 0, + bottom: 200 + } as DOMRect); + let childRowRect = { + top: 40, + bottom: 80 + } as DOMRect; + spyOn(row.nativeElement, 'getBoundingClientRect').and.callFake(() => childRowRect); + const repositionOverlaySpy = spyOn(childGrid, 'repositionRowEditingOverlay'); + const toggleOverlaySpy = spyOn(childGrid, 'toggleRowEditingOverlay').and.callThrough(); + + const scroll = hierarchicalGrid.verticalScrollContainer.getScroll(); + scroll.scrollTop = 10; + (hierarchicalGrid as any).verticalScrollHandler({ target: scroll }); + (hierarchicalGrid as any).zone.onStable.emit(null); + fixture.detectChanges(); + + expect(repositionOverlaySpy).toHaveBeenCalledWith(row); + expect(toggleOverlaySpy).not.toHaveBeenCalledWith(false); + expect(childGrid.rowEditingOverlay.element.style.display).not.toBe('none'); + + repositionOverlaySpy.calls.reset(); + toggleOverlaySpy.calls.reset(); + childRowRect = { + top: -80, + bottom: -40 + } as DOMRect; + scroll.scrollTop = 1000; + (hierarchicalGrid as any).verticalScrollHandler({ target: scroll }); + (hierarchicalGrid as any).zone.onStable.emit(null); + fixture.detectChanges(); + + expect(repositionOverlaySpy).not.toHaveBeenCalled(); + expect(toggleOverlaySpy).toHaveBeenCalledWith(false); + expect(childGrid.rowEditingOverlay.element.style.display).toBe('none'); + + repositionOverlaySpy.calls.reset(); + toggleOverlaySpy.calls.reset(); + childRowRect = { + top: 40, + bottom: 80 + } as DOMRect; + scroll.scrollTop = 10; + (hierarchicalGrid as any).verticalScrollHandler({ target: scroll }); + (hierarchicalGrid as any).zone.onStable.emit(null); + fixture.detectChanges(); + + expect(toggleOverlaySpy).toHaveBeenCalledWith(true); + expect(repositionOverlaySpy).toHaveBeenCalledWith(row); + expect(childGrid.rowEditingOverlay.element.style.display).not.toBe('none'); + }); + it('Should apply runtime option changes to all related child grids (both existing and not yet initialized).', () => { const row = hierarchicalGrid.gridAPI.get_row_by_index(0) as IgxHierarchicalRowComponent; UIInteractions.simulateClickAndSelectEvent(row.expander); From 95904c854affbe9c9942affc15ad153c3fcf93cc Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Fri, 29 May 2026 16:24:19 +0300 Subject: [PATCH 2/2] refactor(hierarchical-grid): defer edited row lookup after overlay guards --- .../hierarchical-grid/src/hierarchical-grid.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts index f9b17f9a2e2..ac56db7b381 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts @@ -1311,12 +1311,12 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti const childGrids = this.gridAPI.getChildGrids(true) as IgxHierarchicalGridComponent[]; childGrids.forEach((grid) => { - const row = grid.crudService.rowInEditMode; - if (!grid.rowEditable || !grid.rowEditingOverlay || grid.rowEditingOverlay.collapsed) { return; } + const row = grid.crudService.rowInEditMode; + if (!row || !this.isElementInVisibleArea(row.nativeElement, visibleArea)) { grid.toggleRowEditingOverlay(false); } else {