UNPKG

70.3 kBJavaScriptView Raw
1import * as i1 from '@angular/cdk/a11y';
2import { FocusKeyManager, isFakeTouchstartFromScreenReader, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y';
3import { coerceBooleanProperty } from '@angular/cdk/coercion';
4import { UP_ARROW, DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW, ESCAPE, hasModifierKey, ENTER, SPACE } from '@angular/cdk/keycodes';
5import * as i0 from '@angular/core';
6import { InjectionToken, Directive, Inject, Component, ChangeDetectionStrategy, ViewEncapsulation, Optional, Input, QueryList, EventEmitter, TemplateRef, ContentChildren, ViewChild, ContentChild, Output, Self, NgModule } from '@angular/core';
7import { Subject, Subscription, merge, of, asapScheduler } from 'rxjs';
8import { startWith, switchMap, take, takeUntil, filter, delay } from 'rxjs/operators';
9import { trigger, state, style, transition, animate } from '@angular/animations';
10import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal';
11import * as i2 from '@angular/common';
12import { DOCUMENT, CommonModule } from '@angular/common';
13import * as i3 from '@angular/material/core';
14import { mixinDisableRipple, mixinDisabled, MatCommonModule, MatRippleModule } from '@angular/material/core';
15import * as i1$1 from '@angular/cdk/overlay';
16import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
17import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
18import * as i3$1 from '@angular/cdk/bidi';
19import { CdkScrollableModule } from '@angular/cdk/scrolling';
20
21/**
22 * @license
23 * Copyright Google LLC All Rights Reserved.
24 *
25 * Use of this source code is governed by an MIT-style license that can be
26 * found in the LICENSE file at https://angular.io/license
27 */
28/**
29 * Animations used by the mat-menu component.
30 * Animation duration and timing values are based on:
31 * https://material.io/guidelines/components/menus.html#menus-usage
32 * @docs-private
33 */
34const matMenuAnimations = {
35 /**
36 * This animation controls the menu panel's entry and exit from the page.
37 *
38 * When the menu panel is added to the DOM, it scales in and fades in its border.
39 *
40 * When the menu panel is removed from the DOM, it simply fades out after a brief
41 * delay to display the ripple.
42 */
43 transformMenu: trigger('transformMenu', [
44 state('void', style({
45 opacity: 0,
46 transform: 'scale(0.8)',
47 })),
48 transition('void => enter', animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({
49 opacity: 1,
50 transform: 'scale(1)',
51 }))),
52 transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))),
53 ]),
54 /**
55 * This animation fades in the background color and content of the menu panel
56 * after its containing element is scaled in.
57 */
58 fadeInItems: trigger('fadeInItems', [
59 // TODO(crisbeto): this is inside the `transformMenu`
60 // now. Remove next time we do breaking changes.
61 state('showing', style({ opacity: 1 })),
62 transition('void => *', [
63 style({ opacity: 0 }),
64 animate('400ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)'),
65 ]),
66 ]),
67};
68/**
69 * @deprecated
70 * @breaking-change 8.0.0
71 * @docs-private
72 */
73const fadeInItems = matMenuAnimations.fadeInItems;
74/**
75 * @deprecated
76 * @breaking-change 8.0.0
77 * @docs-private
78 */
79const transformMenu = matMenuAnimations.transformMenu;
80
81/**
82 * @license
83 * Copyright Google LLC All Rights Reserved.
84 *
85 * Use of this source code is governed by an MIT-style license that can be
86 * found in the LICENSE file at https://angular.io/license
87 */
88/**
89 * Injection token that can be used to reference instances of `MatMenuContent`. It serves
90 * as alternative token to the actual `MatMenuContent` class which could cause unnecessary
91 * retention of the class and its directive metadata.
92 */
93const MAT_MENU_CONTENT = new InjectionToken('MatMenuContent');
94class _MatMenuContentBase {
95 constructor(_template, _componentFactoryResolver, _appRef, _injector, _viewContainerRef, _document, _changeDetectorRef) {
96 this._template = _template;
97 this._componentFactoryResolver = _componentFactoryResolver;
98 this._appRef = _appRef;
99 this._injector = _injector;
100 this._viewContainerRef = _viewContainerRef;
101 this._document = _document;
102 this._changeDetectorRef = _changeDetectorRef;
103 /** Emits when the menu content has been attached. */
104 this._attached = new Subject();
105 }
106 /**
107 * Attaches the content with a particular context.
108 * @docs-private
109 */
110 attach(context = {}) {
111 var _a;
112 if (!this._portal) {
113 this._portal = new TemplatePortal(this._template, this._viewContainerRef);
114 }
115 this.detach();
116 if (!this._outlet) {
117 this._outlet = new DomPortalOutlet(this._document.createElement('div'), this._componentFactoryResolver, this._appRef, this._injector);
118 }
119 const element = this._template.elementRef.nativeElement;
120 // Because we support opening the same menu from different triggers (which in turn have their
121 // own `OverlayRef` panel), we have to re-insert the host element every time, otherwise we
122 // risk it staying attached to a pane that's no longer in the DOM.
123 element.parentNode.insertBefore(this._outlet.outletElement, element);
124 // When `MatMenuContent` is used in an `OnPush` component, the insertion of the menu
125 // content via `createEmbeddedView` does not cause the content to be seen as "dirty"
126 // by Angular. This causes the `@ContentChildren` for menu items within the menu to
127 // not be updated by Angular. By explicitly marking for check here, we tell Angular that
128 // it needs to check for new menu items and update the `@ContentChild` in `MatMenu`.
129 // @breaking-change 9.0.0 Make change detector ref required
130 (_a = this._changeDetectorRef) === null || _a === void 0 ? void 0 : _a.markForCheck();
131 this._portal.attach(this._outlet, context);
132 this._attached.next();
133 }
134 /**
135 * Detaches the content.
136 * @docs-private
137 */
138 detach() {
139 if (this._portal.isAttached) {
140 this._portal.detach();
141 }
142 }
143 ngOnDestroy() {
144 if (this._outlet) {
145 this._outlet.dispose();
146 }
147 }
148}
149_MatMenuContentBase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuContentBase, deps: [{ token: i0.TemplateRef }, { token: i0.ComponentFactoryResolver }, { token: i0.ApplicationRef }, { token: i0.Injector }, { token: i0.ViewContainerRef }, { token: DOCUMENT }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
150_MatMenuContentBase.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: _MatMenuContentBase, ngImport: i0 });
151i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuContentBase, decorators: [{
152 type: Directive
153 }], ctorParameters: function () {
154 return [{ type: i0.TemplateRef }, { type: i0.ComponentFactoryResolver }, { type: i0.ApplicationRef }, { type: i0.Injector }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
155 type: Inject,
156 args: [DOCUMENT]
157 }] }, { type: i0.ChangeDetectorRef }];
158 } });
159/**
160 * Menu content that will be rendered lazily once the menu is opened.
161 */
162class MatMenuContent extends _MatMenuContentBase {
163}
164MatMenuContent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuContent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
165MatMenuContent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: MatMenuContent, selector: "ng-template[matMenuContent]", providers: [{ provide: MAT_MENU_CONTENT, useExisting: MatMenuContent }], usesInheritance: true, ngImport: i0 });
166i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuContent, decorators: [{
167 type: Directive,
168 args: [{
169 selector: 'ng-template[matMenuContent]',
170 providers: [{ provide: MAT_MENU_CONTENT, useExisting: MatMenuContent }],
171 }]
172 }] });
173
174/**
175 * @license
176 * Copyright Google LLC All Rights Reserved.
177 *
178 * Use of this source code is governed by an MIT-style license that can be
179 * found in the LICENSE file at https://angular.io/license
180 */
181/**
182 * Throws an exception for the case when menu's x-position value isn't valid.
183 * In other words, it doesn't match 'before' or 'after'.
184 * @docs-private
185 */
186function throwMatMenuInvalidPositionX() {
187 throw Error(`xPosition value must be either 'before' or after'.
188 Example: <mat-menu xPosition="before" #menu="matMenu"></mat-menu>`);
189}
190/**
191 * Throws an exception for the case when menu's y-position value isn't valid.
192 * In other words, it doesn't match 'above' or 'below'.
193 * @docs-private
194 */
195function throwMatMenuInvalidPositionY() {
196 throw Error(`yPosition value must be either 'above' or below'.
197 Example: <mat-menu yPosition="above" #menu="matMenu"></mat-menu>`);
198}
199/**
200 * Throws an exception for the case when a menu is assigned
201 * to a trigger that is placed inside the same menu.
202 * @docs-private
203 */
204function throwMatMenuRecursiveError() {
205 throw Error(`matMenuTriggerFor: menu cannot contain its own trigger. Assign a menu that is ` +
206 `not a parent of the trigger or move the trigger outside of the menu.`);
207}
208
209/**
210 * @license
211 * Copyright Google LLC All Rights Reserved.
212 *
213 * Use of this source code is governed by an MIT-style license that can be
214 * found in the LICENSE file at https://angular.io/license
215 */
216/**
217 * Injection token used to provide the parent menu to menu-specific components.
218 * @docs-private
219 */
220const MAT_MENU_PANEL = new InjectionToken('MAT_MENU_PANEL');
221
222// Boilerplate for applying mixins to MatMenuItem.
223/** @docs-private */
224const _MatMenuItemBase = mixinDisableRipple(mixinDisabled(class {
225}));
226/**
227 * Single item inside of a `mat-menu`. Provides the menu item styling and accessibility treatment.
228 */
229class MatMenuItem extends _MatMenuItemBase {
230 constructor(_elementRef, _document, _focusMonitor, _parentMenu, _changeDetectorRef) {
231 var _a;
232 super();
233 this._elementRef = _elementRef;
234 this._document = _document;
235 this._focusMonitor = _focusMonitor;
236 this._parentMenu = _parentMenu;
237 this._changeDetectorRef = _changeDetectorRef;
238 /** ARIA role for the menu item. */
239 this.role = 'menuitem';
240 /** Stream that emits when the menu item is hovered. */
241 this._hovered = new Subject();
242 /** Stream that emits when the menu item is focused. */
243 this._focused = new Subject();
244 /** Whether the menu item is highlighted. */
245 this._highlighted = false;
246 /** Whether the menu item acts as a trigger for a sub-menu. */
247 this._triggersSubmenu = false;
248 (_a = _parentMenu === null || _parentMenu === void 0 ? void 0 : _parentMenu.addItem) === null || _a === void 0 ? void 0 : _a.call(_parentMenu, this);
249 }
250 /** Focuses the menu item. */
251 focus(origin, options) {
252 if (this._focusMonitor && origin) {
253 this._focusMonitor.focusVia(this._getHostElement(), origin, options);
254 }
255 else {
256 this._getHostElement().focus(options);
257 }
258 this._focused.next(this);
259 }
260 ngAfterViewInit() {
261 if (this._focusMonitor) {
262 // Start monitoring the element so it gets the appropriate focused classes. We want
263 // to show the focus style for menu items only when the focus was not caused by a
264 // mouse or touch interaction.
265 this._focusMonitor.monitor(this._elementRef, false);
266 }
267 }
268 ngOnDestroy() {
269 if (this._focusMonitor) {
270 this._focusMonitor.stopMonitoring(this._elementRef);
271 }
272 if (this._parentMenu && this._parentMenu.removeItem) {
273 this._parentMenu.removeItem(this);
274 }
275 this._hovered.complete();
276 this._focused.complete();
277 }
278 /** Used to set the `tabindex`. */
279 _getTabIndex() {
280 return this.disabled ? '-1' : '0';
281 }
282 /** Returns the host DOM element. */
283 _getHostElement() {
284 return this._elementRef.nativeElement;
285 }
286 /** Prevents the default element actions if it is disabled. */
287 _checkDisabled(event) {
288 if (this.disabled) {
289 event.preventDefault();
290 event.stopPropagation();
291 }
292 }
293 /** Emits to the hover stream. */
294 _handleMouseEnter() {
295 this._hovered.next(this);
296 }
297 /** Gets the label to be used when determining whether the option should be focused. */
298 getLabel() {
299 var _a;
300 const clone = this._elementRef.nativeElement.cloneNode(true);
301 const icons = clone.querySelectorAll('mat-icon, .material-icons');
302 // Strip away icons so they don't show up in the text.
303 for (let i = 0; i < icons.length; i++) {
304 icons[i].remove();
305 }
306 return ((_a = clone.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
307 }
308 _setHighlighted(isHighlighted) {
309 var _a;
310 // We need to mark this for check for the case where the content is coming from a
311 // `matMenuContent` whose change detection tree is at the declaration position,
312 // not the insertion position. See #23175.
313 // @breaking-change 12.0.0 Remove null check for `_changeDetectorRef`.
314 this._highlighted = isHighlighted;
315 (_a = this._changeDetectorRef) === null || _a === void 0 ? void 0 : _a.markForCheck();
316 }
317 _hasFocus() {
318 return this._document && this._document.activeElement === this._getHostElement();
319 }
320}
321MatMenuItem.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuItem, deps: [{ token: i0.ElementRef }, { token: DOCUMENT }, { token: i1.FocusMonitor }, { token: MAT_MENU_PANEL, optional: true }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
322MatMenuItem.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.1", type: MatMenuItem, selector: "[mat-menu-item]", inputs: { disabled: "disabled", disableRipple: "disableRipple", role: "role" }, host: { listeners: { "click": "_checkDisabled($event)", "mouseenter": "_handleMouseEnter()" }, properties: { "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" }, classAttribute: "mat-focus-indicator" }, exportAs: ["matMenuItem"], usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\n<div class=\"mat-menu-ripple\" matRipple\n [matRippleDisabled]=\"disableRipple || disabled\"\n [matRippleTrigger]=\"_getHostElement()\">\n</div>\n\n<svg\n *ngIf=\"_triggersSubmenu\"\n class=\"mat-menu-submenu-icon\"\n viewBox=\"0 0 5 10\"\n focusable=\"false\"><polygon points=\"0,0 5,5 0,10\"/></svg>\n", dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
323i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuItem, decorators: [{
324 type: Component,
325 args: [{ selector: '[mat-menu-item]', exportAs: 'matMenuItem', inputs: ['disabled', 'disableRipple'], host: {
326 '[attr.role]': 'role',
327 '[class.mat-menu-item]': 'true',
328 '[class.mat-menu-item-highlighted]': '_highlighted',
329 '[class.mat-menu-item-submenu-trigger]': '_triggersSubmenu',
330 '[attr.tabindex]': '_getTabIndex()',
331 '[attr.aria-disabled]': 'disabled.toString()',
332 '[attr.disabled]': 'disabled || null',
333 'class': 'mat-focus-indicator',
334 '(click)': '_checkDisabled($event)',
335 '(mouseenter)': '_handleMouseEnter()',
336 }, 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\n<svg\n *ngIf=\"_triggersSubmenu\"\n class=\"mat-menu-submenu-icon\"\n viewBox=\"0 0 5 10\"\n focusable=\"false\"><polygon points=\"0,0 5,5 0,10\"/></svg>\n" }]
337 }], ctorParameters: function () {
338 return [{ type: i0.ElementRef }, { type: undefined, decorators: [{
339 type: Inject,
340 args: [DOCUMENT]
341 }] }, { type: i1.FocusMonitor }, { type: undefined, decorators: [{
342 type: Inject,
343 args: [MAT_MENU_PANEL]
344 }, {
345 type: Optional
346 }] }, { type: i0.ChangeDetectorRef }];
347 }, propDecorators: { role: [{
348 type: Input
349 }] } });
350
351/**
352 * @license
353 * Copyright Google LLC All Rights Reserved.
354 *
355 * Use of this source code is governed by an MIT-style license that can be
356 * found in the LICENSE file at https://angular.io/license
357 */
358/** Injection token to be used to override the default options for `mat-menu`. */
359const MAT_MENU_DEFAULT_OPTIONS = new InjectionToken('mat-menu-default-options', {
360 providedIn: 'root',
361 factory: MAT_MENU_DEFAULT_OPTIONS_FACTORY,
362});
363/** @docs-private */
364function MAT_MENU_DEFAULT_OPTIONS_FACTORY() {
365 return {
366 overlapTrigger: false,
367 xPosition: 'after',
368 yPosition: 'below',
369 backdropClass: 'cdk-overlay-transparent-backdrop',
370 };
371}
372let menuPanelUid = 0;
373/** Base class with all of the `MatMenu` functionality. */
374class _MatMenuBase {
375 constructor(_elementRef, _ngZone, _defaultOptions,
376 // @breaking-change 15.0.0 `_changeDetectorRef` to become a required parameter.
377 _changeDetectorRef) {
378 this._elementRef = _elementRef;
379 this._ngZone = _ngZone;
380 this._defaultOptions = _defaultOptions;
381 this._changeDetectorRef = _changeDetectorRef;
382 this._xPosition = this._defaultOptions.xPosition;
383 this._yPosition = this._defaultOptions.yPosition;
384 /** Only the direct descendant menu items. */
385 this._directDescendantItems = new QueryList();
386 /** Subscription to tab events on the menu panel */
387 this._tabSubscription = Subscription.EMPTY;
388 /** Config object to be passed into the menu's ngClass */
389 this._classList = {};
390 /** Current state of the panel animation. */
391 this._panelAnimationState = 'void';
392 /** Emits whenever an animation on the menu completes. */
393 this._animationDone = new Subject();
394 /** Class or list of classes to be added to the overlay panel. */
395 this.overlayPanelClass = this._defaultOptions.overlayPanelClass || '';
396 /** Class to be added to the backdrop element. */
397 this.backdropClass = this._defaultOptions.backdropClass;
398 this._overlapTrigger = this._defaultOptions.overlapTrigger;
399 this._hasBackdrop = this._defaultOptions.hasBackdrop;
400 /** Event emitted when the menu is closed. */
401 this.closed = new EventEmitter();
402 /**
403 * Event emitted when the menu is closed.
404 * @deprecated Switch to `closed` instead
405 * @breaking-change 8.0.0
406 */
407 this.close = this.closed;
408 this.panelId = `mat-menu-panel-${menuPanelUid++}`;
409 }
410 /** Position of the menu in the X axis. */
411 get xPosition() {
412 return this._xPosition;
413 }
414 set xPosition(value) {
415 if (value !== 'before' &&
416 value !== 'after' &&
417 (typeof ngDevMode === 'undefined' || ngDevMode)) {
418 throwMatMenuInvalidPositionX();
419 }
420 this._xPosition = value;
421 this.setPositionClasses();
422 }
423 /** Position of the menu in the Y axis. */
424 get yPosition() {
425 return this._yPosition;
426 }
427 set yPosition(value) {
428 if (value !== 'above' && value !== 'below' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
429 throwMatMenuInvalidPositionY();
430 }
431 this._yPosition = value;
432 this.setPositionClasses();
433 }
434 /** Whether the menu should overlap its trigger. */
435 get overlapTrigger() {
436 return this._overlapTrigger;
437 }
438 set overlapTrigger(value) {
439 this._overlapTrigger = coerceBooleanProperty(value);
440 }
441 /** Whether the menu has a backdrop. */
442 get hasBackdrop() {
443 return this._hasBackdrop;
444 }
445 set hasBackdrop(value) {
446 this._hasBackdrop = coerceBooleanProperty(value);
447 }
448 /**
449 * This method takes classes set on the host mat-menu element and applies them on the
450 * menu template that displays in the overlay container. Otherwise, it's difficult
451 * to style the containing menu from outside the component.
452 * @param classes list of class names
453 */
454 set panelClass(classes) {
455 const previousPanelClass = this._previousPanelClass;
456 if (previousPanelClass && previousPanelClass.length) {
457 previousPanelClass.split(' ').forEach((className) => {
458 this._classList[className] = false;
459 });
460 }
461 this._previousPanelClass = classes;
462 if (classes && classes.length) {
463 classes.split(' ').forEach((className) => {
464 this._classList[className] = true;
465 });
466 this._elementRef.nativeElement.className = '';
467 }
468 }
469 /**
470 * This method takes classes set on the host mat-menu element and applies them on the
471 * menu template that displays in the overlay container. Otherwise, it's difficult
472 * to style the containing menu from outside the component.
473 * @deprecated Use `panelClass` instead.
474 * @breaking-change 8.0.0
475 */
476 get classList() {
477 return this.panelClass;
478 }
479 set classList(classes) {
480 this.panelClass = classes;
481 }
482 ngOnInit() {
483 this.setPositionClasses();
484 }
485 ngAfterContentInit() {
486 this._updateDirectDescendants();
487 this._keyManager = new FocusKeyManager(this._directDescendantItems)
488 .withWrap()
489 .withTypeAhead()
490 .withHomeAndEnd();
491 this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));
492 // If a user manually (programmatically) focuses a menu item, we need to reflect that focus
493 // change back to the key manager. Note that we don't need to unsubscribe here because _focused
494 // is internal and we know that it gets completed on destroy.
495 this._directDescendantItems.changes
496 .pipe(startWith(this._directDescendantItems), switchMap(items => merge(...items.map((item) => item._focused))))
497 .subscribe(focusedItem => this._keyManager.updateActiveItem(focusedItem));
498 this._directDescendantItems.changes.subscribe((itemsList) => {
499 var _a;
500 // Move focus to another item, if the active item is removed from the list.
501 // We need to debounce the callback, because multiple items might be removed
502 // in quick succession.
503 const manager = this._keyManager;
504 if (this._panelAnimationState === 'enter' && ((_a = manager.activeItem) === null || _a === void 0 ? void 0 : _a._hasFocus())) {
505 const items = itemsList.toArray();
506 const index = Math.max(0, Math.min(items.length - 1, manager.activeItemIndex || 0));
507 if (items[index] && !items[index].disabled) {
508 manager.setActiveItem(index);
509 }
510 else {
511 manager.setNextItemActive();
512 }
513 }
514 });
515 }
516 ngOnDestroy() {
517 this._directDescendantItems.destroy();
518 this._tabSubscription.unsubscribe();
519 this.closed.complete();
520 }
521 /** Stream that emits whenever the hovered menu item changes. */
522 _hovered() {
523 // Coerce the `changes` property because Angular types it as `Observable<any>`
524 const itemChanges = this._directDescendantItems.changes;
525 return itemChanges.pipe(startWith(this._directDescendantItems), switchMap(items => merge(...items.map((item) => item._hovered))));
526 }
527 /*
528 * Registers a menu item with the menu.
529 * @docs-private
530 * @deprecated No longer being used. To be removed.
531 * @breaking-change 9.0.0
532 */
533 addItem(_item) { }
534 /**
535 * Removes an item from the menu.
536 * @docs-private
537 * @deprecated No longer being used. To be removed.
538 * @breaking-change 9.0.0
539 */
540 removeItem(_item) { }
541 /** Handle a keyboard event from the menu, delegating to the appropriate action. */
542 _handleKeydown(event) {
543 const keyCode = event.keyCode;
544 const manager = this._keyManager;
545 switch (keyCode) {
546 case ESCAPE:
547 if (!hasModifierKey(event)) {
548 event.preventDefault();
549 this.closed.emit('keydown');
550 }
551 break;
552 case LEFT_ARROW:
553 if (this.parentMenu && this.direction === 'ltr') {
554 this.closed.emit('keydown');
555 }
556 break;
557 case RIGHT_ARROW:
558 if (this.parentMenu && this.direction === 'rtl') {
559 this.closed.emit('keydown');
560 }
561 break;
562 default:
563 if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
564 manager.setFocusOrigin('keyboard');
565 }
566 manager.onKeydown(event);
567 return;
568 }
569 // Don't allow the event to propagate if we've already handled it, or it may
570 // end up reaching other overlays that were opened earlier (see #22694).
571 event.stopPropagation();
572 }
573 /**
574 * Focus the first item in the menu.
575 * @param origin Action from which the focus originated. Used to set the correct styling.
576 */
577 focusFirstItem(origin = 'program') {
578 // Wait for `onStable` to ensure iOS VoiceOver screen reader focuses the first item (#24735).
579 this._ngZone.onStable.pipe(take(1)).subscribe(() => {
580 let menuPanel = null;
581 if (this._directDescendantItems.length) {
582 // Because the `mat-menuPanel` is at the DOM insertion point, not inside the overlay, we don't
583 // have a nice way of getting a hold of the menuPanel panel. We can't use a `ViewChild` either
584 // because the panel is inside an `ng-template`. We work around it by starting from one of
585 // the items and walking up the DOM.
586 menuPanel = this._directDescendantItems.first._getHostElement().closest('[role="menu"]');
587 }
588 // If an item in the menuPanel is already focused, avoid overriding the focus.
589 if (!menuPanel || !menuPanel.contains(document.activeElement)) {
590 const manager = this._keyManager;
591 manager.setFocusOrigin(origin).setFirstItemActive();
592 // If there's no active item at this point, it means that all the items are disabled.
593 // Move focus to the menuPanel panel so keyboard events like Escape still work. Also this will
594 // give _some_ feedback to screen readers.
595 if (!manager.activeItem && menuPanel) {
596 menuPanel.focus();
597 }
598 }
599 });
600 }
601 /**
602 * Resets the active item in the menu. This is used when the menu is opened, allowing
603 * the user to start from the first option when pressing the down arrow.
604 */
605 resetActiveItem() {
606 this._keyManager.setActiveItem(-1);
607 }
608 /**
609 * Sets the menu panel elevation.
610 * @param depth Number of parent menus that come before the menu.
611 */
612 setElevation(depth) {
613 // The elevation starts at the base and increases by one for each level.
614 // Capped at 24 because that's the maximum elevation defined in the Material design spec.
615 const elevation = Math.min(this._baseElevation + depth, 24);
616 const newElevation = `${this._elevationPrefix}${elevation}`;
617 const customElevation = Object.keys(this._classList).find(className => {
618 return className.startsWith(this._elevationPrefix);
619 });
620 if (!customElevation || customElevation === this._previousElevation) {
621 if (this._previousElevation) {
622 this._classList[this._previousElevation] = false;
623 }
624 this._classList[newElevation] = true;
625 this._previousElevation = newElevation;
626 }
627 }
628 /**
629 * Adds classes to the menu panel based on its position. Can be used by
630 * consumers to add specific styling based on the position.
631 * @param posX Position of the menu along the x axis.
632 * @param posY Position of the menu along the y axis.
633 * @docs-private
634 */
635 setPositionClasses(posX = this.xPosition, posY = this.yPosition) {
636 var _a;
637 const classes = this._classList;
638 classes['mat-menu-before'] = posX === 'before';
639 classes['mat-menu-after'] = posX === 'after';
640 classes['mat-menu-above'] = posY === 'above';
641 classes['mat-menu-below'] = posY === 'below';
642 // @breaking-change 15.0.0 Remove null check for `_changeDetectorRef`.
643 (_a = this._changeDetectorRef) === null || _a === void 0 ? void 0 : _a.markForCheck();
644 }
645 /** Starts the enter animation. */
646 _startAnimation() {
647 // @breaking-change 8.0.0 Combine with _resetAnimation.
648 this._panelAnimationState = 'enter';
649 }
650 /** Resets the panel animation to its initial state. */
651 _resetAnimation() {
652 // @breaking-change 8.0.0 Combine with _startAnimation.
653 this._panelAnimationState = 'void';
654 }
655 /** Callback that is invoked when the panel animation completes. */
656 _onAnimationDone(event) {
657 this._animationDone.next(event);
658 this._isAnimating = false;
659 }
660 _onAnimationStart(event) {
661 this._isAnimating = true;
662 // Scroll the content element to the top as soon as the animation starts. This is necessary,
663 // because we move focus to the first item while it's still being animated, which can throw
664 // the browser off when it determines the scroll position. Alternatively we can move focus
665 // when the animation is done, however moving focus asynchronously will interrupt screen
666 // readers which are in the process of reading out the menu already. We take the `element`
667 // from the `event` since we can't use a `ViewChild` to access the pane.
668 if (event.toState === 'enter' && this._keyManager.activeItemIndex === 0) {
669 event.element.scrollTop = 0;
670 }
671 }
672 /**
673 * Sets up a stream that will keep track of any newly-added menu items and will update the list
674 * of direct descendants. We collect the descendants this way, because `_allItems` can include
675 * items that are part of child menus, and using a custom way of registering items is unreliable
676 * when it comes to maintaining the item order.
677 */
678 _updateDirectDescendants() {
679 this._allItems.changes
680 .pipe(startWith(this._allItems))
681 .subscribe((items) => {
682 this._directDescendantItems.reset(items.filter(item => item._parentMenu === this));
683 this._directDescendantItems.notifyOnChanges();
684 });
685 }
686}
687_MatMenuBase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuBase, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: MAT_MENU_DEFAULT_OPTIONS }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
688_MatMenuBase.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: _MatMenuBase, inputs: { backdropClass: "backdropClass", ariaLabel: ["aria-label", "ariaLabel"], ariaLabelledby: ["aria-labelledby", "ariaLabelledby"], ariaDescribedby: ["aria-describedby", "ariaDescribedby"], xPosition: "xPosition", yPosition: "yPosition", overlapTrigger: "overlapTrigger", hasBackdrop: "hasBackdrop", panelClass: ["class", "panelClass"], classList: "classList" }, outputs: { closed: "closed", close: "close" }, queries: [{ propertyName: "lazyContent", first: true, predicate: MAT_MENU_CONTENT, descendants: true }, { propertyName: "_allItems", predicate: MatMenuItem, descendants: true }, { propertyName: "items", predicate: MatMenuItem }], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0 });
689i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuBase, decorators: [{
690 type: Directive
691 }], ctorParameters: function () {
692 return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: undefined, decorators: [{
693 type: Inject,
694 args: [MAT_MENU_DEFAULT_OPTIONS]
695 }] }, { type: i0.ChangeDetectorRef }];
696 }, propDecorators: { _allItems: [{
697 type: ContentChildren,
698 args: [MatMenuItem, { descendants: true }]
699 }], backdropClass: [{
700 type: Input
701 }], ariaLabel: [{
702 type: Input,
703 args: ['aria-label']
704 }], ariaLabelledby: [{
705 type: Input,
706 args: ['aria-labelledby']
707 }], ariaDescribedby: [{
708 type: Input,
709 args: ['aria-describedby']
710 }], xPosition: [{
711 type: Input
712 }], yPosition: [{
713 type: Input
714 }], templateRef: [{
715 type: ViewChild,
716 args: [TemplateRef]
717 }], items: [{
718 type: ContentChildren,
719 args: [MatMenuItem, { descendants: false }]
720 }], lazyContent: [{
721 type: ContentChild,
722 args: [MAT_MENU_CONTENT]
723 }], overlapTrigger: [{
724 type: Input
725 }], hasBackdrop: [{
726 type: Input
727 }], panelClass: [{
728 type: Input,
729 args: ['class']
730 }], classList: [{
731 type: Input
732 }], closed: [{
733 type: Output
734 }], close: [{
735 type: Output
736 }] } });
737/** @docs-public MatMenu */
738class MatMenu extends _MatMenuBase {
739 constructor(elementRef, ngZone, defaultOptions, changeDetectorRef) {
740 super(elementRef, ngZone, defaultOptions, changeDetectorRef);
741 this._elevationPrefix = 'mat-elevation-z';
742 this._baseElevation = 4;
743 }
744}
745MatMenu.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenu, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: MAT_MENU_DEFAULT_OPTIONS }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
746MatMenu.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.1", type: MatMenu, selector: "mat-menu", host: { properties: { "attr.aria-label": "null", "attr.aria-labelledby": "null", "attr.aria-describedby": "null" } }, providers: [{ provide: MAT_MENU_PANEL, useExisting: MatMenu }], exportAs: ["matMenu"], usesInheritance: true, ngImport: i0, 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", styles: ["mat-menu{display:none}.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;position:relative}.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;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:rgba(0,0,0,0);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]::before{display:block;position:absolute;content:\"\";top:0;left:0;bottom:0;right:0}.cdk-high-contrast-active .mat-menu-item{margin-top:1px}.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}[dir=rtl] .mat-menu-item-submenu-trigger{padding-right:16px;padding-left:32px}.mat-menu-submenu-icon{position:absolute;top:50%;right:16px;transform:translateY(-50%);width:5px;height:10px;fill:currentColor}[dir=rtl] .mat-menu-submenu-icon{right:auto;left:16px;transform:translateY(-50%) scaleX(-1)}.cdk-high-contrast-active .mat-menu-submenu-icon{fill:CanvasText}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}"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], animations: [matMenuAnimations.transformMenu, matMenuAnimations.fadeInItems], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
747i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenu, decorators: [{
748 type: Component,
749 args: [{ selector: 'mat-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, exportAs: 'matMenu', host: {
750 '[attr.aria-label]': 'null',
751 '[attr.aria-labelledby]': 'null',
752 '[attr.aria-describedby]': 'null',
753 }, animations: [matMenuAnimations.transformMenu, matMenuAnimations.fadeInItems], providers: [{ provide: MAT_MENU_PANEL, useExisting: MatMenu }], 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", styles: ["mat-menu{display:none}.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;position:relative}.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;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:rgba(0,0,0,0);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]::before{display:block;position:absolute;content:\"\";top:0;left:0;bottom:0;right:0}.cdk-high-contrast-active .mat-menu-item{margin-top:1px}.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}[dir=rtl] .mat-menu-item-submenu-trigger{padding-right:16px;padding-left:32px}.mat-menu-submenu-icon{position:absolute;top:50%;right:16px;transform:translateY(-50%);width:5px;height:10px;fill:currentColor}[dir=rtl] .mat-menu-submenu-icon{right:auto;left:16px;transform:translateY(-50%) scaleX(-1)}.cdk-high-contrast-active .mat-menu-submenu-icon{fill:CanvasText}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}"] }]
754 }], ctorParameters: function () {
755 return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: undefined, decorators: [{
756 type: Inject,
757 args: [MAT_MENU_DEFAULT_OPTIONS]
758 }] }, { type: i0.ChangeDetectorRef }];
759 } });
760
761/**
762 * @license
763 * Copyright Google LLC All Rights Reserved.
764 *
765 * Use of this source code is governed by an MIT-style license that can be
766 * found in the LICENSE file at https://angular.io/license
767 */
768/** Injection token that determines the scroll handling while the menu is open. */
769const MAT_MENU_SCROLL_STRATEGY = new InjectionToken('mat-menu-scroll-strategy');
770/** @docs-private */
771function MAT_MENU_SCROLL_STRATEGY_FACTORY(overlay) {
772 return () => overlay.scrollStrategies.reposition();
773}
774/** @docs-private */
775const MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER = {
776 provide: MAT_MENU_SCROLL_STRATEGY,
777 deps: [Overlay],
778 useFactory: MAT_MENU_SCROLL_STRATEGY_FACTORY,
779};
780/**
781 * Default top padding of the menu panel.
782 * @deprecated No longer being used. Will be removed.
783 * @breaking-change 15.0.0
784 */
785const MENU_PANEL_TOP_PADDING = 8;
786/** Options for binding a passive event listener. */
787const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
788// TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors
789class _MatMenuTriggerBase {
790 constructor(_overlay, _element, _viewContainerRef, scrollStrategy, parentMenu,
791 // `MatMenuTrigger` is commonly used in combination with a `MatMenuItem`.
792 // tslint:disable-next-line: lightweight-tokens
793 _menuItemInstance, _dir, _focusMonitor, _ngZone) {
794 this._overlay = _overlay;
795 this._element = _element;
796 this._viewContainerRef = _viewContainerRef;
797 this._menuItemInstance = _menuItemInstance;
798 this._dir = _dir;
799 this._focusMonitor = _focusMonitor;
800 this._ngZone = _ngZone;
801 this._overlayRef = null;
802 this._menuOpen = false;
803 this._closingActionsSubscription = Subscription.EMPTY;
804 this._hoverSubscription = Subscription.EMPTY;
805 this._menuCloseSubscription = Subscription.EMPTY;
806 /**
807 * Handles touch start events on the trigger.
808 * Needs to be an arrow function so we can easily use addEventListener and removeEventListener.
809 */
810 this._handleTouchStart = (event) => {
811 if (!isFakeTouchstartFromScreenReader(event)) {
812 this._openedBy = 'touch';
813 }
814 };
815 // Tracking input type is necessary so it's possible to only auto-focus
816 // the first item of the list when the menu is opened via the keyboard
817 this._openedBy = undefined;
818 /**
819 * Whether focus should be restored when the menu is closed.
820 * Note that disabling this option can have accessibility implications
821 * and it's up to you to manage focus, if you decide to turn it off.
822 */
823 this.restoreFocus = true;
824 /** Event emitted when the associated menu is opened. */
825 this.menuOpened = new EventEmitter();
826 /**
827 * Event emitted when the associated menu is opened.
828 * @deprecated Switch to `menuOpened` instead
829 * @breaking-change 8.0.0
830 */
831 // tslint:disable-next-line:no-output-on-prefix
832 this.onMenuOpen = this.menuOpened;
833 /** Event emitted when the associated menu is closed. */
834 this.menuClosed = new EventEmitter();
835 /**
836 * Event emitted when the associated menu is closed.
837 * @deprecated Switch to `menuClosed` instead
838 * @breaking-change 8.0.0
839 */
840 // tslint:disable-next-line:no-output-on-prefix
841 this.onMenuClose = this.menuClosed;
842 this._scrollStrategy = scrollStrategy;
843 this._parentMaterialMenu = parentMenu instanceof _MatMenuBase ? parentMenu : undefined;
844 _element.nativeElement.addEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
845 if (_menuItemInstance) {
846 _menuItemInstance._triggersSubmenu = this.triggersSubmenu();
847 }
848 }
849 /**
850 * @deprecated
851 * @breaking-change 8.0.0
852 */
853 get _deprecatedMatMenuTriggerFor() {
854 return this.menu;
855 }
856 set _deprecatedMatMenuTriggerFor(v) {
857 this.menu = v;
858 }
859 /** References the menu instance that the trigger is associated with. */
860 get menu() {
861 return this._menu;
862 }
863 set menu(menu) {
864 if (menu === this._menu) {
865 return;
866 }
867 this._menu = menu;
868 this._menuCloseSubscription.unsubscribe();
869 if (menu) {
870 if (menu === this._parentMaterialMenu && (typeof ngDevMode === 'undefined' || ngDevMode)) {
871 throwMatMenuRecursiveError();
872 }
873 this._menuCloseSubscription = menu.close.subscribe((reason) => {
874 this._destroyMenu(reason);
875 // If a click closed the menu, we should close the entire chain of nested menus.
876 if ((reason === 'click' || reason === 'tab') && this._parentMaterialMenu) {
877 this._parentMaterialMenu.closed.emit(reason);
878 }
879 });
880 }
881 }
882 ngAfterContentInit() {
883 this._handleHover();
884 }
885 ngOnDestroy() {
886 if (this._overlayRef) {
887 this._overlayRef.dispose();
888 this._overlayRef = null;
889 }
890 this._element.nativeElement.removeEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
891 this._menuCloseSubscription.unsubscribe();
892 this._closingActionsSubscription.unsubscribe();
893 this._hoverSubscription.unsubscribe();
894 }
895 /** Whether the menu is open. */
896 get menuOpen() {
897 return this._menuOpen;
898 }
899 /** The text direction of the containing app. */
900 get dir() {
901 return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
902 }
903 /** Whether the menu triggers a sub-menu or a top-level one. */
904 triggersSubmenu() {
905 return !!(this._menuItemInstance && this._parentMaterialMenu);
906 }
907 /** Toggles the menu between the open and closed states. */
908 toggleMenu() {
909 return this._menuOpen ? this.closeMenu() : this.openMenu();
910 }
911 /** Opens the menu. */
912 openMenu() {
913 const menu = this.menu;
914 if (this._menuOpen || !menu) {
915 return;
916 }
917 const overlayRef = this._createOverlay(menu);
918 const overlayConfig = overlayRef.getConfig();
919 const positionStrategy = overlayConfig.positionStrategy;
920 this._setPosition(menu, positionStrategy);
921 overlayConfig.hasBackdrop =
922 menu.hasBackdrop == null ? !this.triggersSubmenu() : menu.hasBackdrop;
923 overlayRef.attach(this._getPortal(menu));
924 if (menu.lazyContent) {
925 menu.lazyContent.attach(this.menuData);
926 }
927 this._closingActionsSubscription = this._menuClosingActions().subscribe(() => this.closeMenu());
928 this._initMenu(menu);
929 if (menu instanceof _MatMenuBase) {
930 menu._startAnimation();
931 menu._directDescendantItems.changes.pipe(takeUntil(menu.close)).subscribe(() => {
932 // Re-adjust the position without locking when the amount of items
933 // changes so that the overlay is allowed to pick a new optimal position.
934 positionStrategy.withLockedPosition(false).reapplyLastPosition();
935 positionStrategy.withLockedPosition(true);
936 });
937 }
938 }
939 /** Closes the menu. */
940 closeMenu() {
941 var _a;
942 (_a = this.menu) === null || _a === void 0 ? void 0 : _a.close.emit();
943 }
944 /**
945 * Focuses the menu trigger.
946 * @param origin Source of the menu trigger's focus.
947 */
948 focus(origin, options) {
949 if (this._focusMonitor && origin) {
950 this._focusMonitor.focusVia(this._element, origin, options);
951 }
952 else {
953 this._element.nativeElement.focus(options);
954 }
955 }
956 /**
957 * Updates the position of the menu to ensure that it fits all options within the viewport.
958 */
959 updatePosition() {
960 var _a;
961 (_a = this._overlayRef) === null || _a === void 0 ? void 0 : _a.updatePosition();
962 }
963 /** Closes the menu and does the necessary cleanup. */
964 _destroyMenu(reason) {
965 var _a;
966 if (!this._overlayRef || !this.menuOpen) {
967 return;
968 }
969 const menu = this.menu;
970 this._closingActionsSubscription.unsubscribe();
971 this._overlayRef.detach();
972 // Always restore focus if the user is navigating using the keyboard or the menu was opened
973 // programmatically. We don't restore for non-root triggers, because it can prevent focus
974 // from making it back to the root trigger when closing a long chain of menus by clicking
975 // on the backdrop.
976 if (this.restoreFocus && (reason === 'keydown' || !this._openedBy || !this.triggersSubmenu())) {
977 this.focus(this._openedBy);
978 }
979 this._openedBy = undefined;
980 if (menu instanceof _MatMenuBase) {
981 menu._resetAnimation();
982 if (menu.lazyContent) {
983 // Wait for the exit animation to finish before detaching the content.
984 menu._animationDone
985 .pipe(filter(event => event.toState === 'void'), take(1),
986 // Interrupt if the content got re-attached.
987 takeUntil(menu.lazyContent._attached))
988 .subscribe({
989 next: () => menu.lazyContent.detach(),
990 // No matter whether the content got re-attached, reset the menu.
991 complete: () => this._setIsMenuOpen(false),
992 });
993 }
994 else {
995 this._setIsMenuOpen(false);
996 }
997 }
998 else {
999 this._setIsMenuOpen(false);
1000 (_a = menu === null || menu === void 0 ? void 0 : menu.lazyContent) === null || _a === void 0 ? void 0 : _a.detach();
1001 }
1002 }
1003 /**
1004 * This method sets the menu state to open and focuses the first item if
1005 * the menu was opened via the keyboard.
1006 */
1007 _initMenu(menu) {
1008 menu.parentMenu = this.triggersSubmenu() ? this._parentMaterialMenu : undefined;
1009 menu.direction = this.dir;
1010 this._setMenuElevation(menu);
1011 menu.focusFirstItem(this._openedBy || 'program');
1012 this._setIsMenuOpen(true);
1013 }
1014 /** Updates the menu elevation based on the amount of parent menus that it has. */
1015 _setMenuElevation(menu) {
1016 if (menu.setElevation) {
1017 let depth = 0;
1018 let parentMenu = menu.parentMenu;
1019 while (parentMenu) {
1020 depth++;
1021 parentMenu = parentMenu.parentMenu;
1022 }
1023 menu.setElevation(depth);
1024 }
1025 }
1026 // set state rather than toggle to support triggers sharing a menu
1027 _setIsMenuOpen(isOpen) {
1028 this._menuOpen = isOpen;
1029 this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit();
1030 if (this.triggersSubmenu()) {
1031 this._menuItemInstance._setHighlighted(isOpen);
1032 }
1033 }
1034 /**
1035 * This method creates the overlay from the provided menu's template and saves its
1036 * OverlayRef so that it can be attached to the DOM when openMenu is called.
1037 */
1038 _createOverlay(menu) {
1039 if (!this._overlayRef) {
1040 const config = this._getOverlayConfig(menu);
1041 this._subscribeToPositions(menu, config.positionStrategy);
1042 this._overlayRef = this._overlay.create(config);
1043 // Consume the `keydownEvents` in order to prevent them from going to another overlay.
1044 // Ideally we'd also have our keyboard event logic in here, however doing so will
1045 // break anybody that may have implemented the `MatMenuPanel` themselves.
1046 this._overlayRef.keydownEvents().subscribe();
1047 }
1048 return this._overlayRef;
1049 }
1050 /**
1051 * This method builds the configuration object needed to create the overlay, the OverlayState.
1052 * @returns OverlayConfig
1053 */
1054 _getOverlayConfig(menu) {
1055 return new OverlayConfig({
1056 positionStrategy: this._overlay
1057 .position()
1058 .flexibleConnectedTo(this._element)
1059 .withLockedPosition()
1060 .withGrowAfterOpen()
1061 .withTransformOriginOn('.mat-menu-panel, .mat-mdc-menu-panel'),
1062 backdropClass: menu.backdropClass || 'cdk-overlay-transparent-backdrop',
1063 panelClass: menu.overlayPanelClass,
1064 scrollStrategy: this._scrollStrategy(),
1065 direction: this._dir,
1066 });
1067 }
1068 /**
1069 * Listens to changes in the position of the overlay and sets the correct classes
1070 * on the menu based on the new position. This ensures the animation origin is always
1071 * correct, even if a fallback position is used for the overlay.
1072 */
1073 _subscribeToPositions(menu, position) {
1074 if (menu.setPositionClasses) {
1075 position.positionChanges.subscribe(change => {
1076 const posX = change.connectionPair.overlayX === 'start' ? 'after' : 'before';
1077 const posY = change.connectionPair.overlayY === 'top' ? 'below' : 'above';
1078 // @breaking-change 15.0.0 Remove null check for `ngZone`.
1079 // `positionChanges` fires outside of the `ngZone` and `setPositionClasses` might be
1080 // updating something in the view so we need to bring it back in.
1081 if (this._ngZone) {
1082 this._ngZone.run(() => menu.setPositionClasses(posX, posY));
1083 }
1084 else {
1085 menu.setPositionClasses(posX, posY);
1086 }
1087 });
1088 }
1089 }
1090 /**
1091 * Sets the appropriate positions on a position strategy
1092 * so the overlay connects with the trigger correctly.
1093 * @param positionStrategy Strategy whose position to update.
1094 */
1095 _setPosition(menu, positionStrategy) {
1096 let [originX, originFallbackX] = menu.xPosition === 'before' ? ['end', 'start'] : ['start', 'end'];
1097 let [overlayY, overlayFallbackY] = menu.yPosition === 'above' ? ['bottom', 'top'] : ['top', 'bottom'];
1098 let [originY, originFallbackY] = [overlayY, overlayFallbackY];
1099 let [overlayX, overlayFallbackX] = [originX, originFallbackX];
1100 let offsetY = 0;
1101 if (this.triggersSubmenu()) {
1102 // When the menu is a sub-menu, it should always align itself
1103 // to the edges of the trigger, instead of overlapping it.
1104 overlayFallbackX = originX = menu.xPosition === 'before' ? 'start' : 'end';
1105 originFallbackX = overlayX = originX === 'end' ? 'start' : 'end';
1106 if (this._parentMaterialMenu) {
1107 if (this._parentInnerPadding == null) {
1108 const firstItem = this._parentMaterialMenu.items.first;
1109 this._parentInnerPadding = firstItem ? firstItem._getHostElement().offsetTop : 0;
1110 }
1111 offsetY = overlayY === 'bottom' ? this._parentInnerPadding : -this._parentInnerPadding;
1112 }
1113 }
1114 else if (!menu.overlapTrigger) {
1115 originY = overlayY === 'top' ? 'bottom' : 'top';
1116 originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top';
1117 }
1118 positionStrategy.withPositions([
1119 { originX, originY, overlayX, overlayY, offsetY },
1120 { originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY },
1121 {
1122 originX,
1123 originY: originFallbackY,
1124 overlayX,
1125 overlayY: overlayFallbackY,
1126 offsetY: -offsetY,
1127 },
1128 {
1129 originX: originFallbackX,
1130 originY: originFallbackY,
1131 overlayX: overlayFallbackX,
1132 overlayY: overlayFallbackY,
1133 offsetY: -offsetY,
1134 },
1135 ]);
1136 }
1137 /** Returns a stream that emits whenever an action that should close the menu occurs. */
1138 _menuClosingActions() {
1139 const backdrop = this._overlayRef.backdropClick();
1140 const detachments = this._overlayRef.detachments();
1141 const parentClose = this._parentMaterialMenu ? this._parentMaterialMenu.closed : of();
1142 const hover = this._parentMaterialMenu
1143 ? this._parentMaterialMenu._hovered().pipe(filter(active => active !== this._menuItemInstance), filter(() => this._menuOpen))
1144 : of();
1145 return merge(backdrop, parentClose, hover, detachments);
1146 }
1147 /** Handles mouse presses on the trigger. */
1148 _handleMousedown(event) {
1149 if (!isFakeMousedownFromScreenReader(event)) {
1150 // Since right or middle button clicks won't trigger the `click` event,
1151 // we shouldn't consider the menu as opened by mouse in those cases.
1152 this._openedBy = event.button === 0 ? 'mouse' : undefined;
1153 // Since clicking on the trigger won't close the menu if it opens a sub-menu,
1154 // we should prevent focus from moving onto it via click to avoid the
1155 // highlight from lingering on the menu item.
1156 if (this.triggersSubmenu()) {
1157 event.preventDefault();
1158 }
1159 }
1160 }
1161 /** Handles key presses on the trigger. */
1162 _handleKeydown(event) {
1163 const keyCode = event.keyCode;
1164 // Pressing enter on the trigger will trigger the click handler later.
1165 if (keyCode === ENTER || keyCode === SPACE) {
1166 this._openedBy = 'keyboard';
1167 }
1168 if (this.triggersSubmenu() &&
1169 ((keyCode === RIGHT_ARROW && this.dir === 'ltr') ||
1170 (keyCode === LEFT_ARROW && this.dir === 'rtl'))) {
1171 this._openedBy = 'keyboard';
1172 this.openMenu();
1173 }
1174 }
1175 /** Handles click events on the trigger. */
1176 _handleClick(event) {
1177 if (this.triggersSubmenu()) {
1178 // Stop event propagation to avoid closing the parent menu.
1179 event.stopPropagation();
1180 this.openMenu();
1181 }
1182 else {
1183 this.toggleMenu();
1184 }
1185 }
1186 /** Handles the cases where the user hovers over the trigger. */
1187 _handleHover() {
1188 // Subscribe to changes in the hovered item in order to toggle the panel.
1189 if (!this.triggersSubmenu() || !this._parentMaterialMenu) {
1190 return;
1191 }
1192 this._hoverSubscription = this._parentMaterialMenu
1193 ._hovered()
1194 // Since we might have multiple competing triggers for the same menu (e.g. a sub-menu
1195 // with different data and triggers), we have to delay it by a tick to ensure that
1196 // it won't be closed immediately after it is opened.
1197 .pipe(filter(active => active === this._menuItemInstance && !active.disabled), delay(0, asapScheduler))
1198 .subscribe(() => {
1199 this._openedBy = 'mouse';
1200 // If the same menu is used between multiple triggers, it might still be animating
1201 // while the new trigger tries to re-open it. Wait for the animation to finish
1202 // before doing so. Also interrupt if the user moves to another item.
1203 if (this.menu instanceof _MatMenuBase && this.menu._isAnimating) {
1204 // We need the `delay(0)` here in order to avoid
1205 // 'changed after checked' errors in some cases. See #12194.
1206 this.menu._animationDone
1207 .pipe(take(1), delay(0, asapScheduler), takeUntil(this._parentMaterialMenu._hovered()))
1208 .subscribe(() => this.openMenu());
1209 }
1210 else {
1211 this.openMenu();
1212 }
1213 });
1214 }
1215 /** Gets the portal that should be attached to the overlay. */
1216 _getPortal(menu) {
1217 // Note that we can avoid this check by keeping the portal on the menu panel.
1218 // While it would be cleaner, we'd have to introduce another required method on
1219 // `MatMenuPanel`, making it harder to consume.
1220 if (!this._portal || this._portal.templateRef !== menu.templateRef) {
1221 this._portal = new TemplatePortal(menu.templateRef, this._viewContainerRef);
1222 }
1223 return this._portal;
1224 }
1225}
1226_MatMenuTriggerBase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuTriggerBase, deps: [{ token: i1$1.Overlay }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: MAT_MENU_SCROLL_STRATEGY }, { token: MAT_MENU_PANEL, optional: true }, { token: MatMenuItem, optional: true, self: true }, { token: i3$1.Directionality, optional: true }, { token: i1.FocusMonitor }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
1227_MatMenuTriggerBase.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: _MatMenuTriggerBase, inputs: { _deprecatedMatMenuTriggerFor: ["mat-menu-trigger-for", "_deprecatedMatMenuTriggerFor"], menu: ["matMenuTriggerFor", "menu"], menuData: ["matMenuTriggerData", "menuData"], restoreFocus: ["matMenuTriggerRestoreFocus", "restoreFocus"] }, outputs: { menuOpened: "menuOpened", onMenuOpen: "onMenuOpen", menuClosed: "menuClosed", onMenuClose: "onMenuClose" }, host: { listeners: { "click": "_handleClick($event)", "mousedown": "_handleMousedown($event)", "keydown": "_handleKeydown($event)" }, properties: { "attr.aria-haspopup": "menu ? \"menu\" : null", "attr.aria-expanded": "menuOpen || null", "attr.aria-controls": "menuOpen ? menu.panelId : null" } }, ngImport: i0 });
1228i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatMenuTriggerBase, decorators: [{
1229 type: Directive,
1230 args: [{
1231 host: {
1232 '[attr.aria-haspopup]': 'menu ? "menu" : null',
1233 '[attr.aria-expanded]': 'menuOpen || null',
1234 '[attr.aria-controls]': 'menuOpen ? menu.panelId : null',
1235 '(click)': '_handleClick($event)',
1236 '(mousedown)': '_handleMousedown($event)',
1237 '(keydown)': '_handleKeydown($event)',
1238 },
1239 }]
1240 }], ctorParameters: function () {
1241 return [{ type: i1$1.Overlay }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
1242 type: Inject,
1243 args: [MAT_MENU_SCROLL_STRATEGY]
1244 }] }, { type: undefined, decorators: [{
1245 type: Inject,
1246 args: [MAT_MENU_PANEL]
1247 }, {
1248 type: Optional
1249 }] }, { type: MatMenuItem, decorators: [{
1250 type: Optional
1251 }, {
1252 type: Self
1253 }] }, { type: i3$1.Directionality, decorators: [{
1254 type: Optional
1255 }] }, { type: i1.FocusMonitor }, { type: i0.NgZone }];
1256 }, propDecorators: { _deprecatedMatMenuTriggerFor: [{
1257 type: Input,
1258 args: ['mat-menu-trigger-for']
1259 }], menu: [{
1260 type: Input,
1261 args: ['matMenuTriggerFor']
1262 }], menuData: [{
1263 type: Input,
1264 args: ['matMenuTriggerData']
1265 }], restoreFocus: [{
1266 type: Input,
1267 args: ['matMenuTriggerRestoreFocus']
1268 }], menuOpened: [{
1269 type: Output
1270 }], onMenuOpen: [{
1271 type: Output
1272 }], menuClosed: [{
1273 type: Output
1274 }], onMenuClose: [{
1275 type: Output
1276 }] } });
1277/** Directive applied to an element that should trigger a `mat-menu`. */
1278class MatMenuTrigger extends _MatMenuTriggerBase {
1279}
1280MatMenuTrigger.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuTrigger, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1281MatMenuTrigger.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", host: { classAttribute: "mat-menu-trigger" }, exportAs: ["matMenuTrigger"], usesInheritance: true, ngImport: i0 });
1282i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuTrigger, decorators: [{
1283 type: Directive,
1284 args: [{
1285 selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`,
1286 host: {
1287 'class': 'mat-menu-trigger',
1288 },
1289 exportAs: 'matMenuTrigger',
1290 }]
1291 }] });
1292
1293/**
1294 * @license
1295 * Copyright Google LLC All Rights Reserved.
1296 *
1297 * Use of this source code is governed by an MIT-style license that can be
1298 * found in the LICENSE file at https://angular.io/license
1299 */
1300class MatMenuModule {
1301}
1302MatMenuModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1303MatMenuModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.1", ngImport: i0, type: MatMenuModule, declarations: [MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent], imports: [CommonModule, MatCommonModule, MatRippleModule, OverlayModule], exports: [CdkScrollableModule,
1304 MatCommonModule,
1305 MatMenu,
1306 MatMenuItem,
1307 MatMenuTrigger,
1308 MatMenuContent] });
1309MatMenuModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuModule, providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER], imports: [CommonModule, MatCommonModule, MatRippleModule, OverlayModule, CdkScrollableModule,
1310 MatCommonModule] });
1311i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatMenuModule, decorators: [{
1312 type: NgModule,
1313 args: [{
1314 imports: [CommonModule, MatCommonModule, MatRippleModule, OverlayModule],
1315 exports: [
1316 CdkScrollableModule,
1317 MatCommonModule,
1318 MatMenu,
1319 MatMenuItem,
1320 MatMenuTrigger,
1321 MatMenuContent,
1322 ],
1323 declarations: [MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent],
1324 providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER],
1325 }]
1326 }] });
1327
1328/**
1329 * @license
1330 * Copyright Google LLC All Rights Reserved.
1331 *
1332 * Use of this source code is governed by an MIT-style license that can be
1333 * found in the LICENSE file at https://angular.io/license
1334 */
1335
1336/**
1337 * @license
1338 * Copyright Google LLC All Rights Reserved.
1339 *
1340 * Use of this source code is governed by an MIT-style license that can be
1341 * found in the LICENSE file at https://angular.io/license
1342 */
1343
1344/**
1345 * Generated bundle index. Do not edit.
1346 */
1347
1348export { MAT_MENU_CONTENT, MAT_MENU_DEFAULT_OPTIONS, MAT_MENU_PANEL, MAT_MENU_SCROLL_STRATEGY, MatMenu, MatMenuContent, MatMenuItem, MatMenuModule, MatMenuTrigger, _MatMenuBase, _MatMenuContentBase, _MatMenuTriggerBase, fadeInItems, matMenuAnimations, transformMenu };
1349//# sourceMappingURL=menu.mjs.map