menu.js 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599
  1. /**
  2. * @license
  3. * Copyright Google LLC All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import { trigger, state, style, animate, transition, query, group } from '@angular/animations';
  9. import { DomPortalOutlet, TemplatePortal } from '@angular/cdk/portal';
  10. import { DOCUMENT, CommonModule } from '@angular/common';
  11. import { ApplicationRef, ChangeDetectorRef, ComponentFactoryResolver, Directive, Inject, Injector, TemplateRef, ViewContainerRef, InjectionToken, ChangeDetectionStrategy, Component, ElementRef, ViewEncapsulation, Optional, Input, HostListener, ContentChild, ContentChildren, EventEmitter, NgZone, Output, QueryList, ViewChild, Self, NgModule } from '@angular/core';
  12. import { Subject, merge, Subscription, asapScheduler, of } from 'rxjs';
  13. import { FocusMonitor, FocusKeyManager, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y';
  14. import { mixinDisabled, mixinDisableRipple, MatCommonModule, MatRippleModule } from '@angular/material/core';
  15. import { coerceBooleanProperty } from '@angular/cdk/coercion';
  16. import { ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, UP_ARROW, HOME, END, hasModifierKey } from '@angular/cdk/keycodes';
  17. import { startWith, switchMap, take, delay, filter, takeUntil } from 'rxjs/operators';
  18. import { Directionality } from '@angular/cdk/bidi';
  19. import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
  20. import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
  21. /**
  22. * @fileoverview added by tsickle
  23. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  24. */
  25. /**
  26. * Animations used by the mat-menu component.
  27. * Animation duration and timing values are based on:
  28. * https://material.io/guidelines/components/menus.html#menus-usage
  29. * \@docs-private
  30. * @type {?}
  31. */
  32. const matMenuAnimations = {
  33. /**
  34. * This animation controls the menu panel's entry and exit from the page.
  35. *
  36. * When the menu panel is added to the DOM, it scales in and fades in its border.
  37. *
  38. * When the menu panel is removed from the DOM, it simply fades out after a brief
  39. * delay to display the ripple.
  40. */
  41. transformMenu: trigger('transformMenu', [
  42. state('void', style({
  43. opacity: 0,
  44. transform: 'scale(0.8)'
  45. })),
  46. transition('void => enter', group([
  47. query('.mat-menu-content, .mat-mdc-menu-content', animate('100ms linear', style({
  48. opacity: 1
  49. }))),
  50. animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({ 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. * @type {?}
  73. */
  74. const fadeInItems = matMenuAnimations.fadeInItems;
  75. /**
  76. * @deprecated
  77. * \@breaking-change 8.0.0
  78. * \@docs-private
  79. * @type {?}
  80. */
  81. const transformMenu = matMenuAnimations.transformMenu;
  82. /**
  83. * @fileoverview added by tsickle
  84. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  85. */
  86. /**
  87. * Menu content that will be rendered lazily once the menu is opened.
  88. */
  89. class MatMenuContent {
  90. /**
  91. * @param {?} _template
  92. * @param {?} _componentFactoryResolver
  93. * @param {?} _appRef
  94. * @param {?} _injector
  95. * @param {?} _viewContainerRef
  96. * @param {?} _document
  97. * @param {?=} _changeDetectorRef
  98. */
  99. constructor(_template, _componentFactoryResolver, _appRef, _injector, _viewContainerRef, _document, _changeDetectorRef) {
  100. this._template = _template;
  101. this._componentFactoryResolver = _componentFactoryResolver;
  102. this._appRef = _appRef;
  103. this._injector = _injector;
  104. this._viewContainerRef = _viewContainerRef;
  105. this._document = _document;
  106. this._changeDetectorRef = _changeDetectorRef;
  107. /**
  108. * Emits when the menu content has been attached.
  109. */
  110. this._attached = new Subject();
  111. }
  112. /**
  113. * Attaches the content with a particular context.
  114. * \@docs-private
  115. * @param {?=} context
  116. * @return {?}
  117. */
  118. attach(context = {}) {
  119. if (!this._portal) {
  120. this._portal = new TemplatePortal(this._template, this._viewContainerRef);
  121. }
  122. this.detach();
  123. if (!this._outlet) {
  124. this._outlet = new DomPortalOutlet(this._document.createElement('div'), this._componentFactoryResolver, this._appRef, this._injector);
  125. }
  126. /** @type {?} */
  127. const element = this._template.elementRef.nativeElement;
  128. // Because we support opening the same menu from different triggers (which in turn have their
  129. // own `OverlayRef` panel), we have to re-insert the host element every time, otherwise we
  130. // risk it staying attached to a pane that's no longer in the DOM.
  131. (/** @type {?} */ (element.parentNode)).insertBefore(this._outlet.outletElement, element);
  132. // When `MatMenuContent` is used in an `OnPush` component, the insertion of the menu
  133. // content via `createEmbeddedView` does not cause the content to be seen as "dirty"
  134. // by Angular. This causes the `@ContentChildren` for menu items within the menu to
  135. // not be updated by Angular. By explicitly marking for check here, we tell Angular that
  136. // it needs to check for new menu items and update the `@ContentChild` in `MatMenu`.
  137. // @breaking-change 9.0.0 Make change detector ref required
  138. if (this._changeDetectorRef) {
  139. this._changeDetectorRef.markForCheck();
  140. }
  141. this._portal.attach(this._outlet, context);
  142. this._attached.next();
  143. }
  144. /**
  145. * Detaches the content.
  146. * \@docs-private
  147. * @return {?}
  148. */
  149. detach() {
  150. if (this._portal.isAttached) {
  151. this._portal.detach();
  152. }
  153. }
  154. /**
  155. * @return {?}
  156. */
  157. ngOnDestroy() {
  158. if (this._outlet) {
  159. this._outlet.dispose();
  160. }
  161. }
  162. }
  163. MatMenuContent.decorators = [
  164. { type: Directive, args: [{
  165. selector: 'ng-template[matMenuContent]'
  166. },] },
  167. ];
  168. /** @nocollapse */
  169. MatMenuContent.ctorParameters = () => [
  170. { type: TemplateRef },
  171. { type: ComponentFactoryResolver },
  172. { type: ApplicationRef },
  173. { type: Injector },
  174. { type: ViewContainerRef },
  175. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
  176. { type: ChangeDetectorRef }
  177. ];
  178. /**
  179. * @fileoverview added by tsickle
  180. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  181. */
  182. /**
  183. * Throws an exception for the case when menu trigger doesn't have a valid mat-menu instance
  184. * \@docs-private
  185. * @return {?}
  186. */
  187. function throwMatMenuMissingError() {
  188. throw Error(`matMenuTriggerFor: must pass in an mat-menu instance.
  189. Example:
  190. <mat-menu #menu="matMenu"></mat-menu>
  191. <button [matMenuTriggerFor]="menu"></button>`);
  192. }
  193. /**
  194. * Throws an exception for the case when menu's x-position value isn't valid.
  195. * In other words, it doesn't match 'before' or 'after'.
  196. * \@docs-private
  197. * @return {?}
  198. */
  199. function throwMatMenuInvalidPositionX() {
  200. throw Error(`xPosition value must be either 'before' or after'.
  201. Example: <mat-menu xPosition="before" #menu="matMenu"></mat-menu>`);
  202. }
  203. /**
  204. * Throws an exception for the case when menu's y-position value isn't valid.
  205. * In other words, it doesn't match 'above' or 'below'.
  206. * \@docs-private
  207. * @return {?}
  208. */
  209. function throwMatMenuInvalidPositionY() {
  210. throw Error(`yPosition value must be either 'above' or below'.
  211. Example: <mat-menu yPosition="above" #menu="matMenu"></mat-menu>`);
  212. }
  213. /**
  214. * @fileoverview added by tsickle
  215. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  216. */
  217. /**
  218. * Injection token used to provide the parent menu to menu-specific components.
  219. * \@docs-private
  220. * @type {?}
  221. */
  222. const MAT_MENU_PANEL = new InjectionToken('MAT_MENU_PANEL');
  223. /**
  224. * @fileoverview added by tsickle
  225. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  226. */
  227. // Boilerplate for applying mixins to MatMenuItem.
  228. /**
  229. * \@docs-private
  230. */
  231. class MatMenuItemBase {
  232. }
  233. /** @type {?} */
  234. const _MatMenuItemMixinBase = mixinDisableRipple(mixinDisabled(MatMenuItemBase));
  235. /**
  236. * This directive is intended to be used inside an mat-menu tag.
  237. * It exists mostly to set the role attribute.
  238. */
  239. class MatMenuItem extends _MatMenuItemMixinBase {
  240. /**
  241. * @param {?} _elementRef
  242. * @param {?=} document
  243. * @param {?=} _focusMonitor
  244. * @param {?=} _parentMenu
  245. */
  246. constructor(_elementRef, document, _focusMonitor, _parentMenu) {
  247. // @breaking-change 8.0.0 make `_focusMonitor` and `document` required params.
  248. super();
  249. this._elementRef = _elementRef;
  250. this._focusMonitor = _focusMonitor;
  251. this._parentMenu = _parentMenu;
  252. /**
  253. * ARIA role for the menu item.
  254. */
  255. this.role = 'menuitem';
  256. /**
  257. * Stream that emits when the menu item is hovered.
  258. */
  259. this._hovered = new Subject();
  260. /**
  261. * Whether the menu item is highlighted.
  262. */
  263. this._highlighted = false;
  264. /**
  265. * Whether the menu item acts as a trigger for a sub-menu.
  266. */
  267. this._triggersSubmenu = false;
  268. if (_focusMonitor) {
  269. // Start monitoring the element so it gets the appropriate focused classes. We want
  270. // to show the focus style for menu items only when the focus was not caused by a
  271. // mouse or touch interaction.
  272. _focusMonitor.monitor(this._elementRef, false);
  273. }
  274. if (_parentMenu && _parentMenu.addItem) {
  275. _parentMenu.addItem(this);
  276. }
  277. this._document = document;
  278. }
  279. /**
  280. * Focuses the menu item.
  281. * @param {?=} origin
  282. * @param {?=} options
  283. * @return {?}
  284. */
  285. focus(origin = 'program', options) {
  286. if (this._focusMonitor) {
  287. this._focusMonitor.focusVia(this._getHostElement(), origin, options);
  288. }
  289. else {
  290. this._getHostElement().focus(options);
  291. }
  292. }
  293. /**
  294. * @return {?}
  295. */
  296. ngOnDestroy() {
  297. if (this._focusMonitor) {
  298. this._focusMonitor.stopMonitoring(this._elementRef);
  299. }
  300. if (this._parentMenu && this._parentMenu.removeItem) {
  301. this._parentMenu.removeItem(this);
  302. }
  303. this._hovered.complete();
  304. }
  305. /**
  306. * Used to set the `tabindex`.
  307. * @return {?}
  308. */
  309. _getTabIndex() {
  310. return this.disabled ? '-1' : '0';
  311. }
  312. /**
  313. * Returns the host DOM element.
  314. * @return {?}
  315. */
  316. _getHostElement() {
  317. return this._elementRef.nativeElement;
  318. }
  319. /**
  320. * Prevents the default element actions if it is disabled.
  321. * @param {?} event
  322. * @return {?}
  323. */
  324. // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
  325. // In Ivy the `host` bindings will be merged when this class is extended, whereas in
  326. // ViewEngine they're overwritten.
  327. // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
  328. // tslint:disable-next-line:no-host-decorator-in-concrete
  329. _checkDisabled(event) {
  330. if (this.disabled) {
  331. event.preventDefault();
  332. event.stopPropagation();
  333. }
  334. }
  335. /**
  336. * Emits to the hover stream.
  337. * @return {?}
  338. */
  339. // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
  340. // In Ivy the `host` bindings will be merged when this class is extended, whereas in
  341. // ViewEngine they're overwritten.
  342. // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
  343. // tslint:disable-next-line:no-host-decorator-in-concrete
  344. _handleMouseEnter() {
  345. this._hovered.next(this);
  346. }
  347. /**
  348. * Gets the label to be used when determining whether the option should be focused.
  349. * @return {?}
  350. */
  351. getLabel() {
  352. /** @type {?} */
  353. const element = this._elementRef.nativeElement;
  354. /** @type {?} */
  355. const textNodeType = this._document ? this._document.TEXT_NODE : 3;
  356. /** @type {?} */
  357. let output = '';
  358. if (element.childNodes) {
  359. /** @type {?} */
  360. const length = element.childNodes.length;
  361. // Go through all the top-level text nodes and extract their text.
  362. // We skip anything that's not a text node to prevent the text from
  363. // being thrown off by something like an icon.
  364. for (let i = 0; i < length; i++) {
  365. if (element.childNodes[i].nodeType === textNodeType) {
  366. output += element.childNodes[i].textContent;
  367. }
  368. }
  369. }
  370. return output.trim();
  371. }
  372. }
  373. MatMenuItem.decorators = [
  374. { type: Component, args: [{selector: '[mat-menu-item]',
  375. exportAs: 'matMenuItem',
  376. inputs: ['disabled', 'disableRipple'],
  377. host: {
  378. '[attr.role]': 'role',
  379. 'class': 'mat-menu-item',
  380. '[class.mat-menu-item-highlighted]': '_highlighted',
  381. '[class.mat-menu-item-submenu-trigger]': '_triggersSubmenu',
  382. '[attr.tabindex]': '_getTabIndex()',
  383. '[attr.aria-disabled]': 'disabled.toString()',
  384. '[attr.disabled]': 'disabled || null',
  385. },
  386. changeDetection: ChangeDetectionStrategy.OnPush,
  387. encapsulation: ViewEncapsulation.None,
  388. template: "<ng-content></ng-content><div class=\"mat-menu-ripple\" matRipple [matRippleDisabled]=\"disableRipple || disabled\" [matRippleTrigger]=\"_getHostElement()\"></div>",
  389. },] },
  390. ];
  391. /** @nocollapse */
  392. MatMenuItem.ctorParameters = () => [
  393. { type: ElementRef },
  394. { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
  395. { type: FocusMonitor },
  396. { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_PANEL,] }, { type: Optional }] }
  397. ];
  398. MatMenuItem.propDecorators = {
  399. role: [{ type: Input }],
  400. _checkDisabled: [{ type: HostListener, args: ['click', ['$event'],] }],
  401. _handleMouseEnter: [{ type: HostListener, args: ['mouseenter',] }]
  402. };
  403. /**
  404. * @fileoverview added by tsickle
  405. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  406. */
  407. /**
  408. * Injection token to be used to override the default options for `mat-menu`.
  409. * @type {?}
  410. */
  411. const MAT_MENU_DEFAULT_OPTIONS = new InjectionToken('mat-menu-default-options', {
  412. providedIn: 'root',
  413. factory: MAT_MENU_DEFAULT_OPTIONS_FACTORY
  414. });
  415. /**
  416. * \@docs-private
  417. * @return {?}
  418. */
  419. function MAT_MENU_DEFAULT_OPTIONS_FACTORY() {
  420. return {
  421. overlapTrigger: false,
  422. xPosition: 'after',
  423. yPosition: 'below',
  424. backdropClass: 'cdk-overlay-transparent-backdrop',
  425. };
  426. }
  427. /**
  428. * Start elevation for the menu panel.
  429. * \@docs-private
  430. * @type {?}
  431. */
  432. const MAT_MENU_BASE_ELEVATION = 4;
  433. /**
  434. * Base class with all of the `MatMenu` functionality.
  435. */
  436. // tslint:disable-next-line:class-name
  437. class _MatMenuBase {
  438. /**
  439. * @param {?} _elementRef
  440. * @param {?} _ngZone
  441. * @param {?} _defaultOptions
  442. */
  443. constructor(_elementRef, _ngZone, _defaultOptions) {
  444. this._elementRef = _elementRef;
  445. this._ngZone = _ngZone;
  446. this._defaultOptions = _defaultOptions;
  447. this._xPosition = this._defaultOptions.xPosition;
  448. this._yPosition = this._defaultOptions.yPosition;
  449. /**
  450. * Only the direct descendant menu items.
  451. */
  452. this._directDescendantItems = new QueryList();
  453. /**
  454. * Subscription to tab events on the menu panel
  455. */
  456. this._tabSubscription = Subscription.EMPTY;
  457. /**
  458. * Config object to be passed into the menu's ngClass
  459. */
  460. this._classList = {};
  461. /**
  462. * Current state of the panel animation.
  463. */
  464. this._panelAnimationState = 'void';
  465. /**
  466. * Emits whenever an animation on the menu completes.
  467. */
  468. this._animationDone = new Subject();
  469. /**
  470. * Class to be added to the backdrop element.
  471. */
  472. this.backdropClass = this._defaultOptions.backdropClass;
  473. this._overlapTrigger = this._defaultOptions.overlapTrigger;
  474. this._hasBackdrop = this._defaultOptions.hasBackdrop;
  475. /**
  476. * Event emitted when the menu is closed.
  477. */
  478. this.closed = new EventEmitter();
  479. /**
  480. * Event emitted when the menu is closed.
  481. * @deprecated Switch to `closed` instead
  482. * \@breaking-change 8.0.0
  483. */
  484. this.close = this.closed;
  485. }
  486. /**
  487. * Position of the menu in the X axis.
  488. * @return {?}
  489. */
  490. get xPosition() { return this._xPosition; }
  491. /**
  492. * @param {?} value
  493. * @return {?}
  494. */
  495. set xPosition(value) {
  496. if (value !== 'before' && value !== 'after') {
  497. throwMatMenuInvalidPositionX();
  498. }
  499. this._xPosition = value;
  500. this.setPositionClasses();
  501. }
  502. /**
  503. * Position of the menu in the Y axis.
  504. * @return {?}
  505. */
  506. get yPosition() { return this._yPosition; }
  507. /**
  508. * @param {?} value
  509. * @return {?}
  510. */
  511. set yPosition(value) {
  512. if (value !== 'above' && value !== 'below') {
  513. throwMatMenuInvalidPositionY();
  514. }
  515. this._yPosition = value;
  516. this.setPositionClasses();
  517. }
  518. /**
  519. * Whether the menu should overlap its trigger.
  520. * @return {?}
  521. */
  522. get overlapTrigger() { return this._overlapTrigger; }
  523. /**
  524. * @param {?} value
  525. * @return {?}
  526. */
  527. set overlapTrigger(value) {
  528. this._overlapTrigger = coerceBooleanProperty(value);
  529. }
  530. /**
  531. * Whether the menu has a backdrop.
  532. * @return {?}
  533. */
  534. get hasBackdrop() { return this._hasBackdrop; }
  535. /**
  536. * @param {?} value
  537. * @return {?}
  538. */
  539. set hasBackdrop(value) {
  540. this._hasBackdrop = coerceBooleanProperty(value);
  541. }
  542. /**
  543. * This method takes classes set on the host mat-menu element and applies them on the
  544. * menu template that displays in the overlay container. Otherwise, it's difficult
  545. * to style the containing menu from outside the component.
  546. * @param {?} classes list of class names
  547. * @return {?}
  548. */
  549. set panelClass(classes) {
  550. /** @type {?} */
  551. const previousPanelClass = this._previousPanelClass;
  552. if (previousPanelClass && previousPanelClass.length) {
  553. previousPanelClass.split(' ').forEach((/**
  554. * @param {?} className
  555. * @return {?}
  556. */
  557. (className) => {
  558. this._classList[className] = false;
  559. }));
  560. }
  561. this._previousPanelClass = classes;
  562. if (classes && classes.length) {
  563. classes.split(' ').forEach((/**
  564. * @param {?} className
  565. * @return {?}
  566. */
  567. (className) => {
  568. this._classList[className] = true;
  569. }));
  570. this._elementRef.nativeElement.className = '';
  571. }
  572. }
  573. /**
  574. * This method takes classes set on the host mat-menu element and applies them on the
  575. * menu template that displays in the overlay container. Otherwise, it's difficult
  576. * to style the containing menu from outside the component.
  577. * @deprecated Use `panelClass` instead.
  578. * \@breaking-change 8.0.0
  579. * @return {?}
  580. */
  581. get classList() { return this.panelClass; }
  582. /**
  583. * @param {?} classes
  584. * @return {?}
  585. */
  586. set classList(classes) { this.panelClass = classes; }
  587. /**
  588. * @return {?}
  589. */
  590. ngOnInit() {
  591. this.setPositionClasses();
  592. }
  593. /**
  594. * @return {?}
  595. */
  596. ngAfterContentInit() {
  597. this._updateDirectDescendants();
  598. this._keyManager = new FocusKeyManager(this._directDescendantItems).withWrap().withTypeAhead();
  599. this._tabSubscription = this._keyManager.tabOut.subscribe((/**
  600. * @return {?}
  601. */
  602. () => this.closed.emit('tab')));
  603. }
  604. /**
  605. * @return {?}
  606. */
  607. ngOnDestroy() {
  608. this._directDescendantItems.destroy();
  609. this._tabSubscription.unsubscribe();
  610. this.closed.complete();
  611. }
  612. /**
  613. * Stream that emits whenever the hovered menu item changes.
  614. * @return {?}
  615. */
  616. _hovered() {
  617. // Coerce the `changes` property because Angular types it as `Observable<any>`
  618. /** @type {?} */
  619. const itemChanges = (/** @type {?} */ (this._directDescendantItems.changes));
  620. return itemChanges.pipe(startWith(this._directDescendantItems), switchMap((/**
  621. * @param {?} items
  622. * @return {?}
  623. */
  624. items => merge(...items.map((/**
  625. * @param {?} item
  626. * @return {?}
  627. */
  628. (item) => item._hovered))))));
  629. }
  630. /*
  631. * Registers a menu item with the menu.
  632. * @docs-private
  633. * @deprecated No longer being used. To be removed.
  634. * @breaking-change 9.0.0
  635. */
  636. /**
  637. * @param {?} _item
  638. * @return {?}
  639. */
  640. addItem(_item) { }
  641. /**
  642. * Removes an item from the menu.
  643. * \@docs-private
  644. * @deprecated No longer being used. To be removed.
  645. * \@breaking-change 9.0.0
  646. * @param {?} _item
  647. * @return {?}
  648. */
  649. removeItem(_item) { }
  650. /**
  651. * Handle a keyboard event from the menu, delegating to the appropriate action.
  652. * @param {?} event
  653. * @return {?}
  654. */
  655. _handleKeydown(event) {
  656. /** @type {?} */
  657. const keyCode = event.keyCode;
  658. /** @type {?} */
  659. const manager = this._keyManager;
  660. switch (keyCode) {
  661. case ESCAPE:
  662. if (!hasModifierKey(event)) {
  663. event.preventDefault();
  664. this.closed.emit('keydown');
  665. }
  666. break;
  667. case LEFT_ARROW:
  668. if (this.parentMenu && this.direction === 'ltr') {
  669. this.closed.emit('keydown');
  670. }
  671. break;
  672. case RIGHT_ARROW:
  673. if (this.parentMenu && this.direction === 'rtl') {
  674. this.closed.emit('keydown');
  675. }
  676. break;
  677. case HOME:
  678. case END:
  679. if (!hasModifierKey(event)) {
  680. keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
  681. event.preventDefault();
  682. }
  683. break;
  684. default:
  685. if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
  686. manager.setFocusOrigin('keyboard');
  687. }
  688. manager.onKeydown(event);
  689. }
  690. }
  691. /**
  692. * Focus the first item in the menu.
  693. * @param {?=} origin Action from which the focus originated. Used to set the correct styling.
  694. * @return {?}
  695. */
  696. focusFirstItem(origin = 'program') {
  697. // When the content is rendered lazily, it takes a bit before the items are inside the DOM.
  698. if (this.lazyContent) {
  699. this._ngZone.onStable.asObservable()
  700. .pipe(take(1))
  701. .subscribe((/**
  702. * @return {?}
  703. */
  704. () => this._keyManager.setFocusOrigin(origin).setFirstItemActive()));
  705. }
  706. else {
  707. this._keyManager.setFocusOrigin(origin).setFirstItemActive();
  708. }
  709. }
  710. /**
  711. * Resets the active item in the menu. This is used when the menu is opened, allowing
  712. * the user to start from the first option when pressing the down arrow.
  713. * @return {?}
  714. */
  715. resetActiveItem() {
  716. this._keyManager.setActiveItem(-1);
  717. }
  718. /**
  719. * Sets the menu panel elevation.
  720. * @param {?} depth Number of parent menus that come before the menu.
  721. * @return {?}
  722. */
  723. setElevation(depth) {
  724. // The elevation starts at the base and increases by one for each level.
  725. /** @type {?} */
  726. const newElevation = `mat-elevation-z${MAT_MENU_BASE_ELEVATION + depth}`;
  727. /** @type {?} */
  728. const customElevation = Object.keys(this._classList).find((/**
  729. * @param {?} c
  730. * @return {?}
  731. */
  732. c => c.startsWith('mat-elevation-z')));
  733. if (!customElevation || customElevation === this._previousElevation) {
  734. if (this._previousElevation) {
  735. this._classList[this._previousElevation] = false;
  736. }
  737. this._classList[newElevation] = true;
  738. this._previousElevation = newElevation;
  739. }
  740. }
  741. /**
  742. * Adds classes to the menu panel based on its position. Can be used by
  743. * consumers to add specific styling based on the position.
  744. * \@docs-private
  745. * @param {?=} posX Position of the menu along the x axis.
  746. * @param {?=} posY Position of the menu along the y axis.
  747. * @return {?}
  748. */
  749. setPositionClasses(posX = this.xPosition, posY = this.yPosition) {
  750. /** @type {?} */
  751. const classes = this._classList;
  752. classes['mat-menu-before'] = posX === 'before';
  753. classes['mat-menu-after'] = posX === 'after';
  754. classes['mat-menu-above'] = posY === 'above';
  755. classes['mat-menu-below'] = posY === 'below';
  756. }
  757. /**
  758. * Starts the enter animation.
  759. * @return {?}
  760. */
  761. _startAnimation() {
  762. // @breaking-change 8.0.0 Combine with _resetAnimation.
  763. this._panelAnimationState = 'enter';
  764. }
  765. /**
  766. * Resets the panel animation to its initial state.
  767. * @return {?}
  768. */
  769. _resetAnimation() {
  770. // @breaking-change 8.0.0 Combine with _startAnimation.
  771. this._panelAnimationState = 'void';
  772. }
  773. /**
  774. * Callback that is invoked when the panel animation completes.
  775. * @param {?} event
  776. * @return {?}
  777. */
  778. _onAnimationDone(event) {
  779. this._animationDone.next(event);
  780. this._isAnimating = false;
  781. }
  782. /**
  783. * @param {?} event
  784. * @return {?}
  785. */
  786. _onAnimationStart(event) {
  787. this._isAnimating = true;
  788. // Scroll the content element to the top as soon as the animation starts. This is necessary,
  789. // because we move focus to the first item while it's still being animated, which can throw
  790. // the browser off when it determines the scroll position. Alternatively we can move focus
  791. // when the animation is done, however moving focus asynchronously will interrupt screen
  792. // readers which are in the process of reading out the menu already. We take the `element`
  793. // from the `event` since we can't use a `ViewChild` to access the pane.
  794. if (event.toState === 'enter' && this._keyManager.activeItemIndex === 0) {
  795. event.element.scrollTop = 0;
  796. }
  797. }
  798. /**
  799. * Sets up a stream that will keep track of any newly-added menu items and will update the list
  800. * of direct descendants. We collect the descendants this way, because `_allItems` can include
  801. * items that are part of child menus, and using a custom way of registering items is unreliable
  802. * when it comes to maintaining the item order.
  803. * @private
  804. * @return {?}
  805. */
  806. _updateDirectDescendants() {
  807. this._allItems.changes
  808. .pipe(startWith(this._allItems))
  809. .subscribe((/**
  810. * @param {?} items
  811. * @return {?}
  812. */
  813. (items) => {
  814. this._directDescendantItems.reset(items.filter((/**
  815. * @param {?} item
  816. * @return {?}
  817. */
  818. item => item._parentMenu === this)));
  819. this._directDescendantItems.notifyOnChanges();
  820. }));
  821. }
  822. }
  823. /** @nocollapse */
  824. _MatMenuBase.ctorParameters = () => [
  825. { type: ElementRef },
  826. { type: NgZone },
  827. { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_DEFAULT_OPTIONS,] }] }
  828. ];
  829. _MatMenuBase.propDecorators = {
  830. _allItems: [{ type: ContentChildren, args: [MatMenuItem, { descendants: true },] }],
  831. backdropClass: [{ type: Input }],
  832. xPosition: [{ type: Input }],
  833. yPosition: [{ type: Input }],
  834. templateRef: [{ type: ViewChild, args: [TemplateRef, { static: false },] }],
  835. items: [{ type: ContentChildren, args: [MatMenuItem,] }],
  836. lazyContent: [{ type: ContentChild, args: [MatMenuContent, { static: false },] }],
  837. overlapTrigger: [{ type: Input }],
  838. hasBackdrop: [{ type: Input }],
  839. panelClass: [{ type: Input, args: ['class',] }],
  840. classList: [{ type: Input }],
  841. closed: [{ type: Output }],
  842. close: [{ type: Output }]
  843. };
  844. /**
  845. * \@docs-private We show the "_MatMenu" class as "MatMenu" in the docs.
  846. */
  847. class MatMenu extends _MatMenuBase {
  848. }
  849. // Note on the weird inheritance setup: we need three classes, because the MDC-based menu has to
  850. // extend `MatMenu`, however keeping a reference to it will cause the inlined template and styles
  851. // to be retained as well. The MDC menu also has to provide itself as a `MatMenu` in order for
  852. // queries and DI to work correctly, while still not referencing the actual menu class.
  853. // Class responsibility is split up as follows:
  854. // * _MatMenuBase - provides all the functionality without any of the Angular metadata.
  855. // * MatMenu - keeps the same name symbol name as the current menu and
  856. // is used as a provider for DI and query purposes.
  857. // * _MatMenu - the actual menu component implementation with the Angular metadata that should
  858. // be tree shaken away for MDC.
  859. /**
  860. * \@docs-public MatMenu
  861. */
  862. // tslint:disable-next-line:class-name
  863. class _MatMenu extends MatMenu {
  864. /**
  865. * @param {?} elementRef
  866. * @param {?} ngZone
  867. * @param {?} defaultOptions
  868. */
  869. constructor(elementRef, ngZone, defaultOptions) {
  870. super(elementRef, ngZone, defaultOptions);
  871. }
  872. }
  873. _MatMenu.decorators = [
  874. { type: Component, args: [{selector: 'mat-menu',
  875. 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>",
  876. 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}.mat-menu-item[disabled]{pointer-events:none}@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}"],
  877. changeDetection: ChangeDetectionStrategy.OnPush,
  878. encapsulation: ViewEncapsulation.None,
  879. exportAs: 'matMenu',
  880. animations: [
  881. matMenuAnimations.transformMenu,
  882. matMenuAnimations.fadeInItems
  883. ],
  884. providers: [
  885. { provide: MAT_MENU_PANEL, useExisting: MatMenu },
  886. { provide: MatMenu, useExisting: _MatMenu }
  887. ]
  888. },] },
  889. ];
  890. /** @nocollapse */
  891. _MatMenu.ctorParameters = () => [
  892. { type: ElementRef },
  893. { type: NgZone },
  894. { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_DEFAULT_OPTIONS,] }] }
  895. ];
  896. /**
  897. * @fileoverview added by tsickle
  898. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  899. */
  900. /**
  901. * Injection token that determines the scroll handling while the menu is open.
  902. * @type {?}
  903. */
  904. const MAT_MENU_SCROLL_STRATEGY = new InjectionToken('mat-menu-scroll-strategy');
  905. /**
  906. * \@docs-private
  907. * @param {?} overlay
  908. * @return {?}
  909. */
  910. function MAT_MENU_SCROLL_STRATEGY_FACTORY(overlay) {
  911. return (/**
  912. * @return {?}
  913. */
  914. () => overlay.scrollStrategies.reposition());
  915. }
  916. /**
  917. * \@docs-private
  918. * @type {?}
  919. */
  920. const MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER = {
  921. provide: MAT_MENU_SCROLL_STRATEGY,
  922. deps: [Overlay],
  923. useFactory: MAT_MENU_SCROLL_STRATEGY_FACTORY,
  924. };
  925. /**
  926. * Default top padding of the menu panel.
  927. * @type {?}
  928. */
  929. const MENU_PANEL_TOP_PADDING = 8;
  930. /**
  931. * Options for binding a passive event listener.
  932. * @type {?}
  933. */
  934. const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
  935. // TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors
  936. /**
  937. * This directive is intended to be used in conjunction with an mat-menu tag. It is
  938. * responsible for toggling the display of the provided menu instance.
  939. */
  940. class MatMenuTrigger {
  941. /**
  942. * @param {?} _overlay
  943. * @param {?} _element
  944. * @param {?} _viewContainerRef
  945. * @param {?} scrollStrategy
  946. * @param {?} _parentMenu
  947. * @param {?} _menuItemInstance
  948. * @param {?} _dir
  949. * @param {?=} _focusMonitor
  950. */
  951. constructor(_overlay, _element, _viewContainerRef, scrollStrategy, _parentMenu, _menuItemInstance, _dir, _focusMonitor) {
  952. this._overlay = _overlay;
  953. this._element = _element;
  954. this._viewContainerRef = _viewContainerRef;
  955. this._parentMenu = _parentMenu;
  956. this._menuItemInstance = _menuItemInstance;
  957. this._dir = _dir;
  958. this._focusMonitor = _focusMonitor;
  959. this._overlayRef = null;
  960. this._menuOpen = false;
  961. this._closingActionsSubscription = Subscription.EMPTY;
  962. this._hoverSubscription = Subscription.EMPTY;
  963. this._menuCloseSubscription = Subscription.EMPTY;
  964. /**
  965. * Handles touch start events on the trigger.
  966. * Needs to be an arrow function so we can easily use addEventListener and removeEventListener.
  967. */
  968. this._handleTouchStart = (/**
  969. * @return {?}
  970. */
  971. () => this._openedBy = 'touch');
  972. // Tracking input type is necessary so it's possible to only auto-focus
  973. // the first item of the list when the menu is opened via the keyboard
  974. this._openedBy = null;
  975. /**
  976. * Whether focus should be restored when the menu is closed.
  977. * Note that disabling this option can have accessibility implications
  978. * and it's up to you to manage focus, if you decide to turn it off.
  979. */
  980. this.restoreFocus = true;
  981. /**
  982. * Event emitted when the associated menu is opened.
  983. */
  984. this.menuOpened = new EventEmitter();
  985. /**
  986. * Event emitted when the associated menu is opened.
  987. * @deprecated Switch to `menuOpened` instead
  988. * \@breaking-change 8.0.0
  989. */
  990. // tslint:disable-next-line:no-output-on-prefix
  991. this.onMenuOpen = this.menuOpened;
  992. /**
  993. * Event emitted when the associated menu is closed.
  994. */
  995. this.menuClosed = new EventEmitter();
  996. /**
  997. * Event emitted when the associated menu is closed.
  998. * @deprecated Switch to `menuClosed` instead
  999. * \@breaking-change 8.0.0
  1000. */
  1001. // tslint:disable-next-line:no-output-on-prefix
  1002. this.onMenuClose = this.menuClosed;
  1003. _element.nativeElement.addEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
  1004. if (_menuItemInstance) {
  1005. _menuItemInstance._triggersSubmenu = this.triggersSubmenu();
  1006. }
  1007. this._scrollStrategy = scrollStrategy;
  1008. }
  1009. /**
  1010. * @deprecated
  1011. * \@breaking-change 8.0.0
  1012. * @return {?}
  1013. */
  1014. get _deprecatedMatMenuTriggerFor() { return this.menu; }
  1015. /**
  1016. * @param {?} v
  1017. * @return {?}
  1018. */
  1019. set _deprecatedMatMenuTriggerFor(v) {
  1020. this.menu = v;
  1021. }
  1022. /**
  1023. * References the menu instance that the trigger is associated with.
  1024. * @return {?}
  1025. */
  1026. get menu() { return this._menu; }
  1027. /**
  1028. * @param {?} menu
  1029. * @return {?}
  1030. */
  1031. set menu(menu) {
  1032. if (menu === this._menu) {
  1033. return;
  1034. }
  1035. this._menu = menu;
  1036. this._menuCloseSubscription.unsubscribe();
  1037. if (menu) {
  1038. this._menuCloseSubscription = menu.close.asObservable().subscribe((/**
  1039. * @param {?} reason
  1040. * @return {?}
  1041. */
  1042. reason => {
  1043. this._destroyMenu();
  1044. // If a click closed the menu, we should close the entire chain of nested menus.
  1045. if ((reason === 'click' || reason === 'tab') && this._parentMenu) {
  1046. this._parentMenu.closed.emit(reason);
  1047. }
  1048. }));
  1049. }
  1050. }
  1051. /**
  1052. * @return {?}
  1053. */
  1054. ngAfterContentInit() {
  1055. this._checkMenu();
  1056. this._handleHover();
  1057. }
  1058. /**
  1059. * @return {?}
  1060. */
  1061. ngOnDestroy() {
  1062. if (this._overlayRef) {
  1063. this._overlayRef.dispose();
  1064. this._overlayRef = null;
  1065. }
  1066. this._element.nativeElement.removeEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
  1067. this._menuCloseSubscription.unsubscribe();
  1068. this._closingActionsSubscription.unsubscribe();
  1069. this._hoverSubscription.unsubscribe();
  1070. }
  1071. /**
  1072. * Whether the menu is open.
  1073. * @return {?}
  1074. */
  1075. get menuOpen() {
  1076. return this._menuOpen;
  1077. }
  1078. /**
  1079. * The text direction of the containing app.
  1080. * @return {?}
  1081. */
  1082. get dir() {
  1083. return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
  1084. }
  1085. /**
  1086. * Whether the menu triggers a sub-menu or a top-level one.
  1087. * @return {?}
  1088. */
  1089. triggersSubmenu() {
  1090. return !!(this._menuItemInstance && this._parentMenu);
  1091. }
  1092. /**
  1093. * Toggles the menu between the open and closed states.
  1094. * @return {?}
  1095. */
  1096. toggleMenu() {
  1097. return this._menuOpen ? this.closeMenu() : this.openMenu();
  1098. }
  1099. /**
  1100. * Opens the menu.
  1101. * @return {?}
  1102. */
  1103. openMenu() {
  1104. if (this._menuOpen) {
  1105. return;
  1106. }
  1107. this._checkMenu();
  1108. /** @type {?} */
  1109. const overlayRef = this._createOverlay();
  1110. /** @type {?} */
  1111. const overlayConfig = overlayRef.getConfig();
  1112. this._setPosition((/** @type {?} */ (overlayConfig.positionStrategy)));
  1113. overlayConfig.hasBackdrop = this.menu.hasBackdrop == null ? !this.triggersSubmenu() :
  1114. this.menu.hasBackdrop;
  1115. overlayRef.attach(this._getPortal());
  1116. if (this.menu.lazyContent) {
  1117. this.menu.lazyContent.attach(this.menuData);
  1118. }
  1119. this._closingActionsSubscription = this._menuClosingActions().subscribe((/**
  1120. * @return {?}
  1121. */
  1122. () => this.closeMenu()));
  1123. this._initMenu();
  1124. if (this.menu instanceof MatMenu) {
  1125. this.menu._startAnimation();
  1126. }
  1127. }
  1128. /**
  1129. * Closes the menu.
  1130. * @return {?}
  1131. */
  1132. closeMenu() {
  1133. this.menu.close.emit();
  1134. }
  1135. /**
  1136. * Focuses the menu trigger.
  1137. * @param {?=} origin Source of the menu trigger's focus.
  1138. * @param {?=} options
  1139. * @return {?}
  1140. */
  1141. focus(origin = 'program', options) {
  1142. if (this._focusMonitor) {
  1143. this._focusMonitor.focusVia(this._element, origin, options);
  1144. }
  1145. else {
  1146. this._element.nativeElement.focus(options);
  1147. }
  1148. }
  1149. /**
  1150. * Closes the menu and does the necessary cleanup.
  1151. * @private
  1152. * @return {?}
  1153. */
  1154. _destroyMenu() {
  1155. if (!this._overlayRef || !this.menuOpen) {
  1156. return;
  1157. }
  1158. /** @type {?} */
  1159. const menu = this.menu;
  1160. this._closingActionsSubscription.unsubscribe();
  1161. this._overlayRef.detach();
  1162. if (menu instanceof MatMenu) {
  1163. menu._resetAnimation();
  1164. if (menu.lazyContent) {
  1165. // Wait for the exit animation to finish before detaching the content.
  1166. menu._animationDone
  1167. .pipe(filter((/**
  1168. * @param {?} event
  1169. * @return {?}
  1170. */
  1171. event => event.toState === 'void')), take(1),
  1172. // Interrupt if the content got re-attached.
  1173. takeUntil(menu.lazyContent._attached))
  1174. .subscribe({
  1175. next: (/**
  1176. * @return {?}
  1177. */
  1178. () => (/** @type {?} */ (menu.lazyContent)).detach()),
  1179. // No matter whether the content got re-attached, reset the menu.
  1180. complete: (/**
  1181. * @return {?}
  1182. */
  1183. () => this._resetMenu())
  1184. });
  1185. }
  1186. else {
  1187. this._resetMenu();
  1188. }
  1189. }
  1190. else {
  1191. this._resetMenu();
  1192. if (menu.lazyContent) {
  1193. menu.lazyContent.detach();
  1194. }
  1195. }
  1196. }
  1197. /**
  1198. * This method sets the menu state to open and focuses the first item if
  1199. * the menu was opened via the keyboard.
  1200. * @private
  1201. * @return {?}
  1202. */
  1203. _initMenu() {
  1204. this.menu.parentMenu = this.triggersSubmenu() ? this._parentMenu : undefined;
  1205. this.menu.direction = this.dir;
  1206. this._setMenuElevation();
  1207. this._setIsMenuOpen(true);
  1208. this.menu.focusFirstItem(this._openedBy || 'program');
  1209. }
  1210. /**
  1211. * Updates the menu elevation based on the amount of parent menus that it has.
  1212. * @private
  1213. * @return {?}
  1214. */
  1215. _setMenuElevation() {
  1216. if (this.menu.setElevation) {
  1217. /** @type {?} */
  1218. let depth = 0;
  1219. /** @type {?} */
  1220. let parentMenu = this.menu.parentMenu;
  1221. while (parentMenu) {
  1222. depth++;
  1223. parentMenu = parentMenu.parentMenu;
  1224. }
  1225. this.menu.setElevation(depth);
  1226. }
  1227. }
  1228. /**
  1229. * This method resets the menu when it's closed, most importantly restoring
  1230. * focus to the menu trigger if the menu was opened via the keyboard.
  1231. * @private
  1232. * @return {?}
  1233. */
  1234. _resetMenu() {
  1235. this._setIsMenuOpen(false);
  1236. // We should reset focus if the user is navigating using a keyboard or
  1237. // if we have a top-level trigger which might cause focus to be lost
  1238. // when clicking on the backdrop.
  1239. if (this.restoreFocus) {
  1240. if (!this._openedBy) {
  1241. // Note that the focus style will show up both for `program` and
  1242. // `keyboard` so we don't have to specify which one it is.
  1243. this.focus();
  1244. }
  1245. else if (!this.triggersSubmenu()) {
  1246. this.focus(this._openedBy);
  1247. }
  1248. }
  1249. this._openedBy = null;
  1250. }
  1251. // set state rather than toggle to support triggers sharing a menu
  1252. /**
  1253. * @private
  1254. * @param {?} isOpen
  1255. * @return {?}
  1256. */
  1257. _setIsMenuOpen(isOpen) {
  1258. this._menuOpen = isOpen;
  1259. this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit();
  1260. if (this.triggersSubmenu()) {
  1261. this._menuItemInstance._highlighted = isOpen;
  1262. }
  1263. }
  1264. /**
  1265. * This method checks that a valid instance of MatMenu has been passed into
  1266. * matMenuTriggerFor. If not, an exception is thrown.
  1267. * @private
  1268. * @return {?}
  1269. */
  1270. _checkMenu() {
  1271. if (!this.menu) {
  1272. throwMatMenuMissingError();
  1273. }
  1274. }
  1275. /**
  1276. * This method creates the overlay from the provided menu's template and saves its
  1277. * OverlayRef so that it can be attached to the DOM when openMenu is called.
  1278. * @private
  1279. * @return {?}
  1280. */
  1281. _createOverlay() {
  1282. if (!this._overlayRef) {
  1283. /** @type {?} */
  1284. const config = this._getOverlayConfig();
  1285. this._subscribeToPositions((/** @type {?} */ (config.positionStrategy)));
  1286. this._overlayRef = this._overlay.create(config);
  1287. // Consume the `keydownEvents` in order to prevent them from going to another overlay.
  1288. // Ideally we'd also have our keyboard event logic in here, however doing so will
  1289. // break anybody that may have implemented the `MatMenuPanel` themselves.
  1290. this._overlayRef.keydownEvents().subscribe();
  1291. }
  1292. return this._overlayRef;
  1293. }
  1294. /**
  1295. * This method builds the configuration object needed to create the overlay, the OverlayState.
  1296. * @private
  1297. * @return {?} OverlayConfig
  1298. */
  1299. _getOverlayConfig() {
  1300. return new OverlayConfig({
  1301. positionStrategy: this._overlay.position()
  1302. .flexibleConnectedTo(this._element)
  1303. .withLockedPosition()
  1304. .withTransformOriginOn('.mat-menu-panel, .mat-mdc-menu-panel'),
  1305. backdropClass: this.menu.backdropClass || 'cdk-overlay-transparent-backdrop',
  1306. scrollStrategy: this._scrollStrategy(),
  1307. direction: this._dir
  1308. });
  1309. }
  1310. /**
  1311. * Listens to changes in the position of the overlay and sets the correct classes
  1312. * on the menu based on the new position. This ensures the animation origin is always
  1313. * correct, even if a fallback position is used for the overlay.
  1314. * @private
  1315. * @param {?} position
  1316. * @return {?}
  1317. */
  1318. _subscribeToPositions(position) {
  1319. if (this.menu.setPositionClasses) {
  1320. position.positionChanges.subscribe((/**
  1321. * @param {?} change
  1322. * @return {?}
  1323. */
  1324. change => {
  1325. /** @type {?} */
  1326. const posX = change.connectionPair.overlayX === 'start' ? 'after' : 'before';
  1327. /** @type {?} */
  1328. const posY = change.connectionPair.overlayY === 'top' ? 'below' : 'above';
  1329. (/** @type {?} */ (this.menu.setPositionClasses))(posX, posY);
  1330. }));
  1331. }
  1332. }
  1333. /**
  1334. * Sets the appropriate positions on a position strategy
  1335. * so the overlay connects with the trigger correctly.
  1336. * @private
  1337. * @param {?} positionStrategy Strategy whose position to update.
  1338. * @return {?}
  1339. */
  1340. _setPosition(positionStrategy) {
  1341. let [originX, originFallbackX] = this.menu.xPosition === 'before' ? ['end', 'start'] : ['start', 'end'];
  1342. let [overlayY, overlayFallbackY] = this.menu.yPosition === 'above' ? ['bottom', 'top'] : ['top', 'bottom'];
  1343. let [originY, originFallbackY] = [overlayY, overlayFallbackY];
  1344. let [overlayX, overlayFallbackX] = [originX, originFallbackX];
  1345. /** @type {?} */
  1346. let offsetY = 0;
  1347. if (this.triggersSubmenu()) {
  1348. // When the menu is a sub-menu, it should always align itself
  1349. // to the edges of the trigger, instead of overlapping it.
  1350. overlayFallbackX = originX = this.menu.xPosition === 'before' ? 'start' : 'end';
  1351. originFallbackX = overlayX = originX === 'end' ? 'start' : 'end';
  1352. offsetY = overlayY === 'bottom' ? MENU_PANEL_TOP_PADDING : -MENU_PANEL_TOP_PADDING;
  1353. }
  1354. else if (!this.menu.overlapTrigger) {
  1355. originY = overlayY === 'top' ? 'bottom' : 'top';
  1356. originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top';
  1357. }
  1358. positionStrategy.withPositions([
  1359. { originX, originY, overlayX, overlayY, offsetY },
  1360. { originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY },
  1361. {
  1362. originX,
  1363. originY: originFallbackY,
  1364. overlayX,
  1365. overlayY: overlayFallbackY,
  1366. offsetY: -offsetY
  1367. },
  1368. {
  1369. originX: originFallbackX,
  1370. originY: originFallbackY,
  1371. overlayX: overlayFallbackX,
  1372. overlayY: overlayFallbackY,
  1373. offsetY: -offsetY
  1374. }
  1375. ]);
  1376. }
  1377. /**
  1378. * Returns a stream that emits whenever an action that should close the menu occurs.
  1379. * @private
  1380. * @return {?}
  1381. */
  1382. _menuClosingActions() {
  1383. /** @type {?} */
  1384. const backdrop = (/** @type {?} */ (this._overlayRef)).backdropClick();
  1385. /** @type {?} */
  1386. const detachments = (/** @type {?} */ (this._overlayRef)).detachments();
  1387. /** @type {?} */
  1388. const parentClose = this._parentMenu ? this._parentMenu.closed : of();
  1389. /** @type {?} */
  1390. const hover = this._parentMenu ? this._parentMenu._hovered().pipe(filter((/**
  1391. * @param {?} active
  1392. * @return {?}
  1393. */
  1394. active => active !== this._menuItemInstance)), filter((/**
  1395. * @return {?}
  1396. */
  1397. () => this._menuOpen))) : of();
  1398. return merge(backdrop, parentClose, hover, detachments);
  1399. }
  1400. /**
  1401. * Handles mouse presses on the trigger.
  1402. * @param {?} event
  1403. * @return {?}
  1404. */
  1405. _handleMousedown(event) {
  1406. if (!isFakeMousedownFromScreenReader(event)) {
  1407. // Since right or middle button clicks won't trigger the `click` event,
  1408. // we shouldn't consider the menu as opened by mouse in those cases.
  1409. this._openedBy = event.button === 0 ? 'mouse' : null;
  1410. // Since clicking on the trigger won't close the menu if it opens a sub-menu,
  1411. // we should prevent focus from moving onto it via click to avoid the
  1412. // highlight from lingering on the menu item.
  1413. if (this.triggersSubmenu()) {
  1414. event.preventDefault();
  1415. }
  1416. }
  1417. }
  1418. /**
  1419. * Handles key presses on the trigger.
  1420. * @param {?} event
  1421. * @return {?}
  1422. */
  1423. _handleKeydown(event) {
  1424. /** @type {?} */
  1425. const keyCode = event.keyCode;
  1426. if (this.triggersSubmenu() && ((keyCode === RIGHT_ARROW && this.dir === 'ltr') ||
  1427. (keyCode === LEFT_ARROW && this.dir === 'rtl'))) {
  1428. this.openMenu();
  1429. }
  1430. }
  1431. /**
  1432. * Handles click events on the trigger.
  1433. * @param {?} event
  1434. * @return {?}
  1435. */
  1436. _handleClick(event) {
  1437. if (this.triggersSubmenu()) {
  1438. // Stop event propagation to avoid closing the parent menu.
  1439. event.stopPropagation();
  1440. this.openMenu();
  1441. }
  1442. else {
  1443. this.toggleMenu();
  1444. }
  1445. }
  1446. /**
  1447. * Handles the cases where the user hovers over the trigger.
  1448. * @private
  1449. * @return {?}
  1450. */
  1451. _handleHover() {
  1452. // Subscribe to changes in the hovered item in order to toggle the panel.
  1453. if (!this.triggersSubmenu()) {
  1454. return;
  1455. }
  1456. this._hoverSubscription = this._parentMenu._hovered()
  1457. // Since we might have multiple competing triggers for the same menu (e.g. a sub-menu
  1458. // with different data and triggers), we have to delay it by a tick to ensure that
  1459. // it won't be closed immediately after it is opened.
  1460. .pipe(filter((/**
  1461. * @param {?} active
  1462. * @return {?}
  1463. */
  1464. active => active === this._menuItemInstance && !active.disabled)), delay(0, asapScheduler))
  1465. .subscribe((/**
  1466. * @return {?}
  1467. */
  1468. () => {
  1469. this._openedBy = 'mouse';
  1470. // If the same menu is used between multiple triggers, it might still be animating
  1471. // while the new trigger tries to re-open it. Wait for the animation to finish
  1472. // before doing so. Also interrupt if the user moves to another item.
  1473. if (this.menu instanceof MatMenu && this.menu._isAnimating) {
  1474. // We need the `delay(0)` here in order to avoid
  1475. // 'changed after checked' errors in some cases. See #12194.
  1476. this.menu._animationDone
  1477. .pipe(take(1), delay(0, asapScheduler), takeUntil(this._parentMenu._hovered()))
  1478. .subscribe((/**
  1479. * @return {?}
  1480. */
  1481. () => this.openMenu()));
  1482. }
  1483. else {
  1484. this.openMenu();
  1485. }
  1486. }));
  1487. }
  1488. /**
  1489. * Gets the portal that should be attached to the overlay.
  1490. * @private
  1491. * @return {?}
  1492. */
  1493. _getPortal() {
  1494. // Note that we can avoid this check by keeping the portal on the menu panel.
  1495. // While it would be cleaner, we'd have to introduce another required method on
  1496. // `MatMenuPanel`, making it harder to consume.
  1497. if (!this._portal || this._portal.templateRef !== this.menu.templateRef) {
  1498. this._portal = new TemplatePortal(this.menu.templateRef, this._viewContainerRef);
  1499. }
  1500. return this._portal;
  1501. }
  1502. }
  1503. MatMenuTrigger.decorators = [
  1504. { type: Directive, args: [{
  1505. selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`,
  1506. host: {
  1507. 'class': 'mat-menu-trigger',
  1508. 'aria-haspopup': 'true',
  1509. '[attr.aria-expanded]': 'menuOpen || null',
  1510. '(mousedown)': '_handleMousedown($event)',
  1511. '(keydown)': '_handleKeydown($event)',
  1512. '(click)': '_handleClick($event)',
  1513. },
  1514. exportAs: 'matMenuTrigger'
  1515. },] },
  1516. ];
  1517. /** @nocollapse */
  1518. MatMenuTrigger.ctorParameters = () => [
  1519. { type: Overlay },
  1520. { type: ElementRef },
  1521. { type: ViewContainerRef },
  1522. { type: undefined, decorators: [{ type: Inject, args: [MAT_MENU_SCROLL_STRATEGY,] }] },
  1523. { type: MatMenu, decorators: [{ type: Optional }] },
  1524. { type: MatMenuItem, decorators: [{ type: Optional }, { type: Self }] },
  1525. { type: Directionality, decorators: [{ type: Optional }] },
  1526. { type: FocusMonitor }
  1527. ];
  1528. MatMenuTrigger.propDecorators = {
  1529. _deprecatedMatMenuTriggerFor: [{ type: Input, args: ['mat-menu-trigger-for',] }],
  1530. menu: [{ type: Input, args: ['matMenuTriggerFor',] }],
  1531. menuData: [{ type: Input, args: ['matMenuTriggerData',] }],
  1532. restoreFocus: [{ type: Input, args: ['matMenuTriggerRestoreFocus',] }],
  1533. menuOpened: [{ type: Output }],
  1534. onMenuOpen: [{ type: Output }],
  1535. menuClosed: [{ type: Output }],
  1536. onMenuClose: [{ type: Output }]
  1537. };
  1538. /**
  1539. * @fileoverview added by tsickle
  1540. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1541. */
  1542. /**
  1543. * Used by both the current `MatMenuModule` and the MDC `MatMenuModule`
  1544. * to declare the menu-related directives.
  1545. */
  1546. // tslint:disable-next-line:class-name
  1547. class _MatMenuDirectivesModule {
  1548. }
  1549. _MatMenuDirectivesModule.decorators = [
  1550. { type: NgModule, args: [{
  1551. exports: [MatMenuTrigger, MatMenuContent, MatCommonModule],
  1552. declarations: [MatMenuTrigger, MatMenuContent],
  1553. providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER]
  1554. },] },
  1555. ];
  1556. class MatMenuModule {
  1557. }
  1558. MatMenuModule.decorators = [
  1559. { type: NgModule, args: [{
  1560. imports: [
  1561. CommonModule,
  1562. MatCommonModule,
  1563. MatRippleModule,
  1564. OverlayModule,
  1565. _MatMenuDirectivesModule,
  1566. ],
  1567. exports: [_MatMenu, MatMenuItem, _MatMenuDirectivesModule],
  1568. declarations: [_MatMenu, MatMenuItem],
  1569. providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER]
  1570. },] },
  1571. ];
  1572. /**
  1573. * @fileoverview added by tsickle
  1574. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1575. */
  1576. /**
  1577. * @fileoverview added by tsickle
  1578. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1579. */
  1580. /**
  1581. * @fileoverview added by tsickle
  1582. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1583. */
  1584. 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 };
  1585. //# sourceMappingURL=menu.js.map