@angular-mdc/web
Version:
582 lines (577 loc) • 18.7 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 { Component, ChangeDetectionStrategy, ViewEncapsulation, ElementRef, Input, EventEmitter, NgZone, ChangeDetectorRef, Optional, Inject, Output, ContentChild, Directive, NgModule } from '@angular/core';
import { DOCUMENT, CommonModule } from '@angular/common';
import { FocusTrapFactory, FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Platform } from '@angular/cdk/platform';
import { Subject, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MDCComponent } from '@angular-mdc/web/base';
import { MdcList } from '@angular-mdc/web/list';
import { MDCModalDrawerFoundation, MDCDismissibleDrawerFoundation, cssClasses } from '@material/drawer';
/**
* @fileoverview added by tsickle
* Generated from: drawer/drawer.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class MdcDrawerHeader {
/**
* @param {?} elementRef
*/
constructor(elementRef) {
this.elementRef = elementRef;
}
}
MdcDrawerHeader.decorators = [
{ type: Component, args: [{selector: 'mdc-drawer-header',
template: `
<ng-content></ng-content>
<h3 class="mdc-drawer__title" *ngIf="title">{{title}}</h3>
<h6 class="mdc-drawer__subtitle" *ngIf="subtitle">{{subtitle}}</h6>`,
host: { 'class': 'mdc-drawer__header' },
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
},] },
];
/** @nocollapse */
MdcDrawerHeader.ctorParameters = () => [
{ type: ElementRef }
];
MdcDrawerHeader.propDecorators = {
title: [{ type: Input }],
subtitle: [{ type: Input }]
};
class MdcDrawer extends MDCComponent {
/**
* @param {?} _platform
* @param {?} _ngZone
* @param {?} _changeDetectorRef
* @param {?} _focusTrapFactory
* @param {?} _focusMonitor
* @param {?} _document
* @param {?} elementRef
*/
constructor(_platform, _ngZone, _changeDetectorRef, _focusTrapFactory, _focusMonitor, _document, elementRef) {
super(elementRef);
this._platform = _platform;
this._ngZone = _ngZone;
this._changeDetectorRef = _changeDetectorRef;
this._focusTrapFactory = _focusTrapFactory;
this._focusMonitor = _focusMonitor;
this._document = _document;
this.elementRef = elementRef;
/**
* Emits when the component is destroyed.
*/
this._destroyed = new Subject();
this._scrimElement = null;
/**
* Element that was focused before the drawer was opened. Save this to restore upon close.
*/
this._elementFocusedBeforeDrawerWasOpened = null;
this._open = false;
this._drawer = '';
this._autoFocus = true;
this._restoreFocus = true;
this.opened = new EventEmitter();
this.closed = new EventEmitter();
/**
* Event emitted when the drawer open state is changed.
*/
this.openedChange = new EventEmitter(/* isAsync */ true);
/**
* Event emitted when the drawer variant is changed.
*/
this.drawerChange = new EventEmitter(/* isAsync */ true);
this._scrimSubscription = null;
this.openedChange.subscribe((/**
* @param {?} opened
* @return {?}
*/
(opened) => {
if (opened) {
if (this._document) {
this._elementFocusedBeforeDrawerWasOpened = (/** @type {?} */ (this._document.activeElement));
}
if (this._isFocusTrapEnabled && this._focusTrap) {
this._trapFocus();
}
}
else {
this._releaseFocus();
}
}));
this.drawerChange.subscribe((/**
* @return {?}
*/
() => this._initFoundation()));
/**
* Listen to `keydown` events outside the zone so that change detection is not run every
* time a key is pressed. Instead we re-enter the zone only if the `ESC` key is pressed
* and we don't have close disabled.
*/
this._ngZone.runOutsideAngular((/**
* @return {?}
*/
() => {
((/** @type {?} */ (fromEvent(this._elementRef.nativeElement, 'keydown'))))
.pipe(takeUntil(this._destroyed)).subscribe((/**
* @param {?} event
* @return {?}
*/
event => this._ngZone.run((/**
* @return {?}
*/
() => {
this._foundation.handleKeydown(event);
if (this.modal) {
event.stopPropagation();
event.preventDefault();
}
}))));
}));
}
/**
* @return {?}
*/
get open() {
return this._open;
}
/**
* @param {?} value
* @return {?}
*/
set open(value) {
if (this._platform.isBrowser && this._open !== value) {
this._open = coerceBooleanProperty(value);
this._open ? this._foundation.open() : this._foundation.close();
this.openedChange.emit(this._open);
this._updateFocusTrapState();
this._changeDetectorRef.markForCheck();
}
}
/**
* @return {?}
*/
get drawer() {
return this._drawer;
}
/**
* @param {?} drawer
* @return {?}
*/
set drawer(drawer) {
if (this._drawer !== drawer) {
this._drawer = drawer;
this.drawerChange.emit();
this._updateFocusTrapState();
}
}
/**
* @return {?}
*/
get autoFocus() {
return this._autoFocus;
}
/**
* @param {?} value
* @return {?}
*/
set autoFocus(value) {
this._autoFocus = coerceBooleanProperty(value);
}
/**
* @return {?}
*/
get restoreFocus() {
return this._restoreFocus;
}
/**
* @param {?} value
* @return {?}
*/
set restoreFocus(value) {
this._restoreFocus = coerceBooleanProperty(value);
}
/**
* @return {?}
*/
get fixedAdjustElement() {
return this._fixedAdjustElement;
}
/**
* @param {?} element
* @return {?}
*/
set fixedAdjustElement(element) {
this._fixedAdjustElement = element;
element ? this._getHostElement().style.setProperty('position', 'absolute') :
this._getHostElement().style.removeProperty('position');
this._changeDetectorRef.markForCheck();
}
/**
* @return {?}
*/
get modal() {
return this.drawer === 'modal';
}
/**
* @return {?}
*/
get dismissible() {
return this.drawer === 'dismissible';
}
/**
* @return {?}
*/
get _isFocusTrapEnabled() {
// The focus trap is only enabled when the drawer is open and modal.
return this.open && this.modal;
}
/**
* @return {?}
*/
getDefaultFoundation() {
/** @type {?} */
const adapter = {
addClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getHostElement().classList.add(className)),
removeClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getHostElement().classList.remove(className)),
hasClass: (/**
* @param {?} className
* @return {?}
*/
(className) => this._getHostElement().classList.contains(className)),
elementHasClass: (/**
* @param {?} element
* @param {?} className
* @return {?}
*/
(element, className) => element.classList.contains(className)),
saveFocus: (/**
* @return {?}
*/
() => this._savePreviouslyFocusedElement()),
restoreFocus: (/**
* @return {?}
*/
() => this._releaseFocus()),
focusActiveNavigationItem: (/**
* @return {?}
*/
() => {
var _a;
if (!this._platform.isBrowser || !this._list || !this._autoFocus) {
return;
}
/** @type {?} */
const selectedItem = this._list.getSelectedItem();
if (selectedItem) {
selectedItem.focus();
}
else {
/** @type {?} */
const cdkInitialItem = this._platform.isBrowser ?
(/** @type {?} */ (document.querySelector(`[cdkFocusInitial]`))) : null;
(_a = cdkInitialItem) === null || _a === void 0 ? void 0 : _a.focus();
}
}),
notifyClose: (/**
* @return {?}
*/
() => this.closed.emit()),
notifyOpen: (/**
* @return {?}
*/
() => this.opened.emit()),
trapFocus: (/**
* @return {?}
*/
() => { }),
releaseFocus: (/**
* @return {?}
*/
() => this._releaseFocus())
};
return this.modal ? new MDCModalDrawerFoundation(adapter) : new MDCDismissibleDrawerFoundation(adapter);
}
/**
* @return {?}
*/
ngAfterContentInit() {
this._initListType();
}
/**
* @return {?}
*/
ngOnDestroy() {
var _a, _b, _c;
this.open = false;
(_a = this._focusTrap) === null || _a === void 0 ? void 0 : _a.destroy();
(_b = this._scrimElement) === null || _b === void 0 ? void 0 : _b.remove();
(_c = this._scrimSubscription) === null || _c === void 0 ? void 0 : _c.unsubscribe();
this._destroyed.next();
this._destroyed.complete();
if (this._foundation && this._platform.isBrowser) {
this._foundation.destroy();
}
}
/**
* @param {?} event
* @return {?}
*/
_handleTransitionEnd(event) {
this._foundation.handleTransitionEnd(event);
}
/**
* @private
* @return {?}
*/
_createScrim() {
if (this._platform.isBrowser) {
this._scrimElement = document.createElement('div');
this._scrimElement.classList.add('mdc-drawer-scrim');
this._getHostElement().insertAdjacentElement('afterend', this._scrimElement);
this._scrimSubscription =
this._ngZone.runOutsideAngular((/**
* @return {?}
*/
() => fromEvent((/** @type {?} */ (this._scrimElement)), 'click')
.subscribe((/**
* @return {?}
*/
() => this._ngZone.run((/**
* @return {?}
*/
() => this.open = false))))));
}
}
/**
* @private
* @return {?}
*/
_initFoundation() {
this._getHostElement().classList.remove(cssClasses.MODAL);
this._getHostElement().classList.remove(cssClasses.DISMISSIBLE);
this._foundation = this.getDefaultFoundation();
this._foundation.init();
if (this.modal || this.dismissible) {
this._getHostElement().classList.add(`${cssClasses.ROOT}--${this.drawer}`);
}
if (this._scrimElement) {
if (this._scrimSubscription) {
this._scrimSubscription.unsubscribe();
}
this._scrimElement.remove();
this._scrimElement = null;
}
if (this.modal) {
this._focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement);
this._updateFocusTrapState();
this._createScrim();
}
else if (this._focusTrap) {
this._focusTrap.destroy();
}
this._changeDetectorRef.markForCheck();
}
/**
* @private
* @return {?}
*/
_initListType() {
if (this._list && (this._list.singleSelection || this._list.singleSelection === undefined)) {
this._list.wrapFocus = true;
this._list.singleSelection = true;
this._list.useActivatedClass = true;
}
}
/**
* Updates the enabled state of the focus trap.
* @private
* @return {?}
*/
_updateFocusTrapState() {
if (this._focusTrap) {
this._focusTrap.enabled = this._isFocusTrapEnabled;
}
}
/**
* @private
* @return {?}
*/
_trapFocus() {
if (!this.autoFocus) {
return;
}
this._focusTrap.focusInitialElementWhenReady().then((/**
* @param {?} hasMovedFocus
* @return {?}
*/
hasMovedFocus => {
// If there were no focusable elements, focus the drawer itself so the keyboard navigation
// still works. We need to check that `focus` is a function due to Universal.
if (!hasMovedFocus && typeof this._elementRef.nativeElement.focus === 'function') {
this._elementRef.nativeElement.focus();
}
}));
}
/**
* Restores focus to the element that was focused before the drawer opened.
* @private
* @return {?}
*/
_releaseFocus() {
if (!this.autoFocus) {
return;
}
/** @type {?} */
const activeEl = this._document && this._document.activeElement;
if (activeEl && this._elementRef.nativeElement.contains(activeEl)) {
if (this._elementFocusedBeforeDrawerWasOpened instanceof HTMLElement) {
this._focusMonitor.focusVia(this._elementFocusedBeforeDrawerWasOpened, this._openedVia);
}
else {
this._elementRef.nativeElement.blur();
}
}
this._elementFocusedBeforeDrawerWasOpened = null;
this._openedVia = null;
}
/**
* Saves a reference to the element that was focused before the drawer was opened.
* @private
* @return {?}
*/
_savePreviouslyFocusedElement() {
if (this._document) {
this._elementFocusedBeforeDrawerWasOpened = (/** @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 drawer immediately. Needs to be async, because the element
// may not be focusable immediately.
Promise.resolve().then((/**
* @return {?}
*/
() => this._elementRef.nativeElement.focus()));
}
}
}
/**
* @private
* @return {?}
*/
_getHostElement() {
return this.elementRef.nativeElement;
}
}
MdcDrawer.decorators = [
{ type: Component, args: [{selector: 'mdc-drawer',
exportAs: 'mdcDrawer',
host: {
'role': 'navigation',
'class': 'mdc-drawer',
'(transitionend)': '_handleTransitionEnd($event)'
},
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
},] },
];
/** @nocollapse */
MdcDrawer.ctorParameters = () => [
{ type: Platform },
{ type: NgZone },
{ type: ChangeDetectorRef },
{ type: FocusTrapFactory },
{ type: FocusMonitor },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
{ type: ElementRef }
];
MdcDrawer.propDecorators = {
open: [{ type: Input }],
drawer: [{ type: Input }],
autoFocus: [{ type: Input }],
restoreFocus: [{ type: Input }],
fixedAdjustElement: [{ type: Input }],
opened: [{ type: Output }],
closed: [{ type: Output }],
openedChange: [{ type: Output }],
drawerChange: [{ type: Output }],
_list: [{ type: ContentChild, args: [MdcList, { static: false },] }]
};
/**
* @fileoverview added by tsickle
* Generated from: drawer/drawer-directives.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class MdcDrawerTitle {
}
MdcDrawerTitle.decorators = [
{ type: Directive, args: [{
selector: '[mdcDrawerTitle]',
host: { 'class': 'mdc-drawer__title' }
},] },
];
class MdcDrawerSubtitle {
}
MdcDrawerSubtitle.decorators = [
{ type: Directive, args: [{
selector: '[mdcDrawerSubtitle]',
host: { 'class': 'mdc-drawer__subtitle' }
},] },
];
class MdcDrawerContent {
}
MdcDrawerContent.decorators = [
{ type: Directive, args: [{
selector: 'mdc-drawer-content, [mdcDrawerContent]',
host: { 'class': 'mdc-drawer__content' }
},] },
];
class MdcDrawerAppContent {
}
MdcDrawerAppContent.decorators = [
{ type: Directive, args: [{
selector: 'mdc-drawer-app-content, [mdcDrawerAppContent]',
host: { 'class': 'mdc-drawer-app-content' }
},] },
];
/**
* @fileoverview added by tsickle
* Generated from: drawer/module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const DRAWER_DECLARATIONS = [
MdcDrawer,
MdcDrawerAppContent,
MdcDrawerContent,
MdcDrawerHeader,
MdcDrawerSubtitle,
MdcDrawerTitle
];
class MdcDrawerModule {
}
MdcDrawerModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule],
exports: [DRAWER_DECLARATIONS],
declarations: [DRAWER_DECLARATIONS]
},] },
];
export { MdcDrawer, MdcDrawerAppContent, MdcDrawerContent, MdcDrawerHeader, MdcDrawerModule, MdcDrawerSubtitle, MdcDrawerTitle };
//# sourceMappingURL=drawer.js.map