@progress/kendo-angular-dropdowns
Version:
A wide variety of native Angular dropdown components including AutoComplete, ComboBox, DropDownList, DropDownTree, MultiColumnComboBox, MultiSelect, and MultiSelectTree
174 lines (173 loc) • 7.27 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 { ChangeDetectorRef, Directive, ElementRef, EventEmitter, HostListener, Input, NgZone, Output, Renderer2 } from '@angular/core';
import { TreeViewComponent } from '@progress/kendo-angular-treeview';
import { fetchDescendentNodes, isPresent } from '../../common/util';
import { BaseCheckDirective } from './base-check.directive';
import * as i0 from "@angular/core";
/**
* @hidden
*
* A directive which manages the in-memory checked state of the MultiSelectTree nodes.
*/
export class CheckAllDirective extends BaseCheckDirective {
element;
zone;
cdr;
renderer;
lastAction;
treeview;
/**
* Defines the collection that will store the full checked items.
*/
checkedItems;
/**
* The item key/keys by which the data items will be compared.
*/
valueField;
focused;
/**
* 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();
currentCheckedState;
currentIndeterminateState;
handleChange(event) {
// Need to store the current checkbox state at the moment of click
this.currentCheckedState = event.checked;
this.currentIndeterminateState = this.isIndeterminate;
this.treeview.nodes.map((_value, index) => {
const itemIndex = String(index);
const itemLookup = this.treeview.itemLookup(itemIndex);
this.checkNode(itemLookup);
});
this.checkedItemsChange.emit(this.checkedItems.slice());
}
get isIndeterminate() {
const isIndeterminate = this.treeview.nodes.some((_node, index) => {
const itemIndex = String(index);
const itemLookup = this.treeview.itemLookup(itemIndex);
return this.someChecked(itemLookup);
});
return this.isChecked ? false : isIndeterminate;
}
get isChecked() {
const isChecked = this.treeview.nodes.every((_node, index) => {
const itemIndex = String(index);
const itemLookup = this.treeview.itemLookup(itemIndex);
return this.allChecked(itemLookup);
});
return isChecked;
}
constructor(element, zone, cdr, renderer) {
super();
this.element = element;
this.zone = zone;
this.cdr = cdr;
this.renderer = renderer;
}
ngOnChanges(changes) {
if (isPresent(changes['checkedItems'])) {
this.updateItems();
this.renderer.setProperty(this.element.nativeElement, 'checked', this.isChecked);
this.renderer.setProperty(this.element.nativeElement, 'indeterminate', this.isIndeterminate);
}
}
ngOnInit() {
if (this.focused) {
this.nextTick(() => this.element.nativeElement.focus());
}
}
nextTick(fn) {
this.zone.runOutsideAngular(() => setTimeout(fn));
}
checkNode(itemLookup) {
if (this.treeview.isDisabled(itemLookup.item.dataItem, itemLookup.item.index)) {
return;
}
const pendingCheck = [];
const filter = (item) => this.treeview.isVisible(item.dataItem, item.index) &&
!this.treeview.isDisabled(item.dataItem, item.index);
pendingCheck.push(itemLookup.item);
fetchDescendentNodes(itemLookup, filter)
.forEach(lookup => pendingCheck.push(lookup.item));
pendingCheck.forEach(item => {
if (this.currentIndeterminateState) {
if (this.lastAction === 'check') {
this.addItem(item);
}
else {
this.removeItem(item);
}
return;
}
if (this.currentCheckedState) {
this.addItem(item);
}
else {
this.removeItem(item);
}
});
}
allChecked(lookup) {
const children = lookup && lookup.children;
if (!Array.isArray(children)) {
return;
}
const childrenChecked = children.every(child => {
if (child.children.length) {
return this.isItemChecked(child.item) && this.allChecked(child);
}
return this.isItemChecked(child.item);
});
return childrenChecked && this.isItemChecked(lookup.item);
}
someChecked(lookup) {
const children = lookup && lookup.children;
if (!Array.isArray(children)) {
return;
}
const childrenChecked = children.some(child => {
if (child.children.length) {
return this.isItemChecked(child.item) || this.someChecked(child);
}
return this.isItemChecked(child.item);
});
return childrenChecked || this.isItemChecked(lookup.item);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CheckAllDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CheckAllDirective, isStandalone: true, selector: "[checkAll]", inputs: { lastAction: "lastAction", treeview: "treeview", checkedItems: "checkedItems", valueField: "valueField", focused: ["checkAll", "focused"] }, outputs: { checkedItemsChange: "checkedItemsChange" }, host: { listeners: { "change": "handleChange($event.target)" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CheckAllDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line
selector: '[checkAll]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { lastAction: [{
type: Input
}], treeview: [{
type: Input
}], checkedItems: [{
type: Input
}], valueField: [{
type: Input
}], focused: [{
type: Input,
args: ['checkAll']
}], checkedItemsChange: [{
type: Output
}], handleChange: [{
type: HostListener,
args: ['change', ['$event.target']]
}] } });