Skip to content

Commit 51796e1

Browse files
authored
feat(cdk/overlay): Extend cdkConnectedOverlayOrigin to support more types. (#23253)
`CdkConnectedOverlay` directive's input `cdkConnectedOverlayOrigin` only supports `CdkOverlayOrigin`. `FlexibleConnectedPositionStrategy` supports a lot more types. Add support for them. Fixes #23252
1 parent 5f529db commit 51796e1

File tree

4 files changed

+51
-26
lines changed

4 files changed

+51
-26
lines changed

src/cdk/overlay/overlay-directives.spec.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, ViewChild} from '@angular/core';
1+
import {Component, ElementRef, ViewChild} from '@angular/core';
22
import {By} from '@angular/platform-browser';
33
import {
44
ComponentFixture,
@@ -28,7 +28,10 @@ import {
2828
ConnectedOverlayPositionChange,
2929
ConnectionPositionPair,
3030
} from './position/connected-position';
31-
import {FlexibleConnectedPositionStrategy} from './position/flexible-connected-position-strategy';
31+
import {
32+
FlexibleConnectedPositionStrategy,
33+
FlexibleConnectedPositionStrategyOrigin,
34+
} from './position/flexible-connected-position-strategy';
3235
import {Subject} from 'rxjs';
3336

3437

@@ -408,6 +411,21 @@ describe('Overlay directives', () => {
408411
expect(Math.floor(triggerRect.bottom)).toBe(Math.floor(overlayRect.top));
409412
});
410413

414+
it('should be able to use non-directive origin', () => {
415+
const testComponent = fixture.componentInstance;
416+
417+
testComponent.triggerOverride = testComponent.nonDirectiveTrigger;
418+
testComponent.isOpen = true;
419+
fixture.detectChanges();
420+
421+
const triggerRect =
422+
fixture.nativeElement.querySelector('#nonDirectiveTrigger').getBoundingClientRect();
423+
const overlayRect = getPaneElement().getBoundingClientRect();
424+
425+
expect(Math.floor(triggerRect.left)).toBe(Math.floor(overlayRect.left));
426+
expect(Math.floor(triggerRect.bottom)).toBe(Math.floor(overlayRect.top));
427+
});
428+
411429
it('should update the positions if they change after init', () => {
412430
const trigger = fixture.nativeElement.querySelector('#trigger');
413431

@@ -674,6 +692,7 @@ describe('Overlay directives', () => {
674692
template: `
675693
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
676694
<button cdk-overlay-origin id="otherTrigger" #otherTrigger="cdkOverlayOrigin">Toggle menu</button>
695+
<button id="nonDirectiveTrigger" #nonDirectiveTrigger>Toggle menu</button>
677696
678697
<ng-template cdk-connected-overlay
679698
[cdkConnectedOverlayOpen]="isOpen"
@@ -708,6 +727,7 @@ class ConnectedOverlayDirectiveTest {
708727
@ViewChild(CdkConnectedOverlay) connectedOverlayDirective: CdkConnectedOverlay;
709728
@ViewChild('trigger') trigger: CdkOverlayOrigin;
710729
@ViewChild('otherTrigger') otherTrigger: CdkOverlayOrigin;
730+
@ViewChild('nonDirectiveTrigger') nonDirectiveTrigger: ElementRef<HTMLElement>;
711731

712732
isOpen = false;
713733
width: number | string;
@@ -717,7 +737,7 @@ class ConnectedOverlayDirectiveTest {
717737
minHeight: number | string;
718738
offsetX: number;
719739
offsetY: number;
720-
triggerOverride: CdkOverlayOrigin;
740+
triggerOverride: CdkOverlayOrigin|FlexibleConnectedPositionStrategyOrigin;
721741
hasBackdrop: boolean;
722742
disableClose: boolean;
723743
viewportMargin: number;

src/cdk/overlay/overlay-directives.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {ConnectedOverlayPositionChange} from './position/connected-position';
3434
import {
3535
ConnectedPosition,
3636
FlexibleConnectedPositionStrategy,
37+
FlexibleConnectedPositionStrategyOrigin,
3738
} from './position/flexible-connected-position-strategy';
3839
import {
3940
RepositionScrollStrategy,
@@ -114,7 +115,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
114115
private _scrollStrategyFactory: () => ScrollStrategy;
115116

116117
/** Origin for the connected overlay. */
117-
@Input('cdkConnectedOverlayOrigin') origin: CdkOverlayOrigin;
118+
@Input('cdkConnectedOverlayOrigin')
119+
origin: CdkOverlayOrigin|FlexibleConnectedPositionStrategyOrigin;
118120

119121
/** Registered connected position pairs. */
120122
@Input('cdkConnectedOverlayPositions') positions: ConnectedPosition[];
@@ -352,24 +354,32 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
352354
panelClass: currentPosition.panelClass || undefined,
353355
}));
354356

355-
return positionStrategy
356-
.setOrigin(this.origin.elementRef)
357-
.withPositions(positions)
358-
.withFlexibleDimensions(this.flexibleDimensions)
359-
.withPush(this.push)
360-
.withGrowAfterOpen(this.growAfterOpen)
361-
.withViewportMargin(this.viewportMargin)
362-
.withLockedPosition(this.lockPosition)
363-
.withTransformOriginOn(this.transformOriginSelector);
357+
return positionStrategy.setOrigin(this._getFlexibleConnectedPositionStrategyOrigin())
358+
.withPositions(positions)
359+
.withFlexibleDimensions(this.flexibleDimensions)
360+
.withPush(this.push)
361+
.withGrowAfterOpen(this.growAfterOpen)
362+
.withViewportMargin(this.viewportMargin)
363+
.withLockedPosition(this.lockPosition)
364+
.withTransformOriginOn(this.transformOriginSelector);
364365
}
365366

366367
/** Returns the position strategy of the overlay to be set on the overlay config */
367368
private _createPositionStrategy(): FlexibleConnectedPositionStrategy {
368-
const strategy = this._overlay.position().flexibleConnectedTo(this.origin.elementRef);
369+
const strategy = this._overlay.position().flexibleConnectedTo(
370+
this._getFlexibleConnectedPositionStrategyOrigin());
369371
this._updatePositionStrategy(strategy);
370372
return strategy;
371373
}
372374

375+
private _getFlexibleConnectedPositionStrategyOrigin(): FlexibleConnectedPositionStrategyOrigin {
376+
if (this.origin instanceof CdkOverlayOrigin) {
377+
return this.origin.elementRef;
378+
} else {
379+
return this.origin;
380+
}
381+
}
382+
373383
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
374384
private _attachOverlay() {
375385
if (!this._overlayRef) {
@@ -425,7 +435,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
425435
static ngAcceptInputType_push: BooleanInput;
426436
}
427437

428-
429438
/** @docs-private */
430439
export function CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay):
431440
() => RepositionScrollStrategy {

src/material-experimental/mdc-select/select.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ContentChild,
1414
ContentChildren,
1515
Directive,
16+
ElementRef,
1617
OnInit,
1718
QueryList,
1819
ViewEncapsulation,
@@ -109,7 +110,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
109110
];
110111

111112
/** Ideal origin for the overlay panel. */
112-
_preferredOverlayOrigin: CdkOverlayOrigin | undefined;
113+
_preferredOverlayOrigin: CdkOverlayOrigin | ElementRef | undefined;
113114

114115
/** Width of the overlay panel. */
115116
_overlayWidth: number;
@@ -134,14 +135,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
134135
// Note that it's important that we read this in `ngAfterViewInit`, because
135136
// reading it earlier will cause the form field to return a different element.
136137
if (this._parentFormField) {
137-
// TODO(crisbeto): currently the MDC select is based on the standard one which uses the
138-
// connected overlay directive for its panel. In order to keep the logic as similar as
139-
// possible, we have to use the directive here which only accepts a `CdkOverlayOrigin` as
140-
// its origin. For now we fake an origin directive by constructing an object that looks
141-
// like it, although eventually we should switch to creating the OverlayRef here directly.
142-
this._preferredOverlayOrigin = {
143-
elementRef: this._parentFormField.getConnectedOverlayOrigin()
144-
};
138+
this._preferredOverlayOrigin = this._parentFormField.getConnectedOverlayOrigin();
145139
}
146140
}
147141

@@ -193,7 +187,9 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
193187

194188
/** Gets how wide the overlay panel should be. */
195189
private _getOverlayWidth() {
196-
const refToMeasure = (this._preferredOverlayOrigin?.elementRef || this._elementRef);
190+
const refToMeasure = this._preferredOverlayOrigin instanceof CdkOverlayOrigin ?
191+
this._preferredOverlayOrigin.elementRef :
192+
this._preferredOverlayOrigin || this._elementRef;
197193
return refToMeasure.nativeElement.getBoundingClientRect().width;
198194
}
199195
}

tools/public_api_guard/cdk/overlay.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
9797
get offsetY(): number;
9898
set offsetY(offsetY: number);
9999
open: boolean;
100-
origin: CdkOverlayOrigin;
100+
origin: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin;
101101
readonly overlayKeydown: EventEmitter<KeyboardEvent>;
102102
readonly overlayOutsideClick: EventEmitter<MouseEvent>;
103103
get overlayRef(): OverlayRef;

0 commit comments

Comments
 (0)