UNPKG

@angular/cdk

Version:

Angular Material Component Development Kit

1,826 lines (1,810 loc) 58.2 kB
import * as i0 from '@angular/core'; import { Directive, InjectionToken, inject, Injectable, Injector, ViewContainerRef, EventEmitter, NgZone, RendererFactory2, ElementRef, ChangeDetectorRef, Renderer2, booleanAttribute, Input, Output, QueryList, signal, computed, ContentChildren, NgModule } from '@angular/core'; import { startWith, debounceTime, distinctUntilChanged, takeUntil, mergeMap, mapTo, mergeAll, switchMap, skipWhile, skip } from 'rxjs/operators'; import { UniqueSelectionDispatcher } from './_unique-selection-dispatcher-chunk.mjs'; import { Subject, merge, partition } from 'rxjs'; import { _IdGenerator } from './_id-generator-chunk.mjs'; import { createRepositionScrollStrategy, createOverlayRef, OverlayConfig, createFlexibleConnectedPositionStrategy, STANDARD_DROPDOWN_BELOW_POSITIONS, STANDARD_DROPDOWN_ADJACENT_POSITIONS, OverlayModule } from './_overlay-module-chunk.mjs'; import { TemplatePortal } from './portal.mjs'; import { ENTER, SPACE, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, ESCAPE } from './_keycodes-chunk.mjs'; import { InputModalityDetector, FocusMonitor } from './_focus-monitor-chunk.mjs'; import { Directionality } from './_directionality-chunk.mjs'; import { hasModifierKey } from './keycodes.mjs'; import { _getEventTarget } from './_shadow-dom-chunk.mjs'; import { FocusKeyManager } from './_focus-key-manager-chunk.mjs'; import '@angular/common'; import './_platform-chunk.mjs'; import './_test-environment-chunk.mjs'; import './_style-loader-chunk.mjs'; import './_css-pixel-value-chunk.mjs'; import './_array-chunk.mjs'; import './scrolling.mjs'; import './_element-chunk.mjs'; import './_scrolling-chunk.mjs'; import './bidi.mjs'; import './_recycle-view-repeater-strategy-chunk.mjs'; import './_data-source-chunk.mjs'; import './_fake-event-detection-chunk.mjs'; import './_passive-listeners-chunk.mjs'; import './_list-key-manager-chunk.mjs'; import './_typeahead-chunk.mjs'; class CdkMenuGroup { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", ngImport: i0, type: CdkMenuGroup, decorators: [{ type: Directive, args: [{ selector: '[cdkMenuGroup]', exportAs: 'cdkMenuGroup', host: { 'role': 'group', 'class': 'cdk-menu-group' }, providers: [{ provide: UniqueSelectionDispatcher, useClass: UniqueSelectionDispatcher }] }] }] }); const CDK_MENU = new InjectionToken('cdk-menu'); var FocusNext; (function (FocusNext) { FocusNext[FocusNext["nextItem"] = 0] = "nextItem"; FocusNext[FocusNext["previousItem"] = 1] = "previousItem"; FocusNext[FocusNext["currentItem"] = 2] = "currentItem"; })(FocusNext || (FocusNext = {})); const MENU_STACK = new InjectionToken('cdk-menu-stack'); const PARENT_OR_NEW_MENU_STACK_PROVIDER = { provide: MENU_STACK, useFactory: () => inject(MENU_STACK, { optional: true, skipSelf: true }) || new MenuStack() }; const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER = orientation => ({ provide: MENU_STACK, useFactory: () => inject(MENU_STACK, { optional: true, skipSelf: true }) || MenuStack.inline(orientation) }); class MenuStack { id = inject(_IdGenerator).getId('cdk-menu-stack-'); _elements = []; _close = new Subject(); _empty = new Subject(); _hasFocus = new Subject(); closed = this._close; hasFocus = this._hasFocus.pipe(startWith(false), debounceTime(0), distinctUntilChanged()); emptied = this._empty; _inlineMenuOrientation = null; static inline(orientation) { const stack = new MenuStack(); stack._inlineMenuOrientation = orientation; return stack; } push(menu) { this._elements.push(menu); } 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); } } } 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; } 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); } } isEmpty() { return !this._elements.length; } length() { return this._elements.length; } peek() { return this._elements[this._elements.length - 1]; } hasInlineMenu() { return this._inlineMenuOrientation != null; } inlineMenuOrientation() { return this._inlineMenuOrientation; } setHasFocus(hasFocus) { this._hasFocus.next(hasFocus); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuStack, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuStack }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuStack, decorators: [{ type: Injectable }] }); const MENU_TRIGGER = new InjectionToken('cdk-menu-trigger'); const MENU_SCROLL_STRATEGY = new InjectionToken('cdk-menu-scroll-strategy', { providedIn: 'root', factory: () => { const injector = inject(Injector); return () => createRepositionScrollStrategy(injector); } }); class MenuTracker { static _openMenuTrigger; update(trigger) { if (MenuTracker._openMenuTrigger !== trigger) { MenuTracker._openMenuTrigger?.close(); MenuTracker._openMenuTrigger = trigger; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuTracker, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MenuTracker, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class CdkMenuTriggerBase { injector = inject(Injector); viewContainerRef = inject(ViewContainerRef); menuStack = inject(MENU_STACK); menuScrollStrategy = inject(MENU_SCROLL_STRATEGY); menuPosition; opened = new EventEmitter(); closed = new EventEmitter(); menuTemplateRef; menuData; overlayRef = null; destroyed = new Subject(); stopOutsideClicksListener = merge(this.closed, this.destroyed); childMenu; _menuPortal; _childMenuInjector; ngOnDestroy() { this._destroyOverlay(); this.destroyed.next(); this.destroyed.complete(); } isOpen() { return !!this.overlayRef?.hasAttached(); } registerChildMenu(child) { this.childMenu = child; } 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; } 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; } _destroyOverlay() { if (this.overlayRef) { this.overlayRef.dispose(); this.overlayRef = null; } } _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: "21.0.0", ngImport: i0, type: CdkMenuTriggerBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", ngImport: i0, type: CdkMenuTriggerBase, decorators: [{ type: Directive, args: [{ host: { '[attr.aria-controls]': 'childMenu?.id', '[attr.data-cdk-menu-stack-id]': 'menuStack.id' } }] }] }); function throwMissingPointerFocusTracker() { throw Error('expected an instance of PointerFocusTracker to be provided'); } function throwMissingMenuReference() { throw Error('expected a reference to the parent menu'); } const MENU_AIM = new InjectionToken('cdk-menu-aim'); const MOUSE_MOVE_SAMPLE_FREQUENCY = 3; const NUM_POINTS = 5; const CLOSE_DELAY = 300; function getSlope(a, b) { return (b.y - a.y) / (b.x - a.x); } function getYIntercept(point, slope) { return point.y - slope * point.x; } function isWithinSubmenu(submenuPoints, m, b) { const { left, right, top, bottom } = submenuPoints; 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; } class TargetMenuAim { _ngZone = inject(NgZone); _renderer = inject(RendererFactory2).createRenderer(null, null); _cleanupMousemove; _points = []; _menu; _pointerTracker; _timeoutId; _destroyed = new Subject(); ngOnDestroy() { this._cleanupMousemove?.(); this._destroyed.next(); this._destroyed.complete(); } initialize(menu, pointerTracker) { this._menu = menu; this._pointerTracker = pointerTracker; this._subscribeToMouseMoves(); } toggle(doToggle) { 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(); } } _startTimeout(doToggle) { const timeoutId = setTimeout(() => { if (this._pointerTracker.activeElement && timeoutId === this._timeoutId) { doToggle(); } this._timeoutId = null; }, CLOSE_DELAY); this._timeoutId = timeoutId; } _isMovingToSubmenu() { const submenuPoints = this._getSubmenuBounds(); if (!submenuPoints) { return false; } let numMoving = 0; const currPoint = this._points[this._points.length - 1]; 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); } _getSubmenuBounds() { return this._pointerTracker?.previousElement?.getMenu()?.nativeElement.getBoundingClientRect(); } _checkConfigured() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._pointerTracker) { throwMissingPointerFocusTracker(); } if (!this._menu) { throwMissingMenuReference(); } } } _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: "21.0.0", ngImport: i0, type: TargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: TargetMenuAim }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: TargetMenuAim, decorators: [{ type: Injectable }] }); class CdkTargetMenuAim { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkTargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", type: CdkTargetMenuAim, isStandalone: true, selector: "[cdkTargetMenuAim]", providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }], exportAs: ["cdkTargetMenuAim"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkTargetMenuAim, decorators: [{ type: Directive, args: [{ selector: '[cdkTargetMenuAim]', exportAs: 'cdkTargetMenuAim', providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }] }] }] }); function eventDispatchesNativeClick(elementRef, event) { if (!event.isTrusted) { return false; } const el = elementRef.nativeElement; const keyCode = event.keyCode; if (el.nodeName === 'BUTTON' && !el.disabled) { return keyCode === ENTER || keyCode === SPACE; } if (el.nodeName === 'A') { return keyCode === ENTER; } return false; } class CdkMenuTrigger extends CdkMenuTriggerBase { _elementRef = inject(ElementRef); _ngZone = inject(NgZone); _changeDetectorRef = inject(ChangeDetectorRef); _inputModalityDetector = inject(InputModalityDetector); _directionality = inject(Directionality, { optional: true }); _renderer = inject(Renderer2); _injector = inject(Injector); _cleanupMouseenter; _menuTracker = inject(MenuTracker); _parentMenu = inject(CDK_MENU, { optional: true }); _menuAim = inject(MENU_AIM, { optional: true }); constructor() { super(); this._setRole(); this._registerCloseHandler(); this._subscribeToMenuStackClosed(); this._subscribeToMouseEnter(); this._subscribeToMenuStackHasFocus(); this._setType(); } toggle() { this.isOpen() ? this.close() : this.open(); } open() { if (!this._parentMenu) { this._menuTracker.update(this); } if (!this.isOpen() && this.menuTemplateRef != null) { this.opened.next(); this.overlayRef = this.overlayRef || createOverlayRef(this._injector, this._getOverlayConfig()); this.overlayRef.attach(this.getMenuContentPortal()); this._changeDetectorRef.markForCheck(); this._subscribeToOutsideClicks(); } } close() { if (this.isOpen()) { this.closed.next(); this.overlayRef.detach(); this._changeDetectorRef.markForCheck(); } this._closeSiblingTriggers(); } getMenu() { return this.childMenu; } ngOnChanges(changes) { if (changes['menuPosition'] && this.overlayRef) { this.overlayRef.updatePositionStrategy(this._getOverlayPositionStrategy()); } } ngOnDestroy() { this._cleanupMouseenter(); super.ngOnDestroy(); } _toggleOnKeydown(event) { const isParentVertical = this._parentMenu?.orientation === 'vertical'; switch (event.keyCode) { case SPACE: case ENTER: 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; } } _handleClick() { this.toggle(); this.childMenu?.focusFirstItem('mouse'); } _setHasFocus(hasFocus) { if (!this._parentMenu) { this.menuStack.setHasFocus(hasFocus); } } _subscribeToMouseEnter() { this._cleanupMouseenter = this._ngZone.runOutsideAngular(() => { return this._renderer.listen(this._elementRef.nativeElement, 'mouseenter', () => { if (this._inputModalityDetector.mostRecentModality !== 'touch' && !this.menuStack.isEmpty() && !this.isOpen()) { const toggleMenus = () => this._ngZone.run(() => { this._closeSiblingTriggers(); this.open(); }); if (this._menuAim) { this._menuAim.toggle(toggleMenus); } else { toggleMenus(); } } }); }); } _closeSiblingTriggers() { if (this._parentMenu) { const isParentMenuBar = !this.menuStack.closeSubMenuOf(this._parentMenu) && this.menuStack.peek() !== this._parentMenu; if (isParentMenuBar) { this.menuStack.closeAll(); } } else { this.menuStack.closeAll(); } } _getOverlayConfig() { return new OverlayConfig({ positionStrategy: this._getOverlayPositionStrategy(), scrollStrategy: this.menuScrollStrategy(), direction: this._directionality || undefined }); } _getOverlayPositionStrategy() { return createFlexibleConnectedPositionStrategy(this._injector, this._elementRef).withLockedPosition().withFlexibleDimensions(false).withPositions(this._getOverlayPositions()); } _getOverlayPositions() { return this.menuPosition ?? (!this._parentMenu || this._parentMenu.orientation === 'horizontal' ? STANDARD_DROPDOWN_BELOW_POSITIONS : STANDARD_DROPDOWN_ADJACENT_POSITIONS); } _registerCloseHandler() { if (!this._parentMenu) { this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item }) => { if (item === this.childMenu) { this.close(); } }); } } _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(); } } }); } } _subscribeToMenuStackHasFocus() { if (!this._parentMenu) { this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => { if (!hasFocus) { this.menuStack.closeAll(); } }); } } _subscribeToMenuStackClosed() { if (!this._parentMenu) { this.menuStack.closed.subscribe(({ focusParentTrigger }) => { if (focusParentTrigger && !this.menuStack.length()) { this._elementRef.nativeElement.focus(); } }); } } _setRole() { if (!this._parentMenu) { this._elementRef.nativeElement.setAttribute('role', 'button'); } } _setType() { const element = this._elementRef.nativeElement; if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) { element.setAttribute('type', 'button'); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", 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: () => [] }); class CdkMenuItem { _dir = inject(Directionality, { optional: true }); _elementRef = inject(ElementRef); _ngZone = inject(NgZone); _inputModalityDetector = inject(InputModalityDetector); _renderer = inject(Renderer2); _cleanupMouseEnter; _menuAim = inject(MENU_AIM, { optional: true }); _menuStack = inject(MENU_STACK); _parentMenu = inject(CDK_MENU, { optional: true }); _menuTrigger = inject(CdkMenuTrigger, { optional: true, self: true }); disabled = false; typeaheadLabel; triggered = new EventEmitter(); get hasMenu() { return this._menuTrigger?.menuTemplateRef != null; } _tabindex = -1; closeOnSpacebarTrigger = true; destroyed = new Subject(); constructor() { this._setupMouseEnter(); this._setType(); if (this._isStandaloneItem()) { this._tabindex = 0; } } ngOnDestroy() { this._cleanupMouseEnter?.(); this.destroyed.next(); this.destroyed.complete(); } focus() { this._elementRef.nativeElement.focus(); } trigger(options) { const { keepOpen } = { ...options }; if (!this.disabled && !this.hasMenu) { this.triggered.next(); if (!keepOpen) { this._menuStack.closeAll({ focusParentTrigger: true }); } } } isMenuOpen() { return !!this._menuTrigger?.isOpen(); } getMenu() { return this._menuTrigger?.getMenu(); } getMenuTrigger() { return this._menuTrigger; } getLabel() { return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || ''; } _resetTabIndex() { if (!this._isStandaloneItem()) { this._tabindex = -1; } } _setTabIndex(event) { if (this.disabled) { return; } if (!event || !this._menuStack.isEmpty()) { this._tabindex = 0; } } _handleClick(event) { if (this.disabled) { event.preventDefault(); event.stopPropagation(); } else { this.trigger(); } } _onKeydown(event) { switch (event.keyCode) { case SPACE: case ENTER: if (!hasModifierKey(event) && !eventDispatchesNativeClick(this._elementRef, event)) { const nodeName = this._elementRef.nativeElement.nodeName; 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; } } _isStandaloneItem() { return !this._parentMenu; } _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 }); } } _forwardArrowPressed(event) { if (!this.hasMenu && this._menuStack.inlineMenuOrientation() === 'horizontal') { event.preventDefault(); this._menuStack.closeAll({ focusNextOnEmpty: FocusNext.nextItem, focusParentTrigger: true }); } } _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', () => { if (this._inputModalityDetector.mostRecentModality !== 'touch' && !this._menuStack.isEmpty() && !this.hasMenu) { if (this._menuAim) { this._menuAim.toggle(closeOpenSiblings); } else { closeOpenSiblings(); } } })); } } _isParentVertical() { return this._parentMenu?.orientation === 'vertical'; } _setType() { const element = this._elementRef.nativeElement; if (element.nodeName === 'BUTTON' && !element.getAttribute('type')) { element.setAttribute('type', 'button'); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.0", 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": "_handleClick($event)", "keydown": "_onKeydown($event)" }, properties: { "class.cdk-menu-item-disabled": "disabled", "tabindex": "_tabindex", "attr.aria-disabled": "disabled || null" }, classAttribute: "cdk-menu-item" }, exportAs: ["cdkMenuItem"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuItem, decorators: [{ type: Directive, args: [{ selector: '[cdkMenuItem]', exportAs: 'cdkMenuItem', host: { 'role': 'menuitem', 'class': 'cdk-menu-item', '[class.cdk-menu-item-disabled]': 'disabled', '[tabindex]': '_tabindex', '[attr.aria-disabled]': 'disabled || null', '(blur)': '_resetTabIndex()', '(focus)': '_setTabIndex()', '(click)': '_handleClick($event)', '(keydown)': '_onKeydown($event)' } }] }], ctorParameters: () => [], propDecorators: { disabled: [{ type: Input, args: [{ alias: 'cdkMenuItemDisabled', transform: booleanAttribute }] }], typeaheadLabel: [{ type: Input, args: ['cdkMenuitemTypeaheadLabel'] }], triggered: [{ type: Output, args: ['cdkMenuItemTriggered'] }] } }); class PointerFocusTracker { _renderer; _items; _eventCleanups; _itemsSubscription; entered = new Subject(); exited = new Subject(); activeElement; 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; }); } destroy() { this._cleanupEvents(); this._itemsSubscription?.unsubscribe(); } _bindEvents() { 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); })); }); }); } _cleanupEvents() { this._eventCleanups?.forEach(cleanup => cleanup()); this._eventCleanups = undefined; } } class CdkMenuBase extends CdkMenuGroup { _focusMonitor = inject(FocusMonitor); ngZone = inject(NgZone); _renderer = inject(Renderer2); nativeElement = inject(ElementRef).nativeElement; menuStack = inject(MENU_STACK); menuAim = inject(MENU_AIM, { optional: true, self: true }); dir = inject(Directionality, { optional: true }); _allItems; id = inject(_IdGenerator).getId('cdk-menu-'); items = new QueryList(); orientation = 'vertical'; isInline = false; keyManager; destroyed = new Subject(); triggerItem; pointerTracker; _menuStackHasFocus = signal(false, ...(ngDevMode ? [{ debugName: "_menuStackHasFocus" }] : [])); _tabIndexSignal = computed(() => { const tabindexIfInline = this._menuStackHasFocus() ? -1 : 0; return this.isInline ? tabindexIfInline : null; }, ...(ngDevMode ? [{ debugName: "_tabIndexSignal" }] : [])); ngAfterContentInit() { if (!this.isInline) { this.menuStack.push(this); } this._setItems(); 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(); } focusFirstItem(focusOrigin = 'program') { this.keyManager.setFocusOrigin(focusOrigin); this.keyManager.setFirstItemActive(); } focusLastItem(focusOrigin = 'program') { this.keyManager.setFocusOrigin(focusOrigin); this.keyManager.setLastItemActive(); } setActiveMenuItem(item) { this.keyManager?.setActiveItem(item); } _getTabIndex() { return this._tabIndexSignal(); } closeOpenMenu(menu, options) { const { focusParentTrigger } = { ...options }; const keyManager = this.keyManager; const trigger = this.triggerItem; if (menu === trigger?.getMenuTrigger()?.getMenu()) { trigger?.getMenuTrigger()?.close(); if (focusParentTrigger) { if (trigger) { keyManager.setActiveItem(trigger); } else { keyManager.setFirstItemActive(); } } } } _setItems() { this._allItems.changes.pipe(startWith(this._allItems), takeUntil(this.destroyed)).subscribe(items => { this.items.reset(items.filter(item => item._parentMenu === this)); this.items.notifyOnChanges(); }); } _setKeyManager() { this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd().skipPredicate(() => false); if (this.orientation === 'horizontal') { this.keyManager.withHorizontalOrientation(this.dir?.value || 'ltr'); } else { this.keyManager.withVerticalOrientation(); } } _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); } _subscribeToMenuStackClosed() { this.menuStack.closed.pipe(takeUntil(this.destroyed)).subscribe(({ item, focusParentTrigger }) => this.closeOpenMenu(item, { focusParentTrigger })); } _subscribeToMenuStackHasFocus() { if (this.isInline) { this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => { this._menuStackHasFocus.set(hasFocus); }); } } _setUpPointerTracker() { if (this.menuAim) { this.ngZone.runOutsideAngular(() => { this.pointerTracker = new PointerFocusTracker(this._renderer, this.items); }); this.menuAim.initialize(this, this.pointerTracker); } } _handleFocus() { this._focusMonitor.monitor(this.nativeElement, false).pipe(takeUntil(this.destroyed)).subscribe(origin => { if (origin !== null && origin !== 'mouse') { this.focusFirstItem(origin); } }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuBase, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "_allItems", predicate: CdkMenuItem, descendants: true }], usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuBase, decorators: [{ type: Directive, args: [{ host: { 'role': 'menu', '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: { _allItems: [{ type: ContentChildren, args: [CdkMenuItem, { descendants: true }] }], id: [{ type: Input }] } }); class CdkMenu extends CdkMenuBase { _parentTrigger = inject(MENU_TRIGGER, { optional: true }); closed = new EventEmitter(); orientation = 'vertical'; 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(); } _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); } } _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; } } _subscribeToMenuStackEmptied() { this.menuStack.emptied.pipe(takeUntil(this.destroyed)).subscribe(event => this._toggleMenuFocus(event)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", 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 }] } }); class CdkMenuBar extends CdkMenuBase { orientation = 'horizontal'; isInline = true; ngAfterContentInit() { super.ngAfterContentInit(); this._subscribeToMenuStackEmptied(); } _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; 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); } } _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; } } _subscribeToMenuStackEmptied() { this.menuStack?.emptied.pipe(takeUntil(this.destroyed)).subscribe(event => this._toggleOpenMenu(event)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuBar, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", 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') }] }] }] }); class CdkMenuItemSelectable extends CdkMenuItem { checked = false; closeOnSpacebarTrigger = false; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuItemSelectable, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.0", 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: "21.0.0", 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 }] }] } }); class CdkMenuItemRadio extends CdkMenuItemSelectable { _selectionDispatcher = inject(UniqueSelectionDispatcher); _id = inject(_IdGenerator).getId('cdk-menu-item-radio-'); _removeDispatcherListener; constructor() { super(); this._registerDispatcherListener(); } ngOnDestroy() { super.ngOnDestroy(); this._removeDispatcherListener(); } trigger(options) { super.trigger(options); if (!this.disabled) { this._selectionDispatcher.notify(this._id, ''); } } _registerDispatcherListener() { this._removeDispatcherListener = this._selectionDispatcher.listen(id => { this.checked = this._id === id; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuItemRadio, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", 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: () => [] }); class CdkMenuItemCheckbox extends CdkMenuItemSelectable { trigger(options) { super.trigger(options); if (!this.disabled) { this.checked = !this.checked; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CdkMenuItemCheckbox, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", 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: "21.0.0", ng