@progress/kendo-angular-treeview
Version:
Kendo UI TreeView for Angular
246 lines (245 loc) • 10.4 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 { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
import { anyChanged } from '@progress/kendo-angular-common';
import { NavigationService } from './navigation/navigation.service';
import { SelectionService } from './selection/selection.service';
import { ExpandStateService } from './expand-state.service';
import { IndexBuilderService } from './index-builder.service';
import { TreeViewLookupService } from './treeview-lookup.service';
import { isPresent } from './utils';
import { filter } from 'rxjs/operators';
import * as i0 from "@angular/core";
import * as i1 from "./expand-state.service";
import * as i2 from "./navigation/navigation.service";
import * as i3 from "./selection/selection.service";
import * as i4 from "./treeview-lookup.service";
import * as i5 from "./index-builder.service";
const buildItem = (index, dataItem) => ({ dataItem, index });
let id = 0;
const TREE_ITEM_ROLE = 'treeitem';
const BUTTON_ROLE = 'button';
/**
* @hidden
*
* A directive which manages the expanded state of the TreeView.
*/
export class TreeViewItemDirective {
element;
expandService;
navigationService;
selectionService;
lookupService;
renderer;
ib;
dataItem;
index;
parentDataItem;
parentIndex;
role = TREE_ITEM_ROLE;
loadOnDemand = true;
checkable;
selectable;
expandable;
set isChecked(checked) {
if (checked === 'checked') {
this.ariaChecked = 'true';
}
else if (checked === 'indeterminate') {
this.ariaChecked = 'mixed';
}
else {
this.ariaChecked = 'false';
}
}
isDisabled = false;
isVisible = true;
get isExpanded() {
return this._isExpanded || false;
}
set isExpanded(isExpanded) {
this._isExpanded = isExpanded;
}
get isSelected() {
return this._isSelected || false;
}
set isSelected(isSelected) {
this._isSelected = isSelected;
}
get isButton() {
return this.role === BUTTON_ROLE;
}
get treeItem() {
return buildItem(this.index, this.dataItem);
}
get parentTreeItem() {
return this.parentDataItem ? buildItem(this.parentIndex, this.parentDataItem) : null;
}
ariaChecked = 'false';
id = id++;
_isExpanded;
_isSelected;
isInitialized = false;
subscriptions = [];
constructor(element, expandService, navigationService, selectionService, lookupService, renderer, ib) {
this.element = element;
this.expandService = expandService;
this.navigationService = navigationService;
this.selectionService = selectionService;
this.lookupService = lookupService;
this.renderer = renderer;
this.ib = ib;
this.subscribe();
}
ngOnInit() {
if (this.loadOnDemand && !this.isButton) {
this.lookupService.registerItem(this.treeItem, this.parentTreeItem);
}
this.registerNavigationItem();
this.isInitialized = true;
this.setAttribute('role', this.role);
this.setAriaAttributes();
this.updateTabIndex();
}
ngOnChanges(changes) {
const { index } = changes;
if (anyChanged(['index', 'checkable', 'isChecked', 'expandable', 'isExpanded', 'selectable', 'isSelected'], changes)) {
this.setAriaAttributes();
}
if (this.loadOnDemand && !this.isButton) {
this.moveLookupItem(changes);
}
this.moveNavigationItem(index);
if (anyChanged(['isDisabled', 'isVisible'], changes)) {
this.updateNodeAvailability();
}
}
ngOnDestroy() {
this.navigationService.unregisterItem(this.id, this.index);
if (this.loadOnDemand && !this.isButton) {
this.lookupService.unregisterItem(this.index, this.dataItem);
}
this.subscriptions = this.subscriptions.reduce((list, callback) => (callback.unsubscribe(), list), []);
}
subscribe() {
this.subscriptions = [
this.navigationService.moves
.subscribe((navState) => {
this.updateTabIndex();
this.focusItem(navState.shouldScroll);
}),
this.navigationService.expands
.pipe(filter(({ index }) => index === this.index && !this.isDisabled))
.subscribe(({ expand }) => this.expand(expand))
];
}
registerNavigationItem() {
this.navigationService.registerItem(this.id, this.index, this.isDisabled, this.isButton, this.isVisible);
this.activateItem();
}
activateItem() {
if (this.isDisabled) {
return;
}
const navigationService = this.navigationService;
const selectionService = this.selectionService;
const index = this.index;
selectionService.setFirstSelected(index, this.isSelected);
if (!navigationService.isActive(index) && selectionService.isFirstSelected(index)) {
navigationService.activateIndex(index);
}
}
expand(shouldExpand) {
this.expandService[shouldExpand ? 'expand' : 'collapse'](this.index, this.dataItem);
}
isFocusable() {
return !this.isDisabled && this.navigationService.isFocusable(this.index);
}
focusItem(scrollIntoView = false) {
if (this.isInitialized && this.navigationService.isActive(this.index)) {
this.element.nativeElement.focus({ preventScroll: !scrollIntoView });
}
}
moveLookupItem(changes = {}) {
const { dataItem, index, parentDataItem, parentIndex } = changes;
if ((index && index.firstChange) || //skip first change
(!dataItem && !index && !parentDataItem && !parentIndex)) {
return;
}
const oldIndex = (index || {}).previousValue || this.index;
this.lookupService.replaceItem(oldIndex, this.treeItem, this.parentTreeItem);
}
moveNavigationItem(indexChange = {}) {
const { currentValue, firstChange, previousValue } = indexChange;
if (!firstChange && isPresent(currentValue) && isPresent(previousValue)) {
this.navigationService.unregisterItem(this.id, previousValue);
this.navigationService.registerItem(this.id, currentValue, this.isDisabled, this.isButton);
}
}
updateNodeAvailability() {
const service = this.navigationService;
if (this.isDisabled || !this.isVisible && this.navigationService.isTreeViewActive) {
service.activateClosest(this.index); // activate before updating the item
}
else {
service.activateFocusable();
}
service.updateItem(this.index, this.isDisabled, this.isVisible);
}
setAriaAttributes() {
this.setAttribute('aria-level', this.ib.level(this.index).toString());
// don't render attributes when the component configuration doesn't allow the specified state
this.setAttribute('aria-expanded', this.expandable ? this.isExpanded.toString() : null);
this.setAttribute('aria-selected', this.selectable ? this.isSelected.toString() : null);
this.setAttribute('aria-checked', this.checkable ? this.ariaChecked : null);
}
updateTabIndex() {
this.setAttribute('tabIndex', this.isFocusable() ? '0' : '-1');
}
setAttribute(attr, value) {
if (!isPresent(value)) {
this.renderer.removeAttribute(this.element.nativeElement, attr);
return;
}
this.renderer.setAttribute(this.element.nativeElement, attr, value);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TreeViewItemDirective, deps: [{ token: i0.ElementRef }, { token: i1.ExpandStateService }, { token: i2.NavigationService }, { token: i3.SelectionService }, { token: i4.TreeViewLookupService }, { token: i0.Renderer2 }, { token: i5.IndexBuilderService }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TreeViewItemDirective, isStandalone: true, selector: "[kendoTreeViewItem]", inputs: { dataItem: "dataItem", index: "index", parentDataItem: "parentDataItem", parentIndex: "parentIndex", role: "role", loadOnDemand: "loadOnDemand", checkable: "checkable", selectable: "selectable", expandable: "expandable", isChecked: "isChecked", isDisabled: "isDisabled", isVisible: "isVisible", isExpanded: "isExpanded", isSelected: "isSelected" }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TreeViewItemDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoTreeViewItem]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.ExpandStateService }, { type: i2.NavigationService }, { type: i3.SelectionService }, { type: i4.TreeViewLookupService }, { type: i0.Renderer2 }, { type: i5.IndexBuilderService }]; }, propDecorators: { dataItem: [{
type: Input
}], index: [{
type: Input
}], parentDataItem: [{
type: Input
}], parentIndex: [{
type: Input
}], role: [{
type: Input
}], loadOnDemand: [{
type: Input
}], checkable: [{
type: Input
}], selectable: [{
type: Input
}], expandable: [{
type: Input
}], isChecked: [{
type: Input
}], isDisabled: [{
type: Input
}], isVisible: [{
type: Input
}], isExpanded: [{
type: Input
}], isSelected: [{
type: Input
}] } });