@angular-mdc/web
Version:
1,120 lines (1,111 loc) • 38.2 kB
JavaScript
/**
* @license
* Copyright (c) Dominic Carretto
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/trimox/angular-mdc-web/blob/master/LICENSE
*/
import { Directive, ElementRef, Input, Component, ViewEncapsulation, ChangeDetectionStrategy, NgZone, ContentChild, ContentChildren, Optional, Inject, ViewChild, InjectionToken, TemplateRef, Injectable, Injector, SkipSelf, NgModule } from '@angular/core';
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
import { BasePortalOutlet, CdkPortalOutlet, PortalInjector, ComponentPortal, TemplatePortal, PortalModule } from '@angular/cdk/portal';
import { Platform } from '@angular/cdk/platform';
import { Subject, merge, fromEvent, defer } from 'rxjs';
import { takeUntil, startWith } from 'rxjs/operators';
import { MDCComponent } from '@angular-mdc/web/base';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MdcButton } from '@angular-mdc/web/button';
import { MdcRipple } from '@angular-mdc/web/ripple';
import { matches, closest } from '@angular-mdc/web/dom';
import { MDCDialogFoundation, util, strings } from '@material/dialog';
import { DOCUMENT } from '@angular/common';
import { FocusTrapFactory } from '@angular/cdk/a11y';
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog-directives.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class MdcDialogAction {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
this._action = '';
}
/**
* @return {?}
*/
get action() { return this._action; }
/**
* @param {?} action
* @return {?}
*/
set action(action) {
// If the directive is set without a name (updated programatically), then this setter will
// trigger with an empty string and should not overwrite the programatically set value.
if (!action) {
return;
}
this._action = action;
this.elementRef.nativeElement.setAttribute('data-mdc-dialog-action', this._action);
}
}
MdcDialogAction.decorators = [
{ type: Directive, args: [{ selector: '[mdcDialogAction]' },] },
];
/** @nocollapse */
MdcDialogAction.ctorParameters = () => [
{ type: ElementRef }
];
MdcDialogAction.propDecorators = {
action: [{ type: Input, args: ['mdcDialogAction',] }]
};
class MdcDialogScrim {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDialogScrim.decorators = [
{ type: Directive, args: [{
selector: 'mdc-dialog-scrim',
host: { 'class': 'mdc-dialog__scrim' }
},] },
];
/** @nocollapse */
MdcDialogScrim.ctorParameters = () => [
{ type: ElementRef }
];
class MdcDialogContainer {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDialogContainer.decorators = [
{ type: Directive, args: [{
selector: '[mdcDialogContainer], mdc-dialog-container',
host: { 'class': 'mdc-dialog__container' }
},] },
];
/** @nocollapse */
MdcDialogContainer.ctorParameters = () => [
{ type: ElementRef }
];
class MdcDialogSurface {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDialogSurface.decorators = [
{ type: Directive, args: [{
selector: '[mdcDialogSurface], mdc-dialog-surface',
host: { 'class': 'mdc-dialog__surface' }
},] },
];
/** @nocollapse */
MdcDialogSurface.ctorParameters = () => [
{ type: ElementRef }
];
class MdcDialogTitle {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDialogTitle.decorators = [
{ type: Directive, args: [{
selector: '[mdcDialogTitle], mdc-dialog-title',
host: { 'class': 'mdc-dialog__title' }
},] },
];
/** @nocollapse */
MdcDialogTitle.ctorParameters = () => [
{ type: ElementRef }
];
class MdcDialogContent {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDialogContent.decorators = [
{ type: Directive, args: [{
selector: '[mdcDialogContent], mdc-dialog-content',
host: { 'class': 'mdc-dialog__content' }
},] },
];
/** @nocollapse */
MdcDialogContent.ctorParameters = () => [
{ type: ElementRef }
];
class MdcDialogActions {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
this._stacked = false;
}
/**
* @return {?}
*/
get stacked() { return this._stacked; }
/**
* @param {?} value
* @return {?}
*/
set stacked(value) {
this._stacked = coerceBooleanProperty(value);
}
}
MdcDialogActions.decorators = [
{ type: Component, args: [{selector: 'mdc-dialog-actions, [mdcDialogActions]',
template: '<ng-content></ng-content>',
exportAs: 'mdcDialogActions',
host: {
'class': 'mdc-dialog__actions',
'[class.mdc-dialog--stacked]': 'stacked'
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
},] },
];
/** @nocollapse */
MdcDialogActions.ctorParameters = () => [
{ type: ElementRef }
];
MdcDialogActions.propDecorators = {
stacked: [{ type: Input }]
};
class MdcDialogButton extends MdcButton {
constructor() {
super(...arguments);
this._default = false;
}
/**
* @return {?}
*/
get default() { return this._default; }
/**
* @param {?} value
* @return {?}
*/
set default(value) {
this._default = coerceBooleanProperty(value);
}
}
MdcDialogButton.decorators = [
{ type: Component, args: [{selector: '[mdcDialogButton]',
exportAs: 'mdcDialogButton',
host: {
'class': 'mdc-dialog__button',
'[class.mdc-button]': 'true',
'[class.mdc-dialog__button--default]': 'default'
},
template: `
<div class="mdc-button__ripple"></div>
<ng-content></ng-content>`,
providers: [MdcRipple],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
},] },
];
MdcDialogButton.propDecorators = {
default: [{ type: Input }]
};
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog-ref.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Unique id for the created dialog.
* @type {?}
*/
let uniqueId = 0;
/**
* Reference to a dialog dispatched from the MdcDialog service.
* @template T, R
*/
class MdcDialogRef {
/**
* @param {?} _overlayRef
* @param {?} _portalInstance
* @param {?=} id
*/
constructor(_overlayRef, _portalInstance, id = `mdc-dialog-${uniqueId++}`) {
this._overlayRef = _overlayRef;
this._portalInstance = _portalInstance;
this.id = id;
/**
* Subject for notifying the user that the dialog has finished opening.
*/
this._afterOpened = new Subject();
/**
* Subject for notifying the user that the dialog has started closing.
*/
this._beforeClosed = new Subject();
/**
* Subject for notifying the user that the dialog has finished closing.
*/
this._afterClosed = new Subject();
// Pass the id along to the portal.
_portalInstance._id = id;
_overlayRef.detachments().subscribe((/**
* @return {?}
*/
() => {
this._beforeClosed.next(this._result);
this._beforeClosed.complete();
this._afterClosed.next(this._result);
this._afterClosed.complete();
this.componentInstance = (/** @type {?} */ (null));
this._overlayRef.dispose();
}));
}
/**
* Close the dialog.
* @param {?=} dialogResult Optional result to return to the dialog opener.
* @return {?}
*/
close(dialogResult) {
this._result = dialogResult;
this._overlayRef.dispose();
}
/**
* Marks the dialog as opened.
* @return {?}
*/
opened() {
if (!this._afterOpened.closed) {
this._afterOpened.next();
this._afterOpened.complete();
}
}
/**
* Gets an observable that is notified when the dialog is finished opening.
* @return {?}
*/
afterOpened() {
return this._afterOpened.asObservable();
}
/**
* Gets an observable that is notified when the dialog has started closing.
* @return {?}
*/
beforeClosed() {
return this._beforeClosed.asObservable();
}
/**
* Gets an observable that is notified when the dialog is finished closing.
* @return {?}
*/
afterClosed() {
return this._afterClosed.asObservable();
}
}
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const LAYOUT_EVENTS = ['resize', 'orientationchange'];
class MdcDialogComponent extends MDCComponent {
/**
* @param {?} _ngZone
* @param {?} _platform
* @param {?} elementRef
* @param {?} dialogRef
*/
constructor(_ngZone, _platform, elementRef, dialogRef) {
super(elementRef);
this._ngZone = _ngZone;
this._platform = _platform;
this.elementRef = elementRef;
this.dialogRef = dialogRef;
/**
* Emits whenever the component is destroyed.
*/
this._destroy = new Subject();
this._scrollable = true;
this._layoutEventSubscription = null;
this.config = dialogRef._portalInstance._config;
}
/**
* Combined stream of all of the dialog layout events.
* @return {?}
*/
get layoutEvents() {
return merge(...LAYOUT_EVENTS.map((/**
* @param {?} evt
* @return {?}
*/
evt => fromEvent(window, evt))));
}
/**
* @return {?}
*/
getDefaultFoundation() {
/** @type {?} */
const adapter = {
addClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getDialog().classList.add(className)),
removeClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getDialog().classList.remove(className)),
getInitialFocusEl: (/**
* @return {?}
*/
() => this._platform.isBrowser
? (/** @type {?} */ (document.querySelector(`[cdkFocusInitial]`))) : null),
hasClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getDialog().classList.contains(className)),
addBodyClass: (/**
* @param {?} className
* @return {?}
*/
(className) => {
if (this._platform.isBrowser) {
(/** @type {?} */ (document.body)).classList.add(className);
}
}),
removeBodyClass: (/**
* @param {?} className
* @return {?}
*/
(className) => {
if (this._platform.isBrowser) {
(/** @type {?} */ (document.body)).classList.remove(className);
}
}),
eventTargetMatches: (/**
* @param {?} target
* @param {?} selector
* @return {?}
*/
(target, selector) => matches((/** @type {?} */ (target)), selector)),
trapFocus: (/**
* @return {?}
*/
() => { }),
releaseFocus: (/**
* @return {?}
*/
() => { }),
isContentScrollable: (/**
* @return {?}
*/
() => !!this._content && this._scrollable && util.isScrollable(this._content.elementRef.nativeElement)),
areButtonsStacked: (/**
* @return {?}
*/
() => util.areTopsMisaligned((/** @type {?} */ (this._buttons)))),
getActionFromEvent: (/**
* @param {?} event
* @return {?}
*/
(event) => {
/** @type {?} */
const element = closest((/** @type {?} */ (event.target)), `[${strings.ACTION_ATTRIBUTE}]`);
return element && element.getAttribute(strings.ACTION_ATTRIBUTE);
}),
clickDefaultButton: (/**
* @return {?}
*/
() => {
var _a, _b, _c;
/** @type {?} */
const defaultBtn = this._buttons.find((/**
* @param {?} _
* @return {?}
*/
_ => _.default));
(_c = (_b = (_a = defaultBtn) === null || _a === void 0 ? void 0 : _a.elementRef) === null || _b === void 0 ? void 0 : _b.nativeElement) === null || _c === void 0 ? void 0 : _c.click();
}),
reverseButtons: (/**
* @return {?}
*/
() => {
this._buttons.toArray().reverse();
this._buttons.forEach((/**
* @param {?} button
* @return {?}
*/
button => (/** @type {?} */ (button.getHostElement().parentElement)).appendChild(button.getHostElement())));
}),
notifyOpened: (/**
* @return {?}
*/
() => this.dialogRef.opened()),
notifyOpening: (/**
* @return {?}
*/
() => { }),
notifyClosed: (/**
* @param {?} action
* @return {?}
*/
(action) => this._closeDialogByRef(action)),
notifyClosing: (/**
* @return {?}
*/
() => { })
};
return new MDCDialogFoundation(adapter);
}
/**
* @return {?}
*/
ngAfterViewInit() {
this._foundation = this.getDefaultFoundation();
this._initialize();
this._loadListeners();
this._foundation.open();
}
/**
* @private
* @return {?}
*/
_initialize() {
this._scrollable = !!this.config.scrollable;
if (!this.config.clickOutsideToClose) {
this._foundation.setScrimClickAction('');
}
if (!this.config.escapeToClose) {
this._foundation.setEscapeKeyAction('');
}
if (!this.config.buttonsStacked) {
this._foundation.setAutoStackButtons(false);
}
}
/**
* @return {?}
*/
ngOnDestroy() {
var _a, _b;
this._destroy.next();
this._destroy.complete();
(_a = this._layoutEventSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
(_b = this._foundation) === null || _b === void 0 ? void 0 : _b.destroy();
}
/**
* Recalculates layout and automatically adds/removes modifier classes like --scrollable.
* @return {?}
*/
layout() {
this._foundation.layout();
}
/**
* @param {?} evt
* @return {?}
*/
_onKeydown(evt) {
this._foundation.handleKeydown(evt);
}
/**
* @param {?} evt
* @return {?}
*/
_onClick(evt) {
this._foundation.handleClick(evt);
}
/**
* @private
* @param {?=} action
* @return {?}
*/
_closeDialogByRef(action) {
this.dialogRef.close(action);
}
/**
* @private
* @return {?}
*/
_loadListeners() {
this._layoutEventSubscription = this.layoutEvents.pipe()
.subscribe((/**
* @return {?}
*/
() => this.layout()));
if (this._platform.isBrowser) {
this._ngZone.runOutsideAngular((/**
* @return {?}
*/
() => fromEvent(document, 'keydown').pipe(takeUntil(this._destroy))
.subscribe((/**
* @param {?} evt
* @return {?}
*/
evt => this._ngZone.run((/**
* @return {?}
*/
() => this._foundation.handleDocumentKeydown(evt)))))));
}
}
/**
* Retrieves the DOM element of the component host.
* @private
* @return {?}
*/
_getDialog() {
return this._elementRef.nativeElement;
}
}
MdcDialogComponent.decorators = [
{ type: Component, args: [{selector: 'mdc-dialog',
exportAs: 'mdc-dialog',
host: {
'[attr.id]': 'config?.id',
'role': 'alertdialog',
'class': 'mdc-dialog',
'[attr.aria-modal]': 'true',
'[attr.aria-labelledby]': 'config?.ariaLabel',
'[attr.aria-label]': 'config?.ariaLabel',
'[attr.aria-describedby]': 'config?.ariaDescribedBy || null',
'(click)': '_onClick($event)',
'(keydown)': '_onKeydown($event)'
},
template: `
<mdc-dialog-scrim></mdc-dialog-scrim>
<ng-content></ng-content>`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
},] },
];
/** @nocollapse */
MdcDialogComponent.ctorParameters = () => [
{ type: NgZone },
{ type: Platform },
{ type: ElementRef },
{ type: MdcDialogRef }
];
MdcDialogComponent.propDecorators = {
_surface: [{ type: ContentChild, args: [MdcDialogSurface, { static: false },] }],
_content: [{ type: ContentChild, args: [MdcDialogContent, { static: false },] }],
_buttons: [{ type: ContentChildren, args: [MdcDialogButton, { descendants: true },] }]
};
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog-config.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @template D
*/
class MdcDialogConfig {
constructor() {
/**
* ID of the element that describes the dialog.
*/
this.ariaDescribedBy = null;
/**
* Aria label to assign to the dialog element
*/
this.ariaLabel = null;
/**
* Whether the user can use escape key to close the dialog
*/
this.escapeToClose = true;
/**
* Whether the user can click outside to close the dialog
*/
this.clickOutsideToClose = true;
/**
* Applied automatically when the dialog has overflowing content to warrant scrolling.
*/
this.scrollable = true;
/**
* Applied automatically when the dialog's action buttons can't fit on a single line and must be stacked.
*/
this.buttonsStacked = true;
/**
* Whether the dialog should focus the first focusable element on open.
*/
this.autoFocus = true;
/**
* Whether the dialog should restore focus to the
* previously-focused element, after it's closed.
*/
this.restoreFocus = true;
/**
* Data to be injected into the dialog content.
*/
this.data = null;
}
}
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog-portal.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Throws an exception for the case when a ComponentPortal is
* attached to a DomPortalOutlet without an origin.
* @return {?}
*/
function throwMdcDialogContentAlreadyAttachedError() {
throw Error('Attempting to attach dialog content after content is already attached');
}
class MdcDialogPortal extends BasePortalOutlet {
/**
* @param {?} _elementRef
* @param {?} _focusTrapFactory
* @param {?} _document
* @param {?} _config
*/
constructor(_elementRef, _focusTrapFactory, _document, _config) {
super();
this._elementRef = _elementRef;
this._focusTrapFactory = _focusTrapFactory;
this._document = _document;
this._config = _config;
/**
* Element that was focused before the dialog was opened. Save this to restore upon close.
*/
this._elementFocusedBeforeDialogWasOpened = null;
/**
* A subject emitting after the dialog exits the view.
*/
this._afterExit = new Subject();
}
/**
* Attach a ComponentPortal as content to this dialog container.
* @template T
* @param {?} portal Portal to be attached as the dialog content.
* @return {?}
*/
attachComponentPortal(portal) {
if (this._portalOutlet.hasAttached()) {
throwMdcDialogContentAlreadyAttachedError();
}
this._savePreviouslyFocusedElement();
return this._portalOutlet.attachComponentPortal(portal);
}
/**
* Attach a TemplatePortal as content to this dialog container.
* @template C
* @param {?} portal Portal to be attached as the dialog content.
* @return {?}
*/
attachTemplatePortal(portal) {
if (this._portalOutlet.hasAttached()) {
throwMdcDialogContentAlreadyAttachedError();
}
this._savePreviouslyFocusedElement();
return this._portalOutlet.attachTemplatePortal(portal);
}
/**
* Moves the focus inside the focus trap.
* @return {?}
*/
trapFocus() {
/** @type {?} */
const element = this._elementRef.nativeElement;
if (!this._focusTrap) {
this._focusTrap = this._focusTrapFactory.create(element);
}
// If we were to attempt to focus immediately, then the content of the dialog would not yet be
// ready in instances where change detection has to run first. To deal with this, we simply
// wait for the microtask queue to be empty.
if (this._config.autoFocus) {
this._focusTrap.focusInitialElementWhenReady();
}
else {
/** @type {?} */
const activeElement = this._document.activeElement;
// Otherwise ensure that focus is on the dialog container. It's possible that a different
// component tried to move focus. Note that we only want to do this
// if the focus isn't inside the dialog already, because it's possible that the consumer
// turned off `autoFocus` in order to move focus themselves.
if (activeElement !== element && !element.contains(activeElement)) {
element.focus();
}
}
}
/**
* Restores focus to the element that was focused before the dialog opened.
* @return {?}
*/
restoreFocus() {
/** @type {?} */
const toFocus = this._elementFocusedBeforeDialogWasOpened;
// We need the extra check, because IE can set the `activeElement` to null in some cases.
if (this._config.restoreFocus && toFocus && typeof toFocus.focus === 'function') {
toFocus.focus();
}
if (this._focusTrap) {
this._focusTrap.destroy();
}
}
/**
* Saves a reference to the element that was focused before the dialog was opened.
* @private
* @return {?}
*/
_savePreviouslyFocusedElement() {
if (this._document) {
this._elementFocusedBeforeDialogWasOpened = (/** @type {?} */ (this._document.activeElement));
// Note that there is no focus method when rendering on the server.
if (this._elementRef.nativeElement.focus) {
// Move focus onto the dialog immediately in order to prevent the user from accidentally
// opening multiple dialogs at the same time. Needs to be async, because the element
// may not be focusable immediately.
Promise.resolve().then((/**
* @return {?}
*/
() => this._elementRef.nativeElement.focus()));
}
}
}
}
MdcDialogPortal.decorators = [
{ type: Component, args: [{selector: 'mdc-dialog-portal',
host: {
'[attr.id]': '_id'
},
template: '<ng-template cdkPortalOutlet></ng-template>',
encapsulation: ViewEncapsulation.None
},] },
];
/** @nocollapse */
MdcDialogPortal.ctorParameters = () => [
{ type: ElementRef },
{ type: FocusTrapFactory },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
{ type: MdcDialogConfig }
];
MdcDialogPortal.propDecorators = {
_portalOutlet: [{ type: ViewChild, args: [CdkPortalOutlet, { static: true },] }]
};
/**
* @fileoverview added by tsickle
* Generated from: dialog/dialog.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Injection token that can be used to access the data that was passed in to a dialog.
* @type {?}
*/
const MDC_DIALOG_DATA = new InjectionToken('MdcDialogData');
/**
* Injection token that can be used to specify default dialog options.
* @type {?}
*/
const MDC_DIALOG_DEFAULT_OPTIONS = new InjectionToken('mdc-dialog-default-options');
class MdcDialog {
/**
* @param {?} _overlay
* @param {?} _injector
* @param {?} _defaultOptions
* @param {?} _parentDialog
*/
constructor(_overlay, _injector, _defaultOptions, _parentDialog) {
this._overlay = _overlay;
this._injector = _injector;
this._defaultOptions = _defaultOptions;
this._parentDialog = _parentDialog;
this._openDialogsAtThisLevel = [];
this._afterAllClosedAtThisLevel = new Subject();
this._afterOpenedAtThisLevel = new Subject();
this._ariaHiddenElements = new Map();
/**
* Stream that emits when all open dialog have finished closing.
* Will emit on subscribe if there are no open dialogs to begin with.
*/
this.afterAllClosed = (/** @type {?} */ (defer((/**
* @return {?}
*/
() => this.openDialogs.length ?
this._afterAllClosed :
this._afterAllClosed.pipe(startWith(undefined))))));
}
/**
* Keeps track of the currently-open dialogs.
* @return {?}
*/
get openDialogs() {
return this._parentDialog ? this._parentDialog.openDialogs : this._openDialogsAtThisLevel;
}
/**
* Stream that emits when a dialog has been opened.
* @return {?}
*/
get afterOpened() {
return this._parentDialog ? this._parentDialog.afterOpened : this._afterOpenedAtThisLevel;
}
/**
* @return {?}
*/
get _afterAllClosed() {
/** @type {?} */
const parent = this._parentDialog;
return parent ? parent._afterAllClosed : this._afterAllClosedAtThisLevel;
}
/**
* Opens a modal dialog containing the given template.
* @template T, D, R
* @param {?} componentOrTemplateRef Type of the component to load into the dialog,
* or a TemplateRef to instantiate as the dialog content.
* @param {?=} config Extra configuration options.
* @return {?} Reference to the newly-opened dialog.
*/
open(componentOrTemplateRef, config) {
config = _applyConfigDefaults(config, this._defaultOptions || new MdcDialogConfig());
if (config.id && this.getDialogById(config.id)) {
throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
}
/** @type {?} */
const overlayRef = this._createOverlay();
/** @type {?} */
const dialogContainer = this._attachDialogContainer(overlayRef, config);
/** @type {?} */
const dialogRef = this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config);
this.openDialogs.push(dialogRef);
dialogRef.afterClosed().subscribe((/**
* @return {?}
*/
() => this._removeOpenDialog(dialogRef, dialogContainer)));
this.afterOpened.next(dialogRef);
return dialogRef;
}
/**
* Closes all of the currently-open dialogs.
* @return {?}
*/
closeAll() {
this._closeDialogs(this.openDialogs);
}
/**
* Finds an open dialog by its id.
* @param {?} id ID to use when looking up the dialog.
* @return {?}
*/
getDialogById(id) {
return this.openDialogs.find((/**
* @param {?} dialog
* @return {?}
*/
dialog => dialog.id === id));
}
/**
* @return {?}
*/
ngOnDestroy() {
// Only close the dialogs at this level on destroy
// since the parent service may still be active.
this._closeDialogs(this._openDialogsAtThisLevel);
this._afterAllClosedAtThisLevel.complete();
this._afterOpenedAtThisLevel.complete();
}
/**
* Creates the overlay into which the dialog will be loaded.
* @private
* @return {?} A promise resolving to the OverlayRef for the created overlay.
*/
_createOverlay() {
return this._overlay.create();
}
/**
* Attaches an MdcDialogPortal to a dialog's already-created overlay.
* @private
* @param {?} overlay Reference to the dialog's underlying overlay.
* @param {?} config The dialog configuration.
* @return {?} A promise resolving to a ComponentRef for the attached container.
*/
_attachDialogContainer(overlay, config) {
/** @type {?} */
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
/** @type {?} */
const injector = new PortalInjector(userInjector || this._injector, new WeakMap([
[MdcDialogConfig, config]
]));
/** @type {?} */
const containerPortal = new ComponentPortal(MdcDialogPortal, config.viewContainerRef, injector, config.componentFactoryResolver);
/** @type {?} */
const containerRef = overlay.attach(containerPortal);
return containerRef.instance;
}
/**
* Attaches the user-provided component to the already-created MdcDialogPortal.
* @private
* @template T, R
* @param {?} componentOrTemplateRef The type of component being loaded into the dialog,
* or a TemplateRef to instantiate as the content.
* @param {?} dialogContainer Reference to the wrapping MdcDialogPortal.
* @param {?} overlayRef Reference to the overlay in which the dialog resides.
* @param {?} config The dialog configuration.
* @return {?} A promise resolving to the MdcDialogRef that should be returned to the user.
*/
_attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config) {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
/** @type {?} */
const dialogRef = new MdcDialogRef(overlayRef, dialogContainer, config.id);
if (componentOrTemplateRef instanceof TemplateRef) {
dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, (/** @type {?} */ (null)), (/** @type {?} */ ({ $implicit: config.data, dialogRef }))));
}
else {
/** @type {?} */
const injector = this._createInjector(config, dialogRef, dialogContainer);
/** @type {?} */
const contentRef = dialogContainer.attachComponentPortal(new ComponentPortal(componentOrTemplateRef, config.viewContainerRef, injector));
dialogRef.componentInstance = contentRef.instance;
}
return dialogRef;
}
/**
* Creates a custom injector to be used inside the dialog. This allows a component loaded inside
* of a dialog to close itself and, optionally, to return a value.
* @private
* @template T
* @param {?} config Config object that is used to construct the dialog.
* @param {?} dialogRef Reference to the dialog.
* @param {?} portalContainer
* @return {?} The custom injector that can be used inside the dialog.
*/
_createInjector(config, dialogRef, portalContainer) {
/** @type {?} */
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
portalContainer.trapFocus();
// The MdcDialogPortal is injected in the portal as the MdcDialogPortal and the dialog's
// content are created out of the same ViewContainerRef and as such, are siblings for injector
// purposes. To allow the hierarchy that is expected, the MdcDialogPortal is explicitly
// added to the injection tokens.
/** @type {?} */
const injectionTokens = new WeakMap([
[MdcDialogPortal, portalContainer],
[MDC_DIALOG_DATA, config.data],
[MdcDialogRef, dialogRef]
]);
return new PortalInjector(userInjector || this._injector, injectionTokens);
}
/**
* Removes a dialog from the array of open dialogs.
* @private
* @param {?} dialogRef Dialog to be removed.
* @param {?} dialogContainer
* @return {?}
*/
_removeOpenDialog(dialogRef, dialogContainer) {
/** @type {?} */
const index = this.openDialogs.indexOf(dialogRef);
if (index > -1) {
dialogContainer.restoreFocus();
this.openDialogs.splice(index, 1);
// If all the dialogs were closed, remove/restore the `aria-hidden`
// to a the siblings and emit to the `afterAllClosed` stream.
if (!this.openDialogs.length) {
this._ariaHiddenElements.forEach((/**
* @param {?} previousValue
* @param {?} element
* @return {?}
*/
(previousValue, element) => {
if (previousValue) {
element.setAttribute('aria-hidden', previousValue);
}
else {
element.removeAttribute('aria-hidden');
}
}));
this._ariaHiddenElements.clear();
this._afterAllClosed.next();
}
}
}
/**
* Closes all of the dialogs in an array.
* @private
* @param {?} dialogs
* @return {?}
*/
_closeDialogs(dialogs) {
/** @type {?} */
let i = dialogs.length;
while (i--) {
// The `_openDialogs` property isn't updated after close until the rxjs subscription
// runs on the next microtask, in addition to modifying the array as we're going
// through it. We loop through all of them and call close without assuming that
// they'll be removed from the list instantaneously.
dialogs[i].close();
}
}
}
MdcDialog.decorators = [
{ type: Injectable },
];
/** @nocollapse */
MdcDialog.ctorParameters = () => [
{ type: Overlay },
{ type: Injector },
{ type: MdcDialogConfig, decorators: [{ type: Optional }, { type: Inject, args: [MDC_DIALOG_DEFAULT_OPTIONS,] }] },
{ type: MdcDialog, decorators: [{ type: Optional }, { type: SkipSelf }] }
];
/**
* Applies default options to the dialog config.
* @param {?=} config Config to be modified.
* @param {?=} defaultOptions Default options provided.
* @return {?} The new configuration object.
*/
function _applyConfigDefaults(config, defaultOptions) {
return Object.assign(Object.assign({}, defaultOptions), config);
}
/**
* @fileoverview added by tsickle
* Generated from: dialog/module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const DIALOG_DECLARATIONS = [
MdcDialogAction,
MdcDialogActions,
MdcDialogButton,
MdcDialogComponent,
MdcDialogContainer,
MdcDialogPortal,
MdcDialogContent,
MdcDialogScrim,
MdcDialogSurface,
MdcDialogSurface,
MdcDialogTitle
];
class MdcDialogModule {
}
MdcDialogModule.decorators = [
{ type: NgModule, args: [{
imports: [
OverlayModule,
PortalModule
],
exports: DIALOG_DECLARATIONS,
declarations: DIALOG_DECLARATIONS,
providers: [MdcDialog],
entryComponents: [
MdcDialogPortal,
MdcDialogComponent
]
},] },
];
export { MDC_DIALOG_DATA, MDC_DIALOG_DEFAULT_OPTIONS, MdcDialog, MdcDialogAction, MdcDialogActions, MdcDialogButton, MdcDialogComponent, MdcDialogConfig, MdcDialogContainer, MdcDialogContent, MdcDialogModule, MdcDialogPortal, MdcDialogRef, MdcDialogScrim, MdcDialogSurface, MdcDialogTitle, throwMdcDialogContentAlreadyAttachedError };
//# sourceMappingURL=dialog.js.map