diff --git a/goldens/cdk/overlay/index.api.md b/goldens/cdk/overlay/index.api.md index 855db513f679..b40fc7e0ea37 100644 --- a/goldens/cdk/overlay/index.api.md +++ b/goldens/cdk/overlay/index.api.md @@ -284,7 +284,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { // (undocumented) detach(): void; dispose(): void; - getPopoverInsertionPoint(): Element | null; + getPopoverInsertionPoint(): Element | null | {type: 'parent', element: Element}; _origin: FlexibleConnectedPositionStrategyOrigin; positionChanges: Observable; get positions(): ConnectionPositionPair[]; @@ -311,7 +311,7 @@ export type FlexibleConnectedPositionStrategyOrigin = ElementRef | Element | (Po }); // @public -export type FlexibleOverlayPopoverLocation = 'global' | 'inline'; +export type FlexibleOverlayPopoverLocation = 'global' | 'inline' | {type: 'parent', element: Element }; // @public export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy { @@ -534,7 +534,7 @@ export interface PositionStrategy { attach(overlayRef: OverlayRef): void; detach?(): void; dispose(): void; - getPopoverInsertionPoint?(): Element | null; + getPopoverInsertionPoint?(): Element | null | {type: 'parent', element: Element}; } // @public diff --git a/src/cdk/overlay/overlay-directives.ts b/src/cdk/overlay/overlay-directives.ts index 4fe99f3768b0..f69d079035a9 100644 --- a/src/cdk/overlay/overlay-directives.ts +++ b/src/cdk/overlay/overlay-directives.ts @@ -427,7 +427,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { .withViewportMargin(this.viewportMargin) .withLockedPosition(this.lockPosition) .withTransformOriginOn(this.transformOriginSelector) - .withPopoverLocation(this.usePopover === 'global' ? 'global' : 'inline'); + .withPopoverLocation(this.usePopover === null ? 'global' : this.usePopover); } /** Returns the position strategy of the overlay to be set on the overlay config */ diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 59971cf7b28b..53b0b5e70ac3 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -410,7 +410,13 @@ export class OverlayRef implements PortalOutlet { : null; if (customInsertionPoint) { - customInsertionPoint.after(this._host); + if (customInsertionPoint instanceof Element) { + customInsertionPoint.after(this._host); + } else { + if (customInsertionPoint.type === 'parent') { + customInsertionPoint.element?.appendChild(this._host); + } + } } else { this._previousHostParent?.appendChild(this._host); } diff --git a/src/cdk/overlay/overlay.ts b/src/cdk/overlay/overlay.ts index a52742866812..c55886c67ea5 100644 --- a/src/cdk/overlay/overlay.ts +++ b/src/cdk/overlay/overlay.ts @@ -95,7 +95,13 @@ export function createOverlayRef(injector: Injector, config?: OverlayConfig): Ov // it's going to end up at the custom insertion point anyways. We need to do it, // because some internal clients depend on the host passing through the container first. if (customInsertionPoint) { - customInsertionPoint.after(host); + if (customInsertionPoint instanceof Element) { + customInsertionPoint.after(host); + } else { + if (customInsertionPoint.type === 'parent') { + customInsertionPoint.element?.appendChild(host); + } + } } return new OverlayRef( diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts index 11b7326fb779..45014d01ed85 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts @@ -2959,11 +2959,14 @@ describe('FlexibleConnectedPositionStrategy', () => { let positionStrategy: FlexibleConnectedPositionStrategy; let containerElement: HTMLElement; let originElement: HTMLElement; + let customHostElement: HTMLElement; beforeEach(() => { containerElement = overlayContainer.getContainerElement(); originElement = createPositionedBlockElement(); + customHostElement = createBlockElement('span'); document.body.appendChild(originElement); + document.body.appendChild(customHostElement); positionStrategy = createFlexibleConnectedPositionStrategy(injector, originElement) .withPopoverLocation('inline') @@ -2979,6 +2982,7 @@ describe('FlexibleConnectedPositionStrategy', () => { afterEach(() => { originElement.remove(); + customHostElement.remove(); }); it('should place the overlay inside the overlay container by default', () => { @@ -3014,6 +3018,32 @@ describe('FlexibleConnectedPositionStrategy', () => { overlayRef.attach(portal); expect(originElement.nextElementSibling).toBe(overlayRef.hostElement); }); + + it('should insert the overlay as a child of a custom element', () => { + if (!('showPopover' in document.body)) { + return; + } + + positionStrategy.withPopoverLocation({type: 'parent', element: customHostElement}); + attachOverlay({positionStrategy, usePopover: true}); + + expect(containerElement.contains(overlayRef.hostElement)).toBe(false); + expect(customHostElement.contains(overlayRef.hostElement)).toBe(true); + expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual'); + }); + + it('should insert the overlay as a child of the origin', () => { + if (!('showPopover' in document.body)) { + return; + } + + positionStrategy.withPopoverLocation({type: 'parent', element: originElement}); + attachOverlay({positionStrategy, usePopover: true}); + + expect(containerElement.contains(overlayRef.hostElement)).toBe(false); + expect(originElement.contains(overlayRef.hostElement)).toBe(true); + expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual'); + }); }); }); diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 9552c9c7ee7d..18c271b4e601 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -64,7 +64,10 @@ export function createFlexibleConnectedPositionStrategy( } /** Supported locations in the DOM for connected overlays. */ -export type FlexibleOverlayPopoverLocation = 'global' | 'inline'; +export type FlexibleOverlayPopoverLocation = + | 'global' + | 'inline' + | {type: 'parent'; element: Element}; /** * A strategy for positioning overlays. Using this strategy, an overlay is given an @@ -522,6 +525,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { * @param location Configures the location in the DOM. Supports the following values: * - `global` - The default which inserts the overlay inside the overlay container. * - `inline` - Inserts the overlay next to the trigger. + * - {type: 'parent', element: element} - Inserts the overlay to a child of a custom parent + * element. */ withPopoverLocation(location: FlexibleOverlayPopoverLocation): this { this._popoverLocation = location; @@ -529,19 +534,34 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { } /** @docs-private */ - getPopoverInsertionPoint(): Element | null { - // Return null so it falls back to inserting into the overlay container. + getPopoverInsertionPoint(): Element | null | {type: 'parent'; element: Element} { if (this._popoverLocation === 'global') { return null; } - const origin = this._origin; + let hostElement: Element | null = null; - if (origin instanceof ElementRef) { - return origin.nativeElement; - } else if (origin instanceof Element) { - return origin; + if (this._popoverLocation === 'inline') { + if (this._origin instanceof ElementRef) { + hostElement = this._origin.nativeElement; + } else if (this._origin instanceof Element) { + hostElement = this._origin; + } + } else { + // this._popoverLocation is {type: 'parent', element: Element} + hostElement = this._popoverLocation.element; } + + // If the location is 'inline', we're inserting as a sibling. + if (this._popoverLocation === 'inline') { + return hostElement; + } + + // Otherwise we're inserting as a child. + if (hostElement) { + return {type: 'parent', element: hostElement}; + } + return null; } diff --git a/src/cdk/overlay/position/position-strategy.ts b/src/cdk/overlay/position/position-strategy.ts index 1191acb2dc0e..34c548d1da13 100644 --- a/src/cdk/overlay/position/position-strategy.ts +++ b/src/cdk/overlay/position/position-strategy.ts @@ -26,5 +26,5 @@ export interface PositionStrategy { * Gets the element in the DOM after which to insert * the overlay when it is rendered out as a popover. */ - getPopoverInsertionPoint?(): Element | null; + getPopoverInsertionPoint?(): Element | null | {type: 'parent'; element: Element}; }