diff --git a/projects/igniteui-angular/core/src/services/overlay/overlay.spec.ts b/projects/igniteui-angular/core/src/services/overlay/overlay.spec.ts index a408033166e..f5130815fd6 100644 --- a/projects/igniteui-angular/core/src/services/overlay/overlay.spec.ts +++ b/projects/igniteui-angular/core/src/services/overlay/overlay.spec.ts @@ -1240,6 +1240,44 @@ describe('igxOverlay', () => { // expect(mockElement.style.height).toBe('100px'); // }); + it('#16988 - should not reposition overlay when detached', fakeAsync(() => { + const fixture = TestBed.createComponent(EmptyPageComponent); + fixture.debugElement.nativeElement.appendChild(outlet); + outlet.style.width = '800px'; + outlet.style.height = '600px'; + outlet.style.position = 'fixed'; + outlet.style.top = '100px'; + outlet.style.left = '200px'; + outlet.style.overflow = 'hidden'; + fixture.detectChanges(); + + const positionStrategy = new ContainerPositionStrategy(); + const overlaySettings: OverlaySettings = { + outlet, + positionStrategy + }; + + const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, overlaySettings); + fixture.componentInstance.overlay.show(id); + tick(); + + // Capture the content element while it is still in the DOM + const contentElement = fixture.nativeElement.parentElement.getElementsByClassName(CLASS_OVERLAY_CONTENT_MODAL)[0] as HTMLElement; + + // Detaching the overlay calls dispose() on the position strategy which disconnects + // the IntersectionObserver and sets it to null. However, if a callback was already + // queued by the observer before disconnect, it will still fire and call updatePosition + // with the now-detached contentElement (parentElement === null). + fixture.componentInstance.overlay.detach(id); + tick(); + + // Simulate the stale IntersectionObserver callback firing after detach. + // Should not throw even though contentElement is no longer attached to the DOM. + expect(() => (positionStrategy as any).updatePosition(contentElement)).not.toThrow(); + + fixture.componentInstance.overlay.detachAll(); + })); + it('should close overlay on outside click when target is point, #8297', fakeAsync(() => { const fixture = TestBed.createComponent(EmptyPageComponent); const overlay = fixture.componentInstance.overlay; diff --git a/projects/igniteui-angular/core/src/services/overlay/position/container-position-strategy.ts b/projects/igniteui-angular/core/src/services/overlay/position/container-position-strategy.ts index b686471c351..da0c6f566a8 100644 --- a/projects/igniteui-angular/core/src/services/overlay/position/container-position-strategy.ts +++ b/projects/igniteui-angular/core/src/services/overlay/position/container-position-strategy.ts @@ -42,11 +42,15 @@ export class ContainerPositionStrategy extends GlobalPositionStrategy { } private updatePosition(contentElement: HTMLElement): void { + const outletElement = contentElement.parentElement?.parentElement; + if (!outletElement) + return; + // TODO: consider using new anchor() CSS function when it becomes more widely supported: https://caniuse.com/mdn-css_properties_anchor - const parentRect = contentElement.parentElement.parentElement.getBoundingClientRect(); - contentElement.parentElement.style.width = `${parentRect.width}px`; - contentElement.parentElement.style.height = `${parentRect.height}px`; - contentElement.parentElement.style.top = `${parentRect.top}px`; - contentElement.parentElement.style.left = `${parentRect.left}px`; + const outletRect = outletElement.getBoundingClientRect(); + contentElement.parentElement.style.width = `${outletRect.width}px`; + contentElement.parentElement.style.height = `${outletRect.height}px`; + contentElement.parentElement.style.top = `${outletRect.top}px`; + contentElement.parentElement.style.left = `${outletRect.left}px`; } }