Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions goldens/cdk/overlay/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { EmbeddedViewRef } from '@angular/core';
import { EnvironmentInjector } from '@angular/core';
import { EventEmitter } from '@angular/core';
import * as i0 from '@angular/core';
import { InjectionToken } from '@angular/core';
import { Injector } from '@angular/core';
import { Location as Location_2 } from '@angular/common';
import { NgIterable } from '@angular/core';
Expand All @@ -37,24 +38,28 @@ export class BlockScrollStrategy implements ScrollStrategy {
enable(): void;
}

// @public
export const CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG: InjectionToken<CdkConnectedOverlayConfig>;

// @public
export class CdkConnectedOverlay implements OnDestroy, OnChanges {
constructor(...args: unknown[]);
readonly attach: EventEmitter<void>;
attachOverlay(): void;
backdropClass: string | string[];
readonly backdropClick: EventEmitter<MouseEvent>;
set _config(value: string | CdkConnectedOverlayConfig);
readonly detach: EventEmitter<void>;
detachOverlay(): void;
get dir(): Direction;
disableClose: boolean;
get disposeOnNavigation(): boolean;
set disposeOnNavigation(value: boolean);
disposeOnNavigation: boolean;
flexibleDimensions: boolean;
growAfterOpen: boolean;
hasBackdrop: boolean;
height: number | string;
lockPosition: boolean;
matchWidth: boolean;
minHeight: number | string;
minWidth: number | string;
// (undocumented)
Expand All @@ -68,6 +73,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
// (undocumented)
static ngAcceptInputType_lockPosition: unknown;
// (undocumented)
static ngAcceptInputType_matchWidth: unknown;
// (undocumented)
static ngAcceptInputType_push: unknown;
// (undocumented)
static ngAcceptInputType_usePopover: unknown;
Expand Down Expand Up @@ -95,11 +102,61 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
viewportMargin: ViewportMargin;
width: number | string;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkConnectedOverlay, "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", ["cdkConnectedOverlay"], { "origin": { "alias": "cdkConnectedOverlayOrigin"; "required": false; }; "positions": { "alias": "cdkConnectedOverlayPositions"; "required": false; }; "positionStrategy": { "alias": "cdkConnectedOverlayPositionStrategy"; "required": false; }; "offsetX": { "alias": "cdkConnectedOverlayOffsetX"; "required": false; }; "offsetY": { "alias": "cdkConnectedOverlayOffsetY"; "required": false; }; "width": { "alias": "cdkConnectedOverlayWidth"; "required": false; }; "height": { "alias": "cdkConnectedOverlayHeight"; "required": false; }; "minWidth": { "alias": "cdkConnectedOverlayMinWidth"; "required": false; }; "minHeight": { "alias": "cdkConnectedOverlayMinHeight"; "required": false; }; "backdropClass": { "alias": "cdkConnectedOverlayBackdropClass"; "required": false; }; "panelClass": { "alias": "cdkConnectedOverlayPanelClass"; "required": false; }; "viewportMargin": { "alias": "cdkConnectedOverlayViewportMargin"; "required": false; }; "scrollStrategy": { "alias": "cdkConnectedOverlayScrollStrategy"; "required": false; }; "open": { "alias": "cdkConnectedOverlayOpen"; "required": false; }; "disableClose": { "alias": "cdkConnectedOverlayDisableClose"; "required": false; }; "transformOriginSelector": { "alias": "cdkConnectedOverlayTransformOriginOn"; "required": false; }; "hasBackdrop": { "alias": "cdkConnectedOverlayHasBackdrop"; "required": false; }; "lockPosition": { "alias": "cdkConnectedOverlayLockPosition"; "required": false; }; "flexibleDimensions": { "alias": "cdkConnectedOverlayFlexibleDimensions"; "required": false; }; "growAfterOpen": { "alias": "cdkConnectedOverlayGrowAfterOpen"; "required": false; }; "push": { "alias": "cdkConnectedOverlayPush"; "required": false; }; "disposeOnNavigation": { "alias": "cdkConnectedOverlayDisposeOnNavigation"; "required": false; }; "usePopover": { "alias": "cdkConnectedOverlayUsePopover"; "required": false; }; }, { "backdropClick": "backdropClick"; "positionChange": "positionChange"; "attach": "attach"; "detach": "detach"; "overlayKeydown": "overlayKeydown"; "overlayOutsideClick": "overlayOutsideClick"; }, never, never, true, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkConnectedOverlay, "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", ["cdkConnectedOverlay"], { "origin": { "alias": "cdkConnectedOverlayOrigin"; "required": false; }; "positions": { "alias": "cdkConnectedOverlayPositions"; "required": false; }; "positionStrategy": { "alias": "cdkConnectedOverlayPositionStrategy"; "required": false; }; "offsetX": { "alias": "cdkConnectedOverlayOffsetX"; "required": false; }; "offsetY": { "alias": "cdkConnectedOverlayOffsetY"; "required": false; }; "width": { "alias": "cdkConnectedOverlayWidth"; "required": false; }; "height": { "alias": "cdkConnectedOverlayHeight"; "required": false; }; "minWidth": { "alias": "cdkConnectedOverlayMinWidth"; "required": false; }; "minHeight": { "alias": "cdkConnectedOverlayMinHeight"; "required": false; }; "backdropClass": { "alias": "cdkConnectedOverlayBackdropClass"; "required": false; }; "panelClass": { "alias": "cdkConnectedOverlayPanelClass"; "required": false; }; "viewportMargin": { "alias": "cdkConnectedOverlayViewportMargin"; "required": false; }; "scrollStrategy": { "alias": "cdkConnectedOverlayScrollStrategy"; "required": false; }; "open": { "alias": "cdkConnectedOverlayOpen"; "required": false; }; "disableClose": { "alias": "cdkConnectedOverlayDisableClose"; "required": false; }; "transformOriginSelector": { "alias": "cdkConnectedOverlayTransformOriginOn"; "required": false; }; "hasBackdrop": { "alias": "cdkConnectedOverlayHasBackdrop"; "required": false; }; "lockPosition": { "alias": "cdkConnectedOverlayLockPosition"; "required": false; }; "flexibleDimensions": { "alias": "cdkConnectedOverlayFlexibleDimensions"; "required": false; }; "growAfterOpen": { "alias": "cdkConnectedOverlayGrowAfterOpen"; "required": false; }; "push": { "alias": "cdkConnectedOverlayPush"; "required": false; }; "disposeOnNavigation": { "alias": "cdkConnectedOverlayDisposeOnNavigation"; "required": false; }; "usePopover": { "alias": "cdkConnectedOverlayUsePopover"; "required": false; }; "matchWidth": { "alias": "cdkConnectedOverlayMatchWidth"; "required": false; }; "_config": { "alias": "cdkConnectedOverlay"; "required": false; }; }, { "backdropClick": "backdropClick"; "positionChange": "positionChange"; "attach": "attach"; "detach": "detach"; "overlayKeydown": "overlayKeydown"; "overlayOutsideClick": "overlayOutsideClick"; }, never, never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<CdkConnectedOverlay, never>;
}

