Skip to content

Commit cd8b63a

Browse files
committed
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.
1 parent 866cc99 commit cd8b63a

File tree

8 files changed

+42
-98
lines changed

8 files changed

+42
-98
lines changed

goldens/cdk/overlay/index.api.md

Lines changed: 2 additions & 2 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 | {parent: Element};
288288
_origin: FlexibleConnectedPositionStrategyOrigin;
289289
positionChanges: Observable<ConnectedOverlayPositionChange>;
290290
get positions(): ConnectionPositionPair[];
@@ -536,7 +536,7 @@ export interface PositionStrategy {
536536
attach(overlayRef: OverlayRef): void;
537537
detach?(): void;
538538
dispose(): void;
539-
getPopoverInsertionPoint?(): Element | null;
539+
getPopoverInsertionPoint?(): Element | null | {parent: Element};
540540
}
541541

542542
// @public

src/cdk/overlay/overlay-config.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ export class OverlayConfig {
6767
*/
6868
usePopover?: boolean;
6969

70-
/**
71-
* Whether to attach the popover as a child of the popover host.
72-
* If true, the popover will be attached as a child of the host.
73-
* If false, the popover will be attached after the host.
74-
*/
75-
attachPopoverAsChild?: boolean;
76-
7770
constructor(config?: OverlayConfig) {
7871
if (config) {
7972
// Use `Iterable` instead of `Array` because TypeScript, as of 3.6.3,

src/cdk/overlay/overlay-directives.ts

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -261,22 +261,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
261261
@Input({alias: 'cdkConnectedOverlayMatchWidth', transform: booleanAttribute})
262262
matchWidth: boolean = false;
263263

264-
/**
265-
* A custom element to use as the host for the popover.
266-
* The popover will be inserted after this element in the DOM.
267-
* If null, the overlay will be inserted after the origin.
268-
*/
269-
@Input({alias: 'cdkCustomPopoverInsertionElement'})
270-
customPopoverHostElement: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin | null;
271-
272-
/**
273-
* Whether to attach the popover as a child of the popover host.
274-
* If true, the popover will be attached as a child of the host.
275-
* If false, the popover will be attached after the host.
276-
*/
277-
@Input({alias: 'cdkAttachPopoverAsChild', transform: booleanAttribute})
278-
attachPopoverAsChild: boolean = false;
279-
280264
/** Shorthand for setting multiple overlay options at once. */
281265
@Input('cdkConnectedOverlay')
282266
set _config(value: string | CdkConnectedOverlayConfig) {
@@ -356,8 +340,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
356340
}
357341

358342
if (changes['open']) {
359-
console.log('changes[open]');
360-
console.log(this.open);
361343
this.open ? this.attachOverlay() : this.detachOverlay();
362344
}
363345
}
@@ -401,7 +383,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
401383
hasBackdrop: this.hasBackdrop,
402384
disposeOnNavigation: this.disposeOnNavigation,
403385
usePopover: !!this.usePopover,
404-
attachPopoverAsChild: this.attachPopoverAsChild,
405386
});
406387

407388
if (this.height || this.height === 0) {
@@ -448,9 +429,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
448429
.withViewportMargin(this.viewportMargin)
449430
.withLockedPosition(this.lockPosition)
450431
.withTransformOriginOn(this.transformOriginSelector)
451-
.withPopoverLocation(this.usePopover === 'global' ? 'global' : 'inline')
452-
.withCustomPopoverHostElement(this._getCustomPopoverHostElement())
453-
.withAttachPopoverAsChild(this.attachPopoverAsChild);
432+
.withPopoverLocation(this.usePopover === null ? 'global' : this.usePopover);
454433
}
455434

456435
/** Returns the position strategy of the overlay to be set on the overlay config */
@@ -468,18 +447,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
468447
}
469448
}
470449

