1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966 |
- import * as i0 from '@angular/core';
- 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';
- import { startWith, debounceTime, distinctUntilChanged, takeUntil, mergeMap, mapTo, mergeAll, switchMap, skipWhile, skip } from 'rxjs/operators';
- import { U as UniqueSelectionDispatcher } from './unique-selection-dispatcher-DtHZDqyJ.mjs';
- import { Subject, merge, partition } from 'rxjs';
- import { _ as _IdGenerator } from './id-generator-Dw_9dSDu.mjs';
- 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';
- import { T as TemplatePortal } from './portal-directives-Bw5woq8I.mjs';
- 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';
- import { I as InputModalityDetector, F as FocusMonitor } from './focus-monitor-e2l_RpN3.mjs';
- import { D as Directionality } from './directionality-CBXD4hga.mjs';
- import { hasModifierKey } from './keycodes.mjs';
- import { _ as _getEventTarget } from './shadow-dom-B0oHn41l.mjs';
- import { F as FocusKeyManager } from './focus-key-manager-C1rAQJ5z.mjs';
- import '@angular/common';
- import './platform-DmdVEw_C.mjs';
- import './backwards-compatibility-DHR38MsD.mjs';
- import './test-environment-CT0XxPyp.mjs';
- import './style-loader-Cu9AvjH9.mjs';
- import './css-pixel-value-C_HEqLhI.mjs';
- import './array-I1yfCXUO.mjs';
- import './scrolling.mjs';
- import './element-x4z00URv.mjs';
- import './scrolling-BkvA05C8.mjs';
- import './bidi.mjs';
- import './recycle-view-repeater-strategy-DoWdPqVw.mjs';
- import './data-source-D34wiQZj.mjs';
- import './fake-event-detection-DWOdFTFz.mjs';
- import './passive-listeners-esHZRgIN.mjs';
- import './list-key-manager-CyOIXo8P.mjs';
- import './typeahead-9ZW4Dtsf.mjs';
- /**
- * A grouping container for `CdkMenuItemRadio` instances, similar to a `role="radiogroup"` element.
- */
- class CdkMenuGroup {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuGroup, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuGroup]',
- exportAs: 'cdkMenuGroup',
- host: {
- 'role': 'group',
- 'class': 'cdk-menu-group',
- },
- providers: [{ provide: UniqueSelectionDispatcher, useClass: UniqueSelectionDispatcher }],
- }]
- }] });
- /** Injection token used to return classes implementing the Menu interface */
- const CDK_MENU = new InjectionToken('cdk-menu');
- /** The relative item in the inline menu to focus after closing all popup menus. */
- var FocusNext;
- (function (FocusNext) {
- FocusNext[FocusNext["nextItem"] = 0] = "nextItem";
- FocusNext[FocusNext["previousItem"] = 1] = "previousItem";
- FocusNext[FocusNext["currentItem"] = 2] = "currentItem";
- })(FocusNext || (FocusNext = {}));
- /** Injection token used for an implementation of MenuStack. */
- const MENU_STACK = new InjectionToken('cdk-menu-stack');
- /** Provider that provides the parent menu stack, or a new menu stack if there is no parent one. */
- const PARENT_OR_NEW_MENU_STACK_PROVIDER = {
- provide: MENU_STACK,
- deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],
- useFactory: (parentMenuStack) => parentMenuStack || new MenuStack(),
- };
- /** Provider that provides the parent menu stack, or a new inline menu stack if there is no parent one. */
- const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER = (orientation) => ({
- provide: MENU_STACK,
- deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],
- useFactory: (parentMenuStack) => parentMenuStack || MenuStack.inline(orientation),
- });
- /**
- * MenuStack allows subscribers to listen for close events (when a MenuStackItem is popped off
- * of the stack) in order to perform closing actions. Upon the MenuStack being empty it emits
- * from the `empty` observable specifying the next focus action which the listener should perform
- * as requested by the closer.
- */
- class MenuStack {
- /** The ID of this menu stack. */
- id = inject(_IdGenerator).getId('cdk-menu-stack-');
- /** All MenuStackItems tracked by this MenuStack. */
- _elements = [];
- /** Emits the element which was popped off of the stack when requested by a closer. */
- _close = new Subject();
- /** Emits once the MenuStack has become empty after popping off elements. */
- _empty = new Subject();
- /** Emits whether any menu in the menu stack has focus. */
- _hasFocus = new Subject();
- /** Observable which emits the MenuStackItem which has been requested to close. */
- closed = this._close;
- /** Observable which emits whether any menu in the menu stack has focus. */
- hasFocus = this._hasFocus.pipe(startWith(false), debounceTime(0), distinctUntilChanged());
- /**
- * Observable which emits when the MenuStack is empty after popping off the last element. It
- * emits a FocusNext event which specifies the action the closer has requested the listener
- * perform.
- */
- emptied = this._empty;
- /**
- * Whether the inline menu associated with this menu stack is vertical or horizontal.
- * `null` indicates there is no inline menu associated with this menu stack.
- */
- _inlineMenuOrientation = null;
- /** Creates a menu stack that originates from an inline menu. */
- static inline(orientation) {
- const stack = new MenuStack();
- stack._inlineMenuOrientation = orientation;
- return stack;
- }
- /**
- * Adds an item to the menu stack.
- * @param menu the MenuStackItem to put on the stack.
- */
- push(menu) {
- this._elements.push(menu);
- }
- /**
- * Pop items off of the stack up to and including `lastItem` and emit each on the close
- * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.
- * @param lastItem the last item to pop off the stack.
- * @param options Options that configure behavior on close.
- */
- close(lastItem, options) {
- const { focusNextOnEmpty, focusParentTrigger } = { ...options };
- if (this._elements.indexOf(lastItem) >= 0) {
- let poppedElement;
- do {
- poppedElement = this._elements.pop();
- this._close.next({ item: poppedElement, focusParentTrigger });
- } while (poppedElement !== lastItem);
- if (this.isEmpty()) {
- this._empty.next(focusNextOnEmpty);
- }
- }
- }
- /**
- * Pop items off of the stack up to but excluding `lastItem` and emit each on the close
- * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.
- * @param lastItem the element which should be left on the stack
- * @return whether or not an item was removed from the stack
- */
- closeSubMenuOf(lastItem) {
- let removed = false;
- if (this._elements.indexOf(lastItem) >= 0) {
- removed = this.peek() !== lastItem;
- while (this.peek() !== lastItem) {
- this._close.next({ item: this._elements.pop() });
- }
- }
- return removed;
- }
- /**
- * Pop off all MenuStackItems and emit each one on the `close` observable one by one.
- * @param options Options that configure behavior on close.
- */
- closeAll(options) {
- const { focusNextOnEmpty, focusParentTrigger } = { ...options };
- if (!this.isEmpty()) {
- while (!this.isEmpty()) {
- const menuStackItem = this._elements.pop();
- if (menuStackItem) {
- this._close.next({ item: menuStackItem, focusParentTrigger });
- }
- }
- this._empty.next(focusNextOnEmpty);
- }
- }
- /** Return true if this stack is empty. */
- isEmpty() {
- return !this._elements.length;
- }
- /** Return the length of the stack. */
- length() {
- return this._elements.length;
- }
- /** Get the top most element on the stack. */
- peek() {
- return this._elements[this._elements.length - 1];
- }
- /** Whether the menu stack is associated with an inline menu. */
- hasInlineMenu() {
- return this._inlineMenuOrientation != null;
- }
- /** The orientation of the associated inline menu. */
- inlineMenuOrientation() {
- return this._inlineMenuOrientation;
- }
- /** Sets whether the menu stack contains the focused element. */
- setHasFocus(hasFocus) {
- this._hasFocus.next(hasFocus);
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MenuStack, decorators: [{
- type: Injectable
- }] });
- /** Injection token used for an implementation of MenuStack. */
- const MENU_TRIGGER = new InjectionToken('cdk-menu-trigger');
- /** Injection token used to configure the behavior of the menu when the page is scrolled. */
- const MENU_SCROLL_STRATEGY = new InjectionToken('cdk-menu-scroll-strategy', {
- providedIn: 'root',
- factory: () => {
- const overlay = inject(Overlay);
- return () => overlay.scrollStrategies.reposition();
- },
- });
- /**
- * Abstract directive that implements shared logic common to all menu triggers.
- * This class can be extended to create custom menu trigger types.
- */
- class CdkMenuTriggerBase {
- /** The DI injector for this component. */
- injector = inject(Injector);
- /** The view container ref for this component */
- viewContainerRef = inject(ViewContainerRef);
- /** The menu stack in which this menu resides. */
- menuStack = inject(MENU_STACK);
- /** Function used to configure the scroll strategy for the menu. */
- menuScrollStrategy = inject(MENU_SCROLL_STRATEGY);
- /**
- * A list of preferred menu positions to be used when constructing the
- * `FlexibleConnectedPositionStrategy` for this trigger's menu.
- */
- menuPosition;
- /** Emits when the attached menu is requested to open */
- opened = new EventEmitter();
- /** Emits when the attached menu is requested to close */
- closed = new EventEmitter();
- /** Template reference variable to the menu this trigger opens */
- menuTemplateRef;
- /** Context data to be passed along to the menu template */
- menuData;
- /** A reference to the overlay which manages the triggered menu */
- overlayRef = null;
- /** Emits when this trigger is destroyed. */
- destroyed = new Subject();
- /** Emits when the outside pointer events listener on the overlay should be stopped. */
- stopOutsideClicksListener = merge(this.closed, this.destroyed);
- /** The child menu opened by this trigger. */
- childMenu;
- /** The content of the menu panel opened by this trigger. */
- _menuPortal;
- /** The injector to use for the child menu opened by this trigger. */
- _childMenuInjector;
- ngOnDestroy() {
- this._destroyOverlay();
- this.destroyed.next();
- this.destroyed.complete();
- }
- /** Whether the attached menu is open. */
- isOpen() {
- return !!this.overlayRef?.hasAttached();
- }
- /** Registers a child menu as having been opened by this trigger. */
- registerChildMenu(child) {
- this.childMenu = child;
- }
- /**
- * Get the portal to be attached to the overlay which contains the menu. Allows for the menu
- * content to change dynamically and be reflected in the application.
- */
- getMenuContentPortal() {
- const hasMenuContentChanged = this.menuTemplateRef !== this._menuPortal?.templateRef;
- if (this.menuTemplateRef && (!this._menuPortal || hasMenuContentChanged)) {
- this._menuPortal = new TemplatePortal(this.menuTemplateRef, this.viewContainerRef, this.menuData, this._getChildMenuInjector());
- }
- return this._menuPortal;
- }
- /**
- * Whether the given element is inside the scope of this trigger's menu stack.
- * @param element The element to check.
- * @return Whether the element is inside the scope of this trigger's menu stack.
- */
- isElementInsideMenuStack(element) {
- for (let el = element; el; el = el?.parentElement ?? null) {
- if (el.getAttribute('data-cdk-menu-stack-id') === this.menuStack.id) {
- return true;
- }
- }
- return false;
- }
- /** Destroy and unset the overlay reference it if exists */
- _destroyOverlay() {
- if (this.overlayRef) {
- this.overlayRef.dispose();
- this.overlayRef = null;
- }
- }
- /** Gets the injector to use when creating a child menu. */
- _getChildMenuInjector() {
- this._childMenuInjector =
- this._childMenuInjector ||
- Injector.create({
- providers: [
- { provide: MENU_TRIGGER, useValue: this },
- { provide: MENU_STACK, useValue: this.menuStack },
- ],
- parent: this.injector,
- });
- return this._childMenuInjector;
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTriggerBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTriggerBase, decorators: [{
- type: Directive,
- args: [{
- host: {
- '[attr.aria-controls]': 'childMenu?.id',
- '[attr.data-cdk-menu-stack-id]': 'menuStack.id',
- },
- }]
- }] });
- /**
- * Throws an exception when an instance of the PointerFocusTracker is not provided.
- * @docs-private
- */
- function throwMissingPointerFocusTracker() {
- throw Error('expected an instance of PointerFocusTracker to be provided');
- }
- /**
- * Throws an exception when a reference to the parent menu is not provided.
- * @docs-private
- */
- function throwMissingMenuReference() {
- throw Error('expected a reference to the parent menu');
- }
- /** Injection token used for an implementation of MenuAim. */
- const MENU_AIM = new InjectionToken('cdk-menu-aim');
- /** Capture every nth mouse move event. */
- const MOUSE_MOVE_SAMPLE_FREQUENCY = 3;
- /** The number of mouse move events to track. */
- const NUM_POINTS = 5;
- /**
- * How long to wait before closing a sibling menu if a user stops short of the submenu they were
- * predicted to go into.
- */
- const CLOSE_DELAY = 300;
- /** Calculate the slope between point a and b. */
- function getSlope(a, b) {
- return (b.y - a.y) / (b.x - a.x);
- }
- /** Calculate the y intercept for the given point and slope. */
- function getYIntercept(point, slope) {
- return point.y - slope * point.x;
- }
- /**
- * Whether the given mouse trajectory line defined by the slope and y intercept falls within the
- * submenu as defined by `submenuPoints`
- * @param submenuPoints the submenu DOMRect points.
- * @param m the slope of the trajectory line.
- * @param b the y intercept of the trajectory line.
- * @return true if any point on the line falls within the submenu.
- */
- function isWithinSubmenu(submenuPoints, m, b) {
- const { left, right, top, bottom } = submenuPoints;
- // Check for intersection with each edge of the submenu (left, right, top, bottom)
- // by fixing one coordinate to that edge's coordinate (either x or y) and checking if the
- // other coordinate is within bounds.
- return ((m * left + b >= top && m * left + b <= bottom) ||
- (m * right + b >= top && m * right + b <= bottom) ||
- ((top - b) / m >= left && (top - b) / m <= right) ||
- ((bottom - b) / m >= left && (bottom - b) / m <= right));
- }
- /**
- * TargetMenuAim predicts if a user is moving into a submenu. It calculates the
- * trajectory of the user's mouse movement in the current menu to determine if the
- * mouse is moving towards an open submenu.
- *
- * The determination is made by calculating the slope of the users last NUM_POINTS moves where each
- * pair of points determines if the trajectory line points into the submenu. It uses consensus
- * approach by checking if at least NUM_POINTS / 2 pairs determine that the user is moving towards
- * to submenu.
- */
- class TargetMenuAim {
- _ngZone = inject(NgZone);
- _renderer = inject(RendererFactory2).createRenderer(null, null);
- _cleanupMousemove;
- /** The last NUM_POINTS mouse move events. */
- _points = [];
- /** Reference to the root menu in which we are tracking mouse moves. */
- _menu;
- /** Reference to the root menu's mouse manager. */
- _pointerTracker;
- /** The id associated with the current timeout call waiting to resolve. */
- _timeoutId;
- /** Emits when this service is destroyed. */
- _destroyed = new Subject();
- ngOnDestroy() {
- this._cleanupMousemove?.();
- this._destroyed.next();
- this._destroyed.complete();
- }
- /**
- * Set the Menu and its PointerFocusTracker.
- * @param menu The menu that this menu aim service controls.
- * @param pointerTracker The `PointerFocusTracker` for the given menu.
- */
- initialize(menu, pointerTracker) {
- this._menu = menu;
- this._pointerTracker = pointerTracker;
- this._subscribeToMouseMoves();
- }
- /**
- * Calls the `doToggle` callback when it is deemed that the user is not moving towards
- * the submenu.
- * @param doToggle the function called when the user is not moving towards the submenu.
- */
- toggle(doToggle) {
- // If the menu is horizontal the sub-menus open below and there is no risk of premature
- // closing of any sub-menus therefore we automatically resolve the callback.
- if (this._menu.orientation === 'horizontal') {
- doToggle();
- }
- this._checkConfigured();
- const siblingItemIsWaiting = !!this._timeoutId;
- const hasPoints = this._points.length > 1;
- if (hasPoints && !siblingItemIsWaiting) {
- if (this._isMovingToSubmenu()) {
- this._startTimeout(doToggle);
- }
- else {
- doToggle();
- }
- }
- else if (!siblingItemIsWaiting) {
- doToggle();
- }
- }
- /**
- * Start the delayed toggle handler if one isn't running already.
- *
- * The delayed toggle handler executes the `doToggle` callback after some period of time iff the
- * users mouse is on an item in the current menu.
- *
- * @param doToggle the function called when the user is not moving towards the submenu.
- */
- _startTimeout(doToggle) {
- // If the users mouse is moving towards a submenu we don't want to immediately resolve.
- // Wait for some period of time before determining if the previous menu should close in
- // cases where the user may have moved towards the submenu but stopped on a sibling menu
- // item intentionally.
- const timeoutId = setTimeout(() => {
- // Resolve if the user is currently moused over some element in the root menu
- if (this._pointerTracker.activeElement && timeoutId === this._timeoutId) {
- doToggle();
- }
- this._timeoutId = null;
- }, CLOSE_DELAY);
- this._timeoutId = timeoutId;
- }
- /** Whether the user is heading towards the open submenu. */
- _isMovingToSubmenu() {
- const submenuPoints = this._getSubmenuBounds();
- if (!submenuPoints) {
- return false;
- }
- let numMoving = 0;
- const currPoint = this._points[this._points.length - 1];
- // start from the second last point and calculate the slope between each point and the last
- // point.
- for (let i = this._points.length - 2; i >= 0; i--) {
- const previous = this._points[i];
- const slope = getSlope(currPoint, previous);
- if (isWithinSubmenu(submenuPoints, slope, getYIntercept(currPoint, slope))) {
- numMoving++;
- }
- }
- return numMoving >= Math.floor(NUM_POINTS / 2);
- }
- /** Get the bounding DOMRect for the open submenu. */
- _getSubmenuBounds() {
- return this._pointerTracker?.previousElement?.getMenu()?.nativeElement.getBoundingClientRect();
- }
- /**
- * Check if a reference to the PointerFocusTracker and menu element is provided.
- * @throws an error if neither reference is provided.
- */
- _checkConfigured() {
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- if (!this._pointerTracker) {
- throwMissingPointerFocusTracker();
- }
- if (!this._menu) {
- throwMissingMenuReference();
- }
- }
- }
- /** Subscribe to the root menus mouse move events and update the tracked mouse points. */
- _subscribeToMouseMoves() {
- this._cleanupMousemove?.();
- this._cleanupMousemove = this._ngZone.runOutsideAngular(() => {
- let eventIndex = 0;
- return this._renderer.listen(this._menu.nativeElement, 'mousemove', (event) => {
- if (eventIndex % MOUSE_MOVE_SAMPLE_FREQUENCY === 0) {
- this._points.push({ x: event.clientX, y: event.clientY });
- if (this._points.length > NUM_POINTS) {
- this._points.shift();
- }
- }
- eventIndex++;
- });
- });
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TargetMenuAim, decorators: [{
- type: Injectable
- }] });
- /**
- * CdkTargetMenuAim is a provider for the TargetMenuAim service. It can be added to an
- * element with either the `cdkMenu` or `cdkMenuBar` directive and child menu items.
- */
- class CdkTargetMenuAim {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTargetMenuAim, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkTargetMenuAim]',
- exportAs: 'cdkTargetMenuAim',
- providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }],
- }]
- }] });
- /** Checks whether a keyboard event will trigger a native `click` event on an element. */
- function eventDispatchesNativeClick(elementRef, event) {
- // Synthetic events won't trigger clicks.
- if (!event.isTrusted) {
- return false;
- }
- const el = elementRef.nativeElement;
- const keyCode = event.keyCode;
- // Buttons trigger clicks both on space and enter events.
- if (el.nodeName === 'BUTTON' && !el.disabled) {
- return keyCode === ENTER || keyCode === SPACE;
- }
- // Links only trigger clicks on enter.
- if (el.nodeName === 'A') {
- return keyCode === ENTER;
- }
- // Any other elements won't dispatch clicks from keyboard events.
- return false;
- }
- /**
- * A directive that turns its host element into a trigger for a popup menu.
- * It can be combined with cdkMenuItem to create sub-menus. If the element is in a top level
- * MenuBar it will open the menu on click, or if a sibling is already opened it will open on hover.
- * If it is inside of a Menu it will open the attached Submenu on hover regardless of its sibling
- * state.
- */
- class CdkMenuTrigger extends CdkMenuTriggerBase {
- _elementRef = inject(ElementRef);
- _overlay = inject(Overlay);
- _ngZone = inject(NgZone);
- _changeDetectorRef = inject(ChangeDetectorRef);
- _inputModalityDetector = inject(InputModalityDetector);
- _directionality = inject(Directionality, { optional: true });
- _renderer = inject(Renderer2);
- _cleanupMouseenter;
- /** The parent menu this trigger belongs to. */
- _parentMenu = inject(CDK_MENU, { optional: true });
- /** The menu aim service used by this menu. */
- _menuAim = inject(MENU_AIM, { optional: true });
- constructor() {
- super();
- this._setRole();
- this._registerCloseHandler();
- this._subscribeToMenuStackClosed();
- this._subscribeToMouseEnter();
- this._subscribeToMenuStackHasFocus();
- this._setType();
- }
- /** Toggle the attached menu. */
- toggle() {
- this.isOpen() ? this.close() : this.open();
- }
- /** Open the attached menu. */
- open() {
- if (!this.isOpen() && this.menuTemplateRef != null) {
- this.opened.next();
- this.overlayRef = this.overlayRef || this._overlay.create(this._getOverlayConfig());
- this.overlayRef.attach(this.getMenuContentPortal());
- this._changeDetectorRef.markForCheck();
- this._subscribeToOutsideClicks();
- }
- }
- /** Close the opened menu. */
- close() {
- if (this.isOpen()) {
- this.closed.next();
- this.overlayRef.detach();
- this._changeDetectorRef.markForCheck();
- }
- this._closeSiblingTriggers();
- }
- /**
- * Get a reference to the rendered Menu if the Menu is open and rendered in the DOM.
- */
- getMenu() {
- return this.childMenu;
- }
- ngOnChanges(changes) {
- if (changes['menuPosition'] && this.overlayRef) {
- this.overlayRef.updatePositionStrategy(this._getOverlayPositionStrategy());
- }
- }
- ngOnDestroy() {
- this._cleanupMouseenter();
- super.ngOnDestroy();
- }
- /**
- * Handles keyboard events for the menu item.
- * @param event The keyboard event to handle
- */
- _toggleOnKeydown(event) {
- const isParentVertical = this._parentMenu?.orientation === 'vertical';
- switch (event.keyCode) {
- case SPACE:
- case ENTER:
- // Skip events that will trigger clicks so the handler doesn't get triggered twice.
- if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {
- this.toggle();
- this.childMenu?.focusFirstItem('keyboard');
- }
- break;
- case RIGHT_ARROW:
- if (!hasModifierKey(event)) {
- if (this._parentMenu && isParentVertical && this._directionality?.value !== 'rtl') {
- event.preventDefault();
- this.open();
- this.childMenu?.focusFirstItem('keyboard');
- }
- }
- break;
- case LEFT_ARROW:
- if (!hasModifierKey(event)) {
- if (this._parentMenu && isParentVertical && this._directionality?.value === 'rtl') {
- event.preventDefault();
- this.open();
- this.childMenu?.focusFirstItem('keyboard');
- }
- }
- break;
- case DOWN_ARROW:
- case UP_ARROW:
- if (!hasModifierKey(event)) {
- if (!isParentVertical) {
- event.preventDefault();
- this.open();
- event.keyCode === DOWN_ARROW
- ? this.childMenu?.focusFirstItem('keyboard')
- : this.childMenu?.focusLastItem('keyboard');
- }
- }
- break;
- }
- }
- /** Handles clicks on the menu trigger. */
- _handleClick() {
- this.toggle();
- this.childMenu?.focusFirstItem('mouse');
- }
- /**
- * Sets whether the trigger's menu stack has focus.
- * @param hasFocus Whether the menu stack has focus.
- */
- _setHasFocus(hasFocus) {
- if (!this._parentMenu) {
- this.menuStack.setHasFocus(hasFocus);
- }
- }
- /**
- * Subscribe to the mouseenter events and close any sibling menu items if this element is moused
- * into.
- */
- _subscribeToMouseEnter() {
- this._cleanupMouseenter = this._ngZone.runOutsideAngular(() => {
- return this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {
- if (
- // Skip fake `mouseenter` events dispatched by touch devices.
- this._inputModalityDetector.mostRecentModality !== 'touch' &&
- !this.menuStack.isEmpty() &&
- !this.isOpen()) {
- // Closes any sibling menu items and opens the menu associated with this trigger.
- const toggleMenus = () => this._ngZone.run(() => {
- this._closeSiblingTriggers();
- this.open();
- });
- if (this._menuAim) {
- this._menuAim.toggle(toggleMenus);
- }
- else {
- toggleMenus();
- }
- }
- });
- });
- }
- /** Close out any sibling menu trigger menus. */
- _closeSiblingTriggers() {
- if (this._parentMenu) {
- // If nothing was removed from the stack and the last element is not the parent item
- // that means that the parent menu is a menu bar since we don't put the menu bar on the
- // stack
- const isParentMenuBar = !this.menuStack.closeSubMenuOf(this._parentMenu) &&
- this.menuStack.peek() !== this._parentMenu;
- if (isParentMenuBar) {
- this.menuStack.closeAll();
- }
- }
- else {
- this.menuStack.closeAll();
- }
- }
- /** Get the configuration object used to create the overlay. */
- _getOverlayConfig() {
- return new OverlayConfig({
- positionStrategy: this._getOverlayPositionStrategy(),
- scrollStrategy: this.menuScrollStrategy(),
- direction: this._directionality || undefined,
- });
- }
- /** Build the position strategy for the overlay which specifies where to place the menu. */
- _getOverlayPositionStrategy() {
- return this._overlay
- .position()
- .flexibleConnectedTo(this._elementRef)
- .withLockedPosition()
- .withFlexibleDimensions(false)
- .withPositions(this._getOverlayPositions());
- }
- /** Get the preferred positions for the opened menu relative to the menu item. */
- _getOverlayPositions() {
- return (this.menuPosition ??
- (!this._parentMenu || this._parentMenu.orientation === 'horizontal'
- ? STANDARD_DROPDOWN_BELOW_POSITIONS
- : STANDARD_DROPDOWN_ADJACENT_POSITIONS));
- }
- /**
- * Subscribe to the MenuStack close events if this is a standalone trigger and close out the menu
- * this triggers when requested.
- */
- _registerCloseHandler() {
- if (!this._parentMenu) {
- this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item }) => {
- if (item === this.childMenu) {
- this.close();
- }
- });
- }
- }
- /**
- * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
- * click occurs outside the menus.
- */
- _subscribeToOutsideClicks() {
- if (this.overlayRef) {
- this.overlayRef
- .outsidePointerEvents()
- .pipe(takeUntil(this.stopOutsideClicksListener))
- .subscribe(event => {
- const target = _getEventTarget(event);
- const element = this._elementRef.nativeElement;
- if (target !== element && !element.contains(target)) {
- if (!this.isElementInsideMenuStack(target)) {
- this.menuStack.closeAll();
- }
- else {
- this._closeSiblingTriggers();
- }
- }
- });
- }
- }
- /** Subscribe to the MenuStack hasFocus events. */
- _subscribeToMenuStackHasFocus() {
- if (!this._parentMenu) {
- this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {
- if (!hasFocus) {
- this.menuStack.closeAll();
- }
- });
- }
- }
- /** Subscribe to the MenuStack closed events. */
- _subscribeToMenuStackClosed() {
- if (!this._parentMenu) {
- this.menuStack.closed.subscribe(({ focusParentTrigger }) => {
- if (focusParentTrigger && !this.menuStack.length()) {
- this._elementRef.nativeElement.focus();
- }
- });
- }
- }
- /** Sets the role attribute for this trigger if needed. */
- _setRole() {
- // If this trigger is part of another menu, the cdkMenuItem directive will handle setting the
- // role, otherwise this is a standalone trigger, and we should ensure it has role="button".
- if (!this._parentMenu) {
- this._elementRef.nativeElement.setAttribute('role', 'button');
- }
- }
- /** Sets thte `type` attribute of the trigger. */
- _setType() {
- const element = this._elementRef.nativeElement;
- if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {
- // Prevents form submissions.
- element.setAttribute('type', 'button');
- }
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: MENU_TRIGGER, useExisting: CdkMenuTrigger },
- PARENT_OR_NEW_MENU_STACK_PROVIDER,
- ], exportAs: ["cdkMenuTriggerFor"], usesInheritance: true, usesOnChanges: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuTrigger, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuTriggerFor]',
- exportAs: 'cdkMenuTriggerFor',
- host: {
- 'class': 'cdk-menu-trigger',
- '[attr.aria-haspopup]': 'menuTemplateRef ? "menu" : null',
- '[attr.aria-expanded]': 'menuTemplateRef == null ? null : isOpen()',
- '(focusin)': '_setHasFocus(true)',
- '(focusout)': '_setHasFocus(false)',
- '(keydown)': '_toggleOnKeydown($event)',
- '(click)': '_handleClick()',
- },
- inputs: [
- { name: 'menuTemplateRef', alias: 'cdkMenuTriggerFor' },
- { name: 'menuPosition', alias: 'cdkMenuPosition' },
- { name: 'menuData', alias: 'cdkMenuTriggerData' },
- ],
- outputs: ['opened: cdkMenuOpened', 'closed: cdkMenuClosed'],
- providers: [
- { provide: MENU_TRIGGER, useExisting: CdkMenuTrigger },
- PARENT_OR_NEW_MENU_STACK_PROVIDER,
- ],
- }]
- }], ctorParameters: () => [] });
- /**
- * Directive which provides the ability for an element to be focused and navigated to using the
- * keyboard when residing in a CdkMenu, CdkMenuBar, or CdkMenuGroup. It performs user defined
- * behavior when clicked.
- */
- class CdkMenuItem {
- _dir = inject(Directionality, { optional: true });
- _elementRef = inject(ElementRef);
- _ngZone = inject(NgZone);
- _inputModalityDetector = inject(InputModalityDetector);
- _renderer = inject(Renderer2);
- _cleanupMouseEnter;
- /** The menu aim service used by this menu. */
- _menuAim = inject(MENU_AIM, { optional: true });
- /** The stack of menus this menu belongs to. */
- _menuStack = inject(MENU_STACK);
- /** The parent menu in which this menuitem resides. */
- _parentMenu = inject(CDK_MENU, { optional: true });
- /** Reference to the CdkMenuItemTrigger directive if one is added to the same element */
- _menuTrigger = inject(CdkMenuTrigger, { optional: true, self: true });
- /** Whether the CdkMenuItem is disabled - defaults to false */
- disabled = false;
- /**
- * The text used to locate this item during menu typeahead. If not specified,
- * the `textContent` of the item will be used.
- */
- typeaheadLabel;
- /**
- * If this MenuItem is a regular MenuItem, outputs when it is triggered by a keyboard or mouse
- * event.
- */
- triggered = new EventEmitter();
- /** Whether the menu item opens a menu. */
- get hasMenu() {
- return this._menuTrigger?.menuTemplateRef != null;
- }
- /**
- * The tabindex for this menu item managed internally and used for implementing roving a
- * tab index.
- */
- _tabindex = -1;
- /** Whether the item should close the menu if triggered by the spacebar. */
- closeOnSpacebarTrigger = true;
- /** Emits when the menu item is destroyed. */
- destroyed = new Subject();
- constructor() {
- this._setupMouseEnter();
- this._setType();
- if (this._isStandaloneItem()) {
- this._tabindex = 0;
- }
- }
- ngOnDestroy() {
- this._cleanupMouseEnter?.();
- this.destroyed.next();
- this.destroyed.complete();
- }
- /** Place focus on the element. */
- focus() {
- this._elementRef.nativeElement.focus();
- }
- /**
- * If the menu item is not disabled and the element does not have a menu trigger attached, emit
- * on the cdkMenuItemTriggered emitter and close all open menus.
- * @param options Options the configure how the item is triggered
- * - keepOpen: specifies that the menu should be kept open after triggering the item.
- */
- trigger(options) {
- const { keepOpen } = { ...options };
- if (!this.disabled && !this.hasMenu) {
- this.triggered.next();
- if (!keepOpen) {
- this._menuStack.closeAll({ focusParentTrigger: true });
- }
- }
- }
- /** Return true if this MenuItem has an attached menu and it is open. */
- isMenuOpen() {
- return !!this._menuTrigger?.isOpen();
- }
- /**
- * Get a reference to the rendered Menu if the Menu is open and it is visible in the DOM.
- * @return the menu if it is open, otherwise undefined.
- */
- getMenu() {
- return this._menuTrigger?.getMenu();
- }
- /** Get the CdkMenuTrigger associated with this element. */
- getMenuTrigger() {
- return this._menuTrigger;
- }
- /** Get the label for this element which is required by the FocusableOption interface. */
- getLabel() {
- return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';
- }
- /** Reset the tabindex to -1. */
- _resetTabIndex() {
- if (!this._isStandaloneItem()) {
- this._tabindex = -1;
- }
- }
- /**
- * Set the tab index to 0 if not disabled and it's a focus event, or a mouse enter if this element
- * is not in a menu bar.
- */
- _setTabIndex(event) {
- if (this.disabled) {
- return;
- }
- // don't set the tabindex if there are no open sibling or parent menus
- if (!event || !this._menuStack.isEmpty()) {
- this._tabindex = 0;
- }
- }
- /**
- * Handles keyboard events for the menu item, specifically either triggering the user defined
- * callback or opening/closing the current menu based on whether the left or right arrow key was
- * pressed.
- * @param event the keyboard event to handle
- */
- _onKeydown(event) {
- switch (event.keyCode) {
- case SPACE:
- case ENTER:
- // Skip events that will trigger clicks so the handler doesn't get triggered twice.
- if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {
- const nodeName = this._elementRef.nativeElement.nodeName;
- // Avoid repeat events on non-native elements (see #30250). Note that we don't do this
- // on the native elements so we don't interfere with their behavior (see #26296).
- if (nodeName !== 'A' && nodeName !== 'BUTTON') {
- event.preventDefault();
- }
- this.trigger({ keepOpen: event.keyCode === SPACE && !this.closeOnSpacebarTrigger });
- }
- break;
- case RIGHT_ARROW:
- if (!hasModifierKey(event)) {
- if (this._parentMenu && this._isParentVertical()) {
- if (this._dir?.value !== 'rtl') {
- this._forwardArrowPressed(event);
- }
- else {
- this._backArrowPressed(event);
- }
- }
- }
- break;
- case LEFT_ARROW:
- if (!hasModifierKey(event)) {
- if (this._parentMenu && this._isParentVertical()) {
- if (this._dir?.value !== 'rtl') {
- this._backArrowPressed(event);
- }
- else {
- this._forwardArrowPressed(event);
- }
- }
- }
- break;
- }
- }
- /** Whether this menu item is standalone or within a menu or menu bar. */
- _isStandaloneItem() {
- return !this._parentMenu;
- }
- /**
- * Handles the user pressing the back arrow key.
- * @param event The keyboard event.
- */
- _backArrowPressed(event) {
- const parentMenu = this._parentMenu;
- if (this._menuStack.hasInlineMenu() || this._menuStack.length() > 1) {
- event.preventDefault();
- this._menuStack.close(parentMenu, {
- focusNextOnEmpty: this._menuStack.inlineMenuOrientation() === 'horizontal'
- ? FocusNext.previousItem
- : FocusNext.currentItem,
- focusParentTrigger: true,
- });
- }
- }
- /**
- * Handles the user pressing the forward arrow key.
- * @param event The keyboard event.
- */
- _forwardArrowPressed(event) {
- if (!this.hasMenu && this._menuStack.inlineMenuOrientation() === 'horizontal') {
- event.preventDefault();
- this._menuStack.closeAll({
- focusNextOnEmpty: FocusNext.nextItem,
- focusParentTrigger: true,
- });
- }
- }
- /**
- * Subscribe to the mouseenter events and close any sibling menu items if this element is moused
- * into.
- */
- _setupMouseEnter() {
- if (!this._isStandaloneItem()) {
- const closeOpenSiblings = () => this._ngZone.run(() => this._menuStack.closeSubMenuOf(this._parentMenu));
- this._cleanupMouseEnter = this._ngZone.runOutsideAngular(() => this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {
- // Skip fake `mouseenter` events dispatched by touch devices.
- if (this._inputModalityDetector.mostRecentModality !== 'touch' &&
- !this._menuStack.isEmpty() &&
- !this.hasMenu) {
- if (this._menuAim) {
- this._menuAim.toggle(closeOpenSiblings);
- }
- else {
- closeOpenSiblings();
- }
- }
- }));
- }
- }
- /**
- * Return true if the enclosing parent menu is configured in a horizontal orientation, false
- * otherwise or if no parent.
- */
- _isParentVertical() {
- return this._parentMenu?.orientation === 'vertical';
- }
- /** Sets the `type` attribute of the menu item. */
- _setType() {
- const element = this._elementRef.nativeElement;
- if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {
- // Prevent form submissions.
- element.setAttribute('type', 'button');
- }
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItem, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuItem]',
- exportAs: 'cdkMenuItem',
- host: {
- 'role': 'menuitem',
- 'class': 'cdk-menu-item',
- '[tabindex]': '_tabindex',
- '[attr.aria-disabled]': 'disabled || null',
- '(blur)': '_resetTabIndex()',
- '(focus)': '_setTabIndex()',
- '(click)': 'trigger()',
- '(keydown)': '_onKeydown($event)',
- },
- }]
- }], ctorParameters: () => [], propDecorators: { disabled: [{
- type: Input,
- args: [{ alias: 'cdkMenuItemDisabled', transform: booleanAttribute }]
- }], typeaheadLabel: [{
- type: Input,
- args: ['cdkMenuitemTypeaheadLabel']
- }], triggered: [{
- type: Output,
- args: ['cdkMenuItemTriggered']
- }] } });
- /**
- * PointerFocusTracker keeps track of the currently active item under mouse focus. It also has
- * observables which emit when the users mouse enters and leaves a tracked element.
- */
- class PointerFocusTracker {
- _renderer;
- _items;
- _eventCleanups;
- _itemsSubscription;
- /** Emits when an element is moused into. */
- entered = new Subject();
- /** Emits when an element is moused out. */
- exited = new Subject();
- /** The element currently under mouse focus. */
- activeElement;
- /** The element previously under mouse focus. */
- previousElement;
- constructor(_renderer, _items) {
- this._renderer = _renderer;
- this._items = _items;
- this._bindEvents();
- this.entered.subscribe(element => (this.activeElement = element));
- this.exited.subscribe(() => {
- this.previousElement = this.activeElement;
- this.activeElement = undefined;
- });
- }
- /** Stop the managers listeners. */
- destroy() {
- this._cleanupEvents();
- this._itemsSubscription?.unsubscribe();
- }
- /** Binds the enter/exit events on all the items. */
- _bindEvents() {
- // TODO(crisbeto): this can probably be simplified by binding a single event on a parent node.
- this._itemsSubscription = this._items.changes.pipe(startWith(this._items)).subscribe(() => {
- this._cleanupEvents();
- this._eventCleanups = [];
- this._items.forEach(item => {
- const element = item._elementRef.nativeElement;
- this._eventCleanups.push(this._renderer.listen(element, 'mouseenter', () => {
- this.entered.next(item);
- }), this._renderer.listen(element, 'mouseout', () => {
- this.exited.next(item);
- }));
- });
- });
- }
- /** Cleans up the currently-bound events. */
- _cleanupEvents() {
- this._eventCleanups?.forEach(cleanup => cleanup());
- this._eventCleanups = undefined;
- }
- }
- /**
- * Abstract directive that implements shared logic common to all menus.
- * This class can be extended to create custom menu types.
- */
- class CdkMenuBase extends CdkMenuGroup {
- _focusMonitor = inject(FocusMonitor);
- ngZone = inject(NgZone);
- _renderer = inject(Renderer2);
- /** The menu's native DOM host element. */
- nativeElement = inject(ElementRef).nativeElement;
- /** The stack of menus this menu belongs to. */
- menuStack = inject(MENU_STACK);
- /** The menu aim service used by this menu. */
- menuAim = inject(MENU_AIM, { optional: true, self: true });
- /** The directionality (text direction) of the current page. */
- dir = inject(Directionality, { optional: true });
- /** The id of the menu's host element. */
- id = inject(_IdGenerator).getId('cdk-menu-');
- /** All child MenuItem elements nested in this Menu. */
- items;
- /** The direction items in the menu flow. */
- orientation = 'vertical';
- /**
- * Whether the menu is displayed inline (i.e. always present vs a conditional popup that the
- * user triggers with a trigger element).
- */
- isInline = false;
- /** Handles keyboard events for the menu. */
- keyManager;
- /** Emits when the MenuBar is destroyed. */
- destroyed = new Subject();
- /** The Menu Item which triggered the open submenu. */
- triggerItem;
- /** Tracks the users mouse movements over the menu. */
- pointerTracker;
- /** Whether this menu's menu stack has focus. */
- _menuStackHasFocus = signal(false);
- _tabIndexSignal = computed(() => {
- const tabindexIfInline = this._menuStackHasFocus() ? -1 : 0;
- return this.isInline ? tabindexIfInline : null;
- });
- ngAfterContentInit() {
- if (!this.isInline) {
- this.menuStack.push(this);
- }
- this._setKeyManager();
- this._handleFocus();
- this._subscribeToMenuStackHasFocus();
- this._subscribeToMenuOpen();
- this._subscribeToMenuStackClosed();
- this._setUpPointerTracker();
- }
- ngOnDestroy() {
- this._focusMonitor.stopMonitoring(this.nativeElement);
- this.keyManager?.destroy();
- this.destroyed.next();
- this.destroyed.complete();
- this.pointerTracker?.destroy();
- }
- /**
- * Place focus on the first MenuItem in the menu and set the focus origin.
- * @param focusOrigin The origin input mode of the focus event.
- */
- focusFirstItem(focusOrigin = 'program') {
- this.keyManager.setFocusOrigin(focusOrigin);
- this.keyManager.setFirstItemActive();
- }
- /**
- * Place focus on the last MenuItem in the menu and set the focus origin.
- * @param focusOrigin The origin input mode of the focus event.
- */
- focusLastItem(focusOrigin = 'program') {
- this.keyManager.setFocusOrigin(focusOrigin);
- this.keyManager.setLastItemActive();
- }
- /** Gets the tabindex for this menu. */
- _getTabIndex() {
- return this._tabIndexSignal();
- }
- /**
- * Close the open menu if the current active item opened the requested MenuStackItem.
- * @param menu The menu requested to be closed.
- * @param options Options to configure the behavior on close.
- * - `focusParentTrigger` Whether to focus the parent trigger after closing the menu.
- */
- closeOpenMenu(menu, options) {
- const { focusParentTrigger } = { ...options };
- const keyManager = this.keyManager;
- const trigger = this.triggerItem;
- if (menu === trigger?.getMenuTrigger()?.getMenu()) {
- trigger?.getMenuTrigger()?.close();
- // If the user has moused over a sibling item we want to focus the element under mouse focus
- // not the trigger which previously opened the now closed menu.
- if (focusParentTrigger) {
- if (trigger) {
- keyManager.setActiveItem(trigger);
- }
- else {
- keyManager.setFirstItemActive();
- }
- }
- }
- }
- /** Setup the FocusKeyManager with the correct orientation for the menu. */
- _setKeyManager() {
- this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd();
- if (this.orientation === 'horizontal') {
- this.keyManager.withHorizontalOrientation(this.dir?.value || 'ltr');
- }
- else {
- this.keyManager.withVerticalOrientation();
- }
- }
- /**
- * Subscribe to the menu trigger's open events in order to track the trigger which opened the menu
- * and stop tracking it when the menu is closed.
- */
- _subscribeToMenuOpen() {
- const exitCondition = merge(this.items.changes, this.destroyed);
- this.items.changes
- .pipe(startWith(this.items), mergeMap((list) => list
- .filter(item => item.hasMenu)
- .map(item => item.getMenuTrigger().opened.pipe(mapTo(item), takeUntil(exitCondition)))), mergeAll(), switchMap((item) => {
- this.triggerItem = item;
- return item.getMenuTrigger().closed;
- }), takeUntil(this.destroyed))
- .subscribe(() => (this.triggerItem = undefined));
- }
- /** Subscribe to the MenuStack close events. */
- _subscribeToMenuStackClosed() {
- this.menuStack.closed
- .pipe(takeUntil(this.destroyed))
- .subscribe(({ item, focusParentTrigger }) => this.closeOpenMenu(item, { focusParentTrigger }));
- }
- /** Subscribe to the MenuStack hasFocus events. */
- _subscribeToMenuStackHasFocus() {
- if (this.isInline) {
- this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {
- this._menuStackHasFocus.set(hasFocus);
- });
- }
- }
- /**
- * Set the PointerFocusTracker and ensure that when mouse focus changes the key manager is updated
- * with the latest menu item under mouse focus.
- */
- _setUpPointerTracker() {
- if (this.menuAim) {
- this.ngZone.runOutsideAngular(() => {
- this.pointerTracker = new PointerFocusTracker(this._renderer, this.items);
- });
- this.menuAim.initialize(this, this.pointerTracker);
- }
- }
- /** Handles focus landing on the host element of the menu. */
- _handleFocus() {
- this._focusMonitor
- .monitor(this.nativeElement, false)
- .pipe(takeUntil(this.destroyed))
- .subscribe(origin => {
- // Don't forward focus on mouse interactions, because it can
- // mess with the user's scroll position. See #30130.
- if (origin !== null && origin !== 'mouse') {
- this.focusFirstItem(origin);
- }
- });
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBase, deps: null, target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBase, decorators: [{
- type: Directive,
- args: [{
- host: {
- 'role': 'menu',
- 'class': '', // reset the css class added by the super-class
- '[tabindex]': '_getTabIndex()',
- '[id]': 'id',
- '[attr.aria-orientation]': 'orientation',
- '[attr.data-cdk-menu-stack-id]': 'menuStack.id',
- '(focusin)': 'menuStack.setHasFocus(true)',
- '(focusout)': 'menuStack.setHasFocus(false)',
- },
- }]
- }], propDecorators: { id: [{
- type: Input
- }], items: [{
- type: ContentChildren,
- args: [CdkMenuItem, { descendants: true }]
- }] } });
- /**
- * Directive which configures the element as a Menu which should contain child elements marked as
- * CdkMenuItem or CdkMenuGroup. Sets the appropriate role and aria-attributes for a menu and
- * contains accessible keyboard and mouse handling logic.
- *
- * It also acts as a RadioGroup for elements marked with role `menuitemradio`.
- */
- class CdkMenu extends CdkMenuBase {
- _parentTrigger = inject(MENU_TRIGGER, { optional: true });
- /** Event emitted when the menu is closed. */
- closed = new EventEmitter();
- /** The direction items in the menu flow. */
- orientation = 'vertical';
- /** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
- isInline = !this._parentTrigger;
- constructor() {
- super();
- this.destroyed.subscribe(this.closed);
- this._parentTrigger?.registerChildMenu(this);
- }
- ngAfterContentInit() {
- super.ngAfterContentInit();
- this._subscribeToMenuStackEmptied();
- }
- ngOnDestroy() {
- super.ngOnDestroy();
- this.closed.complete();
- }
- /**
- * Handle keyboard events for the Menu.
- * @param event The keyboard event to be handled.
- */
- _handleKeyEvent(event) {
- const keyManager = this.keyManager;
- switch (event.keyCode) {
- case LEFT_ARROW:
- case RIGHT_ARROW:
- if (!hasModifierKey(event)) {
- event.preventDefault();
- keyManager.setFocusOrigin('keyboard');
- keyManager.onKeydown(event);
- }
- break;
- case ESCAPE:
- if (!hasModifierKey(event)) {
- event.preventDefault();
- this.menuStack.close(this, {
- focusNextOnEmpty: FocusNext.currentItem,
- focusParentTrigger: true,
- });
- }
- break;
- case TAB:
- if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
- this.menuStack.closeAll({ focusParentTrigger: true });
- }
- break;
- default:
- keyManager.onKeydown(event);
- }
- }
- /**
- * Set focus the either the current, previous or next item based on the FocusNext event.
- * @param focusNext The element to focus.
- */
- _toggleMenuFocus(focusNext) {
- const keyManager = this.keyManager;
- switch (focusNext) {
- case FocusNext.nextItem:
- keyManager.setFocusOrigin('keyboard');
- keyManager.setNextItemActive();
- break;
- case FocusNext.previousItem:
- keyManager.setFocusOrigin('keyboard');
- keyManager.setPreviousItemActive();
- break;
- case FocusNext.currentItem:
- if (keyManager.activeItem) {
- keyManager.setFocusOrigin('keyboard');
- keyManager.setActiveItem(keyManager.activeItem);
- }
- break;
- }
- }
- /** Subscribe to the MenuStack emptied events. */
- _subscribeToMenuStackEmptied() {
- this.menuStack.emptied
- .pipe(takeUntil(this.destroyed))
- .subscribe(event => this._toggleMenuFocus(event));
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: CdkMenuGroup, useExisting: CdkMenu },
- { provide: CDK_MENU, useExisting: CdkMenu },
- PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER('vertical'),
- ], exportAs: ["cdkMenu"], usesInheritance: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenu, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenu]',
- exportAs: 'cdkMenu',
- host: {
- 'role': 'menu',
- 'class': 'cdk-menu',
- '[class.cdk-menu-inline]': 'isInline',
- '(keydown)': '_handleKeyEvent($event)',
- },
- providers: [
- { provide: CdkMenuGroup, useExisting: CdkMenu },
- { provide: CDK_MENU, useExisting: CdkMenu },
- PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER('vertical'),
- ],
- }]
- }], ctorParameters: () => [], propDecorators: { closed: [{
- type: Output
- }] } });
- /**
- * Directive applied to an element which configures it as a MenuBar by setting the appropriate
- * role, aria attributes, and accessible keyboard and mouse handling logic. The component that
- * this directive is applied to should contain components marked with CdkMenuItem.
- *
- */
- class CdkMenuBar extends CdkMenuBase {
- /** The direction items in the menu flow. */
- orientation = 'horizontal';
- /** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
- isInline = true;
- ngAfterContentInit() {
- super.ngAfterContentInit();
- this._subscribeToMenuStackEmptied();
- }
- /**
- * Handle keyboard events for the Menu.
- * @param event The keyboard event to be handled.
- */
- _handleKeyEvent(event) {
- const keyManager = this.keyManager;
- switch (event.keyCode) {
- case UP_ARROW:
- case DOWN_ARROW:
- case LEFT_ARROW:
- case RIGHT_ARROW:
- if (!hasModifierKey(event)) {
- const horizontalArrows = event.keyCode === LEFT_ARROW || event.keyCode === RIGHT_ARROW;
- // For a horizontal menu if the left/right keys were clicked, or a vertical menu if the
- // up/down keys were clicked: if the current menu is open, close it then focus and open the
- // next menu.
- if (horizontalArrows) {
- event.preventDefault();
- const prevIsOpen = keyManager.activeItem?.isMenuOpen();
- keyManager.activeItem?.getMenuTrigger()?.close();
- keyManager.setFocusOrigin('keyboard');
- keyManager.onKeydown(event);
- if (prevIsOpen) {
- keyManager.activeItem?.getMenuTrigger()?.open();
- }
- }
- }
- break;
- case ESCAPE:
- if (!hasModifierKey(event)) {
- event.preventDefault();
- keyManager.activeItem?.getMenuTrigger()?.close();
- }
- break;
- case TAB:
- if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
- keyManager.activeItem?.getMenuTrigger()?.close();
- }
- break;
- default:
- keyManager.onKeydown(event);
- }
- }
- /**
- * Set focus to either the current, previous or next item based on the FocusNext event, then
- * open the previous or next item.
- * @param focusNext The element to focus.
- */
- _toggleOpenMenu(focusNext) {
- const keyManager = this.keyManager;
- switch (focusNext) {
- case FocusNext.nextItem:
- keyManager.setFocusOrigin('keyboard');
- keyManager.setNextItemActive();
- keyManager.activeItem?.getMenuTrigger()?.open();
- break;
- case FocusNext.previousItem:
- keyManager.setFocusOrigin('keyboard');
- keyManager.setPreviousItemActive();
- keyManager.activeItem?.getMenuTrigger()?.open();
- break;
- case FocusNext.currentItem:
- if (keyManager.activeItem) {
- keyManager.setFocusOrigin('keyboard');
- keyManager.setActiveItem(keyManager.activeItem);
- }
- break;
- }
- }
- /** Subscribe to the MenuStack emptied events. */
- _subscribeToMenuStackEmptied() {
- this.menuStack?.emptied
- .pipe(takeUntil(this.destroyed))
- .subscribe(event => this._toggleOpenMenu(event));
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBar, deps: null, target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: CdkMenuGroup, useExisting: CdkMenuBar },
- { provide: CDK_MENU, useExisting: CdkMenuBar },
- { provide: MENU_STACK, useFactory: () => MenuStack.inline('horizontal') },
- ], exportAs: ["cdkMenuBar"], usesInheritance: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuBar, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuBar]',
- exportAs: 'cdkMenuBar',
- host: {
- 'role': 'menubar',
- 'class': 'cdk-menu-bar',
- '(keydown)': '_handleKeyEvent($event)',
- },
- providers: [
- { provide: CdkMenuGroup, useExisting: CdkMenuBar },
- { provide: CDK_MENU, useExisting: CdkMenuBar },
- { provide: MENU_STACK, useFactory: () => MenuStack.inline('horizontal') },
- ],
- }]
- }] });
- /** Base class providing checked state for selectable MenuItems. */
- class CdkMenuItemSelectable extends CdkMenuItem {
- /** Whether the element is checked */
- checked = false;
- /** Whether the item should close the menu if triggered by the spacebar. */
- closeOnSpacebarTrigger = false;
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemSelectable, deps: null, target: i0.ɵɵFactoryTarget.Directive });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemSelectable, decorators: [{
- type: Directive,
- args: [{
- host: {
- '[attr.aria-checked]': '!!checked',
- '[attr.aria-disabled]': 'disabled || null',
- },
- }]
- }], propDecorators: { checked: [{
- type: Input,
- args: [{ alias: 'cdkMenuItemChecked', transform: booleanAttribute }]
- }] } });
- /**
- * A directive providing behavior for the "menuitemradio" ARIA role, which behaves similarly to
- * a conventional radio-button. Any sibling `CdkMenuItemRadio` instances within the same `CdkMenu`
- * or `CdkMenuGroup` comprise a radio group with unique selection enforced.
- */
- class CdkMenuItemRadio extends CdkMenuItemSelectable {
- /** The unique selection dispatcher for this radio's `CdkMenuGroup`. */
- _selectionDispatcher = inject(UniqueSelectionDispatcher);
- /** An ID to identify this radio item to the `UniqueSelectionDispatcher`. */
- _id = inject(_IdGenerator).getId('cdk-menu-item-radio-');
- /** Function to unregister the selection dispatcher */
- _removeDispatcherListener;
- constructor() {
- super();
- this._registerDispatcherListener();
- }
- ngOnDestroy() {
- super.ngOnDestroy();
- this._removeDispatcherListener();
- }
- /**
- * Toggles the checked state of the radio-button.
- * @param options Options the configure how the item is triggered
- * - keepOpen: specifies that the menu should be kept open after triggering the item.
- */
- trigger(options) {
- super.trigger(options);
- if (!this.disabled) {
- this._selectionDispatcher.notify(this._id, '');
- }
- }
- /** Configure the unique selection dispatcher listener in order to toggle the checked state */
- _registerDispatcherListener() {
- this._removeDispatcherListener = this._selectionDispatcher.listen((id) => {
- this.checked = this._id === id;
- });
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemRadio, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio },
- { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
- ], exportAs: ["cdkMenuItemRadio"], usesInheritance: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemRadio, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuItemRadio]',
- exportAs: 'cdkMenuItemRadio',
- host: {
- 'role': 'menuitemradio',
- '[class.cdk-menu-item-radio]': 'true',
- },
- providers: [
- { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio },
- { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
- ],
- }]
- }], ctorParameters: () => [] });
- /**
- * A directive providing behavior for the "menuitemcheckbox" ARIA role, which behaves similarly to a
- * conventional checkbox.
- */
- class CdkMenuItemCheckbox extends CdkMenuItemSelectable {
- /**
- * Toggle the checked state of the checkbox.
- * @param options Options the configure how the item is triggered
- * - keepOpen: specifies that the menu should be kept open after triggering the item.
- */
- trigger(options) {
- super.trigger(options);
- if (!this.disabled) {
- this.checked = !this.checked;
- }
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemCheckbox, deps: null, target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemCheckbox },
- { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
- ], exportAs: ["cdkMenuItemCheckbox"], usesInheritance: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuItemCheckbox, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkMenuItemCheckbox]',
- exportAs: 'cdkMenuItemCheckbox',
- host: {
- 'role': 'menuitemcheckbox',
- '[class.cdk-menu-item-checkbox]': 'true',
- },
- providers: [
- { provide: CdkMenuItemSelectable, useExisting: CdkMenuItemCheckbox },
- { provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
- ],
- }]
- }] });
- /** The preferred menu positions for the context menu. */
- const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => {
- // In cases where the first menu item in the context menu is a trigger the submenu opens on a
- // hover event. We offset the context menu 2px by default to prevent this from occurring.
- const offsetX = position.overlayX === 'start' ? 2 : -2;
- const offsetY = position.overlayY === 'top' ? 2 : -2;
- return { ...position, offsetX, offsetY };
- });
- /** Tracks the last open context menu trigger across the entire application. */
- class ContextMenuTracker {
- /** The last open context menu trigger. */
- static _openContextMenuTrigger;
- /**
- * Close the previous open context menu and set the given one as being open.
- * @param trigger The trigger for the currently open Context Menu.
- */
- update(trigger) {
- if (ContextMenuTracker._openContextMenuTrigger !== trigger) {
- ContextMenuTracker._openContextMenuTrigger?.close();
- ContextMenuTracker._openContextMenuTrigger = trigger;
- }
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, providedIn: 'root' });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContextMenuTracker, decorators: [{
- type: Injectable,
- args: [{ providedIn: 'root' }]
- }] });
- /**
- * A directive that opens a menu when a user right-clicks within its host element.
- * It is aware of nested context menus and will trigger only the lowest level non-disabled context menu.
- */
- class CdkContextMenuTrigger extends CdkMenuTriggerBase {
- /** The CDK overlay service. */
- _overlay = inject(Overlay);
- /** The directionality of the page. */
- _directionality = inject(Directionality, { optional: true });
- /** The app's context menu tracking registry */
- _contextMenuTracker = inject(ContextMenuTracker);
- _changeDetectorRef = inject(ChangeDetectorRef);
- /** Whether the context menu is disabled. */
- disabled = false;
- constructor() {
- super();
- this._setMenuStackCloseListener();
- }
- /**
- * Open the attached menu at the specified location.
- * @param coordinates where to open the context menu
- */
- open(coordinates) {
- this._open(null, coordinates);
- this._changeDetectorRef.markForCheck();
- }
- /** Close the currently opened context menu. */
- close() {
- this.menuStack.closeAll();
- }
- /**
- * Open the context menu and closes any previously open menus.
- * @param event the mouse event which opens the context menu.
- */
- _openOnContextMenu(event) {
- if (!this.disabled) {
- // Prevent the native context menu from opening because we're opening a custom one.
- event.preventDefault();
- // Stop event propagation to ensure that only the closest enabled context menu opens.
- // Otherwise, any context menus attached to containing elements would *also* open,
- // resulting in multiple stacked context menus being displayed.
- event.stopPropagation();
- this._contextMenuTracker.update(this);
- this._open(event, { x: event.clientX, y: event.clientY });
- // A context menu can be triggered via a mouse right click or a keyboard shortcut.
- if (event.button === 2) {
- this.childMenu?.focusFirstItem('mouse');
- }
- else if (event.button === 0) {
- this.childMenu?.focusFirstItem('keyboard');
- }
- else {
- this.childMenu?.focusFirstItem('program');
- }
- }
- }
- /**
- * Get the configuration object used to create the overlay.
- * @param coordinates the location to place the opened menu
- */
- _getOverlayConfig(coordinates) {
- return new OverlayConfig({
- positionStrategy: this._getOverlayPositionStrategy(coordinates),
- scrollStrategy: this.menuScrollStrategy(),
- direction: this._directionality || undefined,
- });
- }
- /**
- * Get the position strategy for the overlay which specifies where to place the menu.
- * @param coordinates the location to place the opened menu
- */
- _getOverlayPositionStrategy(coordinates) {
- return this._overlay
- .position()
- .flexibleConnectedTo(coordinates)
- .withLockedPosition()
- .withGrowAfterOpen()
- .withPositions(this.menuPosition ?? CONTEXT_MENU_POSITIONS);
- }
- /** Subscribe to the menu stack close events and close this menu when requested. */
- _setMenuStackCloseListener() {
- this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item }) => {
- if (item === this.childMenu && this.isOpen()) {
- this.closed.next();
- this.overlayRef.detach();
- this.childMenu = undefined;
- this._changeDetectorRef.markForCheck();
- }
- });
- }
- /**
- * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
- * click occurs outside the menus.
- * @param userEvent User-generated event that opened the menu.
- */
- _subscribeToOutsideClicks(userEvent) {
- if (this.overlayRef) {
- let outsideClicks = this.overlayRef.outsidePointerEvents();
- if (userEvent) {
- const [auxClicks, nonAuxClicks] = partition(outsideClicks, ({ type }) => type === 'auxclick');
- outsideClicks = merge(
- // Using a mouse, the `contextmenu` event can fire either when pressing the right button
- // or left button + control. Most browsers won't dispatch a `click` event right after
- // a `contextmenu` event triggered by left button + control, but Safari will (see #27832).
- // This closes the menu immediately. To work around it, we check that both the triggering
- // event and the current outside click event both had the control key pressed, and that
- // that this is the first outside click event.
- nonAuxClicks.pipe(skipWhile((event, index) => userEvent.ctrlKey && index === 0 && event.ctrlKey)),
- // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
- // because it fires when the mouse is released on the same click that opened the menu.
- auxClicks.pipe(skip(1)));
- }
- outsideClicks.pipe(takeUntil(this.stopOutsideClicksListener)).subscribe(event => {
- if (!this.isElementInsideMenuStack(_getEventTarget(event))) {
- this.menuStack.closeAll();
- }
- });
- }
- }
- /**
- * Open the attached menu at the specified location.
- * @param userEvent User-generated event that opened the menu
- * @param coordinates where to open the context menu
- */
- _open(userEvent, coordinates) {
- if (this.disabled) {
- return;
- }
- if (this.isOpen()) {
- // since we're moving this menu we need to close any submenus first otherwise they end up
- // disconnected from this one.
- this.menuStack.closeSubMenuOf(this.childMenu);
- this.overlayRef.getConfig().positionStrategy.setOrigin(coordinates);
- this.overlayRef.updatePosition();
- }
- else {
- this.opened.next();
- if (this.overlayRef) {
- this.overlayRef.getConfig().positionStrategy.setOrigin(coordinates);
- this.overlayRef.updatePosition();
- }
- else {
- this.overlayRef = this._overlay.create(this._getOverlayConfig(coordinates));
- }
- this.overlayRef.attach(this.getMenuContentPortal());
- this._subscribeToOutsideClicks(userEvent);
- }
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkContextMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- { provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger },
- { provide: MENU_STACK, useClass: MenuStack },
- ], exportAs: ["cdkContextMenuTriggerFor"], usesInheritance: true, ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkContextMenuTrigger, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkContextMenuTriggerFor]',
- exportAs: 'cdkContextMenuTriggerFor',
- host: {
- '[attr.data-cdk-menu-stack-id]': 'null',
- '(contextmenu)': '_openOnContextMenu($event)',
- },
- inputs: [
- { name: 'menuTemplateRef', alias: 'cdkContextMenuTriggerFor' },
- { name: 'menuPosition', alias: 'cdkContextMenuPosition' },
- { name: 'menuData', alias: 'cdkContextMenuTriggerData' },
- ],
- outputs: ['opened: cdkContextMenuOpened', 'closed: cdkContextMenuClosed'],
- providers: [
- { provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger },
- { provide: MENU_STACK, useClass: MenuStack },
- ],
- }]
- }], ctorParameters: () => [], propDecorators: { disabled: [{
- type: Input,
- args: [{ alias: 'cdkContextMenuDisabled', transform: booleanAttribute }]
- }] } });
- const MENU_DIRECTIVES = [
- CdkMenuBar,
- CdkMenu,
- CdkMenuItem,
- CdkMenuItemRadio,
- CdkMenuItemCheckbox,
- CdkMenuTrigger,
- CdkMenuGroup,
- CdkContextMenuTrigger,
- CdkTargetMenuAim,
- ];
- /** Module that declares components and directives for the CDK menu. */
- class CdkMenuModule {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, imports: [OverlayModule, CdkMenuBar,
- CdkMenu,
- CdkMenuItem,
- CdkMenuItemRadio,
- CdkMenuItemCheckbox,
- CdkMenuTrigger,
- CdkMenuGroup,
- CdkContextMenuTrigger,
- CdkTargetMenuAim], exports: [CdkMenuBar,
- CdkMenu,
- CdkMenuItem,
- CdkMenuItemRadio,
- CdkMenuItemCheckbox,
- CdkMenuTrigger,
- CdkMenuGroup,
- CdkContextMenuTrigger,
- CdkTargetMenuAim] });
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, imports: [OverlayModule] });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkMenuModule, decorators: [{
- type: NgModule,
- args: [{
- imports: [OverlayModule, ...MENU_DIRECTIVES],
- exports: MENU_DIRECTIVES,
- }]
- }] });
- 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 };
- //# sourceMappingURL=menu.mjs.map
|