@progress/kendo-angular-menu
Version:
Kendo UI Angular Menu component
269 lines (268 loc) • 9.03 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 { Injectable, NgZone } from '@angular/core';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { ItemsService } from './items.service';
import { ActionsService } from './actions.service';
import { Keys, normalizeKeys } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
import * as i1 from "./items.service";
import * as i2 from "./actions.service";
import * as i3 from "@progress/kendo-angular-l10n";
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
*/
export 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 = normalizeKeys(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: "18.2.14", ngImport: i0, type: NavigationService, deps: [{ token: i1.ItemsService }, { token: i2.ActionsService }, { token: i3.LocalizationService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NavigationService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NavigationService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.ItemsService }, { type: i2.ActionsService }, { type: i3.LocalizationService }, { type: i0.NgZone }] });