Skip to content

Commit f6d0e45

Browse files
committed
feat(connected-overlay): support all overlay config properties
1 parent 1ad457b commit f6d0e45

File tree

3 files changed

+199
-25
lines changed

3 files changed

+199
-25
lines changed

src/demo-app/overlay/overlay-demo.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
Open menu
1616
</button>
1717

18-
<template connected-overlay [origin]="trigger">
19-
<div style="background-color: mediumpurple" *ngIf="isMenuOpen">
18+
<template connected-overlay [origin]="trigger" [width]="500" hasBackdrop [open]="isMenuOpen"
19+
(backdropClick)="isMenuOpen=false">
20+
<div style="background-color: mediumpurple" >
2021
This is the menu panel.
2122
</div>
2223
</template>

src/lib/core/overlay/overlay-directives.spec.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,120 @@ describe('Overlay directives', () => {
2727
fixture.detectChanges();
2828
});
2929

30-
it(`should create an overlay and attach the directive's template`, () => {
30+
it(`should attach the overlay based on the open property`, () => {
31+
fixture.componentInstance.isOpen = true;
32+
fixture.detectChanges();
33+
3134
expect(overlayContainerElement.textContent).toContain('Menu content');
35+
36+
fixture.componentInstance.isOpen = false;
37+
fixture.detectChanges();
38+
39+
expect(overlayContainerElement.textContent).toBe('');
3240
});
3341

3442
it('should destroy the overlay when the directive is destroyed', () => {
43+
fixture.componentInstance.isOpen = true;
44+
fixture.detectChanges();
3545
fixture.destroy();
3646

3747
expect(overlayContainerElement.textContent.trim()).toBe('');
3848
});
3949

4050
it('should use a connected position strategy with a default set of positions', () => {
51+
fixture.componentInstance.isOpen = true;
52+
fixture.detectChanges();
53+
4154
let testComponent: ConnectedOverlayDirectiveTest =
4255
fixture.debugElement.componentInstance;
4356
let overlayDirective = testComponent.connectedOverlayDirective;
4457

4558
let strategy =
4659
<ConnectedPositionStrategy> overlayDirective.overlayRef.getState().positionStrategy;
47-
expect(strategy) .toEqual(jasmine.any(ConnectedPositionStrategy));
60+
expect(strategy).toEqual(jasmine.any(ConnectedPositionStrategy));
4861

4962
let positions = strategy.positions;
5063
expect(positions.length).toBeGreaterThan(0);
5164
});
65+
66+
describe('inputs', () => {
67+
68+
it('should set the width', () => {
69+
fixture.componentInstance.width = 250;
70+
fixture.componentInstance.isOpen = true;
71+
fixture.detectChanges();
72+
73+
const pane = overlayContainerElement.children[0] as HTMLElement;
74+
expect(pane.style.width).toEqual('250px');
75+
});
76+
77+
it('should set the height', () => {
78+
fixture.componentInstance.height = '100vh';
79+
fixture.componentInstance.isOpen = true;
80+
fixture.detectChanges();
81+
82+
const pane = overlayContainerElement.children[0] as HTMLElement;
83+
expect(pane.style.height).toEqual('100vh');
84+
});
85+
86+
it('should create the backdrop if designated', () => {
87+
fixture.componentInstance.hasBackdrop = true;
88+
fixture.componentInstance.isOpen = true;
89+
fixture.detectChanges();
90+
91+
let backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop');
92+
expect(backdrop).toBeTruthy();
93+
});
94+
95+
it('should not create the backdrop by default', () => {
96+
fixture.componentInstance.isOpen = true;
97+
fixture.detectChanges();
98+
99+
let backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop');
100+
expect(backdrop).toBeNull();
101+
});
102+
103+
it('should set the custom backdrop class', () => {
104+
fixture.componentInstance.hasBackdrop = true;
105+
fixture.componentInstance.isOpen = true;
106+
fixture.detectChanges();
107+
108+
const backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
109+
expect(backdrop.classList).toContain('md-test-class');
110+
});
111+
112+
it('should emit backdropClick appropriately', () => {
113+
fixture.componentInstance.hasBackdrop = true;
114+
fixture.componentInstance.isOpen = true;
115+
fixture.detectChanges();
116+
117+
const backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
118+
backdrop.click();
119+
fixture.detectChanges();
120+
121+
expect(fixture.componentInstance.backdropClicked).toBe(true);
122+
});
123+
124+
});
125+
52126
});
53127

54128

55129
@Component({
56130
template: `
57131
<button overlay-origin #trigger="overlayOrigin">Toggle menu</button>
58-
<template connected-overlay [origin]="trigger">
132+
<template connected-overlay [origin]="trigger" [open]="isOpen" [width]="width" [height]="height"
133+
[hasBackdrop]="hasBackdrop" backdropClass="md-test-class"
134+
(backdropClick)="backdropClicked=true">
59135
<p>Menu content</p>
60136
</template>`,
61137
})
62138
class ConnectedOverlayDirectiveTest {
139+
isOpen = false;
140+
width: number | string;
141+
height: number | string;
142+
hasBackdrop: boolean;
143+
backdropClicked = false;
144+
63145
@ViewChild(ConnectedOverlayDirective) connectedOverlayDirective: ConnectedOverlayDirective;
64146
}

src/lib/core/overlay/overlay-directives.ts

