UNPKG

@progress/kendo-angular-treeview

Version:
246 lines (245 loc) 10.4 kB
/**----------------------------------------------------------------------------------------- * 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 }] } });