mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(overlay): support all overlay config properties (#1591)
This commit is contained in:
@@ -15,8 +15,9 @@
|
||||
Open menu
|
||||
</button>
|
||||
|
||||
<template connected-overlay [origin]="trigger">
|
||||
<div style="background-color: mediumpurple" *ngIf="isMenuOpen">
|
||||
<template connected-overlay [origin]="trigger" [width]="500" hasBackdrop [open]="isMenuOpen"
|
||||
(backdropClick)="isMenuOpen=false">
|
||||
<div style="background-color: mediumpurple" >
|
||||
This is the menu panel.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,38 +27,120 @@ describe('Overlay directives', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it(`should create an overlay and attach the directive's template`, () => {
|
||||
it(`should attach the overlay based on the open property`, () => {
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(overlayContainerElement.textContent).toContain('Menu content');
|
||||
|
||||
fixture.componentInstance.isOpen = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(overlayContainerElement.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should destroy the overlay when the directive is destroyed', () => {
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
fixture.destroy();
|
||||
|
||||
expect(overlayContainerElement.textContent.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should use a connected position strategy with a default set of positions', () => {
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let testComponent: ConnectedOverlayDirectiveTest =
|
||||
fixture.debugElement.componentInstance;
|
||||
let overlayDirective = testComponent.connectedOverlayDirective;
|
||||
|
||||
let strategy =
|
||||
<ConnectedPositionStrategy> overlayDirective.overlayRef.getState().positionStrategy;
|
||||
expect(strategy) .toEqual(jasmine.any(ConnectedPositionStrategy));
|
||||
expect(strategy).toEqual(jasmine.any(ConnectedPositionStrategy));
|
||||
|
||||
let positions = strategy.positions;
|
||||
expect(positions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
describe('inputs', () => {
|
||||
|
||||
it('should set the width', () => {
|
||||
fixture.componentInstance.width = 250;
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const pane = overlayContainerElement.children[0] as HTMLElement;
|
||||
expect(pane.style.width).toEqual('250px');
|
||||
});
|
||||
|
||||
it('should set the height', () => {
|
||||
fixture.componentInstance.height = '100vh';
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const pane = overlayContainerElement.children[0] as HTMLElement;
|
||||
expect(pane.style.height).toEqual('100vh');
|
||||
});
|
||||
|
||||
it('should create the backdrop if designated', () => {
|
||||
fixture.componentInstance.hasBackdrop = true;
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop');
|
||||
expect(backdrop).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not create the backdrop by default', () => {
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop');
|
||||
expect(backdrop).toBeNull();
|
||||
});
|
||||
|
||||
it('should set the custom backdrop class', () => {
|
||||
fixture.componentInstance.hasBackdrop = true;
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
|
||||
expect(backdrop.classList).toContain('md-test-class');
|
||||
});
|
||||
|
||||
it('should emit backdropClick appropriately', () => {
|
||||
fixture.componentInstance.hasBackdrop = true;
|
||||
fixture.componentInstance.isOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
|
||||
backdrop.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.backdropClicked).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<button overlay-origin #trigger="overlayOrigin">Toggle menu</button>
|
||||
<template connected-overlay [origin]="trigger">
|
||||
<template connected-overlay [origin]="trigger" [open]="isOpen" [width]="width" [height]="height"
|
||||
[hasBackdrop]="hasBackdrop" backdropClass="md-test-class"
|
||||
(backdropClick)="backdropClicked=true">
|
||||
<p>Menu content</p>
|
||||
</template>`,
|
||||
})
|
||||
class ConnectedOverlayDirectiveTest {
|
||||
isOpen = false;
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
hasBackdrop: boolean;
|
||||
backdropClicked = false;
|
||||
|
||||
@ViewChild(ConnectedOverlayDirective) connectedOverlayDirective: ConnectedOverlayDirective;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import {
|
||||
NgModule,
|
||||
ModuleWithProviders,
|
||||
Directive,
|
||||
EventEmitter,
|
||||
TemplateRef,
|
||||
ViewContainerRef,
|
||||
OnInit,
|
||||
Input,
|
||||
OnDestroy,
|
||||
Output,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
import {Overlay, OVERLAY_PROVIDERS} from './overlay';
|
||||
@@ -15,7 +16,8 @@ import {TemplatePortal} from '../portal/portal';
|
||||
import {OverlayState} from './overlay-state';
|
||||
import {ConnectionPositionPair} from './position/connected-position';
|
||||
import {PortalModule} from '../portal/portal-directives';
|
||||
|
||||
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
|
||||
let defaultPositionList = [
|
||||
@@ -50,15 +52,52 @@ export class OverlayOrigin {
|
||||
* Directive to facilitate declarative creation of an Overlay using a ConnectedPositionStrategy.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[connected-overlay]'
|
||||
selector: '[connected-overlay]',
|
||||
exportAs: 'connectedOverlay'
|
||||
})
|
||||
export class ConnectedOverlayDirective implements OnInit, OnDestroy {
|
||||
export class ConnectedOverlayDirective implements OnDestroy {
|
||||
private _overlayRef: OverlayRef;
|
||||
private _templatePortal: TemplatePortal;
|
||||
private _open = false;
|
||||
private _hasBackdrop = false;
|
||||
private _backdropSubscription: Subscription;
|
||||
|
||||
@Input() origin: OverlayOrigin;
|
||||
@Input() positions: ConnectionPositionPair[];
|
||||
|
||||
/** The width of the overlay panel. */
|
||||
@Input() width: number | string;
|
||||
|
||||
/** The height of the overlay panel. */
|
||||
@Input() height: number | string;
|
||||
|
||||
/** The custom class to be set on the backdrop element. */
|
||||
@Input() backdropClass: string;
|
||||
|
||||
/** Whether or not the overlay should attach a backdrop. */
|
||||
@Input()
|
||||
get hasBackdrop() {
|
||||
return this._hasBackdrop;
|
||||
}
|
||||
|
||||
// TODO: move the boolean coercion logic to a shared function in core
|
||||
set hasBackdrop(value: any) {
|
||||
this._hasBackdrop = value != null && `${value}` !== 'false';
|
||||
}
|
||||
|
||||
@Input()
|
||||
get open() {
|
||||
return this._open;
|
||||
}
|
||||
|
||||
set open(value: boolean) {
|
||||
value ? this._attachOverlay() : this._detachOverlay();
|
||||
this._open = value;
|
||||
}
|
||||
|
||||
/** Event emitted when the backdrop is clicked. */
|
||||
@Output() backdropClick: EventEmitter<null> = new EventEmitter();
|
||||
|
||||
// TODO(jelbourn): inputs for size, scroll behavior, animation, etc.
|
||||
|
||||
constructor(
|
||||
@@ -68,40 +107,93 @@ export class ConnectedOverlayDirective implements OnInit, OnDestroy {
|
||||
this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
|
||||
}
|
||||
|
||||
get overlayRef() {
|
||||
get overlayRef(): OverlayRef {
|
||||
return this._overlayRef;
|
||||
}
|
||||
|
||||
/** TODO: internal */
|
||||
ngOnInit() {
|
||||
this._createOverlay();
|
||||
}
|
||||
|
||||
/** TODO: internal */
|
||||
ngOnDestroy() {
|
||||
this._destroyOverlay();
|
||||
}
|
||||
|
||||
/** Creates an overlay and attaches this directive's template to it. */
|
||||
/** Creates an overlay */
|
||||
private _createOverlay() {
|
||||
if (!this.positions || !this.positions.length) {
|
||||
this.positions = defaultPositionList;
|
||||
}
|
||||
|
||||
let overlayConfig = new OverlayState();
|
||||
overlayConfig.positionStrategy =
|
||||
this._overlay.position().connectedTo(
|
||||
this.origin.elementRef,
|
||||
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
|
||||
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
|
||||
this._overlayRef = this._overlay.create(this._buildConfig());
|
||||
}
|
||||
|
||||
this._overlayRef = this._overlay.create(overlayConfig);
|
||||
this._overlayRef.attach(this._templatePortal);
|
||||
/** Builds the overlay config based on the directive's inputs */
|
||||
private _buildConfig(): OverlayState {
|
||||
let overlayConfig = new OverlayState();
|
||||
|
||||
if (this.width || this.width === 0) {
|
||||
overlayConfig.width = this.width;
|
||||
}
|
||||
|
||||
if (this.height || this.height === 0) {
|
||||
overlayConfig.height = this.height;
|
||||
}
|
||||
|
||||
overlayConfig.hasBackdrop = this.hasBackdrop;
|
||||
|
||||
if (this.backdropClass) {
|
||||
overlayConfig.backdropClass = this.backdropClass;
|
||||
}
|
||||
|
||||
overlayConfig.positionStrategy = this._getPosition();
|
||||
|
||||
return overlayConfig;
|
||||
}
|
||||
|
||||
/** Returns the position of the overlay to be set on the overlay config */
|
||||
private _getPosition(): ConnectedPositionStrategy {
|
||||
return this._overlay.position().connectedTo(
|
||||
this.origin.elementRef,
|
||||
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
|
||||
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
|
||||
}
|
||||
|
||||
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
|
||||
private _attachOverlay() {
|
||||
if (!this._overlayRef) {
|
||||
this._createOverlay();
|
||||
}
|
||||
|
||||
if (!this._overlayRef.hasAttached()) {
|
||||
this._overlayRef.attach(this._templatePortal);
|
||||
}
|
||||
|
||||
if (this.hasBackdrop) {
|
||||
this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => {
|
||||
this.backdropClick.emit(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */
|
||||
private _detachOverlay() {
|
||||
if (this._overlayRef) {
|
||||
this._overlayRef.detach();
|
||||
}
|
||||
|
||||
if (this._backdropSubscription) {
|
||||
this._backdropSubscription.unsubscribe();
|
||||
this._backdropSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroys the overlay created by this directive. */
|
||||
private _destroyOverlay() {
|
||||
this._overlayRef.dispose();
|
||||
if (this._overlayRef) {
|
||||
this._overlayRef.dispose();
|
||||
}
|
||||
|
||||
if (this._backdropSubscription) {
|
||||
this._backdropSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user