@progress/kendo-angular-treeview
Version:
Kendo UI TreeView for Angular
213 lines (212 loc) • 8.63 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, EventEmitter, Input, Output } from '@angular/core';
import { ExpandableComponent } from './expandable-component';
import { Subscription, merge } from 'rxjs';
import { map } from 'rxjs/operators';
import { isArrayWithAtLeastOneItem, isBoolean, sameValues } from './utils';
import { isChanged } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
import * as i1 from "./expandable-component";
const DEFAULT_FILTER_EXPAND_SETTINGS = {
maxAutoExpandResults: -1,
expandMatches: false,
expandedOnClear: "none"
};
/**
* A directive which manages the expanded state of the TreeView.
* ([see example]({% slug expandedstate_treeview %})).
*/
export class ExpandDirective {
component;
/**
* @hidden
*/
set isExpanded(value) {
this.component.isExpanded = value;
}
/**
* Defines the item key that will be stored in the `expandedKeys` collection.
*/
expandKey;
/**
* Whether or not to auto-expand the nodes leading from the root node to each filter result.
* To fine-tune this behavior, pass a [`FilterExpandSettings`]({% slug api_treeview_filterexpandsettings %}) object to this input.
* @default false
*/
expandOnFilter = false;
get filterExpandSettings() {
const settings = isBoolean(this.expandOnFilter) ? { enabled: this.expandOnFilter } : { ...this.expandOnFilter, enabled: true };
return Object.assign({}, DEFAULT_FILTER_EXPAND_SETTINGS, settings);
}
/**
* Fires when the `expandedKeys` collection was updated.
*/
expandedKeysChange = new EventEmitter();
/**
* Defines the collection that will store the expanded keys.
*/
expandedKeys;
subscriptions = new Subscription();
/**
* Reflectes the internal `expandedKeys` state.
*/
state = new Set();
originalExpandedKeys = new Set();
isFiltered = false;
/**
* Holds the last emitted `expandedKeys` collection.
*/
lastChange;
constructor(component) {
this.component = component;
this.subscriptions.add(merge(this.component.expand.pipe(map(e => ({ expand: true, ...e }))), this.component.collapse.pipe(map(e => ({ expand: false, ...e })))).subscribe(this.toggleExpand.bind(this)));
if (this.component.filterStateChange) {
this.subscriptions.add(this.component.filterStateChange.subscribe(this.handleAutoExpand.bind(this)));
}
this.component.isExpanded = (dataItem, index) => this.state.has(this.itemKey({ dataItem, index }));
}
ngOnChanges(changes) {
if (isChanged('expandedKeys', changes, false) && changes['expandedKeys'].currentValue !== this.lastChange) {
this.state = new Set(changes['expandedKeys'].currentValue);
}
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
/**
* @hidden
*/
itemKey(e) {
if (this.expandKey) {
if (typeof this.expandKey === "string") {
return e.dataItem[this.expandKey];
}
if (typeof this.expandKey === "function") {
return this.expandKey(e);
}
}
return e.index;
}
toggleExpand({ index, dataItem, expand }) {
const key = this.itemKey({ index, dataItem });
const isExpanded = this.state.has(key);
let notify = false;
if (isExpanded && !expand) {
this.state.delete(key);
notify = true;
}
else if (!isExpanded && expand) {
this.state.add(key);
notify = true;
}
if (notify) {
this.notify();
}
}
handleAutoExpand({ nodes, matchCount, term }) {
if (!this.filterExpandSettings.enabled) {
return;
}
const { maxAutoExpandResults, expandMatches: autoExpandMatches, expandedOnClear } = this.filterExpandSettings;
if (!this.isFiltered) {
this.originalExpandedKeys = new Set(this.state);
}
const exitingFilteredState = this.isFiltered && !term;
const maxExceeded = maxAutoExpandResults !== -1 && matchCount > maxAutoExpandResults;
const exitAutoExpandedState = exitingFilteredState || maxExceeded;
if (exitAutoExpandedState) {
switch (expandedOnClear) {
case "initial": {
if (!sameValues(this.state, this.originalExpandedKeys)) {
this.state = this.originalExpandedKeys;
this.notify();
}
break;
}
case "all": {
this.state = new Set(nodes.reduce((acc, rootNode) => {
this.getEveryExpandKey(acc, rootNode);
return acc;
}, []));
this.notify();
break;
}
case "unchanged": {
break;
}
case "none":
default: {
if (this.state.size !== 0) {
this.state.clear();
this.notify();
}
break;
}
}
this.isFiltered = false;
return;
}
const indicesToExpand = new Set(nodes.reduce((acc, rootNode) => {
this.updateExpandedNodes(acc, rootNode, autoExpandMatches);
return acc;
}, []));
if (!sameValues(this.state, indicesToExpand)) {
this.state = indicesToExpand;
this.notify();
}
this.isFiltered = true;
}
/**
* Fills array with the correct expand keys according to wrapper metadata.
*/
updateExpandedNodes = (collection, node, autoExpandMatches) => {
if (node.containsMatches || node.isMatch && autoExpandMatches && isArrayWithAtLeastOneItem(node.children)) {
collection.push(this.itemKey({ dataItem: node.dataItem, index: node.index }));
}
if (isArrayWithAtLeastOneItem(node.children)) {
node.children.forEach(child => {
this.updateExpandedNodes(collection, child, autoExpandMatches);
});
}
};
/**
* Fills array with the expand key of every node.
*/
getEveryExpandKey = (collection, node) => {
if (isArrayWithAtLeastOneItem(node.children)) {
collection.push(this.itemKey({ dataItem: node.dataItem, index: node.index }));
}
if (isArrayWithAtLeastOneItem(node.children)) {
node.children.forEach(child => {
this.getEveryExpandKey(collection, child);
});
}
};
notify() {
this.lastChange = Array.from(this.state);
this.expandedKeysChange.emit(this.lastChange);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ExpandDirective, deps: [{ token: i1.ExpandableComponent }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ExpandDirective, isStandalone: true, selector: "[kendoTreeViewExpandable]", inputs: { isExpanded: "isExpanded", expandKey: ["expandBy", "expandKey"], expandOnFilter: "expandOnFilter", expandedKeys: "expandedKeys" }, outputs: { expandedKeysChange: "expandedKeysChange" }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ExpandDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoTreeViewExpandable]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i1.ExpandableComponent }]; }, propDecorators: { isExpanded: [{
type: Input
}], expandKey: [{
type: Input,
args: ["expandBy"]
}], expandOnFilter: [{
type: Input
}], expandedKeysChange: [{
type: Output
}], expandedKeys: [{
type: Input
}] } });