@progress/kendo-angular-dropdowns
Version:
A wide variety of native Angular dropdown components including AutoComplete, ComboBox, DropDownList, DropDownTree, MultiColumnComboBox, MultiSelect, and MultiSelectTree
173 lines (172 loc) • 6.95 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 { TreeViewComponent } from '@progress/kendo-angular-treeview';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { fetchDescendentNodes, isPresent } from '../../common/util';
import { BaseCheckDirective } from './base-check.directive';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-treeview";
/**
* @hidden
*
* A directive which manages the in-memory checked state of the MultiSelectTree nodes.
*/
export class CheckDirective extends BaseCheckDirective {
treeView;
/**
* Specifies whether items should be checked on click,
* and whether checking a node will also check the node children as well.
* The `checkChildren` prop also enables or disables parent item checking (i.e. checking all child items automatically checks the parent).
*/
checkable;
/**
* The item key/keys by which the data items will be compared.
*/
valueField;
/**
* Defines the collection that will store the full checked items.
*/
checkedItems;
/**
* Fires when the `checkedItems` collection was updated.
*/
checkedItemsChange = new EventEmitter();
/**
* Holds a Set with just the checked item keys.
*
* Should be updated each time the `checkedItems` value or content is changed.
* Can be used for efficient look-up of whether an item is checked or not (O(1) access time).
*/
checkedKeys = new Set();
subscriptions = new Subscription();
clickSubscription;
constructor(treeView) {
super();
this.treeView = treeView;
this.subscriptions.add(this.treeView.checkedChange
.subscribe(this.handleCheckedChange.bind(this)));
this.treeView.isChecked = this.getCheckedState.bind(this);
}
ngOnChanges(changes) {
if (isPresent(changes['checkable'])) {
this.toggleCheckOnClick();
}
if (isPresent(changes['checkedItems'])) {
this.updateItems();
}
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
this.unsubscribeClick();
}
getCheckedState(dataItem, index) {
if (this.isItemChecked({ dataItem, index })) {
return 'checked';
}
else if (this.checkable.checkChildren && this.isItemIndeterminate(this.treeView.itemLookup(index))) {
return 'indeterminate';
}
else {
return 'none';
}
}
handleCheckedChange(node) {
this.checkNode(node);
// parents should be checked if `checkChildren` is set to `true` (single config option for both)
const checkParents = this.checkable.checkChildren;
if (checkParents) {
this.checkParents(node.parent);
}
this.checkedItemsChange.emit(this.checkedItems.slice());
}
toggleCheckOnClick() {
this.unsubscribeClick();
if (this.checkable.checkOnClick) {
this.clickSubscription = this.treeView.nodeClick
.pipe(filter(event => event.type === 'click'))
.subscribe(event => {
const lookup = this.treeView.itemLookup(event.item.index);
this.handleCheckedChange(lookup);
});
}
}
unsubscribeClick() {
if (this.clickSubscription) {
this.clickSubscription.unsubscribe();
this.clickSubscription = null;
}
}
checkNode(lookup) {
if (this.treeView.isDisabled(lookup.item.dataItem, lookup.item.index)) {
return;
}
const target = lookup.item;
const pendingCheck = [target];
// TODO: extract in a separate `checkChildren` method?
if (this.checkable.checkChildren) {
const filter = (item) => this.treeView.isVisible(item.dataItem, item.index) &&
!this.treeView.isDisabled(item.dataItem, item.index);
fetchDescendentNodes(lookup, filter)
.forEach(lookup => pendingCheck.push(lookup.item));
}
const shouldCheck = !this.isItemChecked(target);
pendingCheck.forEach(item => {
if (shouldCheck) {
this.addItem(item);
}
else {
this.removeItem(item);
}
});
}
checkParents(parent) {
let currentParent = parent;
while (currentParent) {
const allChildrenSelected = currentParent.children.every(item => this.isItemChecked(item));
if (allChildrenSelected) {
this.addItem(currentParent.item);
}
else {
this.removeItem(currentParent.item);
}
currentParent = currentParent.parent;
}
}
isItemIndeterminate(lookup) {
const children = lookup.children;
if (!Array.isArray(children) || children.length === 0) {
return false;
}
let index = 0;
let child = children[index];
while (isPresent(child)) {
if (this.isItemChecked(child.item) || this.isItemIndeterminate(child)) {
return true;
}
index += 1;
child = children[index];
}
return false;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CheckDirective, deps: [{ token: i1.TreeViewComponent }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CheckDirective, isStandalone: true, selector: "[kendoMultiSelectTreeCheckable]", inputs: { checkable: "checkable", valueField: "valueField", checkedItems: "checkedItems" }, outputs: { checkedItemsChange: "checkedItemsChange" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CheckDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoMultiSelectTreeCheckable]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i1.TreeViewComponent }]; }, propDecorators: { checkable: [{
type: Input
}], valueField: [{
type: Input
}], checkedItems: [{
type: Input
}], checkedItemsChange: [{
type: Output
}] } });