menu.mjs 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966
  1. import * as i0 from '@angular/core';
  2. import { Directive, InjectionToken, Optional, SkipSelf, Inject, inject, Injectable, Injector, ViewContainerRef, EventEmitter, NgZone, RendererFactory2, ElementRef, ChangeDetectorRef, Renderer2, booleanAttribute, Input, Output, signal, computed, ContentChildren, NgModule } from '@angular/core';
  3. import { startWith, debounceTime, distinctUntilChanged, takeUntil, mergeMap, mapTo, mergeAll, switchMap, skipWhile, skip } from 'rxjs/operators';
  4. import { U as UniqueSelectionDispatcher } from './unique-selection-dispatcher-DtHZDqyJ.mjs';
  5. import { Subject, merge, partition } from 'rxjs';
  6. import { _ as _IdGenerator } from './id-generator-Dw_9dSDu.mjs';
  7. import { a as Overlay, f as OverlayConfig, e as STANDARD_DROPDOWN_BELOW_POSITIONS, S as STANDARD_DROPDOWN_ADJACENT_POSITIONS, m as OverlayModule } from './overlay-module-BUj0D19H.mjs';
  8. import { T as TemplatePortal } from './portal-directives-Bw5woq8I.mjs';
  9. import { c as ENTER, S as SPACE, U as UP_ARROW, D as DOWN_ARROW, L as LEFT_ARROW, R as RIGHT_ARROW, T as TAB, g as ESCAPE } from './keycodes-CpHkExLC.mjs';
  10. import { I as InputModalityDetector, F as FocusMonitor } from './focus-monitor-e2l_RpN3.mjs';
  11. import { D as Directionality } from './directionality-CBXD4hga.mjs';
  12. import { hasModifierKey } from './keycodes.mjs';
  13. import { _ as _getEventTarget } from './shadow-dom-B0oHn41l.mjs';
  14. import { F as FocusKeyManager } from './focus-key-manager-C1rAQJ5z.mjs';
  15. import '@angular/common';
  16. import './platform-DmdVEw_C.mjs';
  17. import './backwards-compatibility-DHR38MsD.mjs';
  18. import './test-environment-CT0XxPyp.mjs';
  19. import './style-loader-Cu9AvjH9.mjs';
  20. import './css-pixel-value-C_HEqLhI.mjs';
  21. import './array-I1yfCXUO.mjs';
  22. import './scrolling.mjs';
  23. import './element-x4z00URv.mjs';
  24. import './scrolling-BkvA05C8.mjs';
  25. import './bidi.mjs';
  26. import './recycle-view-repeater-strategy-DoWdPqVw.mjs';
  27. import './data-source-D34wiQZj.mjs';
  28. import './fake-event-detection-DWOdFTFz.mjs';
  29. import './passive-listeners-esHZRgIN.mjs';
  30. import './list-key-manager-CyOIXo8P.mjs';
  31. import './typeahead-9ZW4Dtsf.mjs';
  32. /**
  33. * A grouping container for `CdkMenuItemRadio` instances, similar to a `role="radiogroup"` element.
  34. */
  35. class CdkMenuGroup {
  36. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  37. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuGroup, isStandalone: true, selector: "[cdkMenuGroup]", host: { attributes: { "role": "group" }, classAttribute: "cdk-menu-group" }, providers: [{ provide: UniqueSelectionDispatcher, useClass: UniqueSelectionDispatcher }], exportAs: ["cdkMenuGroup"], ngImport: i0 });
  38. }
  39. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuGroup, decorators: [{
  40. type: Directive,
  41. args: [{
  42. selector: '[cdkMenuGroup]',
  43. exportAs: 'cdkMenuGroup',
  44. host: {
  45. 'role': 'group',
  46. 'class': 'cdk-menu-group',
  47. },
  48. providers: [{ provide: UniqueSelectionDispatcher, useClass: UniqueSelectionDispatcher }],
  49. }]
  50. }] });
  51. /** Injection token used to return classes implementing the Menu interface */
  52. const CDK_MENU = new InjectionToken('cdk-menu');
  53. /** The relative item in the inline menu to focus after closing all popup menus. */
  54. var FocusNext;
  55. (function (FocusNext) {
  56. FocusNext[FocusNext["nextItem"] = 0] = "nextItem";
  57. FocusNext[FocusNext["previousItem"] = 1] = "previousItem";
  58. FocusNext[FocusNext["currentItem"] = 2] = "currentItem";
  59. })(FocusNext || (FocusNext = {}));
  60. /** Injection token used for an implementation of MenuStack. */
  61. const MENU_STACK = new InjectionToken('cdk-menu-stack');
  62. /** Provider that provides the parent menu stack, or a new menu stack if there is no parent one. */
  63. const PARENT_OR_NEW_MENU_STACK_PROVIDER = {
  64. provide: MENU_STACK,
  65. deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],
  66. useFactory: (parentMenuStack) => parentMenuStack || new MenuStack(),
  67. };
  68. /** Provider that provides the parent menu stack, or a new inline menu stack if there is no parent one. */
  69. const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER = (orientation) => ({
  70. provide: MENU_STACK,
  71. deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],
  72. useFactory: (parentMenuStack) => parentMenuStack || MenuStack.inline(orientation),
  73. });
  74. /**
  75. * MenuStack allows subscribers to listen for close events (when a MenuStackItem is popped off
  76. * of the stack) in order to perform closing actions. Upon the MenuStack being empty it emits
  77. * from the `empty` observable specifying the next focus action which the listener should perform
  78. * as requested by the closer.
  79. */
  80. class MenuStack {
  81. /** The ID of this menu stack. */
  82. id = inject(_IdGenerator).getId('cdk-menu-stack-');
  83. /** All MenuStackItems tracked by this MenuStack. */
  84. _elements = [];
  85. /** Emits the element which was popped off of the stack when requested by a closer. */
  86. _close = new Subject();
  87. /** Emits once the MenuStack has become empty after popping off elements. */
  88. _empty = new Subject();
  89. /** Emits whether any menu in the menu stack has focus. */
  90. _hasFocus = new Subject();
  91. /** Observable which emits the MenuStackItem which has been requested to close. */
  92. closed = this._close;
  93. /** Observable which emits whether any menu in the menu stack has focus. */
  94. hasFocus = this._hasFocus.pipe(startWith(false), debounceTime(0), distinctUntilChanged());
  95. /**
  96. * Observable which emits when the MenuStack is empty after popping off the last element. It
  97. * emits a FocusNext event which specifies the action the closer has requested the listener
  98. * perform.
  99. */
  100. emptied = this._empty;
  101. /**
  102. * Whether the inline menu associated with this menu stack is vertical or horizontal.
  103. * `null` indicates there is no inline menu associated with this menu stack.
  104. */
  105. _inlineMenuOrientation = null;
  106. /** Creates a menu stack that originates from an inline menu. */
  107. static inline(orientation) {
  108. const stack = new MenuStack();
  109. stack._inlineMenuOrientation = orientation;
  110. return stack;
  111. }
  112. /**
  113. * Adds an item to the menu stack.
  114. * @param menu the MenuStackItem to put on the stack.
  115. */
  116. push(menu) {
  117. this._elements.push(menu);
  118. }
  119. /**
  120. * Pop items off of the stack up to and including `lastItem` and emit each on the close
  121. * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.
  122. * @param lastItem the last item to pop off the stack.
  123. * @param options Options that configure behavior on close.
  124. */
  125. close(lastItem, options) {
  126. const { focusNextOnEmpty, focusParentTrigger } = { ...options };
  127. if (this._elements.indexOf(lastItem) >= 0) {
  128. let poppedElement;
  129. do {
  130. poppedElement = this._elements.pop();
  131. this._close.next({ item: poppedElement, focusParentTrigger });
  132. } while (poppedElement !== lastItem);
  133. if (this.isEmpty()) {
  134. this._empty.next(focusNextOnEmpty);
  135. }
  136. }
  137. }
  138. /**
  139. * Pop items off of the stack up to but excluding `lastItem` and emit each on the close
  140. * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.
  141. * @param lastItem the element which should be left on the stack
  142. * @return whether or not an item was removed from the stack
  143. */
  144. closeSubMenuOf(lastItem) {
  145. let removed = false;
  146. if (this._elements.indexOf(lastItem) >= 0) {
  147. removed = this.peek() !== lastItem;
  148. while (this.peek() !== lastItem) {
  149. this._close.next({ item: this._elements.pop() });
  150. }
  151. }
  152. return removed;
  153. }
  154. /**
  155. * Pop off all MenuStackItems and emit each one on the `close` observable one by one.
  156. * @param options Options that configure behavior on close.
  157. */
  158. closeAll(options) {
  159. const { focusNextOnEmpty, focusParentTrigger } = { ...options };
  160. if (!this.isEmpty()) {
  161. while (!this.isEmpty()) {
  162. const menuStackItem = this._elements.pop();
  163. if (menuStackItem) {
  164. this._close.next({ item: menuStackItem, focusParentTrigger });
  165. }
  166. }
  167. this._empty.next(focusNextOnEmpty);
  168. }
  169. }
  170. /** Return true if this stack is empty. */
  171. isEmpty() {
  172. return !this._elements.length;
  173. }
  174. /** Return the length of the stack. */
  175. length() {
  176. return this._elements.length;
  177. }
  178. /** Get the top most element on the stack. */
  179. peek() {
  180. return this._elements[this._elements.length - 1];
  181. }
  182. /** Whether the menu stack is associated with an inline menu. */
  183. hasInlineMenu() {
  184. return this._inlineMenuOrientation != null;
  185. }
  186. /** The orientation of the associated inline menu. */
  187. inlineMenuOrientation() {
  188. return this._inlineMenuOrientation;
  189. }
  190. /** Sets whether the menu stack contains the focused element. */
  191. setHasFocus(hasFocus) {
  192. this._hasFocus.next(hasFocus);
  193. }
  194. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  195. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack });
  196. }
  197. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack, decorators: [{
  198. type: Injectable
  199. }] });
  200. /** Injection token used for an implementation of MenuStack. */
  201. const MENU_TRIGGER = new InjectionToken('cdk-menu-trigger');
  202. /** Injection token used to configure the behavior of the menu when the page is scrolled. */
  203. const MENU_SCROLL_STRATEGY = new InjectionToken('cdk-menu-scroll-strategy', {
  204. providedIn: 'root',
  205. factory: () => {
  206. const overlay = inject(Overlay);
  207. return () => overlay.scrollStrategies.reposition();
  208. },
  209. });
  210. /**
  211. * Abstract directive that implements shared logic common to all menu triggers.
  212. * This class can be extended to create custom menu trigger types.
  213. */
  214. class CdkMenuTriggerBase {
  215. /** The DI injector for this component. */
  216. injector = inject(Injector);
  217. /** The view container ref for this component */
  218. viewContainerRef = inject(ViewContainerRef);
  219. /** The menu stack in which this menu resides. */
  220. menuStack = inject(MENU_STACK);
  221. /** Function used to configure the scroll strategy for the menu. */
  222. menuScrollStrategy = inject(MENU_SCROLL_STRATEGY);
  223. /**
  224. * A list of preferred menu positions to be used when constructing the
  225. * `FlexibleConnectedPositionStrategy` for this trigger's menu.
  226. */
  227. menuPosition;
  228. /** Emits when the attached menu is requested to open */
  229. opened = new EventEmitter();
  230. /** Emits when the attached menu is requested to close */
  231. closed = new EventEmitter();
  232. /** Template reference variable to the menu this trigger opens */
  233. menuTemplateRef;
  234. /** Context data to be passed along to the menu template */
  235. menuData;
  236. /** A reference to the overlay which manages the triggered menu */
  237. overlayRef = null;
  238. /** Emits when this trigger is destroyed. */
  239. destroyed = new Subject();
  240. /** Emits when the outside pointer events listener on the overlay should be stopped. */
  241. stopOutsideClicksListener = merge(this.closed, this.destroyed);
  242. /** The child menu opened by this trigger. */
  243. childMenu;
  244. /** The content of the menu panel opened by this trigger. */
  245. _menuPortal;
  246. /** The injector to use for the child menu opened by this trigger. */
  247. _childMenuInjector;
  248. ngOnDestroy() {
  249. this._destroyOverlay();
  250. this.destroyed.next();
  251. this.destroyed.complete();
  252. }
  253. /** Whether the attached menu is open. */
  254. isOpen() {
  255. return !!this.overlayRef?.hasAttached();
  256. }
  257. /** Registers a child menu as having been opened by this trigger. */
  258. registerChildMenu(child) {
  259. this.childMenu = child;
  260. }
  261. /**
  262. * Get the portal to be attached to the overlay which contains the menu. Allows for the menu
  263. * content to change dynamically and be reflected in the application.
  264. */
  265. getMenuContentPortal() {
  266. const hasMenuContentChanged = this.menuTemplateRef !== this._menuPortal?.templateRef;
  267. if (this.menuTemplateRef && (!this._menuPortal || hasMenuContentChanged)) {
  268. this._menuPortal = new TemplatePortal(this.menuTemplateRef, this.viewContainerRef, this.menuData, this._getChildMenuInjector());
  269. }
  270. return this._menuPortal;
  271. }
  272. /**
  273. * Whether the given element is inside the scope of this trigger's menu stack.
  274. * @param element The element to check.
  275. * @return Whether the element is inside the scope of this trigger's menu stack.
  276. */
  277. isElementInsideMenuStack(element) {
  278. for (let el = element; el; el = el?.parentElement ?? null) {
  279. if (el.getAttribute('data-cdk-menu-stack-id') === this.menuStack.id) {
  280. return true;
  281. }
  282. }
  283. return false;
  284. }
  285. /** Destroy and unset the overlay reference it if exists */
  286. _destroyOverlay() {
  287. if (this.overlayRef) {
  288. this.overlayRef.dispose();
  289. this.overlayRef = null;
  290. }
  291. }
  292. /** Gets the injector to use when creating a child menu. */
  293. _getChildMenuInjector() {
  294. this._childMenuInjector =
  295. this._childMenuInjector ||
  296. Injector.create({
  297. providers: [
  298. { provide: MENU_TRIGGER, useValue: this },
  299. { provide: MENU_STACK, useValue: this.menuStack },
  300. ],
  301. parent: this.injector,
  302. });
  303. return this._childMenuInjector;
  304. }
  305. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTriggerBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  306. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuTriggerBase, isStandalone: true, host: { properties: { "attr.aria-controls": "childMenu?.id", "attr.data-cdk-menu-stack-id": "menuStack.id" } }, ngImport: i0 });
  307. }
  308. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTriggerBase, decorators: [{
  309. type: Directive,
  310. args: [{
  311. host: {
  312. '[attr.aria-controls]': 'childMenu?.id',
  313. '[attr.data-cdk-menu-stack-id]': 'menuStack.id',
  314. },
  315. }]
  316. }] });
  317. /**
  318. * Throws an exception when an instance of the PointerFocusTracker is not provided.
  319. * @docs-private
  320. */
  321. function throwMissingPointerFocusTracker() {
  322. throw Error('expected an instance of PointerFocusTracker to be provided');
  323. }
  324. /**
  325. * Throws an exception when a reference to the parent menu is not provided.
  326. * @docs-private
  327. */
  328. function throwMissingMenuReference() {
  329. throw Error('expected a reference to the parent menu');
  330. }
  331. /** Injection token used for an implementation of MenuAim. */
  332. const MENU_AIM = new InjectionToken('cdk-menu-aim');
  333. /** Capture every nth mouse move event. */
  334. const MOUSE_MOVE_SAMPLE_FREQUENCY = 3;
  335. /** The number of mouse move events to track. */
  336. const NUM_POINTS = 5;
  337. /**
  338. * How long to wait before closing a sibling menu if a user stops short of the submenu they were
  339. * predicted to go into.
  340. */
  341. const CLOSE_DELAY = 300;
  342. /** Calculate the slope between point a and b. */
  343. function getSlope(a, b) {
  344. return (b.y - a.y) / (b.x - a.x);
  345. }
  346. /** Calculate the y intercept for the given point and slope. */
  347. function getYIntercept(point, slope) {
  348. return point.y - slope * point.x;
  349. }
  350. /**
  351. * Whether the given mouse trajectory line defined by the slope and y intercept falls within the
  352. * submenu as defined by `submenuPoints`
  353. * @param submenuPoints the submenu DOMRect points.
  354. * @param m the slope of the trajectory line.
  355. * @param b the y intercept of the trajectory line.
  356. * @return true if any point on the line falls within the submenu.
  357. */
  358. function isWithinSubmenu(submenuPoints, m, b) {
  359. const { left, right, top, bottom } = submenuPoints;
  360. // Check for intersection with each edge of the submenu (left, right, top, bottom)
  361. // by fixing one coordinate to that edge's coordinate (either x or y) and checking if the
  362. // other coordinate is within bounds.
  363. return ((m * left + b >= top && m * left + b <= bottom) ||
  364. (m * right + b >= top && m * right + b <= bottom) ||
  365. ((top - b) / m >= left && (top - b) / m <= right) ||
  366. ((bottom - b) / m >= left && (bottom - b) / m <= right));
  367. }
  368. /**
  369. * TargetMenuAim predicts if a user is moving into a submenu. It calculates the
  370. * trajectory of the user's mouse movement in the current menu to determine if the
  371. * mouse is moving towards an open submenu.
  372. *
  373. * The determination is made by calculating the slope of the users last NUM_POINTS moves where each
  374. * pair of points determines if the trajectory line points into the submenu. It uses consensus
  375. * approach by checking if at least NUM_POINTS / 2 pairs determine that the user is moving towards
  376. * to submenu.
  377. */
  378. class TargetMenuAim {
  379. _ngZone = inject(NgZone);
  380. _renderer = inject(RendererFactory2).createRenderer(null, null);
  381. _cleanupMousemove;
  382. /** The last NUM_POINTS mouse move events. */
  383. _points = [];
  384. /** Reference to the root menu in which we are tracking mouse moves. */
  385. _menu;
  386. /** Reference to the root menu's mouse manager. */
  387. _pointerTracker;
  388. /** The id associated with the current timeout call waiting to resolve. */
  389. _timeoutId;
  390. /** Emits when this service is destroyed. */
  391. _destroyed = new Subject();
  392. ngOnDestroy() {
  393. this._cleanupMousemove?.();
  394. this._destroyed.next();
  395. this._destroyed.complete();
  396. }
  397. /**
  398. * Set the Menu and its PointerFocusTracker.
  399. * @param menu The menu that this menu aim service controls.
  400. * @param pointerTracker The `PointerFocusTracker` for the given menu.
  401. */
  402. initialize(menu, pointerTracker) {
  403. this._menu = menu;
  404. this._pointerTracker = pointerTracker;
  405. this._subscribeToMouseMoves();
  406. }
  407. /**
  408. * Calls the `doToggle` callback when it is deemed that the user is not moving towards
  409. * the submenu.
  410. * @param doToggle the function called when the user is not moving towards the submenu.
  411. */
  412. toggle(doToggle) {
  413. // If the menu is horizontal the sub-menus open below and there is no risk of premature
  414. // closing of any sub-menus therefore we automatically resolve the callback.
  415. if (this._menu.orientation === 'horizontal') {
  416. doToggle();
  417. }
  418. this._checkConfigured();
  419. const siblingItemIsWaiting = !!this._timeoutId;
  420. const hasPoints = this._points.length > 1;
  421. if (hasPoints && !siblingItemIsWaiting) {
  422. if (this._isMovingToSubmenu()) {
  423. this._startTimeout(doToggle);
  424. }
  425. else {
  426. doToggle();
  427. }
  428. }
  429. else if (!siblingItemIsWaiting) {
  430. doToggle();
  431. }
  432. }
  433. /**
  434. * Start the delayed toggle handler if one isn't running already.
  435. *
  436. * The delayed toggle handler executes the `doToggle` callback after some period of time iff the
  437. * users mouse is on an item in the current menu.
  438. *
  439. * @param doToggle the function called when the user is not moving towards the submenu.
  440. */
  441. _startTimeout(doToggle) {
  442. // If the users mouse is moving towards a submenu we don't want to immediately resolve.
  443. // Wait for some period of time before determining if the previous menu should close in
  444. // cases where the user may have moved towards the submenu but stopped on a sibling menu
  445. // item intentionally.
  446. const timeoutId = setTimeout(() => {
  447. // Resolve if the user is currently moused over some element in the root menu
  448. if (this._pointerTracker.activeElement && timeoutId === this._timeoutId) {
  449. doToggle();
  450. }
  451. this._timeoutId = null;
  452. }, CLOSE_DELAY);
  453. this._timeoutId = timeoutId;
  454. }
  455. /** Whether the user is heading towards the open submenu. */
  456. _isMovingToSubmenu() {
  457. const submenuPoints = this._getSubmenuBounds();
  458. if (!submenuPoints) {
  459. return false;
  460. }
  461. let numMoving = 0;
  462. const currPoint = this._points[this._points.length - 1];
  463. // start from the second last point and calculate the slope between each point and the last
  464. // point.
  465. for (let i = this._points.length - 2; i >= 0; i--) {
  466. const previous = this._points[i];
  467. const slope = getSlope(currPoint, previous);
  468. if (isWithinSubmenu(submenuPoints, slope, getYIntercept(currPoint, slope))) {
  469. numMoving++;
  470. }
  471. }
  472. return numMoving >= Math.floor(NUM_POINTS / 2);
  473. }
  474. /** Get the bounding DOMRect for the open submenu. */
  475. _getSubmenuBounds() {
  476. return this._pointerTracker?.previousElement?.getMenu()?.nativeElement.getBoundingClientRect();
  477. }
  478. /**
  479. * Check if a reference to the PointerFocusTracker and menu element is provided.
  480. * @throws an error if neither reference is provided.
  481. */
  482. _checkConfigured() {
  483. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  484. if (!this._pointerTracker) {
  485. throwMissingPointerFocusTracker();
  486. }
  487. if (!this._menu) {
  488. throwMissingMenuReference();
  489. }
  490. }
  491. }
  492. /** Subscribe to the root menus mouse move events and update the tracked mouse points. */
  493. _subscribeToMouseMoves() {
  494. this._cleanupMousemove?.();
  495. this._cleanupMousemove = this._ngZone.runOutsideAngular(() => {
  496. let eventIndex = 0;
  497. return this._renderer.listen(this._menu.nativeElement, 'mousemove', (event) => {
  498. if (eventIndex % MOUSE_MOVE_SAMPLE_FREQUENCY === 0) {
  499. this._points.push({ x: event.clientX, y: event.clientY });
  500. if (this._points.length > NUM_POINTS) {
  501. this._points.shift();
  502. }
  503. }
  504. eventIndex++;
  505. });
  506. });
  507. }
  508. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  509. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim });
  510. }
  511. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim, decorators: [{
  512. type: Injectable
  513. }] });
  514. /**
  515. * CdkTargetMenuAim is a provider for the TargetMenuAim service. It can be added to an
  516. * element with either the `cdkMenu` or `cdkMenuBar` directive and child menu items.
  517. */
  518. class CdkTargetMenuAim {
  519. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  520. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkTargetMenuAim, isStandalone: true, selector: "[cdkTargetMenuAim]", providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }], exportAs: ["cdkTargetMenuAim"], ngImport: i0 });
  521. }
  522. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTargetMenuAim, decorators: [{
  523. type: Directive,
  524. args: [{
  525. selector: '[cdkTargetMenuAim]',
  526. exportAs: 'cdkTargetMenuAim',
  527. providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }],
  528. }]
  529. }] });
  530. /** Checks whether a keyboard event will trigger a native `click` event on an element. */
  531. function eventDispatchesNativeClick(elementRef, event) {
  532. // Synthetic events won't trigger clicks.
  533. if (!event.isTrusted) {
  534. return false;
  535. }
  536. const el = elementRef.nativeElement;
  537. const keyCode = event.keyCode;
  538. // Buttons trigger clicks both on space and enter events.
  539. if (el.nodeName === 'BUTTON' && !el.disabled) {
  540. return keyCode === ENTER || keyCode === SPACE;
  541. }
  542. // Links only trigger clicks on enter.
  543. if (el.nodeName === 'A') {
  544. return keyCode === ENTER;
  545. }
  546. // Any other elements won't dispatch clicks from keyboard events.
  547. return false;
  548. }
  549. /**
  550. * A directive that turns its host element into a trigger for a popup menu.
  551. * It can be combined with cdkMenuItem to create sub-menus. If the element is in a top level
  552. * MenuBar it will open the menu on click, or if a sibling is already opened it will open on hover.
  553. * If it is inside of a Menu it will open the attached Submenu on hover regardless of its sibling
  554. * state.
  555. */
  556. class CdkMenuTrigger extends CdkMenuTriggerBase {
  557. _elementRef = inject(ElementRef);
  558. _overlay = inject(Overlay);
  559. _ngZone = inject(NgZone);
  560. _changeDetectorRef = inject(ChangeDetectorRef);
  561. _inputModalityDetector = inject(InputModalityDetector);
  562. _directionality = inject(Directionality, { optional: true });
  563. _renderer = inject(Renderer2);
  564. _cleanupMouseenter;
  565. /** The parent menu this trigger belongs to. */
  566. _parentMenu = inject(CDK_MENU, { optional: true });
  567. /** The menu aim service used by this menu. */
  568. _menuAim = inject(MENU_AIM, { optional: true });
  569. constructor() {
  570. super();
  571. this._setRole();
  572. this._registerCloseHandler();
  573. this._subscribeToMenuStackClosed();
  574. this._subscribeToMouseEnter();
  575. this._subscribeToMenuStackHasFocus();
  576. this._setType();
  577. }
  578. /** Toggle the attached menu. */
  579. toggle() {
  580. this.isOpen() ? this.close() : this.open();
  581. }
  582. /** Open the attached menu. */
  583. open() {
  584. if (!this.isOpen() && this.menuTemplateRef != null) {
  585. this.opened.next();
  586. this.overlayRef = this.overlayRef || this._overlay.create(this._getOverlayConfig());
  587. this.overlayRef.attach(this.getMenuContentPortal());
  588. this._changeDetectorRef.markForCheck();
  589. this._subscribeToOutsideClicks();
  590. }
  591. }
  592. /** Close the opened menu. */
  593. close() {
  594. if (this.isOpen()) {
  595. this.closed.next();
  596. this.overlayRef.detach();
  597. this._changeDetectorRef.markForCheck();
  598. }
  599. this._closeSiblingTriggers();
  600. }
  601. /**
  602. * Get a reference to the rendered Menu if the Menu is open and rendered in the DOM.
  603. */
  604. getMenu() {
  605. return this.childMenu;
  606. }
  607. ngOnChanges(changes) {
  608. if (changes['menuPosition'] && this.overlayRef) {
  609. this.overlayRef.updatePositionStrategy(this._getOverlayPositionStrategy());
  610. }
  611. }
  612. ngOnDestroy() {
  613. this._cleanupMouseenter();
  614. super.ngOnDestroy();
  615. }
  616. /**
  617. * Handles keyboard events for the menu item.
  618. * @param event The keyboard event to handle
  619. */
  620. _toggleOnKeydown(event) {
  621. const isParentVertical = this._parentMenu?.orientation === 'vertical';
  622. switch (event.keyCode) {
  623. case SPACE:
  624. case ENTER:
  625. // Skip events that will trigger clicks so the handler doesn't get triggered twice.
  626. if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {
  627. this.toggle();
  628. this.childMenu?.focusFirstItem('keyboard');
  629. }
  630. break;
  631. case RIGHT_ARROW:
  632. if (!hasModifierKey(event)) {
  633. if (this._parentMenu && isParentVertical && this._directionality?.value !== 'rtl') {
  634. event.preventDefault();
  635. this.open();
  636. this.childMenu?.focusFirstItem('keyboard');
  637. }
  638. }
  639. break;
  640. case LEFT_ARROW:
  641. if (!hasModifierKey(event)) {
  642. if (this._parentMenu && isParentVertical && this._directionality?.value === 'rtl') {
  643. event.preventDefault();
  644. this.open();
  645. this.childMenu?.focusFirstItem('keyboard');
  646. }
  647. }
  648. break;
  649. case DOWN_ARROW:
  650. case UP_ARROW:
  651. if (!hasModifierKey(event)) {
  652. if (!isParentVertical) {
  653. event.preventDefault();
  654. this.open();
  655. event.keyCode === DOWN_ARROW
  656. ? this.childMenu?.focusFirstItem('keyboard')
  657. : this.childMenu?.focusLastItem('keyboard');
  658. }
  659. }
  660. break;
  661. }
  662. }
  663. /** Handles clicks on the menu trigger. */
  664. _handleClick() {
  665. this.toggle();
  666. this.childMenu?.focusFirstItem('mouse');
  667. }
  668. /**
  669. * Sets whether the trigger's menu stack has focus.
  670. * @param hasFocus Whether the menu stack has focus.
  671. */
  672. _setHasFocus(hasFocus) {
  673. if (!this._parentMenu) {
  674. this.menuStack.setHasFocus(hasFocus);
  675. }
  676. }
  677. /**
  678. * Subscribe to the mouseenter events and close any sibling menu items if this element is moused
  679. * into.
  680. */
  681. _subscribeToMouseEnter() {
  682. this._cleanupMouseenter = this._ngZone.runOutsideAngular(() => {
  683. return this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {
  684. if (
  685. // Skip fake `mouseenter` events dispatched by touch devices.
  686. this._inputModalityDetector.mostRecentModality !== 'touch' &&
  687. !this.menuStack.isEmpty() &&
  688. !this.isOpen()) {
  689. // Closes any sibling menu items and opens the menu associated with this trigger.
  690. const toggleMenus = () => this._ngZone.run(() => {
  691. this._closeSiblingTriggers();
  692. this.open();
  693. });
  694. if (this._menuAim) {
  695. this._menuAim.toggle(toggleMenus);
  696. }
  697. else {
  698. toggleMenus();
  699. }
  700. }
  701. });
  702. });
  703. }
  704. /** Close out any sibling menu trigger menus. */
  705. _closeSiblingTriggers() {
  706. if (this._parentMenu) {
  707. // If nothing was removed from the stack and the last element is not the parent item
  708. // that means that the parent menu is a menu bar since we don't put the menu bar on the
  709. // stack
  710. const isParentMenuBar = !this.menuStack.closeSubMenuOf(this._parentMenu) &&
  711. this.menuStack.peek() !== this._parentMenu;
  712. if (isParentMenuBar) {
  713. this.menuStack.closeAll();
  714. }
  715. }
  716. else {
  717. this.menuStack.closeAll();
  718. }
  719. }
  720. /** Get the configuration object used to create the overlay. */
  721. _getOverlayConfig() {
  722. return new OverlayConfig({
  723. positionStrategy: this._getOverlayPositionStrategy(),
  724. scrollStrategy: this.menuScrollStrategy(),
  725. direction: this._directionality || undefined,
  726. });
  727. }
  728. /** Build the position strategy for the overlay which specifies where to place the menu. */
  729. _getOverlayPositionStrategy() {
  730. return this._overlay
  731. .position()
  732. .flexibleConnectedTo(this._elementRef)
  733. .withLockedPosition()
  734. .withFlexibleDimensions(false)
  735. .withPositions(this._getOverlayPositions());
  736. }
  737. /** Get the preferred positions for the opened menu relative to the menu item. */
  738. _getOverlayPositions() {
  739. return (this.menuPosition ??
  740. (!this._parentMenu || this._parentMenu.orientation === 'horizontal'
  741. ? STANDARD_DROPDOWN_BELOW_POSITIONS
  742. : STANDARD_DROPDOWN_ADJACENT_POSITIONS));
  743. }
  744. /**
  745. * Subscribe to the MenuStack close events if this is a standalone trigger and close out the menu
  746. * this triggers when requested.
  747. */
  748. _registerCloseHandler() {
  749. if (!this._parentMenu) {
  750. this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item }) => {
  751. if (item === this.childMenu) {
  752. this.close();
  753. }
  754. });
  755. }
  756. }
  757. /**
  758. * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
  759. * click occurs outside the menus.
  760. */
  761. _subscribeToOutsideClicks() {
  762. if (this.overlayRef) {
  763. this.overlayRef
  764. .outsidePointerEvents()
  765. .pipe(takeUntil(this.stopOutsideClicksListener))
  766. .subscribe(event => {
  767. const target = _getEventTarget(event);
  768. const element = this._elementRef.nativeElement;
  769. if (target !== element && !element.contains(target)) {
  770. if (!this.isElementInsideMenuStack(target)) {
  771. this.menuStack.closeAll();
  772. }
  773. else {
  774. this._closeSiblingTriggers();
  775. }
  776. }
  777. });
  778. }
  779. }
  780. /** Subscribe to the MenuStack hasFocus events. */
  781. _subscribeToMenuStackHasFocus() {
  782. if (!this._parentMenu) {
  783. this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {
  784. if (!hasFocus) {
  785. this.menuStack.closeAll();
  786. }
  787. });
  788. }
  789. }
  790. /** Subscribe to the MenuStack closed events. */
  791. _subscribeToMenuStackClosed() {
  792. if (!this._parentMenu) {
  793. this.menuStack.closed.subscribe(({ focusParentTrigger }) => {
  794. if (focusParentTrigger && !this.menuStack.length()) {
  795. this._elementRef.nativeElement.focus();
  796. }
  797. });
  798. }
  799. }
  800. /** Sets the role attribute for this trigger if needed. */
  801. _setRole() {
  802. // If this trigger is part of another menu, the cdkMenuItem directive will handle setting the
  803. // role, otherwise this is a standalone trigger, and we should ensure it has role="button".
  804. if (!this._parentMenu) {
  805. this._elementRef.nativeElement.setAttribute('role', 'button');
  806. }
  807. }
  808. /** Sets thte `type` attribute of the trigger. */
  809. _setType() {
  810. const element = this._elementRef.nativeElement;
  811. if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {
  812. // Prevents form submissions.
  813. element.setAttribute('type', 'button');
  814. }
  815. }
  816. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  817. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuTrigger, isStandalone: true, selector: "[cdkMenuTriggerFor]", inputs: { menuTemplateRef: ["cdkMenuTriggerFor", "menuTemplateRef"], menuPosition: ["cdkMenuPosition", "menuPosition"], menuData: ["cdkMenuTriggerData", "menuData"] }, outputs: { opened: "cdkMenuOpened", closed: "cdkMenuClosed" }, host: { listeners: { "focusin": "_setHasFocus(true)", "focusout": "_setHasFocus(false)", "keydown": "_toggleOnKeydown($event)", "click": "_handleClick()" }, properties: { "attr.aria-haspopup": "menuTemplateRef ? \"menu\" : null", "attr.aria-expanded": "menuTemplateRef == null ? null : isOpen()" }, classAttribute: "cdk-menu-trigger" }, providers: [
  818. { provide: MENU_TRIGGER, useExisting: CdkMenuTrigger },
  819. PARENT_OR_NEW_MENU_STACK_PROVIDER,
  820. ], exportAs: ["cdkMenuTriggerFor"], usesInheritance: true, usesOnChanges: true, ngImport: i0 });
  821. }
  822. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTrigger, decorators: [{
  823. type: Directive,
  824. args: [{
  825. selector: '[cdkMenuTriggerFor]',
  826. exportAs: 'cdkMenuTriggerFor',
  827. host: {
  828. 'class': 'cdk-menu-trigger',
  829. '[attr.aria-haspopup]': 'menuTemplateRef ? "menu" : null',
  830. '[attr.aria-expanded]': 'menuTemplateRef == null ? null : isOpen()',
  831. '(focusin)': '_setHasFocus(true)',
  832. '(focusout)': '_setHasFocus(false)',
  833. '(keydown)': '_toggleOnKeydown($event)',
  834. '(click)': '_handleClick()',
  835. },
  836. inputs: [
  837. { name: 'menuTemplateRef', alias: 'cdkMenuTriggerFor' },
  838. { name: 'menuPosition', alias: 'cdkMenuPosition' },
  839. { name: 'menuData', alias: 'cdkMenuTriggerData' },
  840. ],
  841. outputs: ['opened: cdkMenuOpened', 'closed: cdkMenuClosed'],
  842. providers: [
  843. { provide: MENU_TRIGGER, useExisting: CdkMenuTrigger },
  844. PARENT_OR_NEW_MENU_STACK_PROVIDER,
  845. ],
  846. }]
  847. }], ctorParameters: () => [] });
  848. /**
  849. * Directive which provides the ability for an element to be focused and navigated to using the
  850. * keyboard when residing in a CdkMenu, CdkMenuBar, or CdkMenuGroup. It performs user defined
  851. * behavior when clicked.
  852. */
  853. class CdkMenuItem {
  854. _dir = inject(Directionality, { optional: true });
  855. _elementRef = inject(ElementRef);
  856. _ngZone = inject(NgZone);
  857. _inputModalityDetector = inject(InputModalityDetector);
  858. _renderer = inject(Renderer2);
  859. _cleanupMouseEnter;
  860. /** The menu aim service used by this menu. */
  861. _menuAim = inject(MENU_AIM, { optional: true });
  862. /** The stack of menus this menu belongs to. */
  863. _menuStack = inject(MENU_STACK);
  864. /** The parent menu in which this menuitem resides. */
  865. _parentMenu = inject(CDK_MENU, { optional: true });
  866. /** Reference to the CdkMenuItemTrigger directive if one is added to the same element */
  867. _menuTrigger = inject(CdkMenuTrigger, { optional: true, self: true });
  868. /** Whether the CdkMenuItem is disabled - defaults to false */
  869. disabled = false;
  870. /**
  871. * The text used to locate this item during menu typeahead. If not specified,
  872. * the `textContent` of the item will be used.
  873. */
  874. typeaheadLabel;
  875. /**
  876. * If this MenuItem is a regular MenuItem, outputs when it is triggered by a keyboard or mouse
  877. * event.
  878. */
  879. triggered = new EventEmitter();
  880. /** Whether the menu item opens a menu. */
  881. get hasMenu() {
  882. return this._menuTrigger?.menuTemplateRef != null;
  883. }
  884. /**
  885. * The tabindex for this menu item managed internally and used for implementing roving a
  886. * tab index.
  887. */
  888. _tabindex = -1;
  889. /** Whether the item should close the menu if triggered by the spacebar. */
  890. closeOnSpacebarTrigger = true;
  891. /** Emits when the menu item is destroyed. */
  892. destroyed = new Subject();
  893. constructor() {
  894. this._setupMouseEnter();
  895. this._setType();
  896. if (this._isStandaloneItem()) {
  897. this._tabindex = 0;
  898. }
  899. }
  900. ngOnDestroy() {
  901. this._cleanupMouseEnter?.();
  902. this.destroyed.next();
  903. this.destroyed.complete();
  904. }
  905. /** Place focus on the element. */
  906. focus() {
  907. this._elementRef.nativeElement.focus();
  908. }
  909. /**
  910. * If the menu item is not disabled and the element does not have a menu trigger attached, emit
  911. * on the cdkMenuItemTriggered emitter and close all open menus.
  912. * @param options Options the configure how the item is triggered
  913. * - keepOpen: specifies that the menu should be kept open after triggering the item.
  914. */
  915. trigger(options) {
  916. const { keepOpen } = { ...options };
  917. if (!this.disabled && !this.hasMenu) {
  918. this.triggered.next();
  919. if (!keepOpen) {
  920. this._menuStack.closeAll({ focusParentTrigger: true });
  921. }
  922. }
  923. }
  924. /** Return true if this MenuItem has an attached menu and it is open. */
  925. isMenuOpen() {
  926. return !!this._menuTrigger?.isOpen();
  927. }
  928. /**
  929. * Get a reference to the rendered Menu if the Menu is open and it is visible in the DOM.
  930. * @return the menu if it is open, otherwise undefined.
  931. */
  932. getMenu() {
  933. return this._menuTrigger?.getMenu();
  934. }
  935. /** Get the CdkMenuTrigger associated with this element. */
  936. getMenuTrigger() {
  937. return this._menuTrigger;
  938. }
  939. /** Get the label for this element which is required by the FocusableOption interface. */
  940. getLabel() {
  941. return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';
  942. }
  943. /** Reset the tabindex to -1. */
  944. _resetTabIndex() {
  945. if (!this._isStandaloneItem()) {
  946. this._tabindex = -1;
  947. }
  948. }
  949. /**
  950. * Set the tab index to 0 if not disabled and it's a focus event, or a mouse enter if this element
  951. * is not in a menu bar.
  952. */
  953. _setTabIndex(event) {
  954. if (this.disabled) {
  955. return;
  956. }
  957. // don't set the tabindex if there are no open sibling or parent menus
  958. if (!event || !this._menuStack.isEmpty()) {
  959. this._tabindex = 0;
  960. }
  961. }
  962. /**
  963. * Handles keyboard events for the menu item, specifically either triggering the user defined
  964. * callback or opening/closing the current menu based on whether the left or right arrow key was
  965. * pressed.
  966. * @param event the keyboard event to handle
  967. */
  968. _onKeydown(event) {
  969. switch (event.keyCode) {
  970. case SPACE:
  971. case ENTER:
  972. // Skip events that will trigger clicks so the handler doesn't get triggered twice.
  973. if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {
  974. const nodeName = this._elementRef.nativeElement.nodeName;
  975. // Avoid repeat events on non-native elements (see #30250). Note that we don't do this
  976. // on the native elements so we don't interfere with their behavior (see #26296).
  977. if (nodeName !== 'A' && nodeName !== 'BUTTON') {
  978. event.preventDefault();
  979. }
  980. this.trigger({ keepOpen: event.keyCode === SPACE && !this.closeOnSpacebarTrigger });
  981. }
  982. break;
  983. case RIGHT_ARROW:
  984. if (!hasModifierKey(event)) {
  985. if (this._parentMenu && this._isParentVertical()) {
  986. if (this._dir?.value !== 'rtl') {
  987. this._forwardArrowPressed(event);
  988. }
  989. else {
  990. this._backArrowPressed(event);
  991. }
  992. }
  993. }
  994. break;
  995. case LEFT_ARROW:
  996. if (!hasModifierKey(event)) {
  997. if (this._parentMenu && this._isParentVertical()) {
  998. if (this._dir?.value !== 'rtl') {
  999. this._backArrowPressed(event);
  1000. }
  1001. else {
  1002. this._forwardArrowPressed(event);
  1003. }
  1004. }
  1005. }
  1006. break;
  1007. }
  1008. }
  1009. /** Whether this menu item is standalone or within a menu or menu bar. */
  1010. _isStandaloneItem() {
  1011. return !this._parentMenu;
  1012. }
  1013. /**
  1014. * Handles the user pressing the back arrow key.
  1015. * @param event The keyboard event.
  1016. */
  1017. _backArrowPressed(event) {
  1018. const parentMenu = this._parentMenu;
  1019. if (this._menuStack.hasInlineMenu() || this._menuStack.length() > 1) {
  1020. event.preventDefault();
  1021. this._menuStack.close(parentMenu, {
  1022. focusNextOnEmpty: this._menuStack.inlineMenuOrientation() === 'horizontal'
  1023. ? FocusNext.previousItem
  1024. : FocusNext.currentItem,
  1025. focusParentTrigger: true,
  1026. });
  1027. }
  1028. }
  1029. /**
  1030. * Handles the user pressing the forward arrow key.
  1031. * @param event The keyboard event.
  1032. */
  1033. _forwardArrowPressed(event) {
  1034. if (!this.hasMenu && this._menuStack.inlineMenuOrientation() === 'horizontal') {
  1035. event.preventDefault();
  1036. this._menuStack.closeAll({
  1037. focusNextOnEmpty: FocusNext.nextItem,
  1038. focusParentTrigger: true,
  1039. });
  1040. }
  1041. }
  1042. /**
  1043. * Subscribe to the mouseenter events and close any sibling menu items if this element is moused
  1044. * into.
  1045. */
  1046. _setupMouseEnter() {
  1047. if (!this._isStandaloneItem()) {
  1048. const closeOpenSiblings = () => this._ngZone.run(() => this._menuStack.closeSubMenuOf(this._parentMenu));
  1049. this._cleanupMouseEnter = this._ngZone.runOutsideAngular(() => this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {
  1050. // Skip fake `mouseenter` events dispatched by touch devices.
  1051. if (this._inputModalityDetector.mostRecentModality !== 'touch' &&
  1052. !this._menuStack.isEmpty() &&
  1053. !this.hasMenu) {
  1054. if (this._menuAim) {
  1055. this._menuAim.toggle(closeOpenSiblings);
  1056. }
  1057. else {
  1058. closeOpenSiblings();
  1059. }
  1060. }
  1061. }));
  1062. }
  1063. }
  1064. /**
  1065. * Return true if the enclosing parent menu is configured in a horizontal orientation, false
  1066. * otherwise or if no parent.
  1067. */
  1068. _isParentVertical() {
  1069. return this._parentMenu?.orientation === 'vertical';
  1070. }
  1071. /** Sets the `type` attribute of the menu item. */
  1072. _setType() {
  1073. const element = this._elementRef.nativeElement;
  1074. if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {
  1075. // Prevent form submissions.
  1076. element.setAttribute('type', 'button');
  1077. }
  1078. }
  1079. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1080. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkMenuItem, isStandalone: true, selector: "[cdkMenuItem]", inputs: { disabled: ["cdkMenuItemDisabled", "disabled", booleanAttribute], typeaheadLabel: ["cdkMenuitemTypeaheadLabel", "typeaheadLabel"] }, outputs: { triggered: "cdkMenuItemTriggered" }, host: { attributes: { "role": "menuitem" }, listeners: { "blur": "_resetTabIndex()", "focus": "_setTabIndex()", "click": "trigger()", "keydown": "_onKeydown($event)" }, properties: { "tabindex": "_tabindex", "attr.aria-disabled": "disabled || null" }, classAttribute: "cdk-menu-item" }, exportAs: ["cdkMenuItem"], ngImport: i0 });
  1081. }
  1082. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItem, decorators: [{
  1083. type: Directive,
  1084. args: [{
  1085. selector: '[cdkMenuItem]',
  1086. exportAs: 'cdkMenuItem',
  1087. host: {
  1088. 'role': 'menuitem',
  1089. 'class': 'cdk-menu-item',
  1090. '[tabindex]': '_tabindex',
  1091. '[attr.aria-disabled]': 'disabled || null',
  1092. '(blur)': '_resetTabIndex()',
  1093. '(focus)': '_setTabIndex()',
  1094. '(click)': 'trigger()',
  1095. '(keydown)': '_onKeydown($event)',
  1096. },
  1097. }]
  1098. }], ctorParameters: () => [], propDecorators: { disabled: [{
  1099. type: Input,
  1100. args: [{ alias: 'cdkMenuItemDisabled', transform: booleanAttribute }]
  1101. }], typeaheadLabel: [{
  1102. type: Input,
  1103. args: ['cdkMenuitemTypeaheadLabel']
  1104. }], triggered: [{
  1105. type: Output,
  1106. args: ['cdkMenuItemTriggered']
  1107. }] } });
  1108. /**
  1109. * PointerFocusTracker keeps track of the currently active item under mouse focus. It also has
  1110. * observables which emit when the users mouse enters and leaves a tracked element.
  1111. */
  1112. class PointerFocusTracker {
  1113. _renderer;
  1114. _items;
  1115. _eventCleanups;
  1116. _itemsSubscription;
  1117. /** Emits when an element is moused into. */
  1118. entered = new Subject();
  1119. /** Emits when an element is moused out. */
  1120. exited = new Subject();
  1121. /** The element currently under mouse focus. */
  1122. activeElement;
  1123. /** The element previously under mouse focus. */
  1124. previousElement;
  1125. constructor(_renderer, _items) {
  1126. this._renderer = _renderer;
  1127. this._items = _items;
  1128. this._bindEvents();
  1129. this.entered.subscribe(element => (this.activeElement = element));
  1130. this.exited.subscribe(() => {
  1131. this.previousElement = this.activeElement;
  1132. this.activeElement = undefined;
  1133. });
  1134. }
  1135. /** Stop the managers listeners. */
  1136. destroy() {
  1137. this._cleanupEvents();
  1138. this._itemsSubscription?.unsubscribe();
  1139. }
  1140. /** Binds the enter/exit events on all the items. */
  1141. _bindEvents() {
  1142. // TODO(crisbeto): this can probably be simplified by binding a single event on a parent node.
  1143. this._itemsSubscription = this._items.changes.pipe(startWith(this._items)).subscribe(() => {
  1144. this._cleanupEvents();
  1145. this._eventCleanups = [];
  1146. this._items.forEach(item => {
  1147. const element = item._elementRef.nativeElement;
  1148. this._eventCleanups.push(this._renderer.listen(element, 'mouseenter', () => {
  1149. this.entered.next(item);
  1150. }), this._renderer.listen(element, 'mouseout', () => {
  1151. this.exited.next(item);
  1152. }));
  1153. });
  1154. });
  1155. }
  1156. /** Cleans up the currently-bound events. */
  1157. _cleanupEvents() {
  1158. this._eventCleanups?.forEach(cleanup => cleanup());
  1159. this._eventCleanups = undefined;
  1160. }
  1161. }
  1162. /**
  1163. * Abstract directive that implements shared logic common to all menus.
  1164. * This class can be extended to create custom menu types.
  1165. */
  1166. class CdkMenuBase extends CdkMenuGroup {
  1167. _focusMonitor = inject(FocusMonitor);
  1168. ngZone = inject(NgZone);
  1169. _renderer = inject(Renderer2);
  1170. /** The menu's native DOM host element. */
  1171. nativeElement = inject(ElementRef).nativeElement;
  1172. /** The stack of menus this menu belongs to. */
  1173. menuStack = inject(MENU_STACK);
  1174. /** The menu aim service used by this menu. */
  1175. menuAim = inject(MENU_AIM, { optional: true, self: true });
  1176. /** The directionality (text direction) of the current page. */
  1177. dir = inject(Directionality, { optional: true });
  1178. /** The id of the menu's host element. */
  1179. id = inject(_IdGenerator).getId('cdk-menu-');
  1180. /** All child MenuItem elements nested in this Menu. */
  1181. items;
  1182. /** The direction items in the menu flow. */
  1183. orientation = 'vertical';
  1184. /**
  1185. * Whether the menu is displayed inline (i.e. always present vs a conditional popup that the
  1186. * user triggers with a trigger element).
  1187. */
  1188. isInline = false;
  1189. /** Handles keyboard events for the menu. */
  1190. keyManager;
  1191. /** Emits when the MenuBar is destroyed. */
  1192. destroyed = new Subject();
  1193. /** The Menu Item which triggered the open submenu. */
  1194. triggerItem;
  1195. /** Tracks the users mouse movements over the menu. */
  1196. pointerTracker;
  1197. /** Whether this menu's menu stack has focus. */
  1198. _menuStackHasFocus = signal(false);
  1199. _tabIndexSignal = computed(() => {
  1200. const tabindexIfInline = this._menuStackHasFocus() ? -1 : 0;
  1201. return this.isInline ? tabindexIfInline : null;
  1202. });
  1203. ngAfterContentInit() {
  1204. if (!this.isInline) {
  1205. this.menuStack.push(this);
  1206. }
  1207. this._setKeyManager();
  1208. this._handleFocus();
  1209. this._subscribeToMenuStackHasFocus();
  1210. this._subscribeToMenuOpen();
  1211. this._subscribeToMenuStackClosed();
  1212. this._setUpPointerTracker();
  1213. }
  1214. ngOnDestroy() {
  1215. this._focusMonitor.stopMonitoring(this.nativeElement);
  1216. this.keyManager?.destroy();
  1217. this.destroyed.next();
  1218. this.destroyed.complete();
  1219. this.pointerTracker?.destroy();
  1220. }
  1221. /**
  1222. * Place focus on the first MenuItem in the menu and set the focus origin.
  1223. * @param focusOrigin The origin input mode of the focus event.
  1224. */
  1225. focusFirstItem(focusOrigin = 'program') {
  1226. this.keyManager.setFocusOrigin(focusOrigin);
  1227. this.keyManager.setFirstItemActive();
  1228. }
  1229. /**
  1230. * Place focus on the last MenuItem in the menu and set the focus origin.
  1231. * @param focusOrigin The origin input mode of the focus event.
  1232. */
  1233. focusLastItem(focusOrigin = 'program') {
  1234. this.keyManager.setFocusOrigin(focusOrigin);
  1235. this.keyManager.setLastItemActive();
  1236. }
  1237. /** Gets the tabindex for this menu. */
  1238. _getTabIndex() {
  1239. return this._tabIndexSignal();
  1240. }
  1241. /**
  1242. * Close the open menu if the current active item opened the requested MenuStackItem.
  1243. * @param menu The menu requested to be closed.
  1244. * @param options Options to configure the behavior on close.
  1245. * - `focusParentTrigger` Whether to focus the parent trigger after closing the menu.
  1246. */
  1247. closeOpenMenu(menu, options) {
  1248. const { focusParentTrigger } = { ...options };
  1249. const keyManager = this.keyManager;
  1250. const trigger = this.triggerItem;
  1251. if (menu === trigger?.getMenuTrigger()?.getMenu()) {
  1252. trigger?.getMenuTrigger()?.close();
  1253. // If the user has moused over a sibling item we want to focus the element under mouse focus
  1254. // not the trigger which previously opened the now closed menu.
  1255. if (focusParentTrigger) {
  1256. if (trigger) {
  1257. keyManager.setActiveItem(trigger);
  1258. }
  1259. else {
  1260. keyManager.setFirstItemActive();
  1261. }
  1262. }
  1263. }
  1264. }
  1265. /** Setup the FocusKeyManager with the correct orientation for the menu. */
  1266. _setKeyManager() {
  1267. this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd();
  1268. if (this.orientation === 'horizontal') {
  1269. this.keyManager.withHorizontalOrientation(this.dir?.value || 'ltr');
  1270. }
  1271. else {
  1272. this.keyManager.withVerticalOrientation();
  1273. }
  1274. }
  1275. /**
  1276. * Subscribe to the menu trigger's open events in order to track the trigger which opened the menu
  1277. * and stop tracking it when the menu is closed.
  1278. */
  1279. _subscribeToMenuOpen() {
  1280. const exitCondition = merge(this.items.changes, this.destroyed);
  1281. this.items.changes
  1282. .pipe(startWith(this.items), mergeMap((list) => list
  1283. .filter(item => item.hasMenu)
  1284. .map(item => item.getMenuTrigger().opened.pipe(mapTo(item), takeUntil(exitCondition)))), mergeAll(), switchMap((item) => {
  1285. this.triggerItem = item;
  1286. return item.getMenuTrigger().closed;
  1287. }), takeUntil(this.destroyed))
  1288. .subscribe(() => (this.triggerItem = undefined));
  1289. }
  1290. /** Subscribe to the MenuStack close events. */
  1291. _subscribeToMenuStackClosed() {
  1292. this.menuStack.closed
  1293. .pipe(takeUntil(this.destroyed))
  1294. .subscribe(({ item, focusParentTrigger }) => this.closeOpenMenu(item, { focusParentTrigger }));
  1295. }
  1296. /** Subscribe to the MenuStack hasFocus events. */
  1297. _subscribeToMenuStackHasFocus() {
  1298. if (this.isInline) {
  1299. this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {
  1300. this._menuStackHasFocus.set(hasFocus);
  1301. });
  1302. }
  1303. }
  1304. /**
  1305. * Set the PointerFocusTracker and ensure that when mouse focus changes the key manager is updated
  1306. * with the latest menu item under mouse focus.
  1307. */
  1308. _setUpPointerTracker() {
  1309. if (this.menuAim) {
  1310. this.ngZone.runOutsideAngular(() => {
  1311. this.pointerTracker = new PointerFocusTracker(this._renderer, this.items);
  1312. });
  1313. this.menuAim.initialize(this, this.pointerTracker);
  1314. }
  1315. }
  1316. /** Handles focus landing on the host element of the menu. */
  1317. _handleFocus() {
  1318. this._focusMonitor
  1319. .monitor(this.nativeElement, false)
  1320. .pipe(takeUntil(this.destroyed))
  1321. .subscribe(origin => {
  1322. // Don't forward focus on mouse interactions, because it can
  1323. // mess with the user's scroll position. See #30130.
  1324. if (origin !== null && origin !== 'mouse') {
  1325. this.focusFirstItem(origin);
  1326. }
  1327. });
  1328. }
  1329. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBase, deps: null, target: i0.ɵɵFactoryTarget.Directive });
  1330. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuBase, isStandalone: true, inputs: { id: "id" }, host: { attributes: { "role": "menu" }, listeners: { "focusin": "menuStack.setHasFocus(true)", "focusout": "menuStack.setHasFocus(false)" }, properties: { "tabindex": "_getTabIndex()", "id": "id", "attr.aria-orientation": "orientation", "attr.data-cdk-menu-stack-id": "menuStack.id" } }, queries: [{ propertyName: "items", predicate: CdkMenuItem, descendants: true }], usesInheritance: true, ngImport: i0 });
  1331. }
  1332. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBase, decorators: [{
  1333. type: Directive,
  1334. args: [{
  1335. host: {
  1336. 'role': 'menu',
  1337. 'class': '', // reset the css class added by the super-class
  1338. '[tabindex]': '_getTabIndex()',
  1339. '[id]': 'id',
  1340. '[attr.aria-orientation]': 'orientation',
  1341. '[attr.data-cdk-menu-stack-id]': 'menuStack.id',
  1342. '(focusin)': 'menuStack.setHasFocus(true)',
  1343. '(focusout)': 'menuStack.setHasFocus(false)',
  1344. },
  1345. }]
  1346. }], propDecorators: { id: [{
  1347. type: Input
  1348. }], items: [{
  1349. type: ContentChildren,
  1350. args: [CdkMenuItem, { descendants: true }]
  1351. }] } });
  1352. /**
  1353. * Directive which configures the element as a Menu which should contain child elements marked as
  1354. * CdkMenuItem or CdkMenuGroup. Sets the appropriate role and aria-attributes for a menu and
  1355. * contains accessible keyboard and mouse handling logic.
  1356. *
  1357. * It also acts as a RadioGroup for elements marked with role `menuitemradio`.
  1358. */
  1359. class CdkMenu extends CdkMenuBase {
  1360. _parentTrigger = inject(MENU_TRIGGER, { optional: true });
  1361. /** Event emitted when the menu is closed. */
  1362. closed = new EventEmitter();
  1363. /** The direction items in the menu flow. */
  1364. orientation = 'vertical';
  1365. /** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
  1366. isInline = !this._parentTrigger;
  1367. constructor() {
  1368. super();
  1369. this.destroyed.subscribe(this.closed);
  1370. this._parentTrigger?.registerChildMenu(this);
  1371. }
  1372. ngAfterContentInit() {
  1373. super.ngAfterContentInit();
  1374. this._subscribeToMenuStackEmptied();
  1375. }
  1376. ngOnDestroy() {
  1377. super.ngOnDestroy();
  1378. this.closed.complete();
  1379. }
  1380. /**
  1381. * Handle keyboard events for the Menu.
  1382. * @param event The keyboard event to be handled.
  1383. */
  1384. _handleKeyEvent(event) {
  1385. const keyManager = this.keyManager;
  1386. switch (event.keyCode) {
  1387. case LEFT_ARROW:
  1388. case RIGHT_ARROW:
  1389. if (!hasModifierKey(event)) {
  1390. event.preventDefault();
  1391. keyManager.setFocusOrigin('keyboard');
  1392. keyManager.onKeydown(event);
  1393. }
  1394. break;
  1395. case ESCAPE:
  1396. if (!hasModifierKey(event)) {
  1397. event.preventDefault();
  1398. this.menuStack.close(this, {
  1399. focusNextOnEmpty: FocusNext.currentItem,
  1400. focusParentTrigger: true,
  1401. });
  1402. }
  1403. break;
  1404. case TAB:
  1405. if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
  1406. this.menuStack.closeAll({ focusParentTrigger: true });
  1407. }
  1408. break;
  1409. default:
  1410. keyManager.onKeydown(event);
  1411. }
  1412. }
  1413. /**
  1414. * Set focus the either the current, previous or next item based on the FocusNext event.
  1415. * @param focusNext The element to focus.
  1416. */
  1417. _toggleMenuFocus(focusNext) {
  1418. const keyManager = this.keyManager;
  1419. switch (focusNext) {
  1420. case FocusNext.nextItem:
  1421. keyManager.setFocusOrigin('keyboard');
  1422. keyManager.setNextItemActive();
  1423. break;
  1424. case FocusNext.previousItem:
  1425. keyManager.setFocusOrigin('keyboard');
  1426. keyManager.setPreviousItemActive();
  1427. break;
  1428. case FocusNext.currentItem:
  1429. if (keyManager.activeItem) {
  1430. keyManager.setFocusOrigin('keyboard');
  1431. keyManager.setActiveItem(keyManager.activeItem);
  1432. }
  1433. break;
  1434. }
  1435. }
  1436. /** Subscribe to the MenuStack emptied events. */
  1437. _subscribeToMenuStackEmptied() {
  1438. this.menuStack.emptied
  1439. .pipe(takeUntil(this.destroyed))
  1440. .subscribe(event => this._toggleMenuFocus(event));
  1441. }
  1442. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1443. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenu, isStandalone: true, selector: "[cdkMenu]", outputs: { closed: "closed" }, host: { attributes: { "role": "menu" }, listeners: { "keydown": "_handleKeyEvent($event)" }, properties: { "class.cdk-menu-inline": "isInline" }, classAttribute: "cdk-menu" }, providers: [
  1444. { provide: CdkMenuGroup, useExisting: CdkMenu },
  1445. { provide: CDK_MENU, useExisting: CdkMenu },
  1446. PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER('vertical'),
  1447. ], exportAs: ["cdkMenu"], usesInheritance: true, ngImport: i0 });
  1448. }
  1449. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenu, decorators: [{
  1450. type: Directive,
  1451. args: [{
  1452. selector: '[cdkMenu]',
  1453. exportAs: 'cdkMenu',
  1454. host: {
  1455. 'role': 'menu',
  1456. 'class': 'cdk-menu',
  1457. '[class.cdk-menu-inline]': 'isInline',
  1458. '(keydown)': '_handleKeyEvent($event)',
  1459. },
  1460. providers: [
  1461. { provide: CdkMenuGroup, useExisting: CdkMenu },
  1462. { provide: CDK_MENU, useExisting: CdkMenu },
  1463. PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER('vertical'),
  1464. ],
  1465. }]
  1466. }], ctorParameters: () => [], propDecorators: { closed: [{
  1467. type: Output
  1468. }] } });
  1469. /**
  1470. * Directive applied to an element which configures it as a MenuBar by setting the appropriate
  1471. * role, aria attributes, and accessible keyboard and mouse handling logic. The component that
  1472. * this directive is applied to should contain components marked with CdkMenuItem.
  1473. *
  1474. */
  1475. class CdkMenuBar extends CdkMenuBase {
  1476. /** The direction items in the menu flow. */
  1477. orientation = 'horizontal';
  1478. /** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
  1479. isInline = true;
  1480. ngAfterContentInit() {
  1481. super.ngAfterContentInit();
  1482. this._subscribeToMenuStackEmptied();
  1483. }
  1484. /**
  1485. * Handle keyboard events for the Menu.
  1486. * @param event The keyboard event to be handled.
  1487. */
  1488. _handleKeyEvent(event) {
  1489. const keyManager = this.keyManager;
  1490. switch (event.keyCode) {
  1491. case UP_ARROW:
  1492. case DOWN_ARROW:
  1493. case LEFT_ARROW:
  1494. case RIGHT_ARROW:
  1495. if (!hasModifierKey(event)) {
  1496. const horizontalArrows = event.keyCode === LEFT_ARROW || event.keyCode === RIGHT_ARROW;
  1497. // For a horizontal menu if the left/right keys were clicked, or a vertical menu if the
  1498. // up/down keys were clicked: if the current menu is open, close it then focus and open the
  1499. // next menu.
  1500. if (horizontalArrows) {
  1501. event.preventDefault();
  1502. const prevIsOpen = keyManager.activeItem?.isMenuOpen();
  1503. keyManager.activeItem?.getMenuTrigger()?.close();
  1504. keyManager.setFocusOrigin('keyboard');
  1505. keyManager.onKeydown(event);
  1506. if (prevIsOpen) {
  1507. keyManager.activeItem?.getMenuTrigger()?.open();
  1508. }
  1509. }
  1510. }
  1511. break;
  1512. case ESCAPE:
  1513. if (!hasModifierKey(event)) {
  1514. event.preventDefault();
  1515. keyManager.activeItem?.getMenuTrigger()?.close();
  1516. }
  1517. break;
  1518. case TAB:
  1519. if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
  1520. keyManager.activeItem?.getMenuTrigger()?.close();
  1521. }
  1522. break;
  1523. default:
  1524. keyManager.onKeydown(event);
  1525. }
  1526. }
  1527. /**
  1528. * Set focus to either the current, previous or next item based on the FocusNext event, then
  1529. * open the previous or next item.
  1530. * @param focusNext The element to focus.
  1531. */
  1532. _toggleOpenMenu(focusNext) {
  1533. const keyManager = this.keyManager;
  1534. switch (focusNext) {
  1535. case FocusNext.nextItem:
  1536. keyManager.setFocusOrigin('keyboard');
  1537. keyManager.setNextItemActive();
  1538. keyManager.activeItem?.getMenuTrigger()?.open();
  1539. break;
  1540. case FocusNext.previousItem:
  1541. keyManager.setFocusOrigin('keyboard');
  1542. keyManager.setPreviousItemActive();
  1543. keyManager.activeItem?.getMenuTrigger()?.open();
  1544. break;
  1545. case FocusNext.currentItem:
  1546. if (keyManager.activeItem) {
  1547. keyManager.setFocusOrigin('keyboard');
  1548. keyManager.setActiveItem(keyManager.activeItem);
  1549. }
  1550. break;
  1551. }
  1552. }
  1553. /** Subscribe to the MenuStack emptied events. */
  1554. _subscribeToMenuStackEmptied() {
  1555. this.menuStack?.emptied
  1556. .pipe(takeUntil(this.destroyed))
  1557. .subscribe(event => this._toggleOpenMenu(event));
  1558. }
  1559. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBar, deps: null, target: i0.ɵɵFactoryTarget.Directive });
  1560. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuBar, isStandalone: true, selector: "[cdkMenuBar]", host: { attributes: { "role": "menubar" }, listeners: { "keydown": "_handleKeyEvent($event)" }, classAttribute: "cdk-menu-bar" }, providers: [
  1561. { provide: CdkMenuGroup, useExisting: CdkMenuBar },
  1562. { provide: CDK_MENU, useExisting: CdkMenuBar },
  1563. { provide: MENU_STACK, useFactory: () => MenuStack.inline('horizontal') },
  1564. ], exportAs: ["cdkMenuBar"], usesInheritance: true, ngImport: i0 });
  1565. }
  1566. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBar, decorators: [{
  1567. type: Directive,
  1568. args: [{
  1569. selector: '[cdkMenuBar]',
  1570. exportAs: 'cdkMenuBar',
  1571. host: {
  1572. 'role': 'menubar',
  1573. 'class': 'cdk-menu-bar',
  1574. '(keydown)': '_handleKeyEvent($event)',
  1575. },
  1576. providers: [
  1577. { provide: CdkMenuGroup, useExisting: CdkMenuBar },
  1578. { provide: CDK_MENU, useExisting: CdkMenuBar },
  1579. { provide: MENU_STACK, useFactory: () => MenuStack.inline('horizontal') },
  1580. ],
  1581. }]
  1582. }] });
  1583. /** Base class providing checked state for selectable MenuItems. */
  1584. class CdkMenuItemSelectable extends CdkMenuItem {
  1585. /** Whether the element is checked */
  1586. checked = false;
  1587. /** Whether the item should close the menu if triggered by the spacebar. */
  1588. closeOnSpacebarTrigger = false;
  1589. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemSelectable, deps: null, target: i0.ɵɵFactoryTarget.Directive });
  1590. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkMenuItemSelectable, isStandalone: true, inputs: { checked: ["cdkMenuItemChecked", "checked", booleanAttribute] }, host: { properties: { "attr.aria-checked": "!!checked", "attr.aria-disabled": "disabled || null" } }, usesInheritance: true, ngImport: i0 });
  1591. }
  1592. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemSelectable, decorators: [{
  1593. type: Directive,
  1594. args: [{
  1595. host: {
  1596. '[attr.aria-checked]': '!!checked',
  1597. '[attr.aria-disabled]': 'disabled || null',
  1598. },
  1599. }]
  1600. }], propDecorators: { checked: [{
  1601. type: Input,
  1602. args: [{ alias: 'cdkMenuItemChecked', transform: booleanAttribute }]
  1603. }] } });
  1604. /**
  1605. * A directive providing behavior for the "menuitemradio" ARIA role, which behaves similarly to
  1606. * a conventional radio-button. Any sibling `CdkMenuItemRadio` instances within the same `CdkMenu`
  1607. * or `CdkMenuGroup` comprise a radio group with unique selection enforced.
  1608. */
  1609. class CdkMenuItemRadio extends CdkMenuItemSelectable {
  1610. /** The unique selection dispatcher for this radio's `CdkMenuGroup`. */
  1611. _selectionDispatcher = inject(UniqueSelectionDispatcher);
  1612. /** An ID to identify this radio item to the `UniqueSelectionDispatcher`. */
  1613. _id = inject(_IdGenerator).getId('cdk-menu-item-radio-');
  1614. /** Function to unregister the selection dispatcher */
  1615. _removeDispatcherListener;
  1616. constructor() {
  1617. super();
  1618. this._registerDispatcherListener();
  1619. }
  1620. ngOnDestroy() {
  1621. super.ngOnDestroy();
  1622. this._removeDispatcherListener();
  1623. }
  1624. /**
  1625. * Toggles the checked state of the radio-button.
  1626. * @param options Options the configure how the item is triggered
  1627. * - keepOpen: specifies that the menu should be kept open after triggering the item.
  1628. */
  1629. trigger(options) {
  1630. super.trigger(options);
  1631. if (!this.disabled) {
  1632. this._selectionDispatcher.notify(this._id, '');
  1633. }
  1634. }
  1635. /** Configure the unique selection dispatcher listener in order to toggle the checked state */
  1636. _registerDispatcherListener() {
  1637. this._removeDispatcherListener = this._selectionDispatcher.listen((id) => {
  1638. this.checked = this._id === id;
  1639. });
  1640. }
  1641. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemRadio, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1642. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuItemRadio, isStandalone: true, selector: "[cdkMenuItemRadio]", host: { attributes: { "role": "menuitemradio" }, properties: { "class.cdk-menu-item-radio": "true" } }, providers: [
  1643. { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio },
  1644. { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
  1645. ], exportAs: ["cdkMenuItemRadio"], usesInheritance: true, ngImport: i0 });
  1646. }
  1647. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemRadio, decorators: [{
  1648. type: Directive,
  1649. args: [{
  1650. selector: '[cdkMenuItemRadio]',
  1651. exportAs: 'cdkMenuItemRadio',
  1652. host: {
  1653. 'role': 'menuitemradio',
  1654. '[class.cdk-menu-item-radio]': 'true',
  1655. },
  1656. providers: [
  1657. { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio },
  1658. { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
  1659. ],
  1660. }]
  1661. }], ctorParameters: () => [] });
  1662. /**
  1663. * A directive providing behavior for the "menuitemcheckbox" ARIA role, which behaves similarly to a
  1664. * conventional checkbox.
  1665. */
  1666. class CdkMenuItemCheckbox extends CdkMenuItemSelectable {
  1667. /**
  1668. * Toggle the checked state of the checkbox.
  1669. * @param options Options the configure how the item is triggered
  1670. * - keepOpen: specifies that the menu should be kept open after triggering the item.
  1671. */
  1672. trigger(options) {
  1673. super.trigger(options);
  1674. if (!this.disabled) {
  1675. this.checked = !this.checked;
  1676. }
  1677. }
  1678. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemCheckbox, deps: null, target: i0.ɵɵFactoryTarget.Directive });
  1679. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkMenuItemCheckbox, isStandalone: true, selector: "[cdkMenuItemCheckbox]", host: { attributes: { "role": "menuitemcheckbox" }, properties: { "class.cdk-menu-item-checkbox": "true" } }, providers: [
  1680. { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemCheckbox },
  1681. { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
  1682. ], exportAs: ["cdkMenuItemCheckbox"], usesInheritance: true, ngImport: i0 });
  1683. }
  1684. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemCheckbox, decorators: [{
  1685. type: Directive,
  1686. args: [{
  1687. selector: '[cdkMenuItemCheckbox]',
  1688. exportAs: 'cdkMenuItemCheckbox',
  1689. host: {
  1690. 'role': 'menuitemcheckbox',
  1691. '[class.cdk-menu-item-checkbox]': 'true',
  1692. },
  1693. providers: [
  1694. { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemCheckbox },
  1695. { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
  1696. ],
  1697. }]
  1698. }] });
  1699. /** The preferred menu positions for the context menu. */
  1700. const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => {
  1701. // In cases where the first menu item in the context menu is a trigger the submenu opens on a
  1702. // hover event. We offset the context menu 2px by default to prevent this from occurring.
  1703. const offsetX = position.overlayX === 'start' ? 2 : -2;
  1704. const offsetY = position.overlayY === 'top' ? 2 : -2;
  1705. return { ...position, offsetX, offsetY };
  1706. });
  1707. /** Tracks the last open context menu trigger across the entire application. */
  1708. class ContextMenuTracker {
  1709. /** The last open context menu trigger. */
  1710. static _openContextMenuTrigger;
  1711. /**
  1712. * Close the previous open context menu and set the given one as being open.
  1713. * @param trigger The trigger for the currently open Context Menu.
  1714. */
  1715. update(trigger) {
  1716. if (ContextMenuTracker._openContextMenuTrigger !== trigger) {
  1717. ContextMenuTracker._openContextMenuTrigger?.close();
  1718. ContextMenuTracker._openContextMenuTrigger = trigger;
  1719. }
  1720. }
  1721. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  1722. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, providedIn: 'root' });
  1723. }
  1724. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, decorators: [{
  1725. type: Injectable,
  1726. args: [{ providedIn: 'root' }]
  1727. }] });
  1728. /**
  1729. * A directive that opens a menu when a user right-clicks within its host element.
  1730. * It is aware of nested context menus and will trigger only the lowest level non-disabled context menu.
  1731. */
  1732. class CdkContextMenuTrigger extends CdkMenuTriggerBase {
  1733. /** The CDK overlay service. */
  1734. _overlay = inject(Overlay);
  1735. /** The directionality of the page. */
  1736. _directionality = inject(Directionality, { optional: true });
  1737. /** The app's context menu tracking registry */
  1738. _contextMenuTracker = inject(ContextMenuTracker);
  1739. _changeDetectorRef = inject(ChangeDetectorRef);
  1740. /** Whether the context menu is disabled. */
  1741. disabled = false;
  1742. constructor() {
  1743. super();
  1744. this._setMenuStackCloseListener();
  1745. }
  1746. /**
  1747. * Open the attached menu at the specified location.
  1748. * @param coordinates where to open the context menu
  1749. */
  1750. open(coordinates) {
  1751. this._open(null, coordinates);
  1752. this._changeDetectorRef.markForCheck();
  1753. }
  1754. /** Close the currently opened context menu. */
  1755. close() {
  1756. this.menuStack.closeAll();
  1757. }
  1758. /**
  1759. * Open the context menu and closes any previously open menus.
  1760. * @param event the mouse event which opens the context menu.
  1761. */
  1762. _openOnContextMenu(event) {
  1763. if (!this.disabled) {
  1764. // Prevent the native context menu from opening because we're opening a custom one.
  1765. event.preventDefault();
  1766. // Stop event propagation to ensure that only the closest enabled context menu opens.
  1767. // Otherwise, any context menus attached to containing elements would *also* open,
  1768. // resulting in multiple stacked context menus being displayed.
  1769. event.stopPropagation();
  1770. this._contextMenuTracker.update(this);
  1771. this._open(event, { x: event.clientX, y: event.clientY });
  1772. // A context menu can be triggered via a mouse right click or a keyboard shortcut.
  1773. if (event.button === 2) {
  1774. this.childMenu?.focusFirstItem('mouse');
  1775. }
  1776. else if (event.button === 0) {
  1777. this.childMenu?.focusFirstItem('keyboard');
  1778. }
  1779. else {
  1780. this.childMenu?.focusFirstItem('program');
  1781. }
  1782. }
  1783. }
  1784. /**
  1785. * Get the configuration object used to create the overlay.
  1786. * @param coordinates the location to place the opened menu
  1787. */
  1788. _getOverlayConfig(coordinates) {
  1789. return new OverlayConfig({
  1790. positionStrategy: this._getOverlayPositionStrategy(coordinates),
  1791. scrollStrategy: this.menuScrollStrategy(),
  1792. direction: this._directionality || undefined,
  1793. });
  1794. }
  1795. /**
  1796. * Get the position strategy for the overlay which specifies where to place the menu.
  1797. * @param coordinates the location to place the opened menu
  1798. */
  1799. _getOverlayPositionStrategy(coordinates) {
  1800. return this._overlay
  1801. .position()
  1802. .flexibleConnectedTo(coordinates)
  1803. .withLockedPosition()
  1804. .withGrowAfterOpen()
  1805. .withPositions(this.menuPosition ?? CONTEXT_MENU_POSITIONS);
  1806. }
  1807. /** Subscribe to the menu stack close events and close this menu when requested. */
  1808. _setMenuStackCloseListener() {
  1809. this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item }) => {
  1810. if (item === this.childMenu && this.isOpen()) {
  1811. this.closed.next();
  1812. this.overlayRef.detach();
  1813. this.childMenu = undefined;
  1814. this._changeDetectorRef.markForCheck();
  1815. }
  1816. });
  1817. }
  1818. /**
  1819. * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
  1820. * click occurs outside the menus.
  1821. * @param userEvent User-generated event that opened the menu.
  1822. */
  1823. _subscribeToOutsideClicks(userEvent) {
  1824. if (this.overlayRef) {
  1825. let outsideClicks = this.overlayRef.outsidePointerEvents();
  1826. if (userEvent) {
  1827. const [auxClicks, nonAuxClicks] = partition(outsideClicks, ({ type }) => type === 'auxclick');
  1828. outsideClicks = merge(
  1829. // Using a mouse, the `contextmenu` event can fire either when pressing the right button
  1830. // or left button + control. Most browsers won't dispatch a `click` event right after
  1831. // a `contextmenu` event triggered by left button + control, but Safari will (see #27832).
  1832. // This closes the menu immediately. To work around it, we check that both the triggering
  1833. // event and the current outside click event both had the control key pressed, and that
  1834. // that this is the first outside click event.
  1835. nonAuxClicks.pipe(skipWhile((event, index) => userEvent.ctrlKey && index === 0 && event.ctrlKey)),
  1836. // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
  1837. // because it fires when the mouse is released on the same click that opened the menu.
  1838. auxClicks.pipe(skip(1)));
  1839. }
  1840. outsideClicks.pipe(takeUntil(this.stopOutsideClicksListener)).subscribe(event => {
  1841. if (!this.isElementInsideMenuStack(_getEventTarget(event))) {
  1842. this.menuStack.closeAll();
  1843. }
  1844. });
  1845. }
  1846. }
  1847. /**
  1848. * Open the attached menu at the specified location.
  1849. * @param userEvent User-generated event that opened the menu
  1850. * @param coordinates where to open the context menu
  1851. */
  1852. _open(userEvent, coordinates) {
  1853. if (this.disabled) {
  1854. return;
  1855. }
  1856. if (this.isOpen()) {
  1857. // since we're moving this menu we need to close any submenus first otherwise they end up
  1858. // disconnected from this one.
  1859. this.menuStack.closeSubMenuOf(this.childMenu);
  1860. this.overlayRef.getConfig().positionStrategy.setOrigin(coordinates);
  1861. this.overlayRef.updatePosition();
  1862. }
  1863. else {
  1864. this.opened.next();
  1865. if (this.overlayRef) {
  1866. this.overlayRef.getConfig().positionStrategy.setOrigin(coordinates);
  1867. this.overlayRef.updatePosition();
  1868. }
  1869. else {
  1870. this.overlayRef = this._overlay.create(this._getOverlayConfig(coordinates));
  1871. }
  1872. this.overlayRef.attach(this.getMenuContentPortal());
  1873. this._subscribeToOutsideClicks(userEvent);
  1874. }
  1875. }
  1876. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkContextMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1877. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkContextMenuTrigger, isStandalone: true, selector: "[cdkContextMenuTriggerFor]", inputs: { menuTemplateRef: ["cdkContextMenuTriggerFor", "menuTemplateRef"], menuPosition: ["cdkContextMenuPosition", "menuPosition"], menuData: ["cdkContextMenuTriggerData", "menuData"], disabled: ["cdkContextMenuDisabled", "disabled", booleanAttribute] }, outputs: { opened: "cdkContextMenuOpened", closed: "cdkContextMenuClosed" }, host: { listeners: { "contextmenu": "_openOnContextMenu($event)" }, properties: { "attr.data-cdk-menu-stack-id": "null" } }, providers: [
  1878. { provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger },
  1879. { provide: MENU_STACK, useClass: MenuStack },
  1880. ], exportAs: ["cdkContextMenuTriggerFor"], usesInheritance: true, ngImport: i0 });
  1881. }
  1882. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkContextMenuTrigger, decorators: [{
  1883. type: Directive,
  1884. args: [{
  1885. selector: '[cdkContextMenuTriggerFor]',
  1886. exportAs: 'cdkContextMenuTriggerFor',
  1887. host: {
  1888. '[attr.data-cdk-menu-stack-id]': 'null',
  1889. '(contextmenu)': '_openOnContextMenu($event)',
  1890. },
  1891. inputs: [
  1892. { name: 'menuTemplateRef', alias: 'cdkContextMenuTriggerFor' },
  1893. { name: 'menuPosition', alias: 'cdkContextMenuPosition' },
  1894. { name: 'menuData', alias: 'cdkContextMenuTriggerData' },
  1895. ],
  1896. outputs: ['opened: cdkContextMenuOpened', 'closed: cdkContextMenuClosed'],
  1897. providers: [
  1898. { provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger },
  1899. { provide: MENU_STACK, useClass: MenuStack },
  1900. ],
  1901. }]
  1902. }], ctorParameters: () => [], propDecorators: { disabled: [{
  1903. type: Input,
  1904. args: [{ alias: 'cdkContextMenuDisabled', transform: booleanAttribute }]
  1905. }] } });
  1906. const MENU_DIRECTIVES = [
  1907. CdkMenuBar,
  1908. CdkMenu,
  1909. CdkMenuItem,
  1910. CdkMenuItemRadio,
  1911. CdkMenuItemCheckbox,
  1912. CdkMenuTrigger,
  1913. CdkMenuGroup,
  1914. CdkContextMenuTrigger,
  1915. CdkTargetMenuAim,
  1916. ];
  1917. /** Module that declares components and directives for the CDK menu. */
  1918. class CdkMenuModule {
  1919. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  1920. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, imports: [OverlayModule, CdkMenuBar,
  1921. CdkMenu,
  1922. CdkMenuItem,
  1923. CdkMenuItemRadio,
  1924. CdkMenuItemCheckbox,
  1925. CdkMenuTrigger,
  1926. CdkMenuGroup,
  1927. CdkContextMenuTrigger,
  1928. CdkTargetMenuAim], exports: [CdkMenuBar,
  1929. CdkMenu,
  1930. CdkMenuItem,
  1931. CdkMenuItemRadio,
  1932. CdkMenuItemCheckbox,
  1933. CdkMenuTrigger,
  1934. CdkMenuGroup,
  1935. CdkContextMenuTrigger,
  1936. CdkTargetMenuAim] });
  1937. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, imports: [OverlayModule] });
  1938. }
  1939. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, decorators: [{
  1940. type: NgModule,
  1941. args: [{
  1942. imports: [OverlayModule, ...MENU_DIRECTIVES],
  1943. exports: MENU_DIRECTIVES,
  1944. }]
  1945. }] });
  1946. export { CDK_MENU, CdkContextMenuTrigger, CdkMenu, CdkMenuBar, CdkMenuBase, CdkMenuGroup, CdkMenuItem, CdkMenuItemCheckbox, CdkMenuItemRadio, CdkMenuItemSelectable, CdkMenuModule, CdkMenuTrigger, CdkMenuTriggerBase, CdkTargetMenuAim, ContextMenuTracker, FocusNext, MENU_AIM, MENU_SCROLL_STRATEGY, MENU_STACK, MENU_TRIGGER, MenuStack, PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER, PARENT_OR_NEW_MENU_STACK_PROVIDER, PointerFocusTracker, TargetMenuAim };
  1947. //# sourceMappingURL=menu.mjs.map