@progress/kendo-angular-menu
Version:
Kendo UI Angular Menu component
1,501 lines (1,484 loc) • 133 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import * as i0 from '@angular/core';
import { Injectable, NgZone, EventEmitter, Directive, Optional, Component, Input, ContentChildren, ElementRef, forwardRef, ViewChild, HostBinding, Output, isDevMode, ContentChild, NgModule } from '@angular/core';
import { NgFor, NgIf, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
import * as i3 from '@progress/kendo-angular-l10n';
import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import { validatePackage } from '@progress/kendo-licensing';
import { PreventableEvent as PreventableEvent$1, hasObservers, Keys, normalizeNumpadKeys, isDocumentAvailable, ResizeBatchService } from '@progress/kendo-angular-common';
import { caretAltLeftIcon, caretAltRightIcon, caretAltDownIcon } from '@progress/kendo-svg-icons';
import * as i5 from '@progress/kendo-angular-popup';
import { PopupService, POPUP_CONTAINER } from '@progress/kendo-angular-popup';
import { IconWrapperComponent, IconsService } from '@progress/kendo-angular-icons';
/**
* @hidden
*/
const packageMetadata = {
name: '@progress/kendo-angular-menu',
productName: 'Kendo UI for Angular',
productCode: 'KENDOUIANGULAR',
productCodes: ['KENDOUIANGULAR'],
publishDate: 1756992827,
version: '20.0.3',
licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/'
};
const PARENT_REGEX = /_?\d+$/;
const SEPARATOR = '_';
let id = 0;
const itemIndexComparer = (a, b) => a.siblingIndex - b.siblingIndex;
const next = (idx, items, dir) => {
let current = items[idx + dir];
while (!current) {
if (idx < 0) {
idx = items.length - 1;
}
else if (idx >= items.length) {
idx = 0;
}
else {
idx += dir;
}
current = items[idx];
}
return current;
};
/**
* @hidden
*/
class ItemsService {
items = {};
lists = [];
idPrefix = `k-menu${id++}`;
get hasItems() {
return Object.keys(this.items).length > 0;
}
childId(index) {
return `${this.idPrefix}-child${index}`;
}
itemIndex(parentIndex, index) {
return (parentIndex ? parentIndex + SEPARATOR : '') + index;
}
get(index) {
return this.items[index];
}
add(item) {
this.items[item.index] = item;
}
remove(item) {
if (this.items[item.index] === item) {
delete this.items[item.index];
}
}
addList(list) {
this.lists.push(list);
}
removeList(list) {
const index = this.lists.indexOf(list);
if (index >= 0) {
this.lists.splice(index, 1);
}
}
containsList(element) {
return Boolean(this.lists.find(list => list.element.nativeElement === element));
}
siblings(item) {
const parentIndex = this.parentIndex(item.index);
return this.filter((index) => this.parentIndex(index) === parentIndex);
}
otherSiblings(item) {
const parentIndex = this.parentIndex(item.index);
return this.filter((index) => this.parentIndex(index) === parentIndex && index !== item.index);
}
children(item) {
return this.filter((index) => this.parentIndex(index) === item.index);
}
parent(item) {
return this.items[this.parentIndex(item.index)];
}
root(item) {
return this.items[this.indices(item.index)[0]];
}
indices(index) {
return index.split(SEPARATOR);
}
filter(predicate) {
const result = [];
const items = this.items;
for (const index in items) {
if (predicate(index, items[index])) {
result.push(items[index]);
}
}
return result.sort(itemIndexComparer);
}
previous(item) {
const siblings = this.siblings(item);
const itemIndex = siblings.indexOf(item);
return next(itemIndex, siblings, -1);
}
next(item) {
const siblings = this.siblings(item);
const itemIndex = siblings.indexOf(item);
return next(itemIndex, siblings, 1);
}
hasParent(item, parent) {
return item.index.startsWith(parent.index);
}
areSiblings(item1, item2) {
return item1 !== item2 && this.parent(item1) === this.parent(item2);
}
forEach(callback) {
const items = this.items;
for (const index in items) {
if (items.hasOwnProperty(index)) {
callback(items[index]);
}
}
}
parentIndex(index) {
return index.replace(PARENT_REGEX, '');
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemsService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemsService, decorators: [{
type: Injectable
}] });
const canPerformAction = (item, action) => !((action === 'open' && item.opened) || (action === 'close' && !item.opened));
/**
* Used to remove cyclic dependency error. Dublicates MenuEvent
* @hidden
*/
class MenuStateEvent extends PreventableEvent$1 {
/**
* The MenuComponent that triggered the event.
*/
sender;
/**
* The item data of the event.
*/
item;
/**
* The item index of the event.
*/
index;
constructor(args) {
super();
Object.assign(this, args);
}
}
/**
* @hidden
*/
class ActionsService {
ngZone;
items;
owner;
actions = [];
constructor(ngZone, items) {
this.ngZone = ngZone;
this.items = items;
}
open(item, finished) {
if (item.disabled) {
return;
}
if (item.hasContent && !item.opened) {
this.actions.push({
name: 'open',
requiresZone: item.hasContentTemplates(),
item,
finished
});
}
else if (finished) {
finished();
}
}
close(item) {
this.closeChildren(item);
this.closeItem(item);
}
closeItem(item) {
if (item.opened) {
this.actions.push({
name: 'close',
item
});
}
}
closeToRoot(item) {
this.closeChildren(item);
let current = item;
do {
this.closeItem(current);
current = this.items.parent(current);
} while (current);
}
closeOthers(item) {
this.closeChildren(item);
let current = item;
while (current) {
const siblings = this.items.otherSiblings(current);
this.closeItems(siblings);
current = this.items.parent(current);
}
}
closeAll() {
this.items.forEach((item) => {
if (item.opened && item.level === 0) {
this.close(item);
}
});
}
select(item, domEvent, prevented, finished) {
this.actions.push({
name: 'select',
item,
prevented,
finished,
domEvent
});
}
emit(name, item, domEvent) {
const owner = this.owner;
const eventArgs = new MenuStateEvent({
sender: owner,
item: item.item,
index: item.index,
originalEvent: domEvent,
hasContent: item.hasContent
});
owner[name].emit(eventArgs);
if (owner.contextService) {
owner.contextService.emit(name, eventArgs);
}
return eventArgs.isDefaultPrevented();
}
get hasPending() {
return this.actions.length > 0;
}
execute(toExecute) {
if (!this.hasPending && !toExecute) {
return;
}
const actions = toExecute || this.clear();
if (!NgZone.isInAngularZone() && this.requiresZone(actions)) {
this.ngZone.run(() => {
this.executeActions(actions);
});
}
else {
this.executeActions(actions);
}
}
clear() {
const actions = this.actions;
this.actions = [];
return actions;
}
executeActions(actions) {
for (let idx = 0; idx < actions.length; idx++) {
const { item, name, prevented, finished, domEvent } = actions[idx];
if (!canPerformAction(item, name)) {
continue;
}
if (!this.emit(name, item, domEvent)) {
if (item[name]) {
item[name]();
}
if (finished) {
finished();
}
}
else if (prevented) {
prevented();
}
}
}
requiresZone(toExecute) {
const actions = toExecute || this.actions;
const owner = this.owner;
const contextService = owner.contextService;
for (let idx = 0; idx < actions.length; idx++) {
const action = actions[idx];
const name = action.name;
if (action.requiresZone || (name && (hasObservers(owner[name]) || (contextService && contextService.hasObservers(name))))) {
return true;
}
}
return false;
}
closeChildren(item) {
if (!item.opened) {
return;
}
const children = this.items.children(item);
this.closeItems(children);
}
closeItems(items) {
for (let idx = 0; idx < items.length; idx++) {
this.close(items[idx]);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionsService, deps: [{ token: i0.NgZone }, { token: ItemsService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionsService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionsService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: ItemsService }]; } });
const DEFAULT_ACTIVE = '0';
const NO_SPACE_REGEX = /\S/;
const handlers = {};
handlers[Keys.ArrowLeft] = 'left';
handlers[Keys.ArrowRight] = 'right';
handlers[Keys.ArrowUp] = 'up';
handlers[Keys.ArrowDown] = 'down';
handlers[Keys.Home] = 'home';
handlers[Keys.End] = 'end';
handlers[Keys.Space] = 'enter';
handlers[Keys.Enter] = 'enter';
handlers[Keys.NumpadEnter] = 'enter';
handlers[Keys.KeyN] = 'enter';
handlers[Keys.Escape] = 'esc';
handlers[Keys.Tab] = 'tab';
const handlersRTL = Object.assign({}, handlers, {
'ArrowLeft': 'right',
'ArrowRight': 'left'
});
function isPrintableCharacter(key) {
return key.length === 1 && NO_SPACE_REGEX.test(key);
}
const resolvedPromise = Promise.resolve(null);
/**
* @hidden
*/
class NavigationService {
items;
actions;
localization;
ngZone;
vertical = false;
activeIndex = DEFAULT_ACTIVE;
focusedIdx;
get focusedItem() {
return this.items.get(this.focusedIdx);
}
get activeItem() {
return this.items.get(this.activeIndex);
}
get handlers() {
return this.localization.rtl ? handlersRTL : handlers;
}
constructor(items, actions, localization, ngZone) {
this.items = items;
this.actions = actions;
this.localization = localization;
this.ngZone = ngZone;
}
focus(item) {
if (item.index === this.focusedIdx) {
return;
}
if (!this.activeItem || !this.items.hasParent(item, this.activeItem)) {
this.setActive(item);
}
this.setFocus(item);
}
setFocus(item) {
this.focusedIdx = item.index;
item.focus();
}
focusLeave() {
const focused = this.focusedItem;
if (focused) {
this.actions.closeToRoot(focused);
this.actions.execute();
}
this.focusedIdx = null;
}
updateActive() {
if (!this.activeItem && this.items.hasItems) {
const firstItem = this.items.get(DEFAULT_ACTIVE);
firstItem.toggleActive(true);
this.ngZone.runOutsideAngular(() => {
resolvedPromise.then(() => {
this.activeIndex = DEFAULT_ACTIVE;
});
});
}
}
keydown(e) {
const current = this.focusedItem || this.activeItem;
if (!current) {
return;
}
const code = normalizeNumpadKeys(e);
const handler = this.handlers[code];
if (handler) {
if (handler !== 'tab') {
e.preventDefault();
}
this[handler](current, e);
}
else if (isPrintableCharacter(e.key)) {
this.search(current, e.key);
}
this.actions.execute();
}
focusIndex(index) {
if (!index && this.activeItem) {
this.setFocus(this.activeItem);
}
else if (index === 'first') {
this.focusFirst();
}
else if (index === 'last') {
this.focusLast();
}
else {
const item = this.items.get(index);
if (item) {
this.focus(item);
}
}
}
focusFirst() {
const items = this.items.siblings(this.items.get('0'));
this.focus(items[0]);
}
focusLast() {
const items = this.items.siblings(this.items.get('0'));
this.focus(items[items.length - 1]);
}
search(current, key) {
const siblings = this.items.siblings(current);
const startIndex = siblings.indexOf(current);
const items = siblings.slice(startIndex + 1).concat(siblings.slice(0, startIndex));
for (let idx = 0; idx < items.length; idx++) {
const sibling = items[idx];
const text = sibling.item.text || "";
if (text.toLowerCase().startsWith(key.toLowerCase())) {
this.focus(sibling);
break;
}
}
}
down(current) {
if (current.level === 0 && !this.vertical) {
if (current.hasContent) {
this.actions.open(current, this.focusChild(current, 0));
}
}
else {
this.focus(this.items.next(current));
}
}
up(current) {
if (current.level === 0 && !this.vertical) {
if (current.hasContent) {
this.actions.open(current, this.focusChild(current, current.children.length - 1));
}
}
else {
this.focus(this.items.previous(current));
}
}
left(current) {
if (this.vertical && current.level === 0 && current.disabled) {
return;
}
if (current.level > 1 || (this.vertical && current.level > 0)) {
const parent = this.items.parent(current);
this.focus(parent);
this.actions.close(parent);
}
else if (this.vertical && current.level === 0 && !current.disabled) {
if (current.hasContent) {
this.actions.open(current, this.focusChild(current, current.children.length - 1));
}
}
else {
this.focus(this.items.previous(this.activeItem));
}
}
right(current) {
if (this.vertical && current.level === 0 && current.disabled) {
return;
}
if (current.horizontal && !current.disabled) {
if (current.hasContent) {
this.actions.open(current, this.focusChild(current, 0));
}
else if (!this.vertical || current.level > 0) {
this.focus(this.items.next(this.activeItem));
}
}
else {
this.focus(this.items.next(this.activeItem));
}
}
home(current) {
const siblings = this.items.siblings(current);
this.focus(siblings[0]);
}
end(current) {
const siblings = this.items.siblings(current);
this.focus(siblings[siblings.length - 1]);
}
enter(current, domEvent) {
const actions = this.actions;
if (current.disabled) {
return;
}
if (current.hasContent) {
actions.select(current, domEvent);
actions.open(current, this.focusChild(current, 0));
}
else {
actions.select(current, domEvent, null, () => {
current.navigate();
});
this.focus(this.items.root(current));
actions.closeToRoot(current);
}
}
esc(current) {
if (current.level > 0) {
const parent = this.items.parent(current);
this.actions.close(parent);
this.focus(parent);
}
}
tab(current) {
if (current.level > 0) {
this.activeItem.focus();
}
}
focusChild(item, index) {
return () => {
const child = this.items.children(item)[index];
this.setFocus(child);
};
}
setActive(item) {
const focused = this.focusedItem;
const active = this.items.root(item);
if (this.activeItem) {
this.activeItem.toggleActive(false);
}
this.activeIndex = active.index;
active.toggleActive(true);
if (focused) {
this.actions.closeToRoot(focused);
if (focused.level > 0) {
this.actions.open(active);
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService, deps: [{ token: ItemsService }, { token: ActionsService }, { token: i3.LocalizationService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: ItemsService }, { type: ActionsService }, { type: i3.LocalizationService }, { type: i0.NgZone }]; } });
const DISABLE_OPEN_ON_OVER_DELAY = 500;
/**
* @hidden
*/
class HoverService {
actions;
items;
delay = 100;
get openOnOver() {
return this._openOnOver;
}
set openOnOver(value) {
this.cancelActions();
this._openOnOver = value;
}
_openOnOver = true;
hoveredIdx;
get hovered() {
return this.items.get(this.hoveredIdx);
}
set hovered(item) {
this.hoveredIdx = item ? item.index : null;
}
scheduled = [];
constructor(actions, items) {
this.actions = actions;
this.items = items;
}
ngOnDestroy() {
this.cancelActions();
}
over(item) {
this.cancelActions((action) => action.name === 'openOnOver');
if (!this.hovered || this.hovered !== item) {
this.actions.closeOthers(item);
this.hovered = item;
if ((item.level > 0 || this.openOnOver) && !item.disabled) {
this.actions.open(item);
this.cancelActions((action) => (action.name === 'close' && (item === action.item || this.items.hasParent(item, action.item))) ||
(action.name === 'open' && !this.items.hasParent(item, action.item)));
}
this.scheduleActions();
}
}
leave(disableOpenOnOver) {
const hovered = this.hovered;
if (hovered) {
this.actions.closeToRoot(hovered);
this.cancelActions(action => action.name === 'open');
this.scheduleActions();
}
if (disableOpenOnOver && this._openOnOver) {
this.scheduleDisableOpenOnOver();
}
this.hovered = null;
}
closeCurrent() {
const hovered = this.hovered;
if (hovered) {
this.actions.closeToRoot(hovered);
this.hovered = null;
}
}
scheduleActions() {
if (this.actions.hasPending) {
const item = {};
item.actions = this.actions.clear();
item.id = setTimeout(() => {
this.actions.execute(item.actions);
this.removeScheduled(item);
}, this.delay);
this.scheduled.push(item);
}
}
scheduleDisableOpenOnOver() {
const item = {
actions: [{ name: 'openOnOver' }]
};
item.id = setTimeout(() => {
this._openOnOver = false;
this.removeScheduled(item);
}, Math.max(this.delay, DISABLE_OPEN_ON_OVER_DELAY));
this.scheduled.push(item);
}
removeScheduled(item) {
const scheduled = this.scheduled;
for (let idx = 0; idx < scheduled.length; idx++) {
if (scheduled[idx] === item) {
scheduled.splice(idx, 1);
return;
}
}
}
cancelActions(predicate) {
const scheduled = this.scheduled;
for (let idx = scheduled.length - 1; idx >= 0; idx--) {
const item = scheduled[idx];
const actions = item.actions;
if (predicate) {
for (let actionIdx = actions.length - 1; actionIdx >= 0; actionIdx--) {
if (predicate(actions[actionIdx])) {
actions.splice(actionIdx, 1);
}
}
}
if (!predicate || actions.length === 0) {
clearTimeout(item.id);
scheduled.splice(idx, 1);
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HoverService, deps: [{ token: ActionsService }, { token: ItemsService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HoverService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HoverService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: ActionsService }, { type: ItemsService }]; } });
/**
* @hidden
*/
const normalize = (settings) => settings && Object.assign({
toggle: 'select'
}, settings);
/**
* @hidden
*/
const NODE_INDEX = 'data-kendo-menu-index';
const DEFAULT_ID = 'kendo-matches-container';
const focusableRegex = /^(?:a|input|select|option|textarea|button|object)$/i;
const matches = (element, selector) => (element.matches || element.msMatchesSelector).call(element, selector);
/**
* @hidden
*/
const closest = (node, predicate) => {
while (node && !predicate(node)) {
node = node.parentNode;
}
return node;
};
/**
* @hidden
*/
const closestInScope = (node, predicate, scope) => {
while (node && node !== scope && !predicate(node)) {
node = node.parentNode;
}
if (node !== scope) {
return node;
}
};
/**
* @hidden
*/
const isFocusable = (element) => {
if (element.tagName) {
const tagName = element.tagName.toLowerCase();
const tabIndex = element.getAttribute('tabIndex');
const skipTab = tabIndex === '-1';
let focusable = tabIndex !== null && !skipTab;
if (focusableRegex.test(tagName)) {
focusable = !element.disabled && !skipTab;
}
return focusable;
}
return false;
};
const toClassList = (classNames) => String(classNames).trim().split(' ');
/**
* @hidden
*/
const hasClass = (element, name) => {
return toClassList(element.className).indexOf(name) >= 0;
};
/**
* @hidden
*/
const matchesClasses = (classes) => {
const list = toClassList(classes);
return (element) => {
const classList = toClassList(element.className);
return Boolean(list.find(name => classList.indexOf(name) >= 0));
};
};
/**
* @hidden
*/
const nodeIndex = (node) => node.getAttribute(NODE_INDEX);
/**
* @hidden
*/
const closestItem = (node, scope) => closestInScope(node, nodeIndex, scope);
/**
* @hidden
*/
const closestList = (node) => {
let list = closest(node, matchesClasses('k-menu-popup k-menu k-menu-group'));
if (list && hasClass(list, 'k-menu-popup')) {
list = list.querySelector('.k-menu-group');
}
return list;
};
/**
* @hidden
*/
const inMenu = (node, itemsService) => {
if (node === itemsService.lists[0].element.nativeElement) {
return false;
}
const list = closestList(node);
return list && itemsService.containsList(list);
};
/**
* @hidden
*/
const findInContainer = (element, selector, container) => {
const id = container.getAttribute('id');
if (!id) {
container.setAttribute('id', DEFAULT_ID);
}
const contextSelector = `#${id || DEFAULT_ID} ${selector}`;
const match = closestInScope(element, node => matches(node, contextSelector), container);
if (!id) {
container.removeAttribute('id');
}
return match;
};
/**
* @hidden
*/
class ContextMenuService {
keydown = new EventEmitter();
owner;
items;
emit(name, args) {
this.owner.emitMenuEvent(name, args);
}
hasObservers(name) {
return this.owner && hasObservers(this.owner[name]);
}
leaveMenu(e) {
return this.items ? !inMenu(e.target, this.items) : true;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ContextMenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ContextMenuService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ContextMenuService, decorators: [{
type: Injectable
}] });
/**
* Represents a template for the Menu items ([see example]({% slug templates_menu %})). To define a template
* for an item, nest an `<ng-template>` tag with the `kendoMenuItemTemplate` directive inside a `<kendo-menu-item>`
* component. To define a template for all Menu items, nest the template inside the `<kendo-menu>` component.
*
* The available fields in the template context are:
* - `item`—The item data.
* - `index`—The item index.
*
* @example
* ```ts-preview
*
* _@Component({
* selector: 'my-app',
* template: `
* <kendo-menu>
* <kendo-menu-item text="item2">
* <ng-template kendoMenuItemTemplate let-item="item" let-index="index">
* <div style="padding: 10px;">
* My Template for: {{ item.text }} at index: {{ index }}
* </div>
* </ng-template>
* </kendo-menu-item>
* </kendo-menu>
* `
* })
*
* class AppComponent {
* }
* ```
*/
class ItemTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ItemTemplateDirective, isStandalone: true, selector: "[kendoMenuItemTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoMenuItemTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{
type: Optional
}] }]; } });
/**
* Represents a template for the links of the Menu items ([see example]({% slug templates_menu %})). To define a template
* for an item, nest an `<ng-template>` tag with the `kendoMenuItemLinkTemplate` directive inside a `<kendo-menu-item>`
* component. To define a template for all Menu items, nest the template inside the `<kendo-menu>` component.
*
* The available fields in the template context are:
* - `item`—The item data.
* - `index`—The item index.
*
* @example
* ```ts-preview
*
* _@Component({
* selector: 'my-app',
* template: `
* <kendo-menu>
* <kendo-menu-item text="item2">
* <ng-template kendoMenuItemLinkTemplate let-item="item" let-index="index">
* <span [kendoMenuItemLink]="index">
* {{ item.text }}
* <span *ngIf="item.items && item.items.length" [kendoMenuExpandArrow]="index"></span>
* </span>
* </ng-template>
* </kendo-menu-item>
* </kendo-menu>
* `
* })
*
* class AppComponent {
* }
* ```
*/
class ItemLinkTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemLinkTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ItemLinkTemplateDirective, isStandalone: true, selector: "[kendoMenuItemLinkTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemLinkTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoMenuItemLinkTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{
type: Optional
}] }]; } });
/**
* Represents a template for the content of the Menu items ([see example]({% slug templates_menu %})). To define the template,
* nest an `<ng-template>` tag with the `kendoMenuItemContentTemplate` directive inside a `<kendo-menu-item>` component.
*
* The available fields in the template context are:
* - `item`—The item data.
* - `index`—The item index.
*
* @example
* ```ts-preview
*
* _@Component({
* selector: 'my-app',
* template: `
* <kendo-menu>
* <kendo-menu-item text="item2">
* <ng-template kendoMenuItemContentTemplate let-item="item" let-index="index">
* <div style="padding: 10px;">
* My Content Template for: {{ item.text }} at index: {{ index }}
* </div>
* </ng-template>
* </kendo-menu-item>
* </kendo-menu>
* `
* })
*
* class AppComponent {
* }
* ```
*/
class ItemContentTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemContentTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ItemContentTemplateDirective, isStandalone: true, selector: "[kendoMenuItemContentTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemContentTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoMenuItemContentTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{
type: Optional
}] }]; } });
/**
* Represents a component that can be used to specify the Menu items
* ([more information and examples]({% slug items_menu %})).
*
* @example
* ```html
* <kendo-menu>
* <kendo-menu-item text="item1">
* <kendo-menu-item text="item1.1" url="https://example.com">
* </kendo-menu-item>
* <kendo-menu-item text="item1.2" [disabled]="true">
* </kendo-menu-item>
* </kendo-menu-item>
* <kendo-menu-item text="item2">
* <ng-template kendoMenuItemContentTemplate let-item="item">
* <div style="padding: 10px;">
* My Content Template: {{ item.text }}
* </div>
* </ng-template>
* <ng-template kendoMenuItemTemplate let-item="item">
* <div style="padding: 10px;">
* My Template: {{ item.text }}
* </div>
* </ng-template>
* </kendo-menu-item>
* <kendo-menu-item text="item3">
* <ng-template kendoMenuItemLinkTemplate let-item="item" let-index="index">
* <span [kendoMenuItemLink]="index">
* {{ item.text }}
* <span *ngIf="item.items && item.items.length" [kendoMenuExpandArrow]="index"></span>
* </span>
* </ng-template>
* </kendo-menu-item>
* </kendo-menu>
* ```
*
* @remarks
* Supported children components are: {@link MenuItemComponent}.
*/
class MenuItemComponent {
text;
url;
disabled;
cssClass;
cssStyle;
icon;
svgIcon;
data;
separator;
/**
* @hidden
*/
itemTemplate;
/**
* @hidden
*/
itemLinkTemplate;
/**
* @hidden
*/
itemContentTemplate;
/**
* @hidden
*/
children;
/**
* @hidden
*/
get template() {
if (this.itemTemplate && this.itemTemplate.length) {
return this.itemTemplate.first.templateRef;
}
}
/**
* @hidden
*/
get linkTemplate() {
if (this.itemLinkTemplate && this.itemLinkTemplate.length) {
return this.itemLinkTemplate.first.templateRef;
}
}
/**
* @hidden
*/
get contentTemplate() {
if (this.itemContentTemplate && this.itemContentTemplate.length) {
return this.itemContentTemplate.first.templateRef;
}
}
/**
* @hidden
*/
get items() {
if (this.children.length) {
return this.children.toArray().filter(c => c !== this);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MenuItemComponent, isStandalone: true, selector: "kendo-menu-item", inputs: { text: "text", url: "url", disabled: "disabled", cssClass: "cssClass", cssStyle: "cssStyle", icon: "icon", svgIcon: "svgIcon", data: "data", separator: "separator" }, queries: [{ propertyName: "itemTemplate", predicate: ItemTemplateDirective }, { propertyName: "itemLinkTemplate", predicate: ItemLinkTemplateDirective }, { propertyName: "itemContentTemplate", predicate: ItemContentTemplateDirective }, { propertyName: "children", predicate: MenuItemComponent }], ngImport: i0, template: ``, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MenuItemComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-menu-item',
template: ``,
standalone: true
}]
}], propDecorators: { text: [{
type: Input
}], url: [{
type: Input
}], disabled: [{
type: Input
}], cssClass: [{
type: Input
}], cssStyle: [{
type: Input
}], icon: [{
type: Input
}], svgIcon: [{
type: Input
}], data: [{
type: Input
}], separator: [{
type: Input
}], itemTemplate: [{
type: ContentChildren,
args: [ItemTemplateDirective]
}], itemLinkTemplate: [{
type: ContentChildren,
args: [ItemLinkTemplateDirective]
}], itemContentTemplate: [{
type: ContentChildren,
args: [ItemContentTemplateDirective]
}], children: [{
type: ContentChildren,
args: [MenuItemComponent]
}] } });
/**
* @hidden
*/
class MenuBase {
/**
* Specifies the Menu items.
*/
items;
/**
* Specifies if the Menu will be vertical ([see example]({% slug vertical_menu %})).
*/
vertical = false;
/**
* Specifies that the root items can be opened only on click
* ([see example]({% slug openclose_menu %}#toc-opening-on-click)).
*/
openOnClick = false;
/**
* Specifies the delay in milliseconds before the Menu items are opened or closed on item hover
* or leave ([see example]({% slug openclose_menu %}#toc-delay-on-hover)). Used to avoid the accidental
* opening or closing of the items.
*/
hoverDelay = 100;
/**
* Sets the Menu animation.
*/
animate = true;
/**
* Sets the Menu size.
*
* The possible values are:
* * `small`
* * `medium` (default)
* * `large`
* * `none`
*
*/
size = 'medium';
/**
* @hidden
*/
itemTemplate;
/**
* @hidden
*/
itemLinkTemplate;
/**
* @hidden
*/
children;
/**
* @hidden
*/
get rootItems() {
return this.items || (this.children ? this.children.toArray() : []);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MenuBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MenuBase, selector: "kendo-menu-base", inputs: { items: "items", vertical: "vertical", openOnClick: "openOnClick", hoverDelay: "hoverDelay", animate: "animate", size: "size" }, queries: [{ propertyName: "itemTemplate", predicate: ItemTemplateDirective }, { propertyName: "itemLinkTemplate", predicate: ItemLinkTemplateDirective }, { propertyName: "children", predicate: MenuItemComponent }], ngImport: i0, template: ``, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MenuBase, decorators: [{
type: Component,
args: [{
selector: 'kendo-menu-base',
template: ``
}]
}], propDecorators: { items: [{
type: Input
}], vertical: [{
type: Input
}], openOnClick: [{
type: Input
}], hoverDelay: [{
type: Input
}], animate: [{
type: Input
}], size: [{
type: Input
}], itemTemplate: [{
type: ContentChildren,
args: [ItemTemplateDirective]
}], itemLinkTemplate: [{
type: ContentChildren,
args: [ItemLinkTemplateDirective]
}], children: [{
type: ContentChildren,
args: [MenuItemComponent]
}] } });
/**
* @hidden
*/
const defined = (value) => typeof value !== 'undefined';
/**
* @hidden
*/
const bodyFactory = () => {
if (isDocumentAvailable()) {
return new ElementRef(document.body);
}
};
/**
* @hidden
*/
const getSizeClass = (size) => {
const SIZE_CLASSES = {
'small': 'k-menu-group-sm',
'medium': 'k-menu-group-md',
'large': 'k-menu-group-lg'
};
return SIZE_CLASSES[size];
};
/**
* @hidden
*/
const getFontIcon = (horizontal, rtl) => {
const icon = horizontal ?
rtl ?
'caret-alt-left' :
'caret-alt-right' :
'caret-alt-down';
return icon;
};
/**
* @hidden
*/
const getSVGIcon = (horizontal, rtl) => {
const icon = horizontal ?
rtl ?
caretAltLeftIcon :
caretAltRightIcon :
caretAltDownIcon;
return icon;
};
const POPUP_ALIGN = {
vertical: 'top',
horizontal: 'left'
};
const POPUP_ALIGN_RTL = {
vertical: 'top',
horizontal: 'right'
};
const VERTICAL_COLLISION = {
vertical: 'flip',
horizontal: 'fit'
};
const HORIZONTAL_COLLISION = {
vertical: 'fit',
horizontal: 'flip'
};
/**
* @hidden
*/
const POPUP_SETTINGS_RTL = {
vertical: {
anchor: {
vertical: 'bottom',
horizontal: 'right'
},
popup: POPUP_ALIGN_RTL,
collision: VERTICAL_COLLISION,
animate: 'down'
},
horizontal: {
anchor: {
vertical: 'top',
horizontal: 'left'
},
popup: POPUP_ALIGN_RTL,
collision: HORIZONTAL_COLLISION,
animate: 'left'
}
};
/**
* @hidden
*/
const POPUP_SETTINGS = {
vertical: {
anchor: {
vertical: 'bottom',
horizontal: 'left'
},
popup: POPUP_ALIGN,
collision: VERTICAL_COLLISION,
animate: 'down'
},
horizontal: {
anchor: {
vertical: 'top',
horizontal: 'right'
},
popup: POPUP_ALIGN,
collision: HORIZONTAL_COLLISION,
animate: 'right'
}
};
/* eslint-disable @angular-eslint/component-selector */
/**
* @hidden
*/
class ListComponent {
itemsService;
hover;
actions;
navigation;
renderer;
ngZone;
element;
appendTo;
items;
level;
index;
animate = true;
size = 'medium';
vertical;
rtl;
openOnClick;
itemTemplate;
itemLinkTemplate;
domSubscriptions;
constructor(itemsService, hover, actions, navigation, renderer, ngZone, element) {
this.itemsService = itemsService;
this.hover = hover;
this.actions = actions;
this.navigation = navigation;
this.renderer = renderer;
this.ngZone = ngZone;
this.element = element;
}
hierarchyIndex(index) {
return this.itemsService.itemIndex(this.index, index);
}
ngOnInit() {
this.itemsService.addList(this);
this.initDomEvents();
}
ngOnDestroy() {
this.itemsService.removeList(this);
if (this.domSubscriptions) {
this.domSubscriptions();
}
}
initDomEvents() {
if (!isDocumentAvailable() || !this.element) {
return;
}
this.ngZone.runOutsideAngular(() => {
const element = this.element.nativeElement;
const container = this.level > 0 ? closest(element, (node) => hasClass(node, 'k-popup')) : element;
const overSubscription = this.renderer.listen(element, 'mouseover', (e) => {
if (e.target === element && this.level === 0) {
this.onLeave();
}
else {
const item = this.nodeItem(e.target) || this.itemsService.get(this.index);
if (item && !(this.openOnClick && this.openOnClick.toggle === 'click' && item.level === 0 && !item.hasContent)) {
this.hover.over(item);
}
}
});
const leaveSubscription = this.renderer.listen(container, 'mouseleave', (e) => {
if (this.leavesMenu(e)) {
this.onLeave();
}
});
const keydownSubscription = this.renderer.listen(element, 'keydown', (e) => {
if (hasClass(e.target, 'k-menu-item')) {
this.navigation.keydown(e);
}
});
const blurSubscription = this.renderer.listen(element, 'focusout', (e) => {
if (this.leavesMenu(e)) {
this.navigation.focusLeave();
}
});
/**
* Handle focus/blur open/close for iOS devices since it behaves inconsistently with the rest
* Refer to: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
*/
const touchSubscription = this.renderer.listen(document, 'touchstart', (e) => {
if (inMenu(e.target, this.itemsService)) {
const item = this.nodeItem(e.target);
// Needs to be called because the 'click' handler will be called only on secondary tap and the item will remain unfocused
this.navigation.focus(item);
// This is needed since the 'mouseover' event is not always dispatched
if (!item.opened) {
this.hover.over(item);
}
}
else if (this.navigation.focusedIdx) {
// If the touch is outside of the menu and the menu is not currently in focus
const activeItem = this.itemsService.get(this.navigation.activeIndex);
this.onLeave(); // needs to be called explicitly since mouseleave event is not triggered
activeItem.blur(); // needs to be called explicitly otherwise the item remains focused => triggers focusout
}
});
const clickSubscription = this.renderer.listen(element, 'click', this.clickHandler.bind(this));
this.domSubscriptions = () => {
overSubscription();
leaveSubscription();
keydownSubscription();
blurSubscription();
clickSubscription();
touchSubscription();
};
});
}
leavesMenu(e) {
if (!e.relatedTarget) {
return true;
}
return !inMenu(e.relatedTarget, this.itemsService);
}
onLeave() {
const openOnClick = this.openOnClick;
if (!openOnClick || openOnClick.toggle !== 'click') {
this.hover.leave(openOnClick && openOnClick.toggle === 'leave');
}
}
nodeItem(target) {
const node = closestItem(target, this.element.nativeElement);
if (node) {
const index = nodeIndex(node);
return this.itemsService.get(index);
}
}
clickHandler(e) {
if (isFocusable(e.target) && !hasClass(e.target, 'k-menu-item')) {
return;
}
const item = this.nodeItem(e.target);
if (!item || item.isContent || item.navigating) {
return;
}
if (item.disabled) {
e.preventDefault();
return;
}
this.actions.select(item, e, () => {
e.preventDefault();
});
this.navigation.focus(item);
if (item.level > 0 && !item.hasContent) {
this.actions.closeToRoot(item);
}
if (this.openOnClick) {
const hover = this.hover;
if (item.opened) {
if (item.level === 0) {
hover.openOnOver = false;
this.actions.close(item);
}
}
else if (item.hasContent) {
hover.openOnOver = true;
this.actions.closeOthers(item);
this.actions.open(item);
}
else {
hover.openOnOver = false;
if (item.level === 0 && this.openOnClick.toggle === 'click') {
this.hover.closeCurrent();
}
}
}
this.actions.execute();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListComponent, deps: [{ token: ItemsService }, { token: HoverService }, { token: ActionsService }, { token: NavigationService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ListComponent, isStandalone: true, selector: "[kendoMenuList]", inputs: { appendTo: "appendTo", items: "items", level: "level", index: "index", animate: "animate", size: "size", vertical: "vertical", rtl: "rtl", openOnClick: "openOnClick", itemTemplate: "itemTemplate", itemLinkTemplate: "itemLinkTemplate" }, ngImport: i0, template: "\n <ng-container *ngFor=\"let item of items; let idx = index\">\n <li *ngIf=\"!item.separator\" \n kendoMenuItem\n [appendTo]=\"appendTo\"\n [item]=\"item\"\n [level]=\"level\"\n [size]=\"size\"\n [vertical]=\"vertical\"\n [animate]=\"animate\"\n [rtl]=\"rtl\"\n [itemTemplate]=\"itemTemplate\"\n