| /** |
| * @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 |
| */ |
| import { trigger, state, style, animate, transition, query, group } from '@angular/animations'; |
| import { Directive, TemplateRef, ComponentFactoryResolver, ApplicationRef, Injector, ViewContainerRef, Inject, InjectionToken, ChangeDetectionStrategy, Component, ElementRef, ViewEncapsulation, Optional, Input, HostListener, ContentChild, ContentChildren, EventEmitter, NgZone, Output, QueryList, ViewChild, Self, NgModule } from '@angular/core'; |
| import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal'; |
| import { DOCUMENT, CommonModule } from '@angular/common'; |
| import { Subject, merge, Subscription, asapScheduler, of } from 'rxjs'; |
| import { FocusMonitor, FocusKeyManager, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y'; |
| import { mixinDisabled, mixinDisableRipple, MatCommonModule, MatRippleModule } from '@angular/material/core'; |
| import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
| import { ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, UP_ARROW, HOME, END, hasModifierKey } from '@angular/cdk/keycodes'; |
| import { startWith, switchMap, take, delay, filter, takeUntil } from 'rxjs/operators'; |
| import { Directionality } from '@angular/cdk/bidi'; |
| import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay'; |
| import { normalizePassiveListenerOptions } from '@angular/cdk/platform'; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,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 |
| * @suppress {checkTypes,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 |
| */ |
| constructor(_template, _componentFactoryResolver, _appRef, _injector, _viewContainerRef, _document) { |
| this._template = _template; |
| this._componentFactoryResolver = _componentFactoryResolver; |
| this._appRef = _appRef; |
| this._injector = _injector; |
| this._viewContainerRef = _viewContainerRef; |
| this._document = _document; |
| /** |
| * 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); |
| 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,] }] } |
| ]; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * 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 |
| * @suppress {checkTypes,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'); |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,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(); |
| /** |
| * 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 |
| * @return {?} |
| */ |
| focus(origin = 'program') { |
| if (this._focusMonitor) { |
| this._focusMonitor.focusVia(this._getHostElement(), origin); |
| } |
| else { |
| this._getHostElement().focus(); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| if (this._focusMonitor) { |
| this._focusMonitor.stopMonitoring(this._elementRef); |
| } |
| if (this._parentMenu && this._parentMenu.removeItem) { |
| this._parentMenu.removeItem(this); |
| } |
| this._hovered.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 overwritte. |
| // 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 overwritte. |
| // 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', |
| '[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><div class=\"mat-menu-ripple\" matRipple [matRippleDisabled]=\"disableRipple || disabled\" [matRippleTrigger]=\"_getHostElement()\"></div>", |
| },] }, |
| ]; |
| /** @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',] }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * 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; |
| /** |
| * 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; |
| } |
| /** |
| * 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'))); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| this._directDescendantItems.destroy(); |
| this._tabSubscription.unsubscribe(); |
| this.closed.complete(); |
| } |
| /** |
| * Stream that emits whenever the hovered menu item changes. |
| * @return {?} |
| */ |
| _hovered() { |
| return this._directDescendantItems.changes.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._keyManager.setFocusOrigin(origin).setFirstItemActive())); |
| } |
| else { |
| this._keyManager.setFocusOrigin(origin).setFirstItemActive(); |
| } |
| } |
| /** |
| * 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. |
| /** @type {?} */ |
| const newElevation = `mat-elevation-z${MAT_MENU_BASE_ELEVATION + depth}`; |
| /** @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(); |
| })); |
| } |
| } |
| /** @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 }], |
| xPosition: [{ type: Input }], |
| yPosition: [{ type: Input }], |
| templateRef: [{ type: ViewChild, args: [TemplateRef, { static: false },] }], |
| items: [{ type: ContentChildren, args: [MatMenuItem,] }], |
| lazyContent: [{ type: ContentChild, args: [MatMenuContent, { static: false },] }], |
| overlapTrigger: [{ type: Input }], |
| hasBackdrop: [{ type: Input }], |
| panelClass: [{ type: Input, args: ['class',] }], |
| classList: [{ type: Input }], |
| closed: [{ type: Output }], |
| close: [{ type: Output }] |
| }; |
| /** |
| * \@docs-private We show the "_MatMenu" class as "MatMenu" in the docs. |
| */ |
| class MatMenu extends _MatMenuBase { |
| } |
| // 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><div class=\"mat-menu-panel\" [ngClass]=\"_classList\" (keydown)=\"_handleKeydown($event)\" (click)=\"closed.emit('click')\" [@transformMenu]=\"_panelAnimationState\" (@transformMenu.start)=\"_onAnimationStart($event)\" (@transformMenu.done)=\"_onAnimationDone($event)\" tabindex=\"-1\" role=\"menu\"><div class=\"mat-menu-content\"><ng-content></ng-content></div></div></ng-template>", |
| 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}@media (-ms-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:0;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}@media (-ms-high-contrast:active){.mat-menu-item-highlighted,.mat-menu-item.cdk-keyboard-focused,.mat-menu-item.cdk-program-focused{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}"], |
| changeDetection: ChangeDetectionStrategy.OnPush, |
| encapsulation: ViewEncapsulation.None, |
| exportAs: 'matMenu', |
| animations: [ |
| matMenuAnimations.transformMenu, |
| matMenuAnimations.fadeInItems |
| ], |
| providers: [ |
| { provide: MAT_MENU_PANEL, useExisting: MatMenu }, |
| { provide: MatMenu, useExisting: _MatMenu } |
| ] |
| },] }, |
| ]; |
| /** @nocollapse */ |
| _MatMenu.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: NgZone }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_DEFAULT_OPTIONS,] }] } |
| ]; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,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 {?} |
| */ |
| get menu() { return this._menu; } |
| /** |
| * @param {?} menu |
| * @return {?} |
| */ |
| set menu(menu) { |
| if (menu === this._menu) { |
| return; |
| } |
| this._menu = menu; |
| this._menuCloseSubscription.unsubscribe(); |
| if (menu) { |
| this._menuCloseSubscription = menu.close.asObservable().subscribe((/** |
| * @param {?} reason |
| * @return {?} |
| */ |
| reason => { |
| this._destroyMenu(); |
| // If a click closed the menu, we should close the entire chain of nested menus. |
| if ((reason === 'click' || reason === 'tab') && this._parentMenu) { |
| this._parentMenu.closed.emit(reason); |
| } |
| })); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| ngAfterContentInit() { |
| this._checkMenu(); |
| this._handleHover(); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| if (this._overlayRef) { |
| this._overlayRef.dispose(); |
| this._overlayRef = null; |
| } |
| this._element.nativeElement.removeEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions); |
| this._menuCloseSubscription.unsubscribe(); |
| this._closingActionsSubscription.unsubscribe(); |
| this._hoverSubscription.unsubscribe(); |
| } |
| /** |
| * Whether the menu is open. |
| * @return {?} |
| */ |
| get menuOpen() { |
| return this._menuOpen; |
| } |
| /** |
| * The text direction of the containing app. |
| * @return {?} |
| */ |
| get dir() { |
| return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; |
| } |
| /** |
| * Whether the menu triggers a sub-menu or a top-level one. |
| * @return {?} |
| */ |
| triggersSubmenu() { |
| return !!(this._menuItemInstance && this._parentMenu); |
| } |
| /** |
| * Toggles the menu between the open and closed states. |
| * @return {?} |
| */ |
| toggleMenu() { |
| return this._menuOpen ? this.closeMenu() : this.openMenu(); |
| } |
| /** |
| * Opens the menu. |
| * @return {?} |
| */ |
| openMenu() { |
| if (this._menuOpen) { |
| return; |
| } |
| this._checkMenu(); |
| /** @type {?} */ |
| const overlayRef = this._createOverlay(); |
| /** @type {?} */ |
| const overlayConfig = overlayRef.getConfig(); |
| this._setPosition((/** @type {?} */ (overlayConfig.positionStrategy))); |
| overlayConfig.hasBackdrop = this.menu.hasBackdrop == null ? !this.triggersSubmenu() : |
| this.menu.hasBackdrop; |
| overlayRef.attach(this._getPortal()); |
| if (this.menu.lazyContent) { |
| this.menu.lazyContent.attach(this.menuData); |
| } |
| this._closingActionsSubscription = this._menuClosingActions().subscribe((/** |
| * @return {?} |
| */ |
| () => this.closeMenu())); |
| this._initMenu(); |
| if (this.menu instanceof MatMenu) { |
| this.menu._startAnimation(); |
| } |
| } |
| /** |
| * Closes the menu. |
| * @return {?} |
| */ |
| closeMenu() { |
| this.menu.close.emit(); |
| } |
| /** |
| * Focuses the menu trigger. |
| * @param {?=} origin Source of the menu trigger's focus. |
| * @return {?} |
| */ |
| focus(origin = 'program') { |
| if (this._focusMonitor) { |
| this._focusMonitor.focusVia(this._element, origin); |
| } |
| else { |
| this._element.nativeElement.focus(); |
| } |
| } |
| /** |
| * Closes the menu and does the necessary cleanup. |
| * @private |
| * @return {?} |
| */ |
| _destroyMenu() { |
| if (!this._overlayRef || !this.menuOpen) { |
| return; |
| } |
| /** @type {?} */ |
| const menu = this.menu; |
| this._closingActionsSubscription.unsubscribe(); |
| this._overlayRef.detach(); |
| if (menu instanceof MatMenu) { |
| menu._resetAnimation(); |
| if (menu.lazyContent) { |
| // Wait for the exit animation to finish before detaching the content. |
| menu._animationDone |
| .pipe(filter((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => event.toState === 'void')), take(1), |
| // Interrupt if the content got re-attached. |
| takeUntil(menu.lazyContent._attached)) |
| .subscribe({ |
| next: (/** |
| * @return {?} |
| */ |
| () => (/** @type {?} */ (menu.lazyContent)).detach()), |
| // No matter whether the content got re-attached, reset the menu. |
| complete: (/** |
| * @return {?} |
| */ |
| () => this._resetMenu()) |
| }); |
| } |
| else { |
| this._resetMenu(); |
| } |
| } |
| else { |
| this._resetMenu(); |
| if (menu.lazyContent) { |
| menu.lazyContent.detach(); |
| } |
| } |
| } |
| /** |
| * This method sets the menu state to open and focuses the first item if |
| * the menu was opened via the keyboard. |
| * @private |
| * @return {?} |
| */ |
| _initMenu() { |
| this.menu.parentMenu = this.triggersSubmenu() ? this._parentMenu : undefined; |
| this.menu.direction = this.dir; |
| this._setMenuElevation(); |
| this._setIsMenuOpen(true); |
| this.menu.focusFirstItem(this._openedBy || 'program'); |
| } |
| /** |
| * Updates the menu elevation based on the amount of parent menus that it has. |
| * @private |
| * @return {?} |
| */ |
| _setMenuElevation() { |
| if (this.menu.setElevation) { |
| /** @type {?} */ |
| let depth = 0; |
| /** @type {?} */ |
| let parentMenu = this.menu.parentMenu; |
| while (parentMenu) { |
| depth++; |
| parentMenu = parentMenu.parentMenu; |
| } |
| this.menu.setElevation(depth); |
| } |
| } |
| /** |
| * This method resets the menu when it's closed, most importantly restoring |
| * focus to the menu trigger if the menu was opened via the keyboard. |
| * @private |
| * @return {?} |
| */ |
| _resetMenu() { |
| this._setIsMenuOpen(false); |
| // We should reset focus if the user is navigating using a keyboard or |
| // if we have a top-level trigger which might cause focus to be lost |
| // when clicking on the backdrop. |
| if (this.restoreFocus) { |
| if (!this._openedBy) { |
| // Note that the focus style will show up both for `program` and |
| // `keyboard` so we don't have to specify which one it is. |
| this.focus(); |
| } |
| else if (!this.triggersSubmenu()) { |
| this.focus(this._openedBy); |
| } |
| } |
| this._openedBy = null; |
| } |
| // set state rather than toggle to support triggers sharing a menu |
| /** |
| * @private |
| * @param {?} isOpen |
| * @return {?} |
| */ |
| _setIsMenuOpen(isOpen) { |
| this._menuOpen = isOpen; |
| this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit(); |
| if (this.triggersSubmenu()) { |
| this._menuItemInstance._highlighted = isOpen; |
| } |
| } |
| /** |
| * This method checks that a valid instance of MatMenu has been passed into |
| * matMenuTriggerFor. If not, an exception is thrown. |
| * @private |
| * @return {?} |
| */ |
| _checkMenu() { |
| if (!this.menu) { |
| throwMatMenuMissingError(); |
| } |
| } |
| /** |
| * This method creates the overlay from the provided menu's template and saves its |
| * OverlayRef so that it can be attached to the DOM when openMenu is called. |
| * @private |
| * @return {?} |
| */ |
| _createOverlay() { |
| if (!this._overlayRef) { |
| /** @type {?} */ |
| const config = this._getOverlayConfig(); |
| this._subscribeToPositions((/** @type {?} */ (config.positionStrategy))); |
| this._overlayRef = this._overlay.create(config); |
| // Consume the `keydownEvents` in order to prevent them from going to another overlay. |
| // Ideally we'd also have our keyboard event logic in here, however doing so will |
| // break anybody that may have implemented the `MatMenuPanel` themselves. |
| this._overlayRef.keydownEvents().subscribe(); |
| } |
| return this._overlayRef; |
| } |
| /** |
| * This method builds the configuration object needed to create the overlay, the OverlayState. |
| * @private |
| * @return {?} OverlayConfig |
| */ |
| _getOverlayConfig() { |
| return new OverlayConfig({ |
| positionStrategy: this._overlay.position() |
| .flexibleConnectedTo(this._element) |
| .withLockedPosition() |
| .withTransformOriginOn('.mat-menu-panel, .mat-mdc-menu-panel'), |
| backdropClass: this.menu.backdropClass || 'cdk-overlay-transparent-backdrop', |
| scrollStrategy: this._scrollStrategy(), |
| direction: this._dir |
| }); |
| } |
| /** |
| * Listens to changes in the position of the overlay and sets the correct classes |
| * on the menu based on the new position. This ensures the animation origin is always |
| * correct, even if a fallback position is used for the overlay. |
| * @private |
| * @param {?} position |
| * @return {?} |
| */ |
| _subscribeToPositions(position) { |
| if (this.menu.setPositionClasses) { |
| position.positionChanges.subscribe((/** |
| * @param {?} change |
| * @return {?} |
| */ |
| change => { |
| /** @type {?} */ |
| const posX = change.connectionPair.overlayX === 'start' ? 'after' : 'before'; |
| /** @type {?} */ |
| const posY = change.connectionPair.overlayY === 'top' ? 'below' : 'above'; |
| (/** @type {?} */ (this.menu.setPositionClasses))(posX, posY); |
| })); |
| } |
| } |
| /** |
| * Sets the appropriate positions on a position strategy |
| * so the overlay connects with the trigger correctly. |
| * @private |
| * @param {?} positionStrategy Strategy whose position to update. |
| * @return {?} |
| */ |
| _setPosition(positionStrategy) { |
| let [originX, originFallbackX] = this.menu.xPosition === 'before' ? ['end', 'start'] : ['start', 'end']; |
| let [overlayY, overlayFallbackY] = this.menu.yPosition === 'above' ? ['bottom', 'top'] : ['top', 'bottom']; |
| let [originY, originFallbackY] = [overlayY, overlayFallbackY]; |
| let [overlayX, overlayFallbackX] = [originX, originFallbackX]; |
| /** @type {?} */ |
| let offsetY = 0; |
| if (this.triggersSubmenu()) { |
| // When the menu is a sub-menu, it should always align itself |
| // to the edges of the trigger, instead of overlapping it. |
| overlayFallbackX = originX = this.menu.xPosition === 'before' ? 'start' : 'end'; |
| originFallbackX = overlayX = originX === 'end' ? 'start' : 'end'; |
| offsetY = overlayY === 'bottom' ? MENU_PANEL_TOP_PADDING : -MENU_PANEL_TOP_PADDING; |
| } |
| else if (!this.menu.overlapTrigger) { |
| originY = overlayY === 'top' ? 'bottom' : 'top'; |
| originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top'; |
| } |
| positionStrategy.withPositions([ |
| { originX, originY, overlayX, overlayY, offsetY }, |
| { originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY }, |
| { |
| originX, |
| originY: originFallbackY, |
| overlayX, |
| overlayY: overlayFallbackY, |
| offsetY: -offsetY |
| }, |
| { |
| originX: originFallbackX, |
| originY: originFallbackY, |
| overlayX: overlayFallbackX, |
| overlayY: overlayFallbackY, |
| offsetY: -offsetY |
| } |
| ]); |
| } |
| /** |
| * Returns a stream that emits whenever an action that should close the menu occurs. |
| * @private |
| * @return {?} |
| */ |
| _menuClosingActions() { |
| /** @type {?} */ |
| const backdrop = (/** @type {?} */ (this._overlayRef)).backdropClick(); |
| /** @type {?} */ |
| const detachments = (/** @type {?} */ (this._overlayRef)).detachments(); |
| /** @type {?} */ |
| const parentClose = this._parentMenu ? this._parentMenu.closed : of(); |
| /** @type {?} */ |
| const hover = this._parentMenu ? this._parentMenu._hovered().pipe(filter((/** |
| * @param {?} active |
| * @return {?} |
| */ |
| active => active !== this._menuItemInstance)), filter((/** |
| * @return {?} |
| */ |
| () => this._menuOpen))) : of(); |
| return merge(backdrop, parentClose, hover, detachments); |
| } |
| /** |
| * Handles mouse presses on the trigger. |
| * @param {?} event |
| * @return {?} |
| */ |
| _handleMousedown(event) { |
| if (!isFakeMousedownFromScreenReader(event)) { |
| // Since right or middle button clicks won't trigger the `click` event, |
| // we shouldn't consider the menu as opened by mouse in those cases. |
| this._openedBy = event.button === 0 ? 'mouse' : null; |
| // Since clicking on the trigger won't close the menu if it opens a sub-menu, |
| // we should prevent focus from moving onto it via click to avoid the |
| // highlight from lingering on the menu item. |
| if (this.triggersSubmenu()) { |
| event.preventDefault(); |
| } |
| } |
| } |
| /** |
| * Handles key presses on the trigger. |
| * @param {?} event |
| * @return {?} |
| */ |
| _handleKeydown(event) { |
| /** @type {?} */ |
| const keyCode = event.keyCode; |
| if (this.triggersSubmenu() && ((keyCode === RIGHT_ARROW && this.dir === 'ltr') || |
| (keyCode === LEFT_ARROW && this.dir === 'rtl'))) { |
| this.openMenu(); |
| } |
| } |
| /** |
| * Handles click events on the trigger. |
| * @param {?} event |
| * @return {?} |
| */ |
| _handleClick(event) { |
| if (this.triggersSubmenu()) { |
| // Stop event propagation to avoid closing the parent menu. |
| event.stopPropagation(); |
| this.openMenu(); |
| } |
| else { |
| this.toggleMenu(); |
| } |
| } |
| /** |
| * Handles the cases where the user hovers over the trigger. |
| * @private |
| * @return {?} |
| */ |
| _handleHover() { |
| // Subscribe to changes in the hovered item in order to toggle the panel. |
| if (!this.triggersSubmenu()) { |
| return; |
| } |
| this._hoverSubscription = this._parentMenu._hovered() |
| // Since we might have multiple competing triggers for the same menu (e.g. a sub-menu |
| // with different data and triggers), we have to delay it by a tick to ensure that |
| // it won't be closed immediately after it is opened. |
| .pipe(filter((/** |
| * @param {?} active |
| * @return {?} |
| */ |
| active => active === this._menuItemInstance && !active.disabled)), delay(0, asapScheduler)) |
| .subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this._openedBy = 'mouse'; |
| // If the same menu is used between multiple triggers, it might still be animating |
| // while the new trigger tries to re-open it. Wait for the animation to finish |
| // before doing so. Also interrupt if the user moves to another item. |
| if (this.menu instanceof MatMenu && this.menu._isAnimating) { |
| // We need the `delay(0)` here in order to avoid |
| // 'changed after checked' errors in some cases. See #12194. |
| this.menu._animationDone |
| .pipe(take(1), delay(0, asapScheduler), takeUntil(this._parentMenu._hovered())) |
| .subscribe((/** |
| * @return {?} |
| */ |
| () => this.openMenu())); |
| } |
| else { |
| this.openMenu(); |
| } |
| })); |
| } |
| /** |
| * Gets the portal that should be attached to the overlay. |
| * @private |
| * @return {?} |
| */ |
| _getPortal() { |
| // Note that we can avoid this check by keeping the portal on the menu panel. |
| // While it would be cleaner, we'd have to introduce another required method on |
| // `MatMenuPanel`, making it harder to consume. |
| if (!this._portal || this._portal.templateRef !== this.menu.templateRef) { |
| this._portal = new TemplatePortal(this.menu.templateRef, this._viewContainerRef); |
| } |
| return this._portal; |
| } |
| } |
| MatMenuTrigger.decorators = [ |
| { type: Directive, args: [{ |
| selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`, |
| host: { |
| 'aria-haspopup': 'true', |
| '[attr.aria-expanded]': 'menuOpen || null', |
| '(mousedown)': '_handleMousedown($event)', |
| '(keydown)': '_handleKeydown($event)', |
| '(click)': '_handleClick($event)', |
| }, |
| exportAs: 'matMenuTrigger' |
| },] }, |
| ]; |
| /** @nocollapse */ |
| MatMenuTrigger.ctorParameters = () => [ |
| { type: Overlay }, |
| { type: ElementRef }, |
| { type: ViewContainerRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_SCROLL_STRATEGY,] }] }, |
| { type: MatMenu, decorators: [{ type: Optional }] }, |
| { type: MatMenuItem, decorators: [{ type: Optional }, { type: Self }] }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: FocusMonitor } |
| ]; |
| MatMenuTrigger.propDecorators = { |
| _deprecatedMatMenuTriggerFor: [{ type: Input, args: ['mat-menu-trigger-for',] }], |
| menu: [{ type: Input, args: ['matMenuTriggerFor',] }], |
| menuData: [{ type: Input, args: ['matMenuTriggerData',] }], |
| restoreFocus: [{ type: Input, args: ['matMenuTriggerRestoreFocus',] }], |
| menuOpened: [{ type: Output }], |
| onMenuOpen: [{ type: Output }], |
| menuClosed: [{ type: Output }], |
| onMenuClose: [{ type: Output }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Used by both the current `MatMenuModule` and the MDC `MatMenuModule` |
| * to declare the menu-related directives. |
| */ |
| // tslint:disable-next-line:class-name |
| class _MatMenuDirectivesModule { |
| } |
| _MatMenuDirectivesModule.decorators = [ |
| { type: NgModule, args: [{ |
| exports: [MatMenuTrigger, MatMenuContent, MatCommonModule], |
| declarations: [MatMenuTrigger, MatMenuContent], |
| providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER] |
| },] }, |
| ]; |
| class MatMenuModule { |
| } |
| MatMenuModule.decorators = [ |
| { type: NgModule, args: [{ |
| imports: [ |
| CommonModule, |
| MatCommonModule, |
| MatRippleModule, |
| OverlayModule, |
| _MatMenuDirectivesModule, |
| ], |
| exports: [_MatMenu, MatMenuItem, _MatMenuDirectivesModule], |
| declarations: [_MatMenu, MatMenuItem], |
| providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER] |
| },] }, |
| ]; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| export { MatMenu, MAT_MENU_DEFAULT_OPTIONS, _MatMenu, _MatMenuBase, MatMenuItem, MatMenuTrigger, MAT_MENU_SCROLL_STRATEGY, MAT_MENU_PANEL, _MatMenuDirectivesModule, MatMenuModule, matMenuAnimations, fadeInItems, transformMenu, MatMenuContent, MAT_MENU_DEFAULT_OPTIONS_FACTORY as ɵa24, MAT_MENU_SCROLL_STRATEGY_FACTORY as ɵb24, MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER as ɵc24 }; |
| //# sourceMappingURL=menu.js.map |