UNPKG

@angular-mdc/web

Version:
1,120 lines (1,111 loc) 38.2 kB
/** * @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