1890 lines
84 KiB
JavaScript
1890 lines
84 KiB
JavaScript
|
import * as i0 from '@angular/core';
|
||
|
import { Directive, InjectionToken, Optional, SkipSelf, Inject, Injectable, inject, Injector, ViewContainerRef, EventEmitter, NgZone, ElementRef, ChangeDetectorRef, Input, Output, ContentChildren, NgModule } from '@angular/core';
|
||
|
import { Overlay, OverlayConfig, STANDARD_DROPDOWN_BELOW_POSITIONS, STANDARD_DROPDOWN_ADJACENT_POSITIONS, OverlayModule } from '@angular/cdk/overlay';
|
||
|
import { ENTER, SPACE, UP_ARROW, hasModifierKey, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, ESCAPE } from '@angular/cdk/keycodes';
|
||
|
import { startWith, debounceTime, distinctUntilChanged, filter, takeUntil, mergeMap, mapTo, mergeAll, switchMap, skipWhile, skip } from 'rxjs/operators';
|
||
|
import { UniqueSelectionDispatcher } from '@angular/cdk/collections';
|
||
|
import { Subject, merge, fromEvent, defer, partition } from 'rxjs';
|
||
|
import { TemplatePortal } from '@angular/cdk/portal';
|
||
|
import { InputModalityDetector, FocusKeyManager } from '@angular/cdk/a11y';
|
||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||
|
import { Directionality } from '@angular/cdk/bidi';
|
||
|
import { _getEventTarget } from '@angular/cdk/platform';
|
||
|
|
||
|
/**
|
||
|
* A grouping container for `CdkMenuItemRadio` instances, similar to a `role="radiogroup"` element.
|
||
|
*/
|
||
|
class CdkMenuGroup {
|
||
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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: "16.1.1", ngImport: i0, type: CdkMenuGroup, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuGroup]',
|
||
|
exportAs: 'cdkMenuGroup',
|
||
|
standalone: true,
|
||
|
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');
|
||
|
|
||
|
/** 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),
|
||
|
});
|
||
|
/** The next available menu stack ID. */
|
||
|
let nextId$2 = 0;
|
||
|
/**
|
||
|
* 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 {
|
||
|
constructor() {
|
||
|
/** The ID of this menu stack. */
|
||
|
this.id = `${nextId$2++}`;
|
||
|
/** All MenuStackItems tracked by this MenuStack. */
|
||
|
this._elements = [];
|
||
|
/** Emits the element which was popped off of the stack when requested by a closer. */
|
||
|
this._close = new Subject();
|
||
|
/** Emits once the MenuStack has become empty after popping off elements. */
|
||
|
this._empty = new Subject();
|
||
|
/** Emits whether any menu in the menu stack has focus. */
|
||
|
this._hasFocus = new Subject();
|
||
|
/** Observable which emits the MenuStackItem which has been requested to close. */
|
||
|
this.closed = this._close;
|
||
|
/** Observable which emits whether any menu in the menu stack has focus. */
|
||
|
this.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.
|
||
|
*/
|
||
|
this.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.
|
||
|
*/
|
||
|
this._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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MenuStack, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
||
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MenuStack }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MenuStack, decorators: [{
|
||
|
type: Injectable
|
||
|
}] });
|
||
|
|
||
|
/** Injection token used for an implementation of MenuStack. */
|
||
|
const MENU_TRIGGER = new InjectionToken('cdk-menu-trigger');
|
||
|
/**
|
||
|
* Abstract directive that implements shared logic common to all menu triggers.
|
||
|
* This class can be extended to create custom menu trigger types.
|
||
|
*/
|
||
|
class CdkMenuTriggerBase {
|
||
|
constructor() {
|
||
|
/** The DI injector for this component. */
|
||
|
this.injector = inject(Injector);
|
||
|
/** The view container ref for this component */
|
||
|
this.viewContainerRef = inject(ViewContainerRef);
|
||
|
/** The menu stack in which this menu resides. */
|
||
|
this.menuStack = inject(MENU_STACK);
|
||
|
/** Emits when the attached menu is requested to open */
|
||
|
this.opened = new EventEmitter();
|
||
|
/** Emits when the attached menu is requested to close */
|
||
|
this.closed = new EventEmitter();
|
||
|
/** A reference to the overlay which manages the triggered menu */
|
||
|
this.overlayRef = null;
|
||
|
/** Emits when this trigger is destroyed. */
|
||
|
this.destroyed = new Subject();
|
||
|
/** Emits when the outside pointer events listener on the overlay should be stopped. */
|
||
|
this.stopOutsideClicksListener = merge(this.closed, this.destroyed);
|
||
|
}
|
||
|
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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuTriggerBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkMenuTriggerBase, host: { properties: { "attr.aria-controls": "childMenu?.id", "attr.data-cdk-menu-stack-id": "menuStack.id" } }, ngImport: i0 }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", 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 {
|
||
|
constructor() {
|
||
|
/** The Angular zone. */
|
||
|
this._ngZone = inject(NgZone);
|
||
|
/** The last NUM_POINTS mouse move events. */
|
||
|
this._points = [];
|
||
|
/** Emits when this service is destroyed. */
|
||
|
this._destroyed = new Subject();
|
||
|
}
|
||
|
ngOnDestroy() {
|
||
|
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._ngZone.runOutsideAngular(() => {
|
||
|
fromEvent(this._menu.nativeElement, 'mousemove')
|
||
|
.pipe(filter((_, index) => index % MOUSE_MOVE_SAMPLE_FREQUENCY === 0), takeUntil(this._destroyed))
|
||
|
.subscribe((event) => {
|
||
|
this._points.push({ x: event.clientX, y: event.clientY });
|
||
|
if (this._points.length > NUM_POINTS) {
|
||
|
this._points.shift();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
||
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TargetMenuAim }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkTargetMenuAim, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkTargetMenuAim, isStandalone: true, selector: "[cdkTargetMenuAim]", providers: [{ provide: MENU_AIM, useClass: TargetMenuAim }], exportAs: ["cdkTargetMenuAim"], ngImport: i0 }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkTargetMenuAim, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkTargetMenuAim]',
|
||
|
exportAs: 'cdkTargetMenuAim',
|
||
|
standalone: true,
|
||
|
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 {
|
||
|
constructor() {
|
||
|
super();
|
||
|
this._elementRef = inject(ElementRef);
|
||
|
this._overlay = inject(Overlay);
|
||
|
this._ngZone = inject(NgZone);
|
||
|
this._changeDetectorRef = inject(ChangeDetectorRef);
|
||
|
this._inputModalityDetector = inject(InputModalityDetector);
|
||
|
this._directionality = inject(Directionality, { optional: true });
|
||
|
/** The parent menu this trigger belongs to. */
|
||
|
this._parentMenu = inject(CDK_MENU, { optional: true });
|
||
|
/** The menu aim service used by this menu. */
|
||
|
this._menuAim = inject(MENU_AIM, { optional: true });
|
||
|
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;
|
||
|
}
|
||
|
/**
|
||
|
* 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._ngZone.runOutsideAngular(() => {
|
||
|
fromEvent(this._elementRef.nativeElement, 'mouseenter')
|
||
|
.pipe(filter(() => {
|
||
|
return (
|
||
|
// Skip fake `mouseenter` events dispatched by touch devices.
|
||
|
this._inputModalityDetector.mostRecentModality !== 'touch' &&
|
||
|
!this.menuStack.isEmpty() &&
|
||
|
!this.isOpen());
|
||
|
}), takeUntil(this.destroyed))
|
||
|
.subscribe(() => {
|
||
|
// 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._overlay.scrollStrategies.reposition(),
|
||
|
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()
|
||
|
.withGrowAfterOpen()
|
||
|
.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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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, ngImport: i0 }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuTrigger, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuTriggerFor]',
|
||
|
exportAs: 'cdkMenuTriggerFor',
|
||
|
standalone: true,
|
||
|
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: [
|
||
|
'menuTemplateRef: cdkMenuTriggerFor',
|
||
|
'menuPosition: cdkMenuPosition',
|
||
|
'menuData: cdkMenuTriggerData',
|
||
|
],
|
||
|
outputs: ['opened: cdkMenuOpened', 'closed: cdkMenuClosed'],
|
||
|
providers: [
|
||
|
{ provide: MENU_TRIGGER, useExisting: CdkMenuTrigger },
|
||
|
PARENT_OR_NEW_MENU_STACK_PROVIDER,
|
||
|
],
|
||
|
}]
|
||
|
}], ctorParameters: function () { return []; } });
|
||
|
|
||
|
/**
|
||
|
* 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 {
|
||
|
/** Whether the CdkMenuItem is disabled - defaults to false */
|
||
|
get disabled() {
|
||
|
return this._disabled;
|
||
|
}
|
||
|
set disabled(value) {
|
||
|
this._disabled = coerceBooleanProperty(value);
|
||
|
}
|
||
|
/** Whether the menu item opens a menu. */
|
||
|
get hasMenu() {
|
||
|
return this._menuTrigger?.menuTemplateRef != null;
|
||
|
}
|
||
|
constructor() {
|
||
|
this._dir = inject(Directionality, { optional: true });
|
||
|
this._elementRef = inject(ElementRef);
|
||
|
this._ngZone = inject(NgZone);
|
||
|
this._inputModalityDetector = inject(InputModalityDetector);
|
||
|
/** The menu aim service used by this menu. */
|
||
|
this._menuAim = inject(MENU_AIM, { optional: true });
|
||
|
/** The stack of menus this menu belongs to. */
|
||
|
this._menuStack = inject(MENU_STACK);
|
||
|
/** The parent menu in which this menuitem resides. */
|
||
|
this._parentMenu = inject(CDK_MENU, { optional: true });
|
||
|
/** Reference to the CdkMenuItemTrigger directive if one is added to the same element */
|
||
|
this._menuTrigger = inject(CdkMenuTrigger, { optional: true, self: true });
|
||
|
this._disabled = false;
|
||
|
/**
|
||
|
* If this MenuItem is a regular MenuItem, outputs when it is triggered by a keyboard or mouse
|
||
|
* event.
|
||
|
*/
|
||
|
this.triggered = new EventEmitter();
|
||
|
/**
|
||
|
* The tabindex for this menu item managed internally and used for implementing roving a
|
||
|
* tab index.
|
||
|
*/
|
||
|
this._tabindex = -1;
|
||
|
/** Whether the item should close the menu if triggered by the spacebar. */
|
||
|
this.closeOnSpacebarTrigger = true;
|
||
|
/** Emits when the menu item is destroyed. */
|
||
|
this.destroyed = new Subject();
|
||
|
this._setupMouseEnter();
|
||
|
this._setType();
|
||
|
if (this._isStandaloneItem()) {
|
||
|
this._tabindex = 0;
|
||
|
}
|
||
|
}
|
||
|
ngOnDestroy() {
|
||
|
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)) {
|
||
|
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'
|
||
|
? 1 /* FocusNext.previousItem */
|
||
|
: 2 /* 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: 0 /* 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._ngZone.runOutsideAngular(() => fromEvent(this._elementRef.nativeElement, 'mouseenter')
|
||
|
.pipe(filter(() => {
|
||
|
return (
|
||
|
// Skip fake `mouseenter` events dispatched by touch devices.
|
||
|
this._inputModalityDetector.mostRecentModality !== 'touch' &&
|
||
|
!this._menuStack.isEmpty() &&
|
||
|
!this.hasMenu);
|
||
|
}), takeUntil(this.destroyed))
|
||
|
.subscribe(() => {
|
||
|
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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkMenuItem, isStandalone: true, selector: "[cdkMenuItem]", inputs: { disabled: ["cdkMenuItemDisabled", "disabled"], 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: "16.1.1", ngImport: i0, type: CdkMenuItem, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuItem]',
|
||
|
exportAs: 'cdkMenuItem',
|
||
|
standalone: true,
|
||
|
host: {
|
||
|
'role': 'menuitem',
|
||
|
'class': 'cdk-menu-item',
|
||
|
'[tabindex]': '_tabindex',
|
||
|
'[attr.aria-disabled]': 'disabled || null',
|
||
|
'(blur)': '_resetTabIndex()',
|
||
|
'(focus)': '_setTabIndex()',
|
||
|
'(click)': 'trigger()',
|
||
|
'(keydown)': '_onKeydown($event)',
|
||
|
},
|
||
|
}]
|
||
|
}], ctorParameters: function () { return []; }, propDecorators: { disabled: [{
|
||
|
type: Input,
|
||
|
args: ['cdkMenuItemDisabled']
|
||
|
}], 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 {
|
||
|
constructor(
|
||
|
/** The list of items being tracked. */
|
||
|
_items) {
|
||
|
this._items = _items;
|
||
|
/** Emits when an element is moused into. */
|
||
|
this.entered = this._getItemPointerEntries();
|
||
|
/** Emits when an element is moused out. */
|
||
|
this.exited = this._getItemPointerExits();
|
||
|
/** Emits when this is destroyed. */
|
||
|
this._destroyed = new Subject();
|
||
|
this.entered.subscribe(element => (this.activeElement = element));
|
||
|
this.exited.subscribe(() => {
|
||
|
this.previousElement = this.activeElement;
|
||
|
this.activeElement = undefined;
|
||
|
});
|
||
|
}
|
||
|
/** Stop the managers listeners. */
|
||
|
destroy() {
|
||
|
this._destroyed.next();
|
||
|
this._destroyed.complete();
|
||
|
}
|
||
|
/**
|
||
|
* Gets a stream of pointer (mouse) entries into the given items.
|
||
|
* This should typically run outside the Angular zone.
|
||
|
*/
|
||
|
_getItemPointerEntries() {
|
||
|
return defer(() => this._items.changes.pipe(startWith(this._items), mergeMap((list) => list.map(element => fromEvent(element._elementRef.nativeElement, 'mouseenter').pipe(mapTo(element), takeUntil(this._items.changes)))), mergeAll()));
|
||
|
}
|
||
|
/**
|
||
|
* Gets a stream of pointer (mouse) exits out of the given items.
|
||
|
* This should typically run outside the Angular zone.
|
||
|
*/
|
||
|
_getItemPointerExits() {
|
||
|
return defer(() => this._items.changes.pipe(startWith(this._items), mergeMap((list) => list.map(element => fromEvent(element._elementRef.nativeElement, 'mouseout').pipe(mapTo(element), takeUntil(this._items.changes)))), mergeAll()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Counter used to create unique IDs for menus. */
|
||
|
let nextId$1 = 0;
|
||
|
/**
|
||
|
* Abstract directive that implements shared logic common to all menus.
|
||
|
* This class can be extended to create custom menu types.
|
||
|
*/
|
||
|
class CdkMenuBase extends CdkMenuGroup {
|
||
|
constructor() {
|
||
|
super(...arguments);
|
||
|
/** The menu's native DOM host element. */
|
||
|
this.nativeElement = inject(ElementRef).nativeElement;
|
||
|
/** The Angular zone. */
|
||
|
this.ngZone = inject(NgZone);
|
||
|
/** The stack of menus this menu belongs to. */
|
||
|
this.menuStack = inject(MENU_STACK);
|
||
|
/** The menu aim service used by this menu. */
|
||
|
this.menuAim = inject(MENU_AIM, { optional: true, self: true });
|
||
|
/** The directionality (text direction) of the current page. */
|
||
|
this.dir = inject(Directionality, { optional: true });
|
||
|
/** The id of the menu's host element. */
|
||
|
this.id = `cdk-menu-${nextId$1++}`;
|
||
|
/** The direction items in the menu flow. */
|
||
|
this.orientation = 'vertical';
|
||
|
/**
|
||
|
* Whether the menu is displayed inline (i.e. always present vs a conditional popup that the
|
||
|
* user triggers with a trigger element).
|
||
|
*/
|
||
|
this.isInline = false;
|
||
|
/** Emits when the MenuBar is destroyed. */
|
||
|
this.destroyed = new Subject();
|
||
|
/** Whether this menu's menu stack has focus. */
|
||
|
this._menuStackHasFocus = false;
|
||
|
}
|
||
|
ngAfterContentInit() {
|
||
|
if (!this.isInline) {
|
||
|
this.menuStack.push(this);
|
||
|
}
|
||
|
this._setKeyManager();
|
||
|
this._subscribeToMenuStackHasFocus();
|
||
|
this._subscribeToMenuOpen();
|
||
|
this._subscribeToMenuStackClosed();
|
||
|
this._setUpPointerTracker();
|
||
|
}
|
||
|
ngOnDestroy() {
|
||
|
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() {
|
||
|
const tabindexIfInline = this._menuStackHasFocus ? -1 : 0;
|
||
|
return this.isInline ? tabindexIfInline : null;
|
||
|
}
|
||
|
/**
|
||
|
* 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 = 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.items);
|
||
|
});
|
||
|
this.menuAim.initialize(this, this.pointerTracker);
|
||
|
}
|
||
|
}
|
||
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuBase, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkMenuBase, inputs: { id: "id" }, host: { attributes: { "role": "menu" }, listeners: { "focus": "focusFirstItem()", "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: "16.1.1", 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',
|
||
|
'(focus)': 'focusFirstItem()',
|
||
|
'(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 {
|
||
|
constructor() {
|
||
|
super();
|
||
|
this._parentTrigger = inject(MENU_TRIGGER, { optional: true });
|
||
|
/** Event emitted when the menu is closed. */
|
||
|
this.closed = new EventEmitter();
|
||
|
/** The direction items in the menu flow. */
|
||
|
this.orientation = 'vertical';
|
||
|
/** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
|
||
|
this.isInline = !this._parentTrigger;
|
||
|
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: 2 /* 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 0 /* FocusNext.nextItem */:
|
||
|
keyManager.setFocusOrigin('keyboard');
|
||
|
keyManager.setNextItemActive();
|
||
|
break;
|
||
|
case 1 /* FocusNext.previousItem */:
|
||
|
keyManager.setFocusOrigin('keyboard');
|
||
|
keyManager.setPreviousItemActive();
|
||
|
break;
|
||
|
case 2 /* 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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: "16.1.1", ngImport: i0, type: CdkMenu, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenu]',
|
||
|
exportAs: 'cdkMenu',
|
||
|
standalone: true,
|
||
|
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: function () { return []; }, 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 {
|
||
|
constructor() {
|
||
|
super(...arguments);
|
||
|
/** The direction items in the menu flow. */
|
||
|
this.orientation = 'horizontal';
|
||
|
/** Whether the menu is displayed inline (i.e. always present vs a conditional popup that the user triggers with a trigger element). */
|
||
|
this.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 0 /* FocusNext.nextItem */:
|
||
|
keyManager.setFocusOrigin('keyboard');
|
||
|
keyManager.setNextItemActive();
|
||
|
keyManager.activeItem?.getMenuTrigger()?.open();
|
||
|
break;
|
||
|
case 1 /* FocusNext.previousItem */:
|
||
|
keyManager.setFocusOrigin('keyboard');
|
||
|
keyManager.setPreviousItemActive();
|
||
|
keyManager.activeItem?.getMenuTrigger()?.open();
|
||
|
break;
|
||
|
case 2 /* 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuBar, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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: "16.1.1", ngImport: i0, type: CdkMenuBar, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuBar]',
|
||
|
exportAs: 'cdkMenuBar',
|
||
|
standalone: true,
|
||
|
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 {
|
||
|
constructor() {
|
||
|
super(...arguments);
|
||
|
this._checked = false;
|
||
|
/** Whether the item should close the menu if triggered by the spacebar. */
|
||
|
this.closeOnSpacebarTrigger = false;
|
||
|
}
|
||
|
/** Whether the element is checked */
|
||
|
get checked() {
|
||
|
return this._checked;
|
||
|
}
|
||
|
set checked(value) {
|
||
|
this._checked = coerceBooleanProperty(value);
|
||
|
}
|
||
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuItemSelectable, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkMenuItemSelectable, inputs: { checked: ["cdkMenuItemChecked", "checked"] }, host: { properties: { "attr.aria-checked": "!!checked", "attr.aria-disabled": "disabled || null" } }, usesInheritance: true, ngImport: i0 }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuItemSelectable, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
host: {
|
||
|
'[attr.aria-checked]': '!!checked',
|
||
|
'[attr.aria-disabled]': 'disabled || null',
|
||
|
},
|
||
|
}]
|
||
|
}], propDecorators: { checked: [{
|
||
|
type: Input,
|
||
|
args: ['cdkMenuItemChecked']
|
||
|
}] } });
|
||
|
|
||
|
/** Counter used to set a unique id and name for a selectable item */
|
||
|
let nextId = 0;
|
||
|
/**
|
||
|
* 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 {
|
||
|
constructor() {
|
||
|
super();
|
||
|
/** The unique selection dispatcher for this radio's `CdkMenuGroup`. */
|
||
|
this._selectionDispatcher = inject(UniqueSelectionDispatcher);
|
||
|
/** An ID to identify this radio item to the `UniqueSelectionDispatcher`. */
|
||
|
this._id = `${nextId++}`;
|
||
|
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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuItemRadio, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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: "16.1.1", ngImport: i0, type: CdkMenuItemRadio, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuItemRadio]',
|
||
|
exportAs: 'cdkMenuItemRadio',
|
||
|
standalone: true,
|
||
|
host: {
|
||
|
'role': 'menuitemradio',
|
||
|
'[class.cdk-menu-item-radio]': 'true',
|
||
|
},
|
||
|
providers: [
|
||
|
{ provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio },
|
||
|
{ provide: CdkMenuItem, useExisting: CdkMenuItemSelectable },
|
||
|
],
|
||
|
}]
|
||
|
}], ctorParameters: function () { return []; } });
|
||
|
|
||
|
/**
|
||
|
* 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuItemCheckbox, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", 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: "16.1.1", ngImport: i0, type: CdkMenuItemCheckbox, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkMenuItemCheckbox]',
|
||
|
exportAs: 'cdkMenuItemCheckbox',
|
||
|
standalone: true,
|
||
|
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 {
|
||
|
/**
|
||
|
* 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: ContextMenuTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
||
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: ContextMenuTracker, providedIn: 'root' }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", 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 {
|
||
|
/** Whether the context menu is disabled. */
|
||
|
get disabled() {
|
||
|
return this._disabled;
|
||
|
}
|
||
|
set disabled(value) {
|
||
|
this._disabled = coerceBooleanProperty(value);
|
||
|
}
|
||
|
constructor() {
|
||
|
super();
|
||
|
/** The CDK overlay service. */
|
||
|
this._overlay = inject(Overlay);
|
||
|
/** The directionality of the page. */
|
||
|
this._directionality = inject(Directionality, { optional: true });
|
||
|
/** The app's context menu tracking registry */
|
||
|
this._contextMenuTracker = inject(ContextMenuTracker);
|
||
|
this._disabled = false;
|
||
|
this._setMenuStackCloseListener();
|
||
|
}
|
||
|
/**
|
||
|
* Open the attached menu at the specified location.
|
||
|
* @param coordinates where to open the context menu
|
||
|
*/
|
||
|
open(coordinates) {
|
||
|
this._open(null, coordinates);
|
||
|
}
|
||
|
/** 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._overlay.scrollStrategies.reposition(),
|
||
|
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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* 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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkContextMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
||
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: CdkContextMenuTrigger, isStandalone: true, selector: "[cdkContextMenuTriggerFor]", inputs: { menuTemplateRef: ["cdkContextMenuTriggerFor", "menuTemplateRef"], menuPosition: ["cdkContextMenuPosition", "menuPosition"], menuData: ["cdkContextMenuTriggerData", "menuData"], disabled: ["cdkContextMenuDisabled", "disabled"] }, 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: "16.1.1", ngImport: i0, type: CdkContextMenuTrigger, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{
|
||
|
selector: '[cdkContextMenuTriggerFor]',
|
||
|
exportAs: 'cdkContextMenuTriggerFor',
|
||
|
standalone: true,
|
||
|
host: {
|
||
|
'[attr.data-cdk-menu-stack-id]': 'null',
|
||
|
'(contextmenu)': '_openOnContextMenu($event)',
|
||
|
},
|
||
|
inputs: [
|
||
|
'menuTemplateRef: cdkContextMenuTriggerFor',
|
||
|
'menuPosition: cdkContextMenuPosition',
|
||
|
'menuData: cdkContextMenuTriggerData',
|
||
|
],
|
||
|
outputs: ['opened: cdkContextMenuOpened', 'closed: cdkContextMenuClosed'],
|
||
|
providers: [
|
||
|
{ provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger },
|
||
|
{ provide: MENU_STACK, useClass: MenuStack },
|
||
|
],
|
||
|
}]
|
||
|
}], ctorParameters: function () { return []; }, propDecorators: { disabled: [{
|
||
|
type: Input,
|
||
|
args: ['cdkContextMenuDisabled']
|
||
|
}] } });
|
||
|
|
||
|
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 { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
||
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.1", 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 { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuModule, imports: [OverlayModule] }); }
|
||
|
}
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: CdkMenuModule, decorators: [{
|
||
|
type: NgModule,
|
||
|
args: [{
|
||
|
imports: [OverlayModule, ...MENU_DIRECTIVES],
|
||
|
exports: MENU_DIRECTIVES,
|
||
|
}]
|
||
|
}] });
|
||
|
|
||
|
/**
|
||
|
* Generated bundle index. Do not edit.
|
||
|
*/
|
||
|
|
||
|
export { CDK_MENU, CdkContextMenuTrigger, CdkMenu, CdkMenuBar, CdkMenuBase, CdkMenuGroup, CdkMenuItem, CdkMenuItemCheckbox, CdkMenuItemRadio, CdkMenuItemSelectable, CdkMenuModule, CdkMenuTrigger, CdkMenuTriggerBase, CdkTargetMenuAim, ContextMenuTracker, MENU_AIM, MENU_STACK, MENU_TRIGGER, MenuStack, PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER, PARENT_OR_NEW_MENU_STACK_PROVIDER, PointerFocusTracker, TargetMenuAim };
|
||
|
//# sourceMappingURL=menu.mjs.map
|