// @public
export interface CdkConnectedOverlayConfig {
// (undocumented)
backdropClass?: string | string[];
// (undocumented)
disableClose?: boolean;
// (undocumented)
disposeOnNavigation?: boolean;
// (undocumented)
flexibleDimensions?: boolean;
// (undocumented)
growAfterOpen?: boolean;
// (undocumented)
hasBackdrop?: boolean;
// (undocumented)
height?: number | string;
// (undocumented)
lockPosition?: boolean;
// (undocumented)
matchWidth?: boolean;
// (undocumented)
minHeight?: number | string;
// (undocumented)
minWidth?: number | string;
// (undocumented)
offsetX?: number;
// (undocumented)
offsetY?: number;
// (undocumented)
origin?: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin;
// (undocumented)
panelClass?: string | string[];
// (undocumented)
positions?: ConnectedPosition[];
// (undocumented)
positionStrategy?: FlexibleConnectedPositionStrategy;
// (undocumented)
push?: boolean;
// (undocumented)
scrollStrategy?: ScrollStrategy;
// (undocumented)
transformOriginSelector?: string;
// (undocumented)
usePopover?: boolean;
// (undocumented)
viewportMargin?: ViewportMargin;
// (undocumented)
width?: number | string;
}

// @public
export class CdkOverlayOrigin {
constructor(...args: unknown[]);
Expand Down
5 changes: 5 additions & 0 deletions src/cdk/overlay/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
// with `align-self` can break the positioning (see #29809).
inset: auto;

// For the time being we're using our `.cdk-overlay-backdrop` element instead of the native one.
&::backdrop {
display: none;
}

.cdk-overlay-backdrop {
position: fixed;
}
Expand Down
28 changes: 22 additions & 6 deletions src/cdk/overlay/overlay-directives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,19 @@ describe('Overlay directives', () => {

expect(target.style.transformOrigin).toContain('left bottom');
});

it('should match the trigger width', () => {
const trigger = fixture.nativeElement.querySelector('#trigger') as HTMLElement;
trigger.style.width = '128px';

fixture.componentInstance.matchWidth = true;
fixture.componentInstance.isOpen = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();

const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(pane.style.width).toBe('128px');
});
});

