Skip to content

Commit a3b985f

Browse files
authored
refactor(cdk/overlay): enhance popover insertion control with new inputs (angular#32362)
* refactor(cdk/overlay): enhance popover insertion control with new inputs This commit introduces two new inputs to the CDK overlay, providing more granular control over how popovers are inserted into the DOM: - : Allows specifying a custom element to be used as the host for the popover. The popover will be inserted after this element in the DOM. - : A boolean that, when true, attaches the popover as a child of the popover host, rather than as a sibling. * refactor(cdk/overlay): simplify popover insertion logic This commit refactors the popover insertion logic in the CDK overlay by removing and in favor of a more flexible API. This change simplifies the API and provides more granular control over where popovers are inserted into the DOM. * refactor(cdk/overlay): add overlay changes * refactor(cdk/overlay): streamline popover insertion point logic * fix(cdk/overlay): update the insertionpoint type
1 parent 636054b commit a3b985f

File tree

7 files changed

+77
-15
lines changed

7 files changed

+77
-15
lines changed

goldens/cdk/overlay/index.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
284284
// (undocumented)
285285
detach(): void;
286286
dispose(): void;
287-
getPopoverInsertionPoint(): Element | null;
287+
getPopoverInsertionPoint(): Element | null | {type: 'parent', element: Element};
288288
_origin: FlexibleConnectedPositionStrategyOrigin;
289289
positionChanges: Observable<ConnectedOverlayPositionChange>;
290290
get positions(): ConnectionPositionPair[];
@@ -311,7 +311,7 @@ export type FlexibleConnectedPositionStrategyOrigin = ElementRef | Element | (Po
311311
});
312312

313313
// @public
314-
export type FlexibleOverlayPopoverLocation = 'global' | 'inline';
314+
export type FlexibleOverlayPopoverLocation = 'global' | 'inline' | {type: 'parent', element: Element };
315315

316316
// @public
317317
export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy {
@@ -534,7 +534,7 @@ export interface PositionStrategy {
534534
attach(overlayRef: OverlayRef): void;
535535
detach?(): void;
536536
dispose(): void;
537-
getPopoverInsertionPoint?(): Element | null;
537+
getPopoverInsertionPoint?(): Element | null | {type: 'parent', element: Element};
538538
}
539539

540540
// @public

src/cdk/overlay/overlay-directives.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
427427
.withViewportMargin(this.viewportMargin)
428428
.withLockedPosition(this.lockPosition)
429429
.withTransformOriginOn(this.transformOriginSelector)
430-
.withPopoverLocation(this.usePopover === 'global' ? 'global' : 'inline');
430+
.withPopoverLocation(this.usePopover === null ? 'global' : this.usePopover);
431431
}
432432

433433
/** Returns the position strategy of the overlay to be set on the overlay config */

