@angular/cdk
Version:
Angular Material Component Development Kit
1 lines • 137 kB
Source Map (JSON)
{"version":3,"file":"menu.mjs","sources":["../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-group.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-interface.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-stack.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-trigger-base.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-errors.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-aim.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/event-detection.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-trigger.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-item.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/pointer-focus-tracker.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-base.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-bar.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-item-selectable.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-item-radio.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-item-checkbox.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/context-menu-trigger.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/menu/menu-module.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Directive} from '@angular/core';\nimport {UniqueSelectionDispatcher} from '../collections';\n\n/**\n * A grouping container for `CdkMenuItemRadio` instances, similar to a `role=\"radiogroup\"` element.\n */\n@Directive({\n selector: '[cdkMenuGroup]',\n exportAs: 'cdkMenuGroup',\n host: {\n 'role': 'group',\n 'class': 'cdk-menu-group',\n },\n providers: [{provide: UniqueSelectionDispatcher, useClass: UniqueSelectionDispatcher}],\n})\nexport class CdkMenuGroup {}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {InjectionToken} from '@angular/core';\nimport {MenuStackItem} from './menu-stack';\nimport {FocusOrigin} from '../a11y';\n\n/** Injection token used to return classes implementing the Menu interface */\nexport const CDK_MENU = new InjectionToken<Menu>('cdk-menu');\n\n/** Interface which specifies Menu operations and used to break circular dependency issues */\nexport interface Menu extends MenuStackItem {\n /** The id of the menu's host element. */\n id: string;\n\n /** The menu's native DOM host element. */\n nativeElement: HTMLElement;\n\n /** The direction items in the menu flow. */\n readonly orientation: 'horizontal' | 'vertical';\n\n /** Place focus on the first MenuItem in the menu. */\n focusFirstItem(focusOrigin: FocusOrigin): void;\n\n /** Place focus on the last MenuItem in the menu. */\n focusLastItem(focusOrigin: FocusOrigin): void;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {inject, Inject, Injectable, InjectionToken, Optional, SkipSelf} from '@angular/core';\nimport {_IdGenerator} from '../a11y';\nimport {Observable, Subject} from 'rxjs';\nimport {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';\n\n/** The relative item in the inline menu to focus after closing all popup menus. */\nexport enum FocusNext {\n nextItem,\n previousItem,\n currentItem,\n}\n\n/** A single item (menu) in the menu stack. */\nexport interface MenuStackItem {\n /** A reference to the menu stack this menu stack item belongs to. */\n menuStack?: MenuStack;\n}\n\n/** Injection token used for an implementation of MenuStack. */\nexport const MENU_STACK = new InjectionToken<MenuStack>('cdk-menu-stack');\n\n/** Provider that provides the parent menu stack, or a new menu stack if there is no parent one. */\nexport const PARENT_OR_NEW_MENU_STACK_PROVIDER = {\n provide: MENU_STACK,\n deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],\n useFactory: (parentMenuStack?: MenuStack) => parentMenuStack || new MenuStack(),\n};\n\n/** Provider that provides the parent menu stack, or a new inline menu stack if there is no parent one. */\nexport const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER = (\n orientation: 'vertical' | 'horizontal',\n) => ({\n provide: MENU_STACK,\n deps: [[new Optional(), new SkipSelf(), new Inject(MENU_STACK)]],\n useFactory: (parentMenuStack?: MenuStack) => parentMenuStack || MenuStack.inline(orientation),\n});\n\n/** Options that can be provided to the close or closeAll methods. */\nexport interface CloseOptions {\n /** The element to focus next if the close operation causes the menu stack to become empty. */\n focusNextOnEmpty?: FocusNext;\n /** Whether to focus the parent trigger after closing the menu. */\n focusParentTrigger?: boolean;\n}\n\n/** Event dispatched when a menu is closed. */\nexport interface MenuStackCloseEvent {\n /** The menu being closed. */\n item: MenuStackItem;\n /** Whether to focus the parent trigger after closing the menu. */\n focusParentTrigger?: boolean;\n}\n\n/**\n * MenuStack allows subscribers to listen for close events (when a MenuStackItem is popped off\n * of the stack) in order to perform closing actions. Upon the MenuStack being empty it emits\n * from the `empty` observable specifying the next focus action which the listener should perform\n * as requested by the closer.\n */\n@Injectable()\nexport class MenuStack {\n /** The ID of this menu stack. */\n readonly id = inject(_IdGenerator).getId('cdk-menu-stack-');\n\n /** All MenuStackItems tracked by this MenuStack. */\n private readonly _elements: MenuStackItem[] = [];\n\n /** Emits the element which was popped off of the stack when requested by a closer. */\n private readonly _close = new Subject<MenuStackCloseEvent>();\n\n /** Emits once the MenuStack has become empty after popping off elements. */\n private readonly _empty = new Subject<FocusNext | undefined>();\n\n /** Emits whether any menu in the menu stack has focus. */\n private readonly _hasFocus = new Subject<boolean>();\n\n /** Observable which emits the MenuStackItem which has been requested to close. */\n readonly closed: Observable<MenuStackCloseEvent> = this._close;\n\n /** Observable which emits whether any menu in the menu stack has focus. */\n readonly hasFocus: Observable<boolean> = this._hasFocus.pipe(\n startWith(false),\n debounceTime(0),\n distinctUntilChanged(),\n );\n\n /**\n * Observable which emits when the MenuStack is empty after popping off the last element. It\n * emits a FocusNext event which specifies the action the closer has requested the listener\n * perform.\n */\n readonly emptied: Observable<FocusNext | undefined> = this._empty;\n\n /**\n * Whether the inline menu associated with this menu stack is vertical or horizontal.\n * `null` indicates there is no inline menu associated with this menu stack.\n */\n private _inlineMenuOrientation: 'vertical' | 'horizontal' | null = null;\n\n /** Creates a menu stack that originates from an inline menu. */\n static inline(orientation: 'vertical' | 'horizontal') {\n const stack = new MenuStack();\n stack._inlineMenuOrientation = orientation;\n return stack;\n }\n\n /**\n * Adds an item to the menu stack.\n * @param menu the MenuStackItem to put on the stack.\n */\n push(menu: MenuStackItem) {\n this._elements.push(menu);\n }\n\n /**\n * Pop items off of the stack up to and including `lastItem` and emit each on the close\n * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.\n * @param lastItem the last item to pop off the stack.\n * @param options Options that configure behavior on close.\n */\n close(lastItem: MenuStackItem, options?: CloseOptions) {\n const {focusNextOnEmpty, focusParentTrigger} = {...options};\n if (this._elements.indexOf(lastItem) >= 0) {\n let poppedElement;\n do {\n poppedElement = this._elements.pop()!;\n this._close.next({item: poppedElement, focusParentTrigger});\n } while (poppedElement !== lastItem);\n\n if (this.isEmpty()) {\n this._empty.next(focusNextOnEmpty);\n }\n }\n }\n\n /**\n * Pop items off of the stack up to but excluding `lastItem` and emit each on the close\n * observable. If the stack is empty or `lastItem` is not on the stack it does nothing.\n * @param lastItem the element which should be left on the stack\n * @return whether or not an item was removed from the stack\n */\n closeSubMenuOf(lastItem: MenuStackItem) {\n let removed = false;\n if (this._elements.indexOf(lastItem) >= 0) {\n removed = this.peek() !== lastItem;\n while (this.peek() !== lastItem) {\n this._close.next({item: this._elements.pop()!});\n }\n }\n return removed;\n }\n\n /**\n * Pop off all MenuStackItems and emit each one on the `close` observable one by one.\n * @param options Options that configure behavior on close.\n */\n closeAll(options?: CloseOptions) {\n const {focusNextOnEmpty, focusParentTrigger} = {...options};\n if (!this.isEmpty()) {\n while (!this.isEmpty()) {\n const menuStackItem = this._elements.pop();\n if (menuStackItem) {\n this._close.next({item: menuStackItem, focusParentTrigger});\n }\n }\n this._empty.next(focusNextOnEmpty);\n }\n }\n\n /** Return true if this stack is empty. */\n isEmpty() {\n return !this._elements.length;\n }\n\n /** Return the length of the stack. */\n length() {\n return this._elements.length;\n }\n\n /** Get the top most element on the stack. */\n peek(): MenuStackItem | undefined {\n return this._elements[this._elements.length - 1];\n }\n\n /** Whether the menu stack is associated with an inline menu. */\n hasInlineMenu() {\n return this._inlineMenuOrientation != null;\n }\n\n /** The orientation of the associated inline menu. */\n inlineMenuOrientation() {\n return this._inlineMenuOrientation;\n }\n\n /** Sets whether the menu stack contains the focused element. */\n setHasFocus(hasFocus: boolean) {\n this._hasFocus.next(hasFocus);\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n Directive,\n EventEmitter,\n inject,\n InjectionToken,\n Injector,\n OnDestroy,\n TemplateRef,\n ViewContainerRef,\n} from '@angular/core';\nimport {Menu} from './menu-interface';\nimport {MENU_STACK, MenuStack} from './menu-stack';\nimport {ConnectedPosition, Overlay, OverlayRef, ScrollStrategy} from '../overlay';\nimport {TemplatePortal} from '../portal';\nimport {merge, Subject} from 'rxjs';\n\n/** Injection token used for an implementation of MenuStack. */\nexport const MENU_TRIGGER = new InjectionToken<CdkMenuTriggerBase>('cdk-menu-trigger');\n\n/** Injection token used to configure the behavior of the menu when the page is scrolled. */\nexport const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(\n 'cdk-menu-scroll-strategy',\n {\n providedIn: 'root',\n factory: () => {\n const overlay = inject(Overlay);\n return () => overlay.scrollStrategies.reposition();\n },\n },\n);\n\n/**\n * Abstract directive that implements shared logic common to all menu triggers.\n * This class can be extended to create custom menu trigger types.\n */\n@Directive({\n host: {\n '[attr.aria-controls]': 'childMenu?.id',\n '[attr.data-cdk-menu-stack-id]': 'menuStack.id',\n },\n})\nexport abstract class CdkMenuTriggerBase implements OnDestroy {\n /** The DI injector for this component. */\n readonly injector = inject(Injector);\n\n /** The view container ref for this component */\n protected readonly viewContainerRef = inject(ViewContainerRef);\n\n /** The menu stack in which this menu resides. */\n protected readonly menuStack: MenuStack = inject(MENU_STACK);\n\n /** Function used to configure the scroll strategy for the menu. */\n protected readonly menuScrollStrategy = inject(MENU_SCROLL_STRATEGY);\n\n /**\n * A list of preferred menu positions to be used when constructing the\n * `FlexibleConnectedPositionStrategy` for this trigger's menu.\n */\n menuPosition: ConnectedPosition[];\n\n /** Emits when the attached menu is requested to open */\n readonly opened: EventEmitter<void> = new EventEmitter();\n\n /** Emits when the attached menu is requested to close */\n readonly closed: EventEmitter<void> = new EventEmitter();\n\n /** Template reference variable to the menu this trigger opens */\n menuTemplateRef: TemplateRef<unknown> | null;\n\n /** Context data to be passed along to the menu template */\n menuData: unknown;\n\n /** A reference to the overlay which manages the triggered menu */\n protected overlayRef: OverlayRef | null = null;\n\n /** Emits when this trigger is destroyed. */\n protected readonly destroyed: Subject<void> = new Subject();\n\n /** Emits when the outside pointer events listener on the overlay should be stopped. */\n protected readonly stopOutsideClicksListener = merge(this.closed, this.destroyed);\n\n /** The child menu opened by this trigger. */\n protected childMenu?: Menu;\n\n /** The content of the menu panel opened by this trigger. */\n private _menuPortal: TemplatePortal;\n\n /** The injector to use for the child menu opened by this trigger. */\n private _childMenuInjector?: Injector;\n\n ngOnDestroy() {\n this._destroyOverlay();\n\n this.destroyed.next();\n this.destroyed.complete();\n }\n\n /** Whether the attached menu is open. */\n isOpen() {\n return !!this.overlayRef?.hasAttached();\n }\n\n /** Registers a child menu as having been opened by this trigger. */\n registerChildMenu(child: Menu) {\n this.childMenu = child;\n }\n\n /**\n * Get the portal to be attached to the overlay which contains the menu. Allows for the menu\n * content to change dynamically and be reflected in the application.\n */\n protected getMenuContentPortal() {\n const hasMenuContentChanged = this.menuTemplateRef !== this._menuPortal?.templateRef;\n if (this.menuTemplateRef && (!this._menuPortal || hasMenuContentChanged)) {\n this._menuPortal = new TemplatePortal(\n this.menuTemplateRef,\n this.viewContainerRef,\n this.menuData,\n this._getChildMenuInjector(),\n );\n }\n\n return this._menuPortal;\n }\n\n /**\n * Whether the given element is inside the scope of this trigger's menu stack.\n * @param element The element to check.\n * @return Whether the element is inside the scope of this trigger's menu stack.\n */\n protected isElementInsideMenuStack(element: Element) {\n for (let el: Element | null = element; el; el = el?.parentElement ?? null) {\n if (el.getAttribute('data-cdk-menu-stack-id') === this.menuStack.id) {\n return true;\n }\n }\n return false;\n }\n\n /** Destroy and unset the overlay reference it if exists */\n private _destroyOverlay() {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n }\n }\n\n /** Gets the injector to use when creating a child menu. */\n private _getChildMenuInjector() {\n this._childMenuInjector =\n this._childMenuInjector ||\n Injector.create({\n providers: [\n {provide: MENU_TRIGGER, useValue: this},\n {provide: MENU_STACK, useValue: this.menuStack},\n ],\n parent: this.injector,\n });\n return this._childMenuInjector;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * Throws an exception when an instance of the PointerFocusTracker is not provided.\n * @docs-private\n */\nexport function throwMissingPointerFocusTracker() {\n throw Error('expected an instance of PointerFocusTracker to be provided');\n}\n\n/**\n * Throws an exception when a reference to the parent menu is not provided.\n * @docs-private\n */\nexport function throwMissingMenuReference() {\n throw Error('expected a reference to the parent menu');\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n Directive,\n inject,\n Injectable,\n InjectionToken,\n NgZone,\n OnDestroy,\n RendererFactory2,\n} from '@angular/core';\nimport {Subject} from 'rxjs';\nimport {FocusableElement, PointerFocusTracker} from './pointer-focus-tracker';\nimport {Menu} from './menu-interface';\nimport {throwMissingMenuReference, throwMissingPointerFocusTracker} from './menu-errors';\n\n/**\n * MenuAim is responsible for determining if a sibling menuitem's menu should be closed when a\n * Toggler item is hovered into. It is up to the hovered in item to call the MenuAim service in\n * order to determine if it may perform its close actions.\n */\nexport interface MenuAim {\n /**\n * Set the Menu and its PointerFocusTracker.\n * @param menu The menu that this menu aim service controls.\n * @param pointerTracker The `PointerFocusTracker` for the given menu.\n */\n initialize(menu: Menu, pointerTracker: PointerFocusTracker<FocusableElement & Toggler>): void;\n\n /**\n * Calls the `doToggle` callback when it is deemed that the user is not moving towards\n * the submenu.\n * @param doToggle the function called when the user is not moving towards the submenu.\n */\n toggle(doToggle: () => void): void;\n}\n\n/** Injection token used for an implementation of MenuAim. */\nexport const MENU_AIM = new InjectionToken<MenuAim>('cdk-menu-aim');\n\n/** Capture every nth mouse move event. */\nconst MOUSE_MOVE_SAMPLE_FREQUENCY = 3;\n\n/** The number of mouse move events to track. */\nconst NUM_POINTS = 5;\n\n/**\n * How long to wait before closing a sibling menu if a user stops short of the submenu they were\n * predicted to go into.\n */\nconst CLOSE_DELAY = 300;\n\n/** An element which when hovered over may open or close a menu. */\nexport interface Toggler {\n /** Gets the open menu, or undefined if no menu is open. */\n getMenu(): Menu | undefined;\n}\n\n/** Calculate the slope between point a and b. */\nfunction getSlope(a: Point, b: Point) {\n return (b.y - a.y) / (b.x - a.x);\n}\n\n/** Calculate the y intercept for the given point and slope. */\nfunction getYIntercept(point: Point, slope: number) {\n return point.y - slope * point.x;\n}\n\n/** Represents a coordinate of mouse travel. */\ntype Point = {x: number; y: number};\n\n/**\n * Whether the given mouse trajectory line defined by the slope and y intercept falls within the\n * submenu as defined by `submenuPoints`\n * @param submenuPoints the submenu DOMRect points.\n * @param m the slope of the trajectory line.\n * @param b the y intercept of the trajectory line.\n * @return true if any point on the line falls within the submenu.\n */\nfunction isWithinSubmenu(submenuPoints: DOMRect, m: number, b: number) {\n const {left, right, top, bottom} = submenuPoints;\n\n // Check for intersection with each edge of the submenu (left, right, top, bottom)\n // by fixing one coordinate to that edge's coordinate (either x or y) and checking if the\n // other coordinate is within bounds.\n return (\n (m * left + b >= top && m * left + b <= bottom) ||\n (m * right + b >= top && m * right + b <= bottom) ||\n ((top - b) / m >= left && (top - b) / m <= right) ||\n ((bottom - b) / m >= left && (bottom - b) / m <= right)\n );\n}\n\n/**\n * TargetMenuAim predicts if a user is moving into a submenu. It calculates the\n * trajectory of the user's mouse movement in the current menu to determine if the\n * mouse is moving towards an open submenu.\n *\n * The determination is made by calculating the slope of the users last NUM_POINTS moves where each\n * pair of points determines if the trajectory line points into the submenu. It uses consensus\n * approach by checking if at least NUM_POINTS / 2 pairs determine that the user is moving towards\n * to submenu.\n */\n@Injectable()\nexport class TargetMenuAim implements MenuAim, OnDestroy {\n private readonly _ngZone = inject(NgZone);\n private readonly _renderer = inject(RendererFactory2).createRenderer(null, null);\n private _cleanupMousemove: (() => void) | undefined;\n\n /** The last NUM_POINTS mouse move events. */\n private readonly _points: Point[] = [];\n\n /** Reference to the root menu in which we are tracking mouse moves. */\n private _menu: Menu;\n\n /** Reference to the root menu's mouse manager. */\n private _pointerTracker: PointerFocusTracker<Toggler & FocusableElement>;\n\n /** The id associated with the current timeout call waiting to resolve. */\n private _timeoutId: number | null;\n\n /** Emits when this service is destroyed. */\n private readonly _destroyed: Subject<void> = new Subject();\n\n ngOnDestroy() {\n this._cleanupMousemove?.();\n this._destroyed.next();\n this._destroyed.complete();\n }\n\n /**\n * Set the Menu and its PointerFocusTracker.\n * @param menu The menu that this menu aim service controls.\n * @param pointerTracker The `PointerFocusTracker` for the given menu.\n */\n initialize(menu: Menu, pointerTracker: PointerFocusTracker<FocusableElement & Toggler>) {\n this._menu = menu;\n this._pointerTracker = pointerTracker;\n this._subscribeToMouseMoves();\n }\n\n /**\n * Calls the `doToggle` callback when it is deemed that the user is not moving towards\n * the submenu.\n * @param doToggle the function called when the user is not moving towards the submenu.\n */\n toggle(doToggle: () => void) {\n // If the menu is horizontal the sub-menus open below and there is no risk of premature\n // closing of any sub-menus therefore we automatically resolve the callback.\n if (this._menu.orientation === 'horizontal') {\n doToggle();\n }\n\n this._checkConfigured();\n\n const siblingItemIsWaiting = !!this._timeoutId;\n const hasPoints = this._points.length > 1;\n\n if (hasPoints && !siblingItemIsWaiting) {\n if (this._isMovingToSubmenu()) {\n this._startTimeout(doToggle);\n } else {\n doToggle();\n }\n } else if (!siblingItemIsWaiting) {\n doToggle();\n }\n }\n\n /**\n * Start the delayed toggle handler if one isn't running already.\n *\n * The delayed toggle handler executes the `doToggle` callback after some period of time iff the\n * users mouse is on an item in the current menu.\n *\n * @param doToggle the function called when the user is not moving towards the submenu.\n */\n private _startTimeout(doToggle: () => void) {\n // If the users mouse is moving towards a submenu we don't want to immediately resolve.\n // Wait for some period of time before determining if the previous menu should close in\n // cases where the user may have moved towards the submenu but stopped on a sibling menu\n // item intentionally.\n const timeoutId = setTimeout(() => {\n // Resolve if the user is currently moused over some element in the root menu\n if (this._pointerTracker!.activeElement && timeoutId === this._timeoutId) {\n doToggle();\n }\n this._timeoutId = null;\n }, CLOSE_DELAY) as any as number;\n\n this._timeoutId = timeoutId;\n }\n\n /** Whether the user is heading towards the open submenu. */\n private _isMovingToSubmenu() {\n const submenuPoints = this._getSubmenuBounds();\n if (!submenuPoints) {\n return false;\n }\n\n let numMoving = 0;\n const currPoint = this._points[this._points.length - 1];\n // start from the second last point and calculate the slope between each point and the last\n // point.\n for (let i = this._points.length - 2; i >= 0; i--) {\n const previous = this._points[i];\n const slope = getSlope(currPoint, previous);\n if (isWithinSubmenu(submenuPoints, slope, getYIntercept(currPoint, slope))) {\n numMoving++;\n }\n }\n return numMoving >= Math.floor(NUM_POINTS / 2);\n }\n\n /** Get the bounding DOMRect for the open submenu. */\n private _getSubmenuBounds(): DOMRect | undefined {\n return this._pointerTracker?.previousElement?.getMenu()?.nativeElement.getBoundingClientRect();\n }\n\n /**\n * Check if a reference to the PointerFocusTracker and menu element is provided.\n * @throws an error if neither reference is provided.\n */\n private _checkConfigured() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._pointerTracker) {\n throwMissingPointerFocusTracker();\n }\n if (!this._menu) {\n throwMissingMenuReference();\n }\n }\n }\n\n /** Subscribe to the root menus mouse move events and update the tracked mouse points. */\n private _subscribeToMouseMoves() {\n this._cleanupMousemove?.();\n\n this._cleanupMousemove = this._ngZone.runOutsideAngular(() => {\n let eventIndex = 0;\n\n return this._renderer.listen(this._menu.nativeElement, 'mousemove', (event: MouseEvent) => {\n if (eventIndex % MOUSE_MOVE_SAMPLE_FREQUENCY === 0) {\n this._points.push({x: event.clientX, y: event.clientY});\n if (this._points.length > NUM_POINTS) {\n this._points.shift();\n }\n }\n eventIndex++;\n });\n });\n }\n}\n\n/**\n * CdkTargetMenuAim is a provider for the TargetMenuAim service. It can be added to an\n * element with either the `cdkMenu` or `cdkMenuBar` directive and child menu items.\n */\n@Directive({\n selector: '[cdkTargetMenuAim]',\n exportAs: 'cdkTargetMenuAim',\n providers: [{provide: MENU_AIM, useClass: TargetMenuAim}],\n})\nexport class CdkTargetMenuAim {}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {ElementRef} from '@angular/core';\nimport {ENTER, SPACE} from '../keycodes';\n\n/** Checks whether a keyboard event will trigger a native `click` event on an element. */\nexport function eventDispatchesNativeClick(\n elementRef: ElementRef<HTMLElement>,\n event: KeyboardEvent,\n): boolean {\n // Synthetic events won't trigger clicks.\n if (!event.isTrusted) {\n return false;\n }\n\n const el = elementRef.nativeElement;\n const keyCode = event.keyCode;\n\n // Buttons trigger clicks both on space and enter events.\n if (el.nodeName === 'BUTTON' && !(el as HTMLButtonElement).disabled) {\n return keyCode === ENTER || keyCode === SPACE;\n }\n\n // Links only trigger clicks on enter.\n if (el.nodeName === 'A') {\n return keyCode === ENTER;\n }\n\n // Any other elements won't dispatch clicks from keyboard events.\n return false;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n ChangeDetectorRef,\n Directive,\n ElementRef,\n inject,\n NgZone,\n OnChanges,\n OnDestroy,\n Renderer2,\n SimpleChanges,\n} from '@angular/core';\nimport {InputModalityDetector} from '../a11y';\nimport {Directionality} from '../bidi';\nimport {\n ConnectedPosition,\n FlexibleConnectedPositionStrategy,\n Overlay,\n OverlayConfig,\n STANDARD_DROPDOWN_ADJACENT_POSITIONS,\n STANDARD_DROPDOWN_BELOW_POSITIONS,\n} from '../overlay';\nimport {\n DOWN_ARROW,\n ENTER,\n hasModifierKey,\n LEFT_ARROW,\n RIGHT_ARROW,\n SPACE,\n UP_ARROW,\n} from '../keycodes';\nimport {_getEventTarget} from '../platform';\nimport {takeUntil} from 'rxjs/operators';\nimport {CDK_MENU, Menu} from './menu-interface';\nimport {PARENT_OR_NEW_MENU_STACK_PROVIDER} from './menu-stack';\nimport {MENU_AIM} from './menu-aim';\nimport {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';\nimport {eventDispatchesNativeClick} from './event-detection';\n\n/**\n * A directive that turns its host element into a trigger for a popup menu.\n * It can be combined with cdkMenuItem to create sub-menus. If the element is in a top level\n * MenuBar it will open the menu on click, or if a sibling is already opened it will open on hover.\n * If it is inside of a Menu it will open the attached Submenu on hover regardless of its sibling\n * state.\n */\n@Directive({\n selector: '[cdkMenuTriggerFor]',\n exportAs: 'cdkMenuTriggerFor',\n host: {\n 'class': 'cdk-menu-trigger',\n '[attr.aria-haspopup]': 'menuTemplateRef ? \"menu\" : null',\n '[attr.aria-expanded]': 'menuTemplateRef == null ? null : isOpen()',\n '(focusin)': '_setHasFocus(true)',\n '(focusout)': '_setHasFocus(false)',\n '(keydown)': '_toggleOnKeydown($event)',\n '(click)': '_handleClick()',\n },\n inputs: [\n {name: 'menuTemplateRef', alias: 'cdkMenuTriggerFor'},\n {name: 'menuPosition', alias: 'cdkMenuPosition'},\n {name: 'menuData', alias: 'cdkMenuTriggerData'},\n ],\n outputs: ['opened: cdkMenuOpened', 'closed: cdkMenuClosed'],\n providers: [\n {provide: MENU_TRIGGER, useExisting: CdkMenuTrigger},\n PARENT_OR_NEW_MENU_STACK_PROVIDER,\n ],\n})\nexport class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnDestroy {\n private readonly _elementRef: ElementRef<HTMLElement> = inject(ElementRef);\n private readonly _overlay = inject(Overlay);\n private readonly _ngZone = inject(NgZone);\n private readonly _changeDetectorRef = inject(ChangeDetectorRef);\n private readonly _inputModalityDetector = inject(InputModalityDetector);\n private readonly _directionality = inject(Directionality, {optional: true});\n private readonly _renderer = inject(Renderer2);\n private _cleanupMouseenter: () => void;\n\n /** The parent menu this trigger belongs to. */\n private readonly _parentMenu = inject(CDK_MENU, {optional: true});\n\n /** The menu aim service used by this menu. */\n private readonly _menuAim = inject(MENU_AIM, {optional: true});\n\n constructor() {\n super();\n this._setRole();\n this._registerCloseHandler();\n this._subscribeToMenuStackClosed();\n this._subscribeToMouseEnter();\n this._subscribeToMenuStackHasFocus();\n this._setType();\n }\n\n /** Toggle the attached menu. */\n toggle() {\n this.isOpen() ? this.close() : this.open();\n }\n\n /** Open the attached menu. */\n open() {\n if (!this.isOpen() && this.menuTemplateRef != null) {\n this.opened.next();\n\n this.overlayRef = this.overlayRef || this._overlay.create(this._getOverlayConfig());\n this.overlayRef.attach(this.getMenuContentPortal());\n this._changeDetectorRef.markForCheck();\n this._subscribeToOutsideClicks();\n }\n }\n\n /** Close the opened menu. */\n close() {\n if (this.isOpen()) {\n this.closed.next();\n\n this.overlayRef!.detach();\n this._changeDetectorRef.markForCheck();\n }\n this._closeSiblingTriggers();\n }\n\n /**\n * Get a reference to the rendered Menu if the Menu is open and rendered in the DOM.\n */\n getMenu(): Menu | undefined {\n return this.childMenu;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['menuPosition'] && this.overlayRef) {\n this.overlayRef.updatePositionStrategy(this._getOverlayPositionStrategy());\n }\n }\n\n override ngOnDestroy(): void {\n this._cleanupMouseenter();\n super.ngOnDestroy();\n }\n\n /**\n * Handles keyboard events for the menu item.\n * @param event The keyboard event to handle\n */\n _toggleOnKeydown(event: KeyboardEvent) {\n const isParentVertical = this._parentMenu?.orientation === 'vertical';\n switch (event.keyCode) {\n case SPACE:\n case ENTER:\n // Skip events that will trigger clicks so the handler doesn't get triggered twice.\n if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {\n this.toggle();\n this.childMenu?.focusFirstItem('keyboard');\n }\n break;\n\n case RIGHT_ARROW:\n if (!hasModifierKey(event)) {\n if (this._parentMenu && isParentVertical && this._directionality?.value !== 'rtl') {\n event.preventDefault();\n this.open();\n this.childMenu?.focusFirstItem('keyboard');\n }\n }\n break;\n\n case LEFT_ARROW:\n if (!hasModifierKey(event)) {\n if (this._parentMenu && isParentVertical && this._directionality?.value === 'rtl') {\n event.preventDefault();\n this.open();\n this.childMenu?.focusFirstItem('keyboard');\n }\n }\n break;\n\n case DOWN_ARROW:\n case UP_ARROW:\n if (!hasModifierKey(event)) {\n if (!isParentVertical) {\n event.preventDefault();\n this.open();\n event.keyCode === DOWN_ARROW\n ? this.childMenu?.focusFirstItem('keyboard')\n : this.childMenu?.focusLastItem('keyboard');\n }\n }\n break;\n }\n }\n\n /** Handles clicks on the menu trigger. */\n _handleClick() {\n this.toggle();\n this.childMenu?.focusFirstItem('mouse');\n }\n\n /**\n * Sets whether the trigger's menu stack has focus.\n * @param hasFocus Whether the menu stack has focus.\n */\n _setHasFocus(hasFocus: boolean) {\n if (!this._parentMenu) {\n this.menuStack.setHasFocus(hasFocus);\n }\n }\n\n /**\n * Subscribe to the mouseenter events and close any sibling menu items if this element is moused\n * into.\n */\n private _subscribeToMouseEnter() {\n this._cleanupMouseenter = this._ngZone.runOutsideAngular(() => {\n return this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {\n if (\n // Skip fake `mouseenter` events dispatched by touch devices.\n this._inputModalityDetector.mostRecentModality !== 'touch' &&\n !this.menuStack.isEmpty() &&\n !this.isOpen()\n ) {\n // Closes any sibling menu items and opens the menu associated with this trigger.\n const toggleMenus = () =>\n this._ngZone.run(() => {\n this._closeSiblingTriggers();\n this.open();\n });\n\n if (this._menuAim) {\n this._menuAim.toggle(toggleMenus);\n } else {\n toggleMenus();\n }\n }\n });\n });\n }\n\n /** Close out any sibling menu trigger menus. */\n private _closeSiblingTriggers() {\n if (this._parentMenu) {\n // If nothing was removed from the stack and the last element is not the parent item\n // that means that the parent menu is a menu bar since we don't put the menu bar on the\n // stack\n const isParentMenuBar =\n !this.menuStack.closeSubMenuOf(this._parentMenu) &&\n this.menuStack.peek() !== this._parentMenu;\n\n if (isParentMenuBar) {\n this.menuStack.closeAll();\n }\n } else {\n this.menuStack.closeAll();\n }\n }\n\n /** Get the configuration object used to create the overlay. */\n private _getOverlayConfig() {\n return new OverlayConfig({\n positionStrategy: this._getOverlayPositionStrategy(),\n scrollStrategy: this.menuScrollStrategy(),\n direction: this._directionality || undefined,\n });\n }\n\n /** Build the position strategy for the overlay which specifies where to place the menu. */\n private _getOverlayPositionStrategy(): FlexibleConnectedPositionStrategy {\n return this._overlay\n .position()\n .flexibleConnectedTo(this._elementRef)\n .withLockedPosition()\n .withFlexibleDimensions(false)\n .withPositions(this._getOverlayPositions());\n }\n\n /** Get the preferred positions for the opened menu relative to the menu item. */\n private _getOverlayPositions(): ConnectedPosition[] {\n return (\n this.menuPosition ??\n (!this._parentMenu || this._parentMenu.orientation === 'horizontal'\n ? STANDARD_DROPDOWN_BELOW_POSITIONS\n : STANDARD_DROPDOWN_ADJACENT_POSITIONS)\n );\n }\n\n /**\n * Subscribe to the MenuStack close events if this is a standalone trigger and close out the menu\n * this triggers when requested.\n */\n private _registerCloseHandler() {\n if (!this._parentMenu) {\n this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({item}) => {\n if (item === this.childMenu) {\n this.close();\n }\n });\n }\n }\n\n /**\n * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a\n * click occurs outside the menus.\n */\n private _subscribeToOutsideClicks() {\n if (this.overlayRef) {\n this.overlayRef\n .outsidePointerEvents()\n .pipe(takeUntil(this.stopOutsideClicksListener))\n .subscribe(event => {\n const target = _getEventTarget(event) as Element;\n const element = this._elementRef.nativeElement;\n\n if (target !== element && !element.contains(target)) {\n if (!this.isElementInsideMenuStack(target)) {\n this.menuStack.closeAll();\n } else {\n this._closeSiblingTriggers();\n }\n }\n });\n }\n }\n\n /** Subscribe to the MenuStack hasFocus events. */\n private _subscribeToMenuStackHasFocus() {\n if (!this._parentMenu) {\n this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {\n if (!hasFocus) {\n this.menuStack.closeAll();\n }\n });\n }\n }\n\n /** Subscribe to the MenuStack closed events. */\n private _subscribeToMenuStackClosed() {\n if (!this._parentMenu) {\n this.menuStack.closed.subscribe(({focusParentTrigger}) => {\n if (focusParentTrigger && !this.menuStack.length()) {\n this._elementRef.nativeElement.focus();\n }\n });\n }\n }\n\n /** Sets the role attribute for this trigger if needed. */\n private _setRole() {\n // If this trigger is part of another menu, the cdkMenuItem directive will handle setting the\n // role, otherwise this is a standalone trigger, and we should ensure it has role=\"button\".\n if (!this._parentMenu) {\n this._elementRef.nativeElement.setAttribute('role', 'button');\n }\n }\n\n /** Sets thte `type` attribute of the trigger. */\n private _setType() {\n const element = this._elementRef.nativeElement;\n\n if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {\n // Prevents form submissions.\n element.setAttribute('type', 'button');\n }\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n booleanAttribute,\n Directive,\n ElementRef,\n EventEmitter,\n inject,\n Input,\n NgZone,\n OnDestroy,\n Output,\n Renderer2,\n} from '@angular/core';\nimport {FocusableOption, InputModalityDetector} from '../a11y';\nimport {ENTER, hasModifierKey, LEFT_ARROW, RIGHT_ARROW, SPACE} from '../keycodes';\nimport {Directionality} from '../bidi';\nimport {Subject} from 'rxjs';\nimport {CdkMenuTrigger} from './menu-trigger';\nimport {CDK_MENU, Menu} from './menu-interface';\nimport {FocusNext, MENU_STACK} from './menu-stack';\nimport {FocusableElement} from './pointer-focus-tracker';\nimport {MENU_AIM, Toggler} from './menu-aim';\nimport {eventDispatchesNativeClick} from './event-detection';\n\n/**\n * Directive which provides the ability for an element to be focused and navigated to using the\n * keyboard when residing in a CdkMenu, CdkMenuBar, or CdkMenuGroup. It performs user defined\n * behavior when clicked.\n */\n@Directive({\n selector: '[cdkMenuItem]',\n exportAs: 'cdkMenuItem',\n host: {\n 'role': 'menuitem',\n 'class': 'cdk-menu-item',\n '[tabindex]': '_tabindex',\n '[attr.aria-disabled]': 'disabled || null',\n '(blur)': '_resetTabIndex()',\n '(focus)': '_setTabIndex()',\n '(click)': 'trigger()',\n '(keydown)': '_onKeydown($event)',\n },\n})\nexport class CdkMenuItem implements FocusableOption, FocusableElement, Toggler, OnDestroy {\n protected readonly _dir = inject(Directionality, {optional: true});\n readonly _elementRef: ElementRef<HTMLElement> = inject(ElementRef);\n protected _ngZone = inject(NgZone);\n private readonly _inputModalityDetector = inject(InputModalityDetector);\n private readonly _renderer = inject(Renderer2);\n private _cleanupMouseEnter: (() => void) | undefined;\n\n /** The menu aim service used by this menu. */\n private readonly _menuAim = inject(MENU_AIM, {optional: true});\n\n /** The stack of menus this menu belongs to. */\n private readonly _menuStack = inject(MENU_STACK);\n\n /** The parent menu in which this menuitem resides. */\n private readonly _parentMenu = inject(CDK_MENU, {optional: true});\n\n /** Reference to the CdkMenuItemTrigger directive if one is added to the same element */\n private readonly _menuTrigger = inject(CdkMenuTrigger, {optional: true, self: true});\n\n /** Whether the CdkMenuItem is disabled - defaults to false */\n @Input({alias: 'cdkMenuItemDisabled', transform: booleanAttribute}) disabled: boolean = false;\n\n /**\n * The text used to locate this item during menu typeahead. If not specified,\n * the `textContent` of the item will be used.\n */\n @Input('cdkMenuitemTypeaheadLabel') typeaheadLabel: string | null;\n\n /**\n * If this MenuItem is a regular MenuItem, outputs when it is triggered by a keyboard or mouse\n * event.\n */\n @Output('cdkMenuItemTriggered') readonly triggered: EventEmitter<void> = new EventEmitter();\n\n /** Whether the menu item opens a menu. */\n get hasMenu() {\n return this._menuTrigger?.menuTemplateRef != null;\n }\n\n /**\n * The tabindex for this menu item managed internally and used for implementing roving a\n * tab index.\n */\n _tabindex: 0 | -1 = -1;\n\n /** Whether the item should close the menu if triggered by the spacebar. */\n protected closeOnSpacebarTrigger = true;\n\n /** Emits when the menu item is destroyed. */\n protected readonly destroyed = new Subject<void>();\n\n constructor() {\n this._setupMouseEnter();\n this._setType();\n\n if (this._isStandaloneItem()) {\n this._tabindex = 0;\n }\n }\n\n ngOnDestroy() {\n this._cleanupMouseEnter?.();\n this.destroyed.next();\n this.destroyed.complete();\n }\n\n /** Place focus on the element. */\n focus() {\n this._elementRef.nativeElement.focus();\n }\n\n /**\n * If the menu item is not disabled and the element does not have a menu trigger attached, emit\n * on the cdkMenuItemTriggered emitter and close all open menus.\n * @param options Options the configure how the item is triggered\n * - keepOpen: specifies that the menu should be kept open after triggering the item.\n */\n trigger(options?: {keepOpen: boolean}) {\n const {keepOpen} = {...options};\n if (!this.disabled && !this.hasMenu) {\n this.triggered.next();\n if (!keepOpen) {\n this._menuStack.closeAll({focusParentTrigger: true});\n }\n }\n }\n\n /** Return true if this MenuItem has an attached menu and it is open. */\n isMenuOpen() {\n return !!this._menuTrigger?.isOpen();\n }\n\n /**\n * Get a reference to the rendered Menu if the Menu is open and it is visible in the DOM.\n * @return the menu if it is open, otherwise undefined.\n */\n getMenu(): Menu | undefined {\n return this._menuTrigger?.getMenu();\n }\n\n /** Get the CdkMenuTrigger associated with this element. */\n getMenuTrigger(): CdkMenuTrigger | null {\n return this._menuTrigger;\n }\n\n /** Get the label for this element which is required by the FocusableOption interface. */\n getLabel(): string {\n return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';\n }\n\n /** Reset the tabindex to -1. */\n _resetTabIndex() {\n if (!this._isStandaloneItem()) {\n this._tabindex = -1;\n }\n }\n\n /**\n * Set the tab index to 0 if not disabled and it's a focus event, or a mouse enter if this element\n * is not in a menu bar.\n */\n _setTabIndex(event?: MouseEvent) {\n if (this.disabled) {\n return;\n }\n\n // don't set the tabindex if there are no open sibling or parent menus\n if (!event || !this._menuStack.isEmpty()) {\n this._tabindex = 0;\n }\n }\n\n /**\n * Handles keyboard events for the menu item, specifically either triggering the user defined\n * callback or opening/closing the current menu based on whether the left or right arrow key was\n * pressed.\n * @param event the keyboard event to handle\n */\n _onKeydown(event: KeyboardEvent) {\n switch (event.keyCode) {\n case SPACE:\n case ENTER:\n // Skip events that will trigger clicks so the handler doesn't get triggered twice.\n if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) {\n const nodeName = this._elementRef.nativeElement.nodeName;\n\n // Avoid repeat events on non-native elements (see #30250). Note that we don't do this\n // on the native elements so we don't interfere with their behavior (see #26296).\n if (nodeName !== 'A' && nodeName !== 'BUTTON') {\n event.preventDefault();\n }\n\n this.trigger({keepOpen: event.keyCode === SPACE && !this.closeOnSpacebarTrigger});\n }\n break;\n\n case RIGHT_ARROW:\n if (!hasModifierKey(event)) {\n if (this._parentMenu && this._isParentVertical()) {\n if (this._dir?.value !== 'rtl') {\n this._forwardArrowPressed(event);\n } else {\n this._backArrowPressed(event);\n }\n }\n }\n break;\n\n case LEFT_ARROW:\n if (!hasModifierKey(event)) {\n if (this._parentMenu && this._isParentVertical()) {\n if (this._dir?.value !== 'rtl') {\n this._backArrowPressed(event);\n } else {\n this._forwardArrowPressed(event);\n }\n }\n }\n break;\n }\n }\n\n /** Whether this menu item is standalone or within a menu or menu bar. */\n private _isStandaloneItem() {\n return !this._parentMenu;\n }\n\n /**\n * Handles the user pressing the back arrow key.\n * @param event The keyboard event.\n */\n private _backArrowPressed(event: KeyboardEvent) {\n const parentMenu = this._parentMenu!;\n if (this._menuStack.hasInlineMenu() || this._menuStack.length() > 1) {\n event.preventDefault();\n this._menuStack.close(parentMenu, {\n focusNextOnEmpty:\n this._menuStack.inlineMenuOrientation() === 'horizontal'\n ? FocusNext.previousItem\n : FocusNext.currentItem,\n focusParentTrigger: true,\n });\n }\n }\n\n /**\n * Handles the user pressing the forward arrow key.\n * @param event The keyboard event.\n */\n private _forwardArrowPressed(event: KeyboardEvent) {\n if (!this.hasMenu && this._menuStack.inlineMenuOrientation() === 'horizontal') {\n event.preventDefault();\n this._menuStack.closeAll({\n focusNextOnEmpty: FocusNext.nextItem,\n focusParentTrigger: true,\n });\n }\n }\n\n /**\n * Subscribe to the mouseenter events and close any sibling menu items if this element is moused\n * into.\n */\n private _setupMouseEnter() {\n if (!this._isStandaloneItem()) {\n const closeOpenSiblings = () =>\n this._ngZone.run(() => this._menuStack.closeSubMenuOf(this._parentMenu!));\n\n this._cleanupMouseEnter = this._ngZone.runOutsideAngular(() =>\n this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => {\n // Skip fake `mouseenter` events dispatched by touch devices.\n if (\n this._inputModalityDetector.mostRecentModality !== 'touch' &&\n !this._menuStack.isEmpty() &&\n !this.hasMenu\n ) {\n if (this._menuAim) {\n this._menuAim.toggle(closeOpenSiblings);\n } else {\n closeOpenSiblings();\n }\n }\n }),\n );\n }\n }\n\n /**\n * Return true if the enclosing parent menu is configured in a horizontal orientation, false\n * otherwise or if no parent.\n */\n private _isParentVertical() {\n return this._parentMenu?.orientation === 'vertical';\n }\n\n /** Sets the `type` attribute of the menu item. */\n private _setType() {\n const element = this._elementRef.nativeElement;\n\n if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) {\n // Prevent form submissions.\n element.setAttribute('type', 'button');\n }\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is g