UNPKG

@progress/kendo-angular-menu

Version:

Kendo UI Angular Menu component

269 lines (268 loc) 9.03 kB
/**----------------------------------------------------------------------------------------- * 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 }] });