src/cdk/overlay/overlay-ref.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,13 @@ export class OverlayRef implements PortalOutlet {
410410
: null;
411411

412412
if (customInsertionPoint) {
413-
customInsertionPoint.after(this._host);
413+
if (customInsertionPoint instanceof Element) {
414+
customInsertionPoint.after(this._host);
415+
} else {
416+
if (customInsertionPoint.type === 'parent') {
417+
customInsertionPoint.element?.appendChild(this._host);
418+
}
419+
}
414420
} else {
415421
this._previousHostParent?.appendChild(this._host);
416422
}

src/cdk/overlay/overlay.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ export function createOverlayRef(injector: Injector, config?: OverlayConfig): Ov
9595
// it's going to end up at the custom insertion point anyways. We need to do it,
9696
// because some internal clients depend on the host passing through the container first.
9797
if (customInsertionPoint) {
98-
customInsertionPoint.after(host);
98+
if (customInsertionPoint instanceof Element) {
99+
customInsertionPoint.after(host);
100+
} else {
101+
if (customInsertionPoint.type === 'parent') {
102+
customInsertionPoint.element?.appendChild(host);
103+
}
104+
}
99105
}
100106

101107
return new OverlayRef(

src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,11 +2959,14 @@ describe('FlexibleConnectedPositionStrategy', () => {
29592959
let positionStrategy: FlexibleConnectedPositionStrategy;
29602960
let containerElement: HTMLElement;
29612961
let originElement: HTMLElement;
2962+
let customHostElement: HTMLElement;
29622963

29632964
beforeEach(() => {
29642965
containerElement = overlayContainer.getContainerElement();
29652966
originElement = createPositionedBlockElement();
2967+
customHostElement = createBlockElement('span');
29662968
document.body.appendChild(originElement);
2969+
document.body.appendChild(customHostElement);
29672970

29682971
positionStrategy = createFlexibleConnectedPositionStrategy(injector, originElement)
29692972
.withPopoverLocation('inline')
@@ -2979,6 +2982,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
29792982

29802983
afterEach(() => {
29812984
originElement.remove();
2985+
customHostElement.remove();
29822986
});
29832987

29842988
it('should place the overlay inside the overlay container by default', () => {
@@ -3014,6 +3018,32 @@ describe('FlexibleConnectedPositionStrategy', () => {
30143018
overlayRef.attach(portal);
30153019
expect(originElement.nextElementSibling).toBe(overlayRef.hostElement);
30163020
});
3021+
3022+
it('should insert the overlay as a child of a custom element', () => {
3023+
if (!('showPopover' in document.body)) {
3024+
return;
3025+
}
3026+
3027+
positionStrategy.withPopoverLocation({type: 'parent', element: customHostElement});
3028+
attachOverlay({positionStrategy, usePopover: true});
3029+
3030+
expect(containerElement.contains(overlayRef.hostElement)).toBe(false);
3031+
expect(customHostElement.contains(overlayRef.hostElement)).toBe(true);
3032+
expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual');
3033+
});
3034+
3035+
it('should insert the overlay as a child of the origin', () => {
3036+
if (!('showPopover' in document.body)) {
3037+
return;
3038+
}
3039+
3040+
positionStrategy.withPopoverLocation({type: 'parent', element: originElement});
3041+
attachOverlay({positionStrategy, usePopover: true});
3042+
3043+
expect(containerElement.contains(overlayRef.hostElement)).toBe(false);
3044+
expect(originElement.contains(overlayRef.hostElement)).toBe(true);
3045+
expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual');
3046+
});
30173047
});
30183048
});
30193049

src/cdk/overlay/position/flexible-connected-position-strategy.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ export function createFlexibleConnectedPositionStrategy(
6464
}
6565

6666
/** Supported locations in the DOM for connected overlays. */
67-
export type FlexibleOverlayPopoverLocation = 'global' | 'inline';
67+
export type FlexibleOverlayPopoverLocation =
68+
| 'global'
69+
| 'inline'
70+
| {type: 'parent'; element: Element};
6871

6972
/**
7073
* A strategy for positioning overlays. Using this strategy, an overlay is given an
@@ -522,26 +525,43 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
522525
* @param location Configures the location in the DOM. Supports the following values:
523526
* - `global` - The default which inserts the overlay inside the overlay container.
524527
* - `inline` - Inserts the overlay next to the trigger.
528+
* - {type: 'parent', element: element} - Inserts the overlay to a child of a custom parent
529+
* element.
525530
*/
526531
withPopoverLocation(location: FlexibleOverlayPopoverLocation): this {
527532
this._popoverLocation = location;
528533
return this;
529534
}
530535

531536
/** @docs-private */
532-
getPopoverInsertionPoint(): Element | null {
533-
// Return null so it falls back to inserting into the overlay container.
537+
getPopoverInsertionPoint(): Element | null | {type: 'parent'; element: Element} {
534538
if (this._popoverLocation === 'global') {
535539
return null;
536540
}
537541

538-
const origin = this._origin;
542+
let hostElement: Element | null = null;
539543

540-
if (origin instanceof ElementRef) {
541-
return origin.nativeElement;
542-
} else if (origin instanceof Element) {
543-
return origin;
544+
if (this._popoverLocation === 'inline') {
545+
if (this._origin instanceof ElementRef) {
546+
hostElement = this._origin.nativeElement;
547+
} else if (this._origin instanceof Element) {
548+
hostElement = this._origin;
549+
}
550+
} else {
551+
// this._popoverLocation is {type: 'parent', element: Element}
552+
hostElement = this._popoverLocation.element;
544553
}
554+
555+
// If the location is 'inline', we're inserting as a sibling.
556+
if (this._popoverLocation === 'inline') {
557+
return hostElement;
558+
}
559+
560+
// Otherwise we're inserting as a child.
561+
if (hostElement) {
562+
return {type: 'parent', element: hostElement};
563+
}
564+
545565
return null;
546566
}
547567

src/cdk/overlay/position/position-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ export interface PositionStrategy {
2626
* Gets the element in the DOM after which to insert
2727
* the overlay when it is rendered out as a popover.
2828
*/
29-
getPopoverInsertionPoint?(): Element | null;
29+
getPopoverInsertionPoint?(): Element | null | {type: 'parent'; element: Element};
3030
}

0 commit comments

Comments
 (0)