UNPKG

@angular/material

Version:
1,452 lines (1,444 loc) 73.8 kB
import { FocusMonitor, FocusKeyManager, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { UP_ARROW, DOWN_ARROW, END, hasModifierKey, HOME, RIGHT_ARROW, LEFT_ARROW, ESCAPE } from '@angular/cdk/keycodes'; import { Directive, TemplateRef, ComponentFactoryResolver, ApplicationRef, Injector, ViewContainerRef, Inject, ChangeDetectorRef, InjectionToken, Component, ChangeDetectionStrategy, ViewEncapsulation, ElementRef, Optional, Input, HostListener, QueryList, EventEmitter, NgZone, ContentChildren, ViewChild, ContentChild, Output, Self, NgModule } from '@angular/core'; import { Subject, Subscription, merge, of, asapScheduler } from 'rxjs'; import { startWith, switchMap, take, filter, takeUntil, delay } from 'rxjs/operators'; import { trigger, state, style, transition, group, query, animate } from '@angular/animations'; import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal'; import { DOCUMENT, CommonModule } from '@angular/common'; import { mixinDisableRipple, mixinDisabled, MatCommonModule, MatRippleModule } from '@angular/material/core'; import { Directionality } from '@angular/cdk/bidi'; import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay'; import { normalizePassiveListenerOptions } from '@angular/cdk/platform'; /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-animations.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Animations used by the mat-menu component. * Animation duration and timing values are based on: * https://material.io/guidelines/components/menus.html#menus-usage * \@docs-private * @type {?} */ const matMenuAnimations = { /** * This animation controls the menu panel's entry and exit from the page. * * When the menu panel is added to the DOM, it scales in and fades in its border. * * When the menu panel is removed from the DOM, it simply fades out after a brief * delay to display the ripple. */ transformMenu: trigger('transformMenu', [ state('void', style({ opacity: 0, transform: 'scale(0.8)' })), transition('void => enter', group([ query('.mat-menu-content, .mat-mdc-menu-content', animate('100ms linear', style({ opacity: 1 }))), animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({ transform: 'scale(1)' })), ])), transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))) ]), /** * This animation fades in the background color and content of the menu panel * after its containing element is scaled in. */ fadeInItems: trigger('fadeInItems', [ // TODO(crisbeto): this is inside the `transformMenu` // now. Remove next time we do breaking changes. state('showing', style({ opacity: 1 })), transition('void => *', [ style({ opacity: 0 }), animate('400ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)') ]) ]) }; /** * @deprecated * \@breaking-change 8.0.0 * \@docs-private * @type {?} */ const fadeInItems = matMenuAnimations.fadeInItems; /** * @deprecated * \@breaking-change 8.0.0 * \@docs-private * @type {?} */ const transformMenu = matMenuAnimations.transformMenu; /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-content.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Menu content that will be rendered lazily once the menu is opened. */ class MatMenuContent { /** * @param {?} _template * @param {?} _componentFactoryResolver * @param {?} _appRef * @param {?} _injector * @param {?} _viewContainerRef * @param {?} _document * @param {?=} _changeDetectorRef */ constructor(_template, _componentFactoryResolver, _appRef, _injector, _viewContainerRef, _document, _changeDetectorRef) { this._template = _template; this._componentFactoryResolver = _componentFactoryResolver; this._appRef = _appRef; this._injector = _injector; this._viewContainerRef = _viewContainerRef; this._document = _document; this._changeDetectorRef = _changeDetectorRef; /** * Emits when the menu content has been attached. */ this._attached = new Subject(); } /** * Attaches the content with a particular context. * \@docs-private * @param {?=} context * @return {?} */ attach(context = {}) { if (!this._portal) { this._portal = new TemplatePortal(this._template, this._viewContainerRef); } this.detach(); if (!this._outlet) { this._outlet = new DomPortalOutlet(this._document.createElement('div'), this._componentFactoryResolver, this._appRef, this._injector); } /** @type {?} */ const element = this._template.elementRef.nativeElement; // Because we support opening the same menu from different triggers (which in turn have their // own `OverlayRef` panel), we have to re-insert the host element every time, otherwise we // risk it staying attached to a pane that's no longer in the DOM. (/** @type {?} */ (element.parentNode)).insertBefore(this._outlet.outletElement, element); // When `MatMenuContent` is used in an `OnPush` component, the insertion of the menu // content via `createEmbeddedView` does not cause the content to be seen as "dirty" // by Angular. This causes the `@ContentChildren` for menu items within the menu to // not be updated by Angular. By explicitly marking for check here, we tell Angular that // it needs to check for new menu items and update the `@ContentChild` in `MatMenu`. // @breaking-change 9.0.0 Make change detector ref required if (this._changeDetectorRef) { this._changeDetectorRef.markForCheck(); } this._portal.attach(this._outlet, context); this._attached.next(); } /** * Detaches the content. * \@docs-private * @return {?} */ detach() { if (this._portal.isAttached) { this._portal.detach(); } } /** * @return {?} */ ngOnDestroy() { if (this._outlet) { this._outlet.dispose(); } } } MatMenuContent.decorators = [ { type: Directive, args: [{ selector: 'ng-template[matMenuContent]' },] } ]; /** @nocollapse */ MatMenuContent.ctorParameters = () => [ { type: TemplateRef }, { type: ComponentFactoryResolver }, { type: ApplicationRef }, { type: Injector }, { type: ViewContainerRef }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: ChangeDetectorRef } ]; if (false) { /** * @type {?} * @private */ MatMenuContent.prototype._portal; /** * @type {?} * @private */ MatMenuContent.prototype._outlet; /** * Emits when the menu content has been attached. * @type {?} */ MatMenuContent.prototype._attached; /** * @type {?} * @private */ MatMenuContent.prototype._template; /** * @type {?} * @private */ MatMenuContent.prototype._componentFactoryResolver; /** * @type {?} * @private */ MatMenuContent.prototype._appRef; /** * @type {?} * @private */ MatMenuContent.prototype._injector; /** * @type {?} * @private */ MatMenuContent.prototype._viewContainerRef; /** * @type {?} * @private */ MatMenuContent.prototype._document; /** * @type {?} * @private */ MatMenuContent.prototype._changeDetectorRef; } /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-errors.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Throws an exception for the case when menu trigger doesn't have a valid mat-menu instance * \@docs-private * @return {?} */ function throwMatMenuMissingError() { throw Error(`matMenuTriggerFor: must pass in an mat-menu instance. Example: <mat-menu #menu="matMenu"></mat-menu> <button [matMenuTriggerFor]="menu"></button>`); } /** * Throws an exception for the case when menu's x-position value isn't valid. * In other words, it doesn't match 'before' or 'after'. * \@docs-private * @return {?} */ function throwMatMenuInvalidPositionX() { throw Error(`xPosition value must be either 'before' or after'. Example: <mat-menu xPosition="before" #menu="matMenu"></mat-menu>`); } /** * Throws an exception for the case when menu's y-position value isn't valid. * In other words, it doesn't match 'above' or 'below'. * \@docs-private * @return {?} */ function throwMatMenuInvalidPositionY() { throw Error(`yPosition value must be either 'above' or below'. Example: <mat-menu yPosition="above" #menu="matMenu"></mat-menu>`); } /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-panel.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Injection token used to provide the parent menu to menu-specific components. * \@docs-private * @type {?} */ const MAT_MENU_PANEL = new InjectionToken('MAT_MENU_PANEL'); /** * Interface for a custom menu panel that can be used with `matMenuTriggerFor`. * \@docs-private * @record * @template T */ function MatMenuPanel() { } if (false) { /** @type {?} */ MatMenuPanel.prototype.xPosition; /** @type {?} */ MatMenuPanel.prototype.yPosition; /** @type {?} */ MatMenuPanel.prototype.overlapTrigger; /** @type {?} */ MatMenuPanel.prototype.templateRef; /** @type {?} */ MatMenuPanel.prototype.close; /** @type {?|undefined} */ MatMenuPanel.prototype.parentMenu; /** @type {?|undefined} */ MatMenuPanel.prototype.direction; /** @type {?} */ MatMenuPanel.prototype.focusFirstItem; /** @type {?} */ MatMenuPanel.prototype.resetActiveItem; /** @type {?|undefined} */ MatMenuPanel.prototype.setPositionClasses; /** @type {?|undefined} */ MatMenuPanel.prototype.lazyContent; /** @type {?|undefined} */ MatMenuPanel.prototype.backdropClass; /** @type {?|undefined} */ MatMenuPanel.prototype.hasBackdrop; /** @type {?|undefined} */ MatMenuPanel.prototype.panelId; /** * @deprecated To be removed. * \@breaking-change 8.0.0 * @type {?|undefined} */ MatMenuPanel.prototype.addItem; /** * @deprecated To be removed. * \@breaking-change 8.0.0 * @type {?|undefined} */ MatMenuPanel.prototype.removeItem; /** * @param {?} depth * @return {?} */ MatMenuPanel.prototype.setElevation = function (depth) { }; } /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-item.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // Boilerplate for applying mixins to MatMenuItem. /** * \@docs-private */ class MatMenuItemBase { } /** @type {?} */ const _MatMenuItemMixinBase = mixinDisableRipple(mixinDisabled(MatMenuItemBase)); /** * This directive is intended to be used inside an mat-menu tag. * It exists mostly to set the role attribute. */ class MatMenuItem extends _MatMenuItemMixinBase { /** * @param {?} _elementRef * @param {?=} document * @param {?=} _focusMonitor * @param {?=} _parentMenu */ constructor(_elementRef, document, _focusMonitor, _parentMenu) { // @breaking-change 8.0.0 make `_focusMonitor` and `document` required params. super(); this._elementRef = _elementRef; this._focusMonitor = _focusMonitor; this._parentMenu = _parentMenu; /** * ARIA role for the menu item. */ this.role = 'menuitem'; /** * Stream that emits when the menu item is hovered. */ this._hovered = new Subject(); /** * Stream that emits when the menu item is focused. */ this._focused = new Subject(); /** * Whether the menu item is highlighted. */ this._highlighted = false; /** * Whether the menu item acts as a trigger for a sub-menu. */ this._triggersSubmenu = false; if (_focusMonitor) { // Start monitoring the element so it gets the appropriate focused classes. We want // to show the focus style for menu items only when the focus was not caused by a // mouse or touch interaction. _focusMonitor.monitor(this._elementRef, false); } if (_parentMenu && _parentMenu.addItem) { _parentMenu.addItem(this); } this._document = document; } /** * Focuses the menu item. * @param {?=} origin * @param {?=} options * @return {?} */ focus(origin = 'program', options) { if (this._focusMonitor) { this._focusMonitor.focusVia(this._getHostElement(), origin, options); } else { this._getHostElement().focus(options); } this._focused.next(this); } /** * @return {?} */ ngOnDestroy() { if (this._focusMonitor) { this._focusMonitor.stopMonitoring(this._elementRef); } if (this._parentMenu && this._parentMenu.removeItem) { this._parentMenu.removeItem(this); } this._hovered.complete(); this._focused.complete(); } /** * Used to set the `tabindex`. * @return {?} */ _getTabIndex() { return this.disabled ? '-1' : '0'; } /** * Returns the host DOM element. * @return {?} */ _getHostElement() { return this._elementRef.nativeElement; } /** * Prevents the default element actions if it is disabled. * @param {?} event * @return {?} */ // We have to use a `HostListener` here in order to support both Ivy and ViewEngine. // In Ivy the `host` bindings will be merged when this class is extended, whereas in // ViewEngine they're overwritten. // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default. // tslint:disable-next-line:no-host-decorator-in-concrete _checkDisabled(event) { if (this.disabled) { event.preventDefault(); event.stopPropagation(); } } /** * Emits to the hover stream. * @return {?} */ // We have to use a `HostListener` here in order to support both Ivy and ViewEngine. // In Ivy the `host` bindings will be merged when this class is extended, whereas in // ViewEngine they're overwritten. // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default. // tslint:disable-next-line:no-host-decorator-in-concrete _handleMouseEnter() { this._hovered.next(this); } /** * Gets the label to be used when determining whether the option should be focused. * @return {?} */ getLabel() { /** @type {?} */ const element = this._elementRef.nativeElement; /** @type {?} */ const textNodeType = this._document ? this._document.TEXT_NODE : 3; /** @type {?} */ let output = ''; if (element.childNodes) { /** @type {?} */ const length = element.childNodes.length; // Go through all the top-level text nodes and extract their text. // We skip anything that's not a text node to prevent the text from // being thrown off by something like an icon. for (let i = 0; i < length; i++) { if (element.childNodes[i].nodeType === textNodeType) { output += element.childNodes[i].textContent; } } } return output.trim(); } } MatMenuItem.decorators = [ { type: Component, args: [{ selector: '[mat-menu-item]', exportAs: 'matMenuItem', inputs: ['disabled', 'disableRipple'], host: { '[attr.role]': 'role', '[class.mat-menu-item]': 'true', '[class.mat-menu-item-highlighted]': '_highlighted', '[class.mat-menu-item-submenu-trigger]': '_triggersSubmenu', '[attr.tabindex]': '_getTabIndex()', '[attr.aria-disabled]': 'disabled.toString()', '[attr.disabled]': 'disabled || null', }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-content></ng-content>\n<div class=\"mat-menu-ripple\" matRipple\n [matRippleDisabled]=\"disableRipple || disabled\"\n [matRippleTrigger]=\"_getHostElement()\">\n</div>\n" }] } ]; /** @nocollapse */ MatMenuItem.ctorParameters = () => [ { type: ElementRef }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: FocusMonitor }, { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_PANEL,] }, { type: Optional }] } ]; MatMenuItem.propDecorators = { role: [{ type: Input }], _checkDisabled: [{ type: HostListener, args: ['click', ['$event'],] }], _handleMouseEnter: [{ type: HostListener, args: ['mouseenter',] }] }; if (false) { /** @type {?} */ MatMenuItem.ngAcceptInputType_disabled; /** @type {?} */ MatMenuItem.ngAcceptInputType_disableRipple; /** * ARIA role for the menu item. * @type {?} */ MatMenuItem.prototype.role; /** * @type {?} * @private */ MatMenuItem.prototype._document; /** * Stream that emits when the menu item is hovered. * @type {?} */ MatMenuItem.prototype._hovered; /** * Stream that emits when the menu item is focused. * @type {?} */ MatMenuItem.prototype._focused; /** * Whether the menu item is highlighted. * @type {?} */ MatMenuItem.prototype._highlighted; /** * Whether the menu item acts as a trigger for a sub-menu. * @type {?} */ MatMenuItem.prototype._triggersSubmenu; /** * @type {?} * @private */ MatMenuItem.prototype._elementRef; /** * @type {?} * @private */ MatMenuItem.prototype._focusMonitor; /** @type {?} */ MatMenuItem.prototype._parentMenu; } /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Default `mat-menu` options that can be overridden. * @record */ function MatMenuDefaultOptions() { } if (false) { /** * The x-axis position of the menu. * @type {?} */ MatMenuDefaultOptions.prototype.xPosition; /** * The y-axis position of the menu. * @type {?} */ MatMenuDefaultOptions.prototype.yPosition; /** * Whether the menu should overlap the menu trigger. * @type {?} */ MatMenuDefaultOptions.prototype.overlapTrigger; /** * Class to be applied to the menu's backdrop. * @type {?} */ MatMenuDefaultOptions.prototype.backdropClass; /** * Whether the menu has a backdrop. * @type {?|undefined} */ MatMenuDefaultOptions.prototype.hasBackdrop; } /** * Injection token to be used to override the default options for `mat-menu`. * @type {?} */ const MAT_MENU_DEFAULT_OPTIONS = new InjectionToken('mat-menu-default-options', { providedIn: 'root', factory: MAT_MENU_DEFAULT_OPTIONS_FACTORY }); /** * \@docs-private * @return {?} */ function MAT_MENU_DEFAULT_OPTIONS_FACTORY() { return { overlapTrigger: false, xPosition: 'after', yPosition: 'below', backdropClass: 'cdk-overlay-transparent-backdrop', }; } /** * Start elevation for the menu panel. * \@docs-private * @type {?} */ const MAT_MENU_BASE_ELEVATION = 4; /** @type {?} */ let menuPanelUid = 0; /** * Base class with all of the `MatMenu` functionality. */ // tslint:disable-next-line:class-name class _MatMenuBase { /** * @param {?} _elementRef * @param {?} _ngZone * @param {?} _defaultOptions */ constructor(_elementRef, _ngZone, _defaultOptions) { this._elementRef = _elementRef; this._ngZone = _ngZone; this._defaultOptions = _defaultOptions; this._xPosition = this._defaultOptions.xPosition; this._yPosition = this._defaultOptions.yPosition; /** * Only the direct descendant menu items. */ this._directDescendantItems = new QueryList(); /** * Subscription to tab events on the menu panel */ this._tabSubscription = Subscription.EMPTY; /** * Config object to be passed into the menu's ngClass */ this._classList = {}; /** * Current state of the panel animation. */ this._panelAnimationState = 'void'; /** * Emits whenever an animation on the menu completes. */ this._animationDone = new Subject(); /** * Class to be added to the backdrop element. */ this.backdropClass = this._defaultOptions.backdropClass; this._overlapTrigger = this._defaultOptions.overlapTrigger; this._hasBackdrop = this._defaultOptions.hasBackdrop; /** * Event emitted when the menu is closed. */ this.closed = new EventEmitter(); /** * Event emitted when the menu is closed. * @deprecated Switch to `closed` instead * \@breaking-change 8.0.0 */ this.close = this.closed; this.panelId = `mat-menu-panel-${menuPanelUid++}`; } /** * Position of the menu in the X axis. * @return {?} */ get xPosition() { return this._xPosition; } /** * @param {?} value * @return {?} */ set xPosition(value) { if (value !== 'before' && value !== 'after') { throwMatMenuInvalidPositionX(); } this._xPosition = value; this.setPositionClasses(); } /** * Position of the menu in the Y axis. * @return {?} */ get yPosition() { return this._yPosition; } /** * @param {?} value * @return {?} */ set yPosition(value) { if (value !== 'above' && value !== 'below') { throwMatMenuInvalidPositionY(); } this._yPosition = value; this.setPositionClasses(); } /** * Whether the menu should overlap its trigger. * @return {?} */ get overlapTrigger() { return this._overlapTrigger; } /** * @param {?} value * @return {?} */ set overlapTrigger(value) { this._overlapTrigger = coerceBooleanProperty(value); } /** * Whether the menu has a backdrop. * @return {?} */ get hasBackdrop() { return this._hasBackdrop; } /** * @param {?} value * @return {?} */ set hasBackdrop(value) { this._hasBackdrop = coerceBooleanProperty(value); } /** * This method takes classes set on the host mat-menu element and applies them on the * menu template that displays in the overlay container. Otherwise, it's difficult * to style the containing menu from outside the component. * @param {?} classes list of class names * @return {?} */ set panelClass(classes) { /** @type {?} */ const previousPanelClass = this._previousPanelClass; if (previousPanelClass && previousPanelClass.length) { previousPanelClass.split(' ').forEach((/** * @param {?} className * @return {?} */ (className) => { this._classList[className] = false; })); } this._previousPanelClass = classes; if (classes && classes.length) { classes.split(' ').forEach((/** * @param {?} className * @return {?} */ (className) => { this._classList[className] = true; })); this._elementRef.nativeElement.className = ''; } } /** * This method takes classes set on the host mat-menu element and applies them on the * menu template that displays in the overlay container. Otherwise, it's difficult * to style the containing menu from outside the component. * @deprecated Use `panelClass` instead. * \@breaking-change 8.0.0 * @return {?} */ get classList() { return this.panelClass; } /** * @param {?} classes * @return {?} */ set classList(classes) { this.panelClass = classes; } /** * @return {?} */ ngOnInit() { this.setPositionClasses(); } /** * @return {?} */ ngAfterContentInit() { this._updateDirectDescendants(); this._keyManager = new FocusKeyManager(this._directDescendantItems).withWrap().withTypeAhead(); this._tabSubscription = this._keyManager.tabOut.subscribe((/** * @return {?} */ () => this.closed.emit('tab'))); // If a user manually (programatically) focuses a menu item, we need to reflect that focus // change back to the key manager. Note that we don't need to unsubscribe here because _focused // is internal and we know that it gets completed on destroy. this._directDescendantItems.changes.pipe(startWith(this._directDescendantItems), switchMap((/** * @param {?} items * @return {?} */ items => merge(...items.map((/** * @param {?} item * @return {?} */ (item) => item._focused)))))).subscribe((/** * @param {?} focusedItem * @return {?} */ focusedItem => this._keyManager.updateActiveItem(focusedItem))); } /** * @return {?} */ ngOnDestroy() { this._directDescendantItems.destroy(); this._tabSubscription.unsubscribe(); this.closed.complete(); } /** * Stream that emits whenever the hovered menu item changes. * @return {?} */ _hovered() { // Coerce the `changes` property because Angular types it as `Observable<any>` /** @type {?} */ const itemChanges = (/** @type {?} */ (this._directDescendantItems.changes)); return (/** @type {?} */ (itemChanges.pipe(startWith(this._directDescendantItems), switchMap((/** * @param {?} items * @return {?} */ items => merge(...items.map((/** * @param {?} item * @return {?} */ (item) => item._hovered)))))))); } /* * Registers a menu item with the menu. * @docs-private * @deprecated No longer being used. To be removed. * @breaking-change 9.0.0 */ /** * @param {?} _item * @return {?} */ addItem(_item) { } /** * Removes an item from the menu. * \@docs-private * @deprecated No longer being used. To be removed. * \@breaking-change 9.0.0 * @param {?} _item * @return {?} */ removeItem(_item) { } /** * Handle a keyboard event from the menu, delegating to the appropriate action. * @param {?} event * @return {?} */ _handleKeydown(event) { /** @type {?} */ const keyCode = event.keyCode; /** @type {?} */ const manager = this._keyManager; switch (keyCode) { case ESCAPE: if (!hasModifierKey(event)) { event.preventDefault(); this.closed.emit('keydown'); } break; case LEFT_ARROW: if (this.parentMenu && this.direction === 'ltr') { this.closed.emit('keydown'); } break; case RIGHT_ARROW: if (this.parentMenu && this.direction === 'rtl') { this.closed.emit('keydown'); } break; case HOME: case END: if (!hasModifierKey(event)) { keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive(); event.preventDefault(); } break; default: if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) { manager.setFocusOrigin('keyboard'); } manager.onKeydown(event); } } /** * Focus the first item in the menu. * @param {?=} origin Action from which the focus originated. Used to set the correct styling. * @return {?} */ focusFirstItem(origin = 'program') { // When the content is rendered lazily, it takes a bit before the items are inside the DOM. if (this.lazyContent) { this._ngZone.onStable.asObservable() .pipe(take(1)) .subscribe((/** * @return {?} */ () => this._focusFirstItem(origin))); } else { this._focusFirstItem(origin); } } /** * Actual implementation that focuses the first item. Needs to be separated * out so we don't repeat the same logic in the public `focusFirstItem` method. * @private * @param {?} origin * @return {?} */ _focusFirstItem(origin) { /** @type {?} */ const manager = this._keyManager; manager.setFocusOrigin(origin).setFirstItemActive(); // If there's no active item at this point, it means that all the items are disabled. // Move focus to the menu panel so keyboard events like Escape still work. Also this will // give _some_ feedback to screen readers. if (!manager.activeItem && this._directDescendantItems.length) { /** @type {?} */ let element = this._directDescendantItems.first._getHostElement().parentElement; // Because the `mat-menu` is at the DOM insertion point, not inside the overlay, we don't // have a nice way of getting a hold of the menu panel. We can't use a `ViewChild` either // because the panel is inside an `ng-template`. We work around it by starting from one of // the items and walking up the DOM. while (element) { if (element.getAttribute('role') === 'menu') { element.focus(); break; } else { element = element.parentElement; } } } } /** * Resets the active item in the menu. This is used when the menu is opened, allowing * the user to start from the first option when pressing the down arrow. * @return {?} */ resetActiveItem() { this._keyManager.setActiveItem(-1); } /** * Sets the menu panel elevation. * @param {?} depth Number of parent menus that come before the menu. * @return {?} */ setElevation(depth) { // The elevation starts at the base and increases by one for each level. // Capped at 24 because that's the maximum elevation defined in the Material design spec. /** @type {?} */ const elevation = Math.min(MAT_MENU_BASE_ELEVATION + depth, 24); /** @type {?} */ const newElevation = `mat-elevation-z${elevation}`; /** @type {?} */ const customElevation = Object.keys(this._classList).find((/** * @param {?} c * @return {?} */ c => c.startsWith('mat-elevation-z'))); if (!customElevation || customElevation === this._previousElevation) { if (this._previousElevation) { this._classList[this._previousElevation] = false; } this._classList[newElevation] = true; this._previousElevation = newElevation; } } /** * Adds classes to the menu panel based on its position. Can be used by * consumers to add specific styling based on the position. * \@docs-private * @param {?=} posX Position of the menu along the x axis. * @param {?=} posY Position of the menu along the y axis. * @return {?} */ setPositionClasses(posX = this.xPosition, posY = this.yPosition) { /** @type {?} */ const classes = this._classList; classes['mat-menu-before'] = posX === 'before'; classes['mat-menu-after'] = posX === 'after'; classes['mat-menu-above'] = posY === 'above'; classes['mat-menu-below'] = posY === 'below'; } /** * Starts the enter animation. * @return {?} */ _startAnimation() { // @breaking-change 8.0.0 Combine with _resetAnimation. this._panelAnimationState = 'enter'; } /** * Resets the panel animation to its initial state. * @return {?} */ _resetAnimation() { // @breaking-change 8.0.0 Combine with _startAnimation. this._panelAnimationState = 'void'; } /** * Callback that is invoked when the panel animation completes. * @param {?} event * @return {?} */ _onAnimationDone(event) { this._animationDone.next(event); this._isAnimating = false; } /** * @param {?} event * @return {?} */ _onAnimationStart(event) { this._isAnimating = true; // Scroll the content element to the top as soon as the animation starts. This is necessary, // because we move focus to the first item while it's still being animated, which can throw // the browser off when it determines the scroll position. Alternatively we can move focus // when the animation is done, however moving focus asynchronously will interrupt screen // readers which are in the process of reading out the menu already. We take the `element` // from the `event` since we can't use a `ViewChild` to access the pane. if (event.toState === 'enter' && this._keyManager.activeItemIndex === 0) { event.element.scrollTop = 0; } } /** * Sets up a stream that will keep track of any newly-added menu items and will update the list * of direct descendants. We collect the descendants this way, because `_allItems` can include * items that are part of child menus, and using a custom way of registering items is unreliable * when it comes to maintaining the item order. * @private * @return {?} */ _updateDirectDescendants() { this._allItems.changes .pipe(startWith(this._allItems)) .subscribe((/** * @param {?} items * @return {?} */ (items) => { this._directDescendantItems.reset(items.filter((/** * @param {?} item * @return {?} */ item => item._parentMenu === this))); this._directDescendantItems.notifyOnChanges(); })); } } _MatMenuBase.decorators = [ { type: Directive } ]; /** @nocollapse */ _MatMenuBase.ctorParameters = () => [ { type: ElementRef }, { type: NgZone }, { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_DEFAULT_OPTIONS,] }] } ]; _MatMenuBase.propDecorators = { _allItems: [{ type: ContentChildren, args: [MatMenuItem, { descendants: true },] }], backdropClass: [{ type: Input }], ariaLabel: [{ type: Input, args: ['aria-label',] }], ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }], ariaDescribedby: [{ type: Input, args: ['aria-describedby',] }], xPosition: [{ type: Input }], yPosition: [{ type: Input }], templateRef: [{ type: ViewChild, args: [TemplateRef,] }], items: [{ type: ContentChildren, args: [MatMenuItem, { descendants: false },] }], lazyContent: [{ type: ContentChild, args: [MatMenuContent,] }], overlapTrigger: [{ type: Input }], hasBackdrop: [{ type: Input }], panelClass: [{ type: Input, args: ['class',] }], classList: [{ type: Input }], closed: [{ type: Output }], close: [{ type: Output }] }; if (false) { /** @type {?} */ _MatMenuBase.ngAcceptInputType_overlapTrigger; /** @type {?} */ _MatMenuBase.ngAcceptInputType_hasBackdrop; /** * @type {?} * @private */ _MatMenuBase.prototype._keyManager; /** * @type {?} * @private */ _MatMenuBase.prototype._xPosition; /** * @type {?} * @private */ _MatMenuBase.prototype._yPosition; /** * @type {?} * @private */ _MatMenuBase.prototype._previousElevation; /** * All items inside the menu. Includes items nested inside another menu. * @type {?} */ _MatMenuBase.prototype._allItems; /** * Only the direct descendant menu items. * @type {?} * @private */ _MatMenuBase.prototype._directDescendantItems; /** * Subscription to tab events on the menu panel * @type {?} * @private */ _MatMenuBase.prototype._tabSubscription; /** * Config object to be passed into the menu's ngClass * @type {?} */ _MatMenuBase.prototype._classList; /** * Current state of the panel animation. * @type {?} */ _MatMenuBase.prototype._panelAnimationState; /** * Emits whenever an animation on the menu completes. * @type {?} */ _MatMenuBase.prototype._animationDone; /** * Whether the menu is animating. * @type {?} */ _MatMenuBase.prototype._isAnimating; /** * Parent menu of the current menu panel. * @type {?} */ _MatMenuBase.prototype.parentMenu; /** * Layout direction of the menu. * @type {?} */ _MatMenuBase.prototype.direction; /** * Class to be added to the backdrop element. * @type {?} */ _MatMenuBase.prototype.backdropClass; /** * aria-label for the menu panel. * @type {?} */ _MatMenuBase.prototype.ariaLabel; /** * aria-labelledby for the menu panel. * @type {?} */ _MatMenuBase.prototype.ariaLabelledby; /** * aria-describedby for the menu panel. * @type {?} */ _MatMenuBase.prototype.ariaDescribedby; /** * \@docs-private * @type {?} */ _MatMenuBase.prototype.templateRef; /** * List of the items inside of a menu. * @deprecated * \@breaking-change 8.0.0 * @type {?} */ _MatMenuBase.prototype.items; /** * Menu content that will be rendered lazily. * \@docs-private * @type {?} */ _MatMenuBase.prototype.lazyContent; /** * @type {?} * @private */ _MatMenuBase.prototype._overlapTrigger; /** * @type {?} * @private */ _MatMenuBase.prototype._hasBackdrop; /** * @type {?} * @private */ _MatMenuBase.prototype._previousPanelClass; /** * Event emitted when the menu is closed. * @type {?} */ _MatMenuBase.prototype.closed; /** * Event emitted when the menu is closed. * @deprecated Switch to `closed` instead * \@breaking-change 8.0.0 * @type {?} */ _MatMenuBase.prototype.close; /** @type {?} */ _MatMenuBase.prototype.panelId; /** * @type {?} * @private */ _MatMenuBase.prototype._elementRef; /** * @type {?} * @private */ _MatMenuBase.prototype._ngZone; /** * @type {?} * @private */ _MatMenuBase.prototype._defaultOptions; } /** * \@docs-private We show the "_MatMenu" class as "MatMenu" in the docs. */ class MatMenu extends _MatMenuBase { } MatMenu.decorators = [ { type: Directive } ]; // Note on the weird inheritance setup: we need three classes, because the MDC-based menu has to // extend `MatMenu`, however keeping a reference to it will cause the inlined template and styles // to be retained as well. The MDC menu also has to provide itself as a `MatMenu` in order for // queries and DI to work correctly, while still not referencing the actual menu class. // Class responsibility is split up as follows: // * _MatMenuBase - provides all the functionality without any of the Angular metadata. // * MatMenu - keeps the same name symbol name as the current menu and // is used as a provider for DI and query purposes. // * _MatMenu - the actual menu component implementation with the Angular metadata that should // be tree shaken away for MDC. /** * \@docs-public MatMenu */ // tslint:disable-next-line:class-name class _MatMenu extends MatMenu { /** * @param {?} elementRef * @param {?} ngZone * @param {?} defaultOptions */ constructor(elementRef, ngZone, defaultOptions) { super(elementRef, ngZone, defaultOptions); } } _MatMenu.decorators = [ { type: Component, args: [{ selector: 'mat-menu', template: "<ng-template>\n <div\n class=\"mat-menu-panel\"\n [id]=\"panelId\"\n [ngClass]=\"_classList\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"closed.emit('click')\"\n [@transformMenu]=\"_panelAnimationState\"\n (@transformMenu.start)=\"_onAnimationStart($event)\"\n (@transformMenu.done)=\"_onAnimationDone($event)\"\n tabindex=\"-1\"\n role=\"menu\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"ariaLabelledby || null\"\n [attr.aria-describedby]=\"ariaDescribedby || null\">\n <div class=\"mat-menu-content\">\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, exportAs: 'matMenu', animations: [ matMenuAnimations.transformMenu, matMenuAnimations.fadeInItems ], providers: [ { provide: MAT_MENU_PANEL, useExisting: MatMenu }, { provide: MatMenu, useExisting: _MatMenu } ], styles: [".mat-menu-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;max-height:calc(100vh - 48px);border-radius:4px;outline:0;min-height:64px}.mat-menu-panel.ng-animating{pointer-events:none}.cdk-high-contrast-active .mat-menu-panel{outline:solid 1px}.mat-menu-content:not(:empty){padding-top:8px;padding-bottom:8px}.mat-menu-item{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:transparent;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;line-height:48px;height:48px;padding:0 16px;text-align:left;text-decoration:none;max-width:100%;position:relative}.mat-menu-item::-moz-focus-inner{border:0}.mat-menu-item[disabled]{cursor:default}[dir=rtl] .mat-menu-item{text-align:right}.mat-menu-item .mat-icon{margin-right:16px;vertical-align:middle}.mat-menu-item .mat-icon svg{vertical-align:top}[dir=rtl] .mat-menu-item .mat-icon{margin-left:16px;margin-right:0}.mat-menu-item[disabled]{pointer-events:none}.cdk-high-contrast-active .mat-menu-item.cdk-program-focused,.cdk-high-contrast-active .mat-menu-item.cdk-keyboard-focused,.cdk-high-contrast-active .mat-menu-item-highlighted{outline:dotted 1px}.mat-menu-item-submenu-trigger{padding-right:32px}.mat-menu-item-submenu-trigger::after{width:0;height:0;border-style:solid;border-width:5px 0 5px 5px;border-color:transparent transparent transparent currentColor;content:\"\";display:inline-block;position:absolute;top:50%;right:16px;transform:translateY(-50%)}[dir=rtl] .mat-menu-item-submenu-trigger{padding-right:16px;padding-left:32px}[dir=rtl] .mat-menu-item-submenu-trigger::after{right:auto;left:16px;transform:rotateY(180deg) translateY(-50%)}button.mat-menu-item{width:100%}.mat-menu-item .mat-menu-ripple{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none}\n"] }] } ]; /** @nocollapse */ _MatMenu.ctorParameters = () => [ { type: ElementRef }, { type: NgZone }, { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_DEFAULT_OPTIONS,] }] } ]; /** * @fileoverview added by tsickle * Generated from: src/material/menu/menu-trigger.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Injection token that determines the scroll handling while the menu is open. * @type {?} */ const MAT_MENU_SCROLL_STRATEGY = new InjectionToken('mat-menu-scroll-strategy'); /** * \@docs-private * @param {?} overlay * @return {?} */ function MAT_MENU_SCROLL_STRATEGY_FACTORY(overlay) { return (/** * @return {?} */ () => overlay.scrollStrategies.reposition()); } /** * \@docs-private * @type {?} */ const MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER = { provide: MAT_MENU_SCROLL_STRATEGY, deps: [Overlay], useFactory: MAT_MENU_SCROLL_STRATEGY_FACTORY, }; /** * Default top padding of the menu panel. * @type {?} */ const MENU_PANEL_TOP_PADDING = 8; /** * Options for binding a passive event listener. * @type {?} */ const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true }); // TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors /** * This directive is intended to be used in conjunction with an mat-menu tag. It is * responsible for toggling the display of the provided menu instance. */ class MatMenuTrigger { /** * @param {?} _overlay * @param {?} _element * @param {?} _viewContainerRef * @param {?} scrollStrategy * @param {?} _parentMenu * @param {?} _menuItemInstance * @param {?} _dir * @param {?=} _focusMonitor */ constructor(_overlay, _element, _viewContainerRef, scrollStrategy, _parentMenu, _menuItemInstance, _dir, _focusMonitor) { this._overlay = _overlay; this._element = _element; this._viewContainerRef = _viewContainerRef; this._parentMenu = _parentMenu; this._menuItemInstance = _menuItemInstance; this._dir = _dir; this._focusMonitor = _focusMonitor; this._overlayRef = null; this._menuOpen = false; this._closingActionsSubscription = Subscription.EMPTY; this._hoverSubscription = Subscription.EMPTY; this._menuCloseSubscription = Subscription.EMPTY; /** * Handles touch start events on the trigger. * Needs to be an arrow function so we can easily use addEventListener and removeEventListener. */ this._handleTouchStart = (/** * @return {?} */ () => this._openedBy = 'touch'); // Tracking input type is necessary so it's possible to only auto-focus // the first item of the list when the menu is opened via the keyboard this._openedBy = null; /** * Whether focus should be restored when the menu is closed. * Note that disabling this option can have accessibility implications * and it's up to you to manage focus, if you decide to turn it off. */ this.restoreFocus = true; /** * Event emitted when the associated menu is opened. */ this.menuOpened = new EventEmitter(); /** * Event emitted when the associated menu is opened. * @deprecated Switch to `menuOpened` instead * \@breaking-change 8.0.0 */ // tslint:disable-next-line:no-output-on-prefix this.onMenuOpen = this.menuOpened; /** * Event emitted when the associated menu is closed. */ this.menuClosed = new EventEmitter(); /** * Event emitted when the associated menu is closed. * @deprecated Switch to `menuClosed` instead * \@breaking-change 8.0.0 */ // tslint:disable-next-line:no-output-on-prefix this.onMenuClose = this.menuClosed; _element.nativeElement.addEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions); if (_menuItemInstance) { _menuItemInstance._triggersSubmenu = this.triggersSubmenu(); } this._scrollStrategy = scrollStrategy; } /** * @deprecated * \@breaking-change 8.0.0 * @return {?} */ get _deprecatedMatMenuTriggerFor() { return this.menu; } /** * @param {?} v * @return {?} */ set _deprecatedMatMenuTriggerFor(v) { this.menu = v; } /** * References the menu instance that the trigger is associated with. * @return {?} *