Lines changed: 111 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import {
22
NgModule,
33
ModuleWithProviders,
44
Directive,
5+
EventEmitter,
56
TemplateRef,
67
ViewContainerRef,
7-
OnInit,
88
Input,
99
OnDestroy,
10+
Output,
1011
ElementRef
1112
} from '@angular/core';
1213
import {Overlay, OVERLAY_PROVIDERS} from './overlay';
@@ -15,7 +16,8 @@ import {TemplatePortal} from '../portal/portal';
1516
import {OverlayState} from './overlay-state';
1617
import {ConnectionPositionPair} from './position/connected-position';
1718
import {PortalModule} from '../portal/portal-directives';
18-
19+
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
20+
import {Subscription} from 'rxjs/Subscription';
1921

2022
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
2123
let defaultPositionList = [
@@ -50,15 +52,51 @@ export class OverlayOrigin {
5052
* Directive to facilitate declarative creation of an Overlay using a ConnectedPositionStrategy.
5153
*/
5254
@Directive({
53-
selector: '[connected-overlay]'
55+
selector: '[connected-overlay]',
56+
exportAs: 'connectedOverlay'
5457
})
55-
export class ConnectedOverlayDirective implements OnInit, OnDestroy {
58+
export class ConnectedOverlayDirective implements OnDestroy {
5659
private _overlayRef: OverlayRef;
5760
private _templatePortal: TemplatePortal;
61+
private _open = false;
62+
private _hasBackdrop = false;
63+
private _backdropSubscription: Subscription;
5864

5965
@Input() origin: OverlayOrigin;
6066
@Input() positions: ConnectionPositionPair[];
6167

68+
/** The width of the overlay panel. */
69+
@Input() width: number | string;
70+
71+
/** The height of the overlay panel. */
72+
@Input() height: number | string;
73+
74+
/** The custom class to be set on the backdrop element. */
75+
@Input() backdropClass: string;
76+
77+
/** Whether or not the overlay should attach a backdrop. */
78+
@Input()
79+
get hasBackdrop() {
80+
return this._hasBackdrop;
81+
}
82+
83+
set hasBackdrop(value: any) {
84+
this._hasBackdrop = value === '' || (!!value && value !== 'false');
85+
}
86+
87+
@Input()
88+
get open() {
89+
return this._open;
90+
}
91+
92+
set open(value: boolean) {
93+
value ? this._attachOverlay() : this._detachOverlay();
94+
this._open = value;
95+
}
96+
97+
/** Event emitted when the backdrop is clicked. */
98+
@Output() backdropClick = new EventEmitter();
99+
62100
// TODO(jelbourn): inputs for size, scroll behavior, animation, etc.
63101

64102
constructor(
@@ -68,40 +106,93 @@ export class ConnectedOverlayDirective implements OnInit, OnDestroy {
68106
this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
69107
}
70108

71-
get overlayRef() {
109+
get overlayRef(): OverlayRef {
72110
return this._overlayRef;
73111
}
74112

75-
/** TODO: internal */
76-
ngOnInit() {
77-
this._createOverlay();
78-
}
79-
80113
/** TODO: internal */
81114
ngOnDestroy() {
82115
this._destroyOverlay();
83116
}
84117

85-
/** Creates an overlay and attaches this directive's template to it. */
118+
/** Creates an overlay */
86119
private _createOverlay() {
87120
if (!this.positions || !this.positions.length) {
88121
this.positions = defaultPositionList;
89122
}
90123

124+
this._overlayRef = this._overlay.create(this._buildConfig());
125+
}
126+
127+
/** Builds the overlay config based on the directive's inputs */
128+
private _buildConfig(): OverlayState {
91129
let overlayConfig = new OverlayState();
92-
overlayConfig.positionStrategy =
93-
this._overlay.position().connectedTo(
94-
this.origin.elementRef,
95-
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
96-
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
97-
98-
this._overlayRef = this._overlay.create(overlayConfig);
99-
this._overlayRef.attach(this._templatePortal);
130+
131+
if (this.width || this.width === 0) {
132+
overlayConfig.width = this.width;
133+
}
134+
135+
if (this.height || this.height === 0) {
136+
overlayConfig.height = this.height;
137+
}
138+
139+
overlayConfig.hasBackdrop = this.hasBackdrop;
140+
141+
if (this.backdropClass) {
142+
overlayConfig.backdropClass = this.backdropClass;
143+
}
144+
145+
overlayConfig.positionStrategy = this._getPosition();
146+
147+
return overlayConfig;
148+
}
149+
150+
/** Returns the position of the overlay to be set on the overlay config */
151+
private _getPosition(): ConnectedPositionStrategy {
152+
return this._overlay.position().connectedTo(
153+
this.origin.elementRef,
154+
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
155+
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
156+
}
157+
158+
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
159+
private _attachOverlay() {
160+
if (!this._overlayRef) {
161+
this._createOverlay();
162+
}
163+
164+
if (!this._overlayRef.hasAttached()) {
165+
this._overlayRef.attach(this._templatePortal);
166+
}
167+
168+
if (this.hasBackdrop) {
169+
this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => {
170+
this.backdropClick.emit();
171+
});
172+
}
173+
}
174+
175+
/** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */
176+
private _detachOverlay() {
177+
if (this._overlayRef) {
178+
this._overlayRef.detach();
179+
}
180+
181+
if (this._backdropSubscription) {
182+
this._backdropSubscription.unsubscribe();
183+
this._backdropSubscription = null;
184+
}
100185
}
101186

102187
/** Destroys the overlay created by this directive. */
103188
private _destroyOverlay() {
104-
this._overlayRef.dispose();
189+
if (this._overlayRef) {
190+
this._overlayRef.dispose();
191+
}
192+
193+
if (this._backdropSubscription) {
194+
this._backdropSubscription.unsubscribe();
195+
}
105196
}
106197
}
107198

0 commit comments

Comments
 (0)