Skip to content

Commit 2ded560

Browse files
committed
feat(overlay): remove outlet from container position strategy
1 parent 66c36b1 commit 2ded560

File tree

21 files changed

+233
-128
lines changed

21 files changed

+233
-128
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ All notable changes for each version of this project will be documented in this
1010
- The overlay service now integrates the HTML Popover API and prefers rendering content in place / in the top layer, significantly reducing the need for outlet containers. When configured, `OverlaySettings.outlet` is still honored, and overlays may also fall back to being appended to `document.body` when required.
1111

1212
- **Deprecation** - The `outlet` property in `OverlaySettings`, `IgxOverlayOutletDirective`, and `igxToggleOutlet` input on `IgxToggleActionDirective` are deprecated and will be removed in a future version. They remain functional in this release for backward compatibility, but new code should rely on the default in-place / top-layer rendering behavior instead of custom outlet containers.
13+
14+
- `ContainerPositionStrategy` - The `ContainerPositionStrategy` now uses the `target` property from `OverlaySettings` (when set to an `HTMLElement`) as the container in which the overlay is rendered. This replaces the previous reliance on the deprecated `outlet` property and internal DOM traversal. The overlay wrapper is sized and positioned to match the target container's bounds and automatically updates on resize via `ResizeObserver`.
15+
16+
- `IgxOverlayService.createAbsoluteOverlaySettings` - Added a new overload accepting `container?: HTMLElement` as the second parameter. When a container element is provided, a `ContainerPositionStrategy` is used and the `target` in the returned overlay settings is set to the container element. The previous overload accepting `outlet?: IgxOverlayOutletDirective | ElementRef` is still supported but deprecated.
17+
18+
- `IgxNotificationsDirective`, `IgxSnackbarComponent`, `IgxToastComponent`
19+
- Added a new `container` input property of type `HTMLElement`. When set, overlay content is rendered inside the given container using `ContainerPositionStrategy`. The deprecated `outlet` property now points users to `container` as its replacement.
1320
## 21.1.0
1421

1522
### New Features

projects/igniteui-angular/core/src/services/overlay/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ this.overlay.show(component, overlaySettings);
7373

7474
| Name | Type | Description |
7575
| :--- | :--- | :---------- |
76-
| target | Point | HTMLElement | Attaching target for the component to show |
76+
| target | Point | HTMLElement | Attaching target for the component to show. When set to an `HTMLElement` in combination with `ContainerPositionStrategy`, the element serves as the container in which the overlay is rendered. |
7777
| positionStrategy | IPositionStrategy | Position strategy to use with this settings |
7878
| scrollStrategy | IScrollStrategy | Scroll strategy to use with this settings |
7979
| modal | boolean | Set if the overlay should be in modal mode |
8080
| closeOnOutsideClick | boolean | Set if the overlay should closed on outside click |
81-
| outlet | IgxOverlayOutletDirective or ElementRef | **Deprecated.** Still supported and used by the overlay service when provided but will be removed in a future version. Avoid using this property in new code and prefer the default in-place rendering with the HTML Popover API. |
81+
| outlet | IgxOverlayOutletDirective or ElementRef | **Deprecated.** Use `target` with an `HTMLElement` and `ContainerPositionStrategy` instead. Still supported for backward compatibility but will be removed in a future version. |
8282

8383
###### PositionSettings
8484

@@ -135,7 +135,7 @@ this.overlay.show(component, overlaySettings);
135135
| Name | Description | Parameters |
136136
|-----------------|---------------------------------------------------------------------------------|------------|
137137
|getPointFromPositionsSettings| Calculates the point from which the overlay should start showing |settings |
138-
|createAbsoluteOverlaySettings| Creates overlay settings with a global or container position strategy based on preset position settings and the provided outlet. When an outlet is specified, a container strategy is used and the content is rendered inside that outlet; otherwise, a global strategy is used and the content is rendered in place. |position?, outlet?|
138+
|createAbsoluteOverlaySettings| Creates overlay settings with a global or container position strategy based on preset position settings. Accepts either a deprecated outlet (`IgxOverlayOutletDirective` / `ElementRef`) or an `HTMLElement` container. When a container or outlet is provided, a `ContainerPositionStrategy` is used; otherwise, a `GlobalPositionStrategy` is used. |position?, container?|
139139
|createRelativeOverlaySettings| Creates overlay settings with auto, connected or elastic position strategy based on a preset position settings |target, strategy?, position?|
140140