471-
/**
472-
* Gets the custom popover host element from the origin input.
473-
* @docs-private
474-
*/
475-
private _getCustomPopoverHostElement(): FlexibleConnectedPositionStrategyOrigin | null {
476-
if (this.customPopoverHostElement instanceof CdkOverlayOrigin) {
477-
return this.customPopoverHostElement.elementRef;
478-
} else {
479-
return this.customPopoverHostElement;
480-
}
481-
}
482-
483450
private _getOriginElement(): Element | null {
484451
if (this.origin instanceof CdkOverlayOrigin) {
485452
return this.origin.elementRef.nativeElement;
@@ -579,9 +546,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
579546
this.push = config.push ?? this.push;
580547
this.disposeOnNavigation = config.disposeOnNavigation ?? this.disposeOnNavigation;
581548
this.usePopover = config.usePopover ?? this.usePopover;
582-
this.customPopoverHostElement =
583-
config.customPopoverHostElement ?? this.customPopoverHostElement;
584-
this.attachPopoverAsChild = config.attachPopoverAsChild ?? this.attachPopoverAsChild;
585549
this.matchWidth = config.matchWidth ?? this.matchWidth;
586550
}
587551
}

src/cdk/overlay/overlay-ref.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,11 @@ export class OverlayRef implements PortalOutlet {
410410
: null;
411411

412412
if (customInsertionPoint) {
413-
this._config.attachPopoverAsChild
414-
? customInsertionPoint.appendChild(this._host)
415-
: customInsertionPoint.after(this._host);
413+
if (customInsertionPoint instanceof Element) {
414+
customInsertionPoint.after(this._host);
415+
} else {
416+
customInsertionPoint.parent?.appendChild(this._host);
417+
}
416418
} else {
417419
this._previousHostParent?.appendChild(this._host);
418420
}

src/cdk/overlay/overlay.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ 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-
if (overlayConfig.attachPopoverAsChild) {
99-
customInsertionPoint.appendChild(host);
100-
} else {
98+
if (customInsertionPoint instanceof Element) {
10199
customInsertionPoint.after(host);
100+
} else {
101+
customInsertionPoint.parent.appendChild(host);
102102
}
103103
}
104104

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3019,16 +3019,16 @@ describe('FlexibleConnectedPositionStrategy', () => {
30193019
expect(originElement.nextElementSibling).toBe(overlayRef.hostElement);
30203020
});
30213021

3022-
it('should insert the overlay after a custom element', () => {
3022+
it('should insert the overlay as a child of a custom element', () => {
30233023
if (!('showPopover' in document.body)) {
30243024
return;
30253025
}
30263026

3027-
positionStrategy.withCustomPopoverHostElement(customHostElement);
3027+
positionStrategy.withPopoverLocation({parent: customHostElement});
30283028
attachOverlay({positionStrategy, usePopover: true});
30293029

30303030
expect(containerElement.contains(overlayRef.hostElement)).toBe(false);
3031-
expect(customHostElement.nextElementSibling).toBe(overlayRef.hostElement);
3031+
expect(customHostElement.contains(overlayRef.hostElement)).toBe(true);
30323032
expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual');
30333033
});
30343034

@@ -3037,8 +3037,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
30373037
return;
30383038
}
30393039

3040-
console.log(positionStrategy.getPopoverInsertionPoint());
3041-
attachOverlay({positionStrategy, usePopover: true, attachPopoverAsChild: true});
3040+
positionStrategy.withPopoverLocation({parent: originElement});
3041+
attachOverlay({positionStrategy, usePopover: true});
30423042

30433043
expect(containerElement.contains(overlayRef.hostElement)).toBe(false);
30443044
expect(originElement.contains(overlayRef.hostElement)).toBe(true);

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

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export type FlexibleConnectedPositionStrategyOrigin =
4545
/** Equivalent of `DOMRect` without some of the properties we don't care about. */
4646
type Dimensions = Omit<DOMRect, 'x' | 'y' | 'toJSON'>;
4747

48+
/** Possible point to attach a popover to. */
49+
export type PopoverInsertionPoint = Element | {parent: Element} | null;
50+
4851
/**
4952
* Creates a flexible position strategy.
5053
* @param injector Injector used to resolve dependnecies for the position strategy.
@@ -64,7 +67,10 @@ export function createFlexibleConnectedPositionStrategy(
6467
}
6568

6669
/** Supported locations in the DOM for connected overlays. */
67-
export type FlexibleOverlayPopoverLocation = 'global' | 'inline';
70+
export type FlexibleOverlayPopoverLocation =
71+
| 'global'
72+
| 'inline'
73+
| {parent: FlexibleConnectedPositionStrategyOrigin};
6874

6975
/**
7076
* A strategy for positioning overlays. Using this strategy, an overlay is given an
@@ -164,18 +170,6 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
164170
/** Configures where in the DOM to insert the overlay when popovers are enabled. */
165171
private _popoverLocation: FlexibleOverlayPopoverLocation = 'global';
166172

167-
/**
168-
* Defines a specific host element for the popover content. If provided, the popover will attach
169-
* to this element.
170-
* */
171-
private _customPopoverHostElement: FlexibleConnectedPositionStrategyOrigin | null;
172-
173-
/**
174-
* Whether the popover is attached directly as a child of the popover host element instead of
175-
* a sibling element.
176-
* */
177-
private _attachPopoverAsChild = false;
178-
179173
/** Observable sequence of position changes. */
180174
positionChanges: Observable<ConnectedOverlayPositionChange> = this._positionChanges;
181175

@@ -540,44 +534,35 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
540534
return this;
541535
}
542536

543-
/**
544-
* Sets a custom element to use as the host for the popover.
545-
* The popover will be inserted after this element in the DOM.
546-
* If null, the overlay will be inserted after the origin.
547-
* @param element The element to use as the host for the popover.
548-
*/
549-
withCustomPopoverHostElement(element: FlexibleConnectedPositionStrategyOrigin | null): this {
550-
this._customPopoverHostElement = element;
551-
return this;
552-
}
553-
554537
/** @docs-private */
555-
getPopoverInsertionPoint(): Element | null {
556-
// Return null so it falls back to inserting into the overlay container.
538+
getPopoverInsertionPoint(): PopoverInsertionPoint {
557539
if (this._popoverLocation === 'global') {
558540
return null;
559541
}
560542

561543
const origin =
562-
this._customPopoverHostElement != null ? this._customPopoverHostElement : this._origin;
544+
this._popoverLocation === 'inline'
545+
? this._origin
546+
: (this._popoverLocation as {parent: FlexibleConnectedPositionStrategyOrigin}).parent;
547+
let element: Element | null = null;
563548

564549
if (origin instanceof ElementRef) {
565-
return origin.nativeElement;
550+
element = origin.nativeElement;
566551
} else if (origin instanceof Element) {
567-
return origin;
552+
element = origin;
568553
}
569-
return null;
570-
}
571554

572-
/**
573-
* Whether to attach the popover as a child of the popover host.
574-
* If true, the popover will be attached as a child of the host.
575-
* If false, the popover will be attached after the host.
576-
* @param attachPopoverAsChild Whether to attach the popover as a child of the popover host.
577-
*/
578-
withAttachPopoverAsChild(attachPopoverAsChild = false): this {
579-
this._attachPopoverAsChild = attachPopoverAsChild;
580-
return this;
555+
// If the location is 'inline', we're inserting as a sibling.
556+
if (this._popoverLocation === 'inline') {
557+
return element;
558+
}
559+
560+
// Otherwise we're inserting as a child.
561+
if (element) {
562+
return {parent: element};
563+
}
564+
565+
return null;
581566
}
582567

583568
/**

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 | {parent: Element};
3030
}

0 commit comments

Comments
 (0)