@progress/kendo-angular-treeview
Version:
Kendo UI TreeView for Angular
246 lines (245 loc) • 8.3 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 } from '@angular/core';
import { Keys } from '@progress/kendo-angular-common';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { Subject } from 'rxjs';
import { NavigationModel } from './navigation-model';
import { nodeIndex, isPresent } from '../utils';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-l10n";
/**
* @hidden
*/
export class NavigationService {
localization;
expands = new Subject();
moves = new Subject();
checks = new Subject();
selects = new Subject();
deselectAllButCurrentItem = new Subject();
loadMore = new Subject();
navigable = true;
selection = 'single';
isTreeViewActive = false;
get model() {
return this._model;
}
set model(model) {
this._model = model;
}
actions = {
[Keys.ArrowUp]: () => this.activate(this.model.findVisiblePrev(this.focusableItem), true),
[Keys.ArrowDown]: () => this.activate(this.model.findVisibleNext(this.focusableItem), true),
[Keys.ArrowLeft]: () => !this.isLoadMoreButton && (this.expand({
expand: this.localization.rtl,
intercept: this.localization.rtl ? this.moveToFirstVisibleChild : this.moveToParent
})),
[Keys.ArrowRight]: () => !this.isLoadMoreButton && (this.expand({
expand: !this.localization.rtl,
intercept: this.localization.rtl ? this.moveToParent : this.moveToFirstVisibleChild
})),
[Keys.Home]: () => this.activate(this.model.firstVisibleNode(), true),
[Keys.End]: () => this.activate(this.model.lastVisibleNode(), true),
[Keys.Enter]: (e) => this.handleEnter(e),
[Keys.Space]: () => this.handleSpace()
};
activeItem;
isFocused = false;
shouldScroll = false;
_model = new NavigationModel();
get activeIndex() {
return nodeIndex(this.activeItem) || null;
}
get isActiveExpanded() {
return this.activeItem && this.activeItem.children.length > 0;
}
get isLoadMoreButton() {
return this.activeItem && this.activeItem.loadMoreButton;
}
get focusableItem() {
return this.activeItem || this.model.firstFocusableNode();
}
constructor(localization) {
this.localization = localization;
this.moveToFirstVisibleChild = this.moveToFirstVisibleChild.bind(this);
this.moveToParent = this.moveToParent.bind(this);
}
activate(item, shouldScroll = false) {
if (!this.navigable || !item || this.isActive(nodeIndex(item))) {
return;
}
this.isFocused = true;
this.activeItem = item || this.activeItem;
this.shouldScroll = shouldScroll;
this.notifyMove();
}
activateParent(index) {
this.activate(this.model.findParent(index));
}
activateIndex(index) {
if (!index) {
return;
}
this.activate(this.model.findNode(index));
}
activateClosest(index) {
if (!index || nodeIndex(this.focusableItem) !== index) {
return;
}
this.activeItem = this.model.closestNode(index);
this.notifyMove();
}
activateFocusable() {
if (this.activeItem) {
return;
}
this.activeItem = this.model.firstVisibleNode();
this.notifyMove();
}
deactivate() {
if (!this.navigable || !this.isFocused) {
return;
}
this.isFocused = false;
this.notifyMove();
}
checkIndex(index) {
if (!this.isDisabled(index)) {
this.checks.next(index);
}
}
selectIndex(index) {
if (!this.isDisabled(index)) {
this.selects.next(index);
}
}
notifyLoadMore(index) {
if (!isPresent(index)) {
return;
}
this.loadMore.next(index);
}
isActive(index) {
if (!index) {
return false;
}
return this.isFocused && this.activeIndex === index;
}
isFocusable(index) {
return nodeIndex(this.focusableItem) === index;
}
isDisabled(index) {
if (!index) {
return false;
}
return this.model.findNode(index).disabled;
}
registerItem(id, index, disabled, loadMoreButton = false, visible = true) {
const itemAtIndex = this.model.findNode(index);
if (isPresent(itemAtIndex)) {
this.model.unregisterItem(itemAtIndex.id, itemAtIndex.index);
if (this.isActive(index)) {
this.deactivate();
}
}
this.model.registerItem(id, index, disabled, loadMoreButton, visible);
}
updateItem(index, disabled, visible = true) {
const itemAtIndex = this.model.findNode(index);
if (isPresent(itemAtIndex)) {
if (this.isActive(index)) {
this.deactivate();
}
}
itemAtIndex.disabled = disabled;
itemAtIndex.visible = visible;
}
unregisterItem(id, index) {
if (this.isActive(index)) {
this.activateParent(index);
}
this.model.unregisterItem(id, index);
}
move(e) {
if (!this.navigable) {
return;
}
const moveAction = this.actions[e.keyCode];
if (!moveAction) {
return;
}
moveAction(e);
e.preventDefault();
}
expand({ expand, intercept }) {
const index = nodeIndex(this.activeItem);
if (!index || intercept(index)) {
return;
}
this.notifyExpand(expand);
}
moveToParent() {
if (this.isActiveExpanded) {
return false;
}
this.activate(this.model.findParent(nodeIndex(this.activeItem)));
return true;
}
moveToFirstVisibleChild() {
if (!this.isActiveExpanded) {
return false;
}
this.activate(this.model.findVisibleChild(nodeIndex(this.activeItem)));
return true;
}
notifyExpand(expand) {
this.expands.next(this.navigationState(expand));
}
notifyMove() {
this.moves.next(this.navigationState());
}
navigationState(expand = false) {
return ({ expand, index: this.activeIndex, isFocused: this.isFocused, shouldScroll: this.shouldScroll });
}
handleEnter(event) {
if (!this.navigable) {
return;
}
if (this.isLoadMoreButton) {
this.notifyLoadMore(this.activeIndex);
}
else {
const isCtrlPressed = event.ctrlKey || event.metaKey;
if (isCtrlPressed) {
this.selectIndex(this.activeIndex);
}
else {
if (this.selection === 'multiple') {
this.deselectAllButCurrentItem.next({ dataItem: this.activeItem, index: this.activeIndex });
}
else {
this.selectIndex(this.activeIndex);
}
}
}
}
handleSpace() {
if (!this.navigable) {
return;
}
if (this.isLoadMoreButton) {
this.notifyLoadMore(this.activeIndex);
}
else {
this.checkIndex(this.activeIndex);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService, deps: [{ token: i1.LocalizationService }], 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: i1.LocalizationService }]; } });