141141

projects/igniteui-angular/core/src/services/overlay/overlay.spec.ts

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,41 +3414,41 @@ describe('igxOverlay', () => {
34143414
}));
34153415

34163416
// 1.5 GlobalContainer.
3417-
it('Should center the shown component in the outlet.', fakeAsync(() => {
3417+
it('Should center the shown component in the container.', fakeAsync(() => {
34183418
const fixture = TestBed.createComponent(EmptyPageComponent);
34193419

3420-
const outlet = fixture.componentInstance.divElement;
3421-
const outletElement = outlet.nativeElement;
3422-
outletElement.style.width = '800px';
3423-
outletElement.style.height = '600px';
3424-
outletElement.style.position = 'fixed';
3425-
outletElement.style.top = '100px';
3426-
outletElement.style.left = '200px';
3427-
outletElement.style.overflow = 'hidden';
3420+
const container = fixture.componentInstance.divElement;
3421+
const containerElement = container.nativeElement;
3422+
containerElement.style.width = '800px';
3423+
containerElement.style.height = '600px';
3424+
containerElement.style.position = 'fixed';
3425+
containerElement.style.top = '100px';
3426+
containerElement.style.left = '200px';
3427+
containerElement.style.overflow = 'hidden';
34283428

34293429
fixture.detectChanges();
34303430
const overlaySettings: OverlaySettings = {
3431-
outlet,
3431+
target: containerElement,
34323432
positionStrategy: new ContainerPositionStrategy()
34333433
};
34343434

3435-
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, overlaySettings);
3435+
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, fixture.componentInstance.viewContainerRef, overlaySettings);
34363436
fixture.componentInstance.overlay.show(id);
34373437
tick();
34383438

3439-
const overlayElement = outletElement.children[0];
3439+
const overlayElement = containerElement.children[0];
34403440
const overlayElementRect = overlayElement.getBoundingClientRect();
3441-
expect(overlayElementRect.width).toBeCloseTo(800, 1);
3442-
expect(overlayElementRect.height).toBeCloseTo(600, 1);
3441+
expect(overlayElementRect.width).toBeCloseTo(800, 0);
3442+
expect(overlayElementRect.height).toBeCloseTo(600, 0);
34433443

34443444
const wrapperElement = overlayElement.children[0] as HTMLElement;
34453445
const componentElement = wrapperElement.children[0].children[0];
34463446
const componentRect = componentElement.getBoundingClientRect();
3447-
const outletRect = outletElement.getBoundingClientRect();
3447+
const containerRect = containerElement.getBoundingClientRect();
34483448

3449-
// Check component is centered relative to outlet
3450-
const horizontalCenter = Math.abs((componentRect.left + componentRect.width / 2) - (outletRect.left + outletRect.width / 2));
3451-
const verticalCenter = Math.abs((componentRect.top + componentRect.height / 2) - (outletRect.top + outletRect.height / 2));
3449+
// Check component is centered relative to container
3450+
const horizontalCenter = Math.abs((componentRect.left + componentRect.width / 2) - (containerRect.left + containerRect.width / 2));
3451+
const verticalCenter = Math.abs((componentRect.top + componentRect.height / 2) - (containerRect.top + containerRect.height / 2));
34523452
expect(horizontalCenter).toBeLessThan(1);
34533453
expect(verticalCenter).toBeLessThan(1);
34543454

@@ -3458,41 +3458,41 @@ describe('igxOverlay', () => {
34583458
it('Should reposition overlay when outlet is resized with ContainerPositionStrategy.', async() => {
34593459
const fixture = TestBed.createComponent(EmptyPageComponent);
34603460

3461-
const outlet = fixture.componentInstance.divElement;
3462-
const outletElement = outlet.nativeElement;
3463-
outletElement.style.width = '800px';
3464-
outletElement.style.height = '600px';
3465-
outletElement.style.position = 'fixed';
3466-
outletElement.style.top = '100px';
3467-
outletElement.style.left = '200px';
3468-
outletElement.style.overflow = 'hidden';
3461+
const container = fixture.componentInstance.divElement;
3462+
const containerElement = container.nativeElement;
3463+
containerElement.style.width = '800px';
3464+
containerElement.style.height = '600px';
3465+
containerElement.style.position = 'fixed';
3466+
containerElement.style.top = '100px';
3467+
containerElement.style.left = '200px';
3468+
containerElement.style.overflow = 'hidden';
34693469

34703470
fixture.detectChanges();
34713471
const overlaySettings: OverlaySettings = {
3472-
outlet,
3472+
target: containerElement,
34733473
positionStrategy: new ContainerPositionStrategy()
34743474
};
34753475

3476-
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, overlaySettings);
3476+
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, fixture.componentInstance.viewContainerRef, overlaySettings);
34773477
fixture.componentInstance.overlay.show(id);
34783478
fixture.detectChanges();
34793479
await wait(100);
34803480

3481-
let wrapperElement = outletElement.children[0];
3481+
let wrapperElement = containerElement.children[0];
34823482
let wrapperRect = wrapperElement.getBoundingClientRect();
34833483

34843484
// Initial wrapper dimensions should match outlet
34853485
expect(wrapperRect.width).toBeCloseTo(800, 0);
34863486
expect(wrapperRect.height).toBeCloseTo(600, 0);
34873487

34883488
// Resize the outlet
3489-
outletElement.style.width = '1000px';
3490-
outletElement.style.height = '700px';
3489+
containerElement.style.width = '1000px';
3490+
containerElement.style.height = '700px';
34913491
fixture.detectChanges();
34923492
await wait(100);
34933493

34943494
// Wrapper should now match new outlet dimensions
3495-
wrapperElement = outletElement.children[0];
3495+
wrapperElement = containerElement.children[0];
34963496
wrapperRect = wrapperElement.getBoundingClientRect();
34973497
expect(wrapperRect.width).toBeCloseTo(1000, 0);
34983498
expect(wrapperRect.height).toBeCloseTo(700, 0);
@@ -3503,30 +3503,30 @@ describe('igxOverlay', () => {
35033503
it('Should maintain centering when outlet is resized with ContainerPositionStrategy.', async () => {
35043504
const fixture = TestBed.createComponent(EmptyPageComponent);
35053505

3506-
const outlet = fixture.componentInstance.divElement;
3507-
const outletElement = outlet.nativeElement;
3508-
outletElement.style.width = '600px';
3509-
outletElement.style.height = '400px';
3510-
outletElement.style.position = 'fixed';
3511-
outletElement.style.top = '50px';
3512-
outletElement.style.left = '50px';
3513-
outletElement.style.overflow = 'hidden';
3506+
const container = fixture.componentInstance.divElement;
3507+
const containerElement = container.nativeElement;
3508+
containerElement.style.width = '600px';
3509+
containerElement.style.height = '400px';
3510+
containerElement.style.position = 'fixed';
3511+
containerElement.style.top = '50px';
3512+
containerElement.style.left = '50px';
3513+
containerElement.style.overflow = 'hidden';
35143514

35153515
fixture.detectChanges();
35163516
const overlaySettings: OverlaySettings = {
3517-
outlet,
3517+
target: containerElement,
35183518
positionStrategy: new ContainerPositionStrategy()
35193519
};
35203520

3521-
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, overlaySettings);
3521+
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, fixture.componentInstance.viewContainerRef, overlaySettings);
35223522
fixture.componentInstance.overlay.show(id);
35233523
fixture.detectChanges();
35243524
await wait(100);
35253525

3526-
const wrapperElement = outletElement.children[0];
3526+
const wrapperElement = containerElement.children[0];
35273527
let componentElement = wrapperElement.children[0].children[0];
35283528
let componentRect = componentElement.getBoundingClientRect();
3529-
let outletRect = outletElement.getBoundingClientRect();
3529+
let outletRect = containerElement.getBoundingClientRect();
35303530

35313531
// Verify initial centering
35323532
let horizontalCenter = Math.abs((componentRect.left + componentRect.width / 2) - (outletRect.left + outletRect.width / 2));
@@ -3535,49 +3535,49 @@ describe('igxOverlay', () => {
35353535
expect(verticalCenter).toBeLessThan(2);
35363536

35373537
// Resize the outlet
3538-
outletElement.style.width = '900px';
3539-
outletElement.style.height = '600px';
3538+
containerElement.style.width = '900px';
3539+
containerElement.style.height = '600px';
35403540

35413541
// Wait for ResizeObserver to fire for size change
35423542
await wait(100);
35433543

35443544
// Re-check centering with new dimensions
35453545
componentElement = wrapperElement.children[0].children[0];
35463546
componentRect = componentElement.getBoundingClientRect();
3547-
outletRect = outletElement.getBoundingClientRect();
3547+
outletRect = containerElement.getBoundingClientRect();
35483548

35493549
horizontalCenter = Math.abs((componentRect.left + componentRect.width / 2) - (outletRect.left + outletRect.width / 2));
35503550
verticalCenter = Math.abs((componentRect.top + componentRect.height / 2) - (outletRect.top + outletRect.height / 2));
3551-
expect(horizontalCenter).toBeLessThan(2);
3552-
expect(verticalCenter).toBeLessThan(2);
3551+
expect(horizontalCenter).toBeLessThan(1);
3552+
expect(verticalCenter).toBeLessThan(1);
35533553

35543554
fixture.componentInstance.overlay.detachAll();
35553555
});
35563556

35573557
it('Should dispose IntersectionObserver when overlay is detached.', async () => {
35583558
const fixture = TestBed.createComponent(EmptyPageComponent);
35593559

3560-
const outlet = fixture.componentInstance.divElement;
3561-
const outletElement = outlet.nativeElement;
3562-
outletElement.style.width = '800px';
3563-
outletElement.style.height = '600px';
3564-
outletElement.style.position = 'fixed';
3565-
outletElement.style.top = '100px';
3566-
outletElement.style.left = '200px';
3560+
const container = fixture.componentInstance.divElement;
3561+
const containerElement = container.nativeElement;
3562+
containerElement.style.width = '800px';
3563+
containerElement.style.height = '600px';
3564+
containerElement.style.position = 'fixed';
3565+
containerElement.style.top = '100px';
3566+
containerElement.style.left = '200px';
35673567

35683568
fixture.detectChanges();
35693569
const positionStrategy = new ContainerPositionStrategy();
35703570
spyOn(positionStrategy, 'dispose').and.callThrough();
35713571
const overlaySettings: OverlaySettings = {
3572-
outlet,
3572+
target: containerElement,
35733573
positionStrategy
35743574
};
35753575

35763576
const id = fixture.componentInstance.overlay.attach(SimpleDynamicComponent, overlaySettings);
35773577
fixture.componentInstance.overlay.show(id);
35783578
await wait(100);
35793579

3580-
const wrapperElement = outletElement.children[0];
3580+
const wrapperElement = containerElement.children[0];
35813581
const wrapperRect = wrapperElement.getBoundingClientRect();
35823582

35833583
// Initial dimensions
@@ -4851,6 +4851,7 @@ describe('igxOverlay', () => {
48514851
standalone: true
48524852
})
48534853
export class SimpleDynamicComponent {
4854+
public viewContainerRef = inject(ViewContainerRef);
48544855
@HostBinding('style.display')
48554856
public hostDisplay = 'block';
48564857
@HostBinding('style.width')

0 commit comments

Comments
 (0)