describe('outputs', () => {
Expand Down Expand Up @@ -742,11 +755,11 @@ describe('Overlay directives', () => {

@Component({
template: `
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
<button cdk-overlay-origin id="otherTrigger" #otherTrigger="cdkOverlayOrigin">Toggle menu</button>
<button cdkOverlayOrigin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
<button cdkOverlayOrigin id="otherTrigger" #otherTrigger="cdkOverlayOrigin">Toggle menu</button>
<button id="nonDirectiveTrigger" #nonDirectiveTrigger>Toggle menu</button>

<ng-template cdk-connected-overlay
<ng-template cdkConnectedOverlay
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayWidth]="width"
[cdkConnectedOverlayHeight]="height"
Expand All @@ -771,7 +784,8 @@ describe('Overlay directives', () => {
[cdkConnectedOverlayMinWidth]="minWidth"
[cdkConnectedOverlayMinHeight]="minHeight"
[cdkConnectedOverlayPositions]="positionOverrides"
[cdkConnectedOverlayTransformOriginOn]="transformOriginSelector">
[cdkConnectedOverlayTransformOriginOn]="transformOriginSelector"
[cdkConnectedOverlayMatchWidth]="matchWidth">
<p>Menu content</p>
</ng-template>`,
imports: [OverlayModule],
Expand Down Expand Up @@ -809,12 +823,14 @@ class ConnectedOverlayDirectiveTest {
detachHandler = jasmine.createSpy('detachHandler');
attachResult: HTMLElement;
transformOriginSelector: string;
matchWidth = false;
}

@Component({
template: `
<button cdk-overlay-origin #trigger="cdkOverlayOrigin">Toggle menu</button>
<ng-template cdk-connected-overlay>Menu content</ng-template>`,
<button cdkOverlayOrigin #trigger="cdkOverlayOrigin">Toggle menu</button>
<ng-template cdk-connected-overlay>Menu content</ng-template>
`,
imports: [OverlayModule],
})
class ConnectedOverlayPropertyInitOrder {
Expand Down
120 changes: 100 additions & 20 deletions src/cdk/overlay/overlay-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,41 @@ export class CdkOverlayOrigin {
constructor() {}
}

/**
* Injection token that can be used to configure the
* default options for the `CdkConnectedOverlay` directive.
*/
export const CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG = new InjectionToken<CdkConnectedOverlayConfig>(
'cdk-connected-overlay-default-config',
);

/** Object used to configure the `CdkConnectedOverlay` directive. */
export interface CdkConnectedOverlayConfig {
origin?: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin;
positions?: ConnectedPosition[];
positionStrategy?: FlexibleConnectedPositionStrategy;
offsetX?: number;
offsetY?: number;
width?: number | string;
height?: number | string;
minWidth?: number | string;
minHeight?: number | string;
backdropClass?: string | string[];
panelClass?: string | string[];
viewportMargin?: ViewportMargin;
scrollStrategy?: ScrollStrategy;
disableClose?: boolean;
transformOriginSelector?: string;
hasBackdrop?: boolean;
lockPosition?: boolean;
flexibleDimensions?: boolean;
growAfterOpen?: boolean;
push?: boolean;
disposeOnNavigation?: boolean;
usePopover?: boolean;
matchWidth?: boolean;
}

/**
* Directive to facilitate declarative creation of an
* Overlay using a FlexibleConnectedPositionStrategy.
Expand All @@ -118,7 +153,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
private _offsetY: number;
private _position: FlexibleConnectedPositionStrategy;
private _scrollStrategyFactory = inject(CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY);
private _disposeOnNavigation = false;
private _ngZone = inject(NgZone);

/** Origin for the connected overlay. */
Expand Down Expand Up @@ -214,17 +248,24 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {

/** Whether the overlay should be disposed of when the user goes backwards/forwards in history. */
@Input({alias: 'cdkConnectedOverlayDisposeOnNavigation', transform: booleanAttribute})
get disposeOnNavigation(): boolean {
return this._disposeOnNavigation;
}
set disposeOnNavigation(value: boolean) {
this._disposeOnNavigation = value;
}
disposeOnNavigation: boolean = false;

/** Whether the connected overlay should be rendered inside a popover element or the overlay container. */
@Input({alias: 'cdkConnectedOverlayUsePopover', transform: booleanAttribute})
usePopover: boolean = false;

/** Whether the overlay should match the trigger's width. */
@Input({alias: 'cdkConnectedOverlayMatchWidth', transform: booleanAttribute})
matchWidth: boolean = false;

/** Shorthand for setting multiple overlay options at once. */
@Input('cdkConnectedOverlay')
set _config(value: string | CdkConnectedOverlayConfig) {
if (typeof value !== 'string') {
this._assignConfig(value);
}
}

/** Event emitted when the backdrop is clicked. */
@Output() readonly backdropClick = new EventEmitter<MouseEvent>();

Expand All @@ -250,9 +291,14 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
constructor() {
const templateRef = inject<TemplateRef<any>>(TemplateRef);
const viewContainerRef = inject(ViewContainerRef);
const defaultConfig = inject(CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG, {optional: true});

this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
this.scrollStrategy = this._scrollStrategyFactory();

if (defaultConfig) {
this._assignConfig(defaultConfig);
}
}

/** The associated overlay reference. */
Expand All @@ -277,7 +323,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
if (this._position) {
this._updatePositionStrategy(this._position);
this._overlayRef?.updateSize({
width: this.width,
width: this._getWidth(),
minWidth: this.minWidth,
height: this.height,
minHeight: this.minHeight,
Expand Down Expand Up @@ -334,10 +380,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
usePopover: this.usePopover,
});

if (this.width || this.width === 0) {
overlayConfig.width = this.width;
}

if (this.height || this.height === 0) {
overlayConfig.height = this.height;
}
Expand Down Expand Up @@ -415,23 +457,35 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
return null;
}

private _getWidth() {
if (this.width) {
return this.width;
}

// Null check `getBoundingClientRect` in case this is called during SSR.
return this.matchWidth ? this._getOriginElement()?.getBoundingClientRect?.().width : undefined;
}

/** Attaches the overlay. */
attachOverlay() {
if (!this._overlayRef) {
this._createOverlay();
} else {
// Update the overlay size, in case the directive's inputs have changed
this._overlayRef.getConfig().hasBackdrop = this.hasBackdrop;
}

if (!this._overlayRef!.hasAttached()) {
this._overlayRef!.attach(this._templatePortal);
const ref = this._overlayRef!;

// Update the overlay size, in case the directive's inputs have changed
ref.getConfig().hasBackdrop = this.hasBackdrop;
ref.updateSize({width: this._getWidth()});

if (!ref.hasAttached()) {
ref.attach(this._templatePortal);
}

if (this.hasBackdrop) {
this._backdropSubscription = this._overlayRef!.backdropClick().subscribe(event => {
this.backdropClick.emit(event);
});
this._backdropSubscription = ref
.backdropClick()
.subscribe(event => this.backdropClick.emit(event));
} else {
this._backdropSubscription.unsubscribe();
}
Expand Down Expand Up @@ -462,4 +516,30 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
this._positionSubscription.unsubscribe();
this.open = false;
}

private _assignConfig(config: CdkConnectedOverlayConfig) {
this.origin = config.origin ?? this.origin;
this.positions = config.positions ?? this.positions;
this.positionStrategy = config.positionStrategy ?? this.positionStrategy;
this.offsetX = config.offsetX ?? this.offsetX;
this.offsetY = config.offsetY ?? this.offsetY;
this.width = config.width ?? this.width;
this.height = config.height ?? this.height;
this.minWidth = config.minWidth ?? this.minWidth;
this.minHeight = config.minHeight ?? this.minHeight;
this.backdropClass = config.backdropClass ?? this.backdropClass;
this.panelClass = config.panelClass ?? this.panelClass;
this.viewportMargin = config.viewportMargin ?? this.viewportMargin;
this.scrollStrategy = config.scrollStrategy ?? this.scrollStrategy;
this.disableClose = config.disableClose ?? this.disableClose;
this.transformOriginSelector = config.transformOriginSelector ?? this.transformOriginSelector;
this.hasBackdrop = config.hasBackdrop ?? this.hasBackdrop;
this.lockPosition = config.lockPosition ?? this.lockPosition;
this.flexibleDimensions = config.flexibleDimensions ?? this.flexibleDimensions;
this.growAfterOpen = config.growAfterOpen ?? this.growAfterOpen;
this.push = config.push ?? this.push;
this.disposeOnNavigation = config.disposeOnNavigation ?? this.disposeOnNavigation;
this.usePopover = config.usePopover ?? this.usePopover;
this.matchWidth = config.matchWidth ?? this.matchWidth;
}
}
7 changes: 6 additions & 1 deletion src/cdk/overlay/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ export * from './overlay-module';
export * from './dispatchers/index';
export {Overlay, createOverlayRef} from './overlay';
export {OverlayContainer} from './overlay-container';
export {CdkOverlayOrigin, CdkConnectedOverlay} from './overlay-directives';
export {
CdkOverlayOrigin,
CdkConnectedOverlay,
CdkConnectedOverlayConfig,
CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG,
} from './overlay-directives';
export {FullscreenOverlayContainer} from './fullscreen-overlay-container';
export {OverlayRef, OverlaySizeConfig} from './overlay-ref';
export {ViewportRuler} from '../scrolling';
Expand Down
Loading