@progress/kendo-angular-treelist
Version:
Kendo UI TreeList for Angular - Display hierarchical data in an Angular tree grid view that supports sorting, filtering, paging, and much more.
227 lines (226 loc) • 9.69 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, Host, HostBinding, HostListener, Input, NgZone } from '@angular/core';
import { Subscription, of } from 'rxjs';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { isBlank, isPresent, isTruthy } from '../utils';
import { ColumnBase } from '../columns/column-base';
import { expandColumns, leafColumns, columnsToRender } from '../columns/column-common';
import { DraggableDirective } from '@progress/kendo-angular-common';
import { ColumnResizingService } from './column-resizing.service';
import { delay, takeUntil, filter, take, tap, switchMap, map } from 'rxjs/operators';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-common";
import * as i2 from "./column-resizing.service";
import * as i3 from "@progress/kendo-angular-l10n";
/**
* @hidden
*/
const fromPercentage = (value, percent) => {
const sign = percent < 0 ? -1 : 1;
return Math.ceil((Math.abs(percent) / 100) * value) * sign;
};
/**
* @hidden
*/
const toPercentage = (value, whole) => (value / whole) * 100;
/**
* @hidden
*/
const headerWidth = (handle) => handle.nativeElement.parentElement.offsetWidth;
/**
* @hidden
*/
const allLeafColumns = columns => expandColumns(columns)
.filter(c => !c.isColumnGroup);
/**
* @hidden
*/
const stopPropagation = ({ originalEvent: event }) => {
event.stopPropagation();
event.preventDefault();
};
/**
* @hidden
*/
const createMoveStream = (service, draggable) => mouseDown => draggable.kendoDrag.pipe(takeUntil(draggable.kendoRelease.pipe(tap(() => service.end()))), map(({ pageX }) => ({
originalX: mouseDown.pageX,
pageX
})));
/**
* @hidden
*/
const preventOnDblClick = release => mouseDown => of(mouseDown).pipe(delay(150), takeUntil(release));
/**
* @hidden
*/
const isInSpanColumn = column => !!(column.parent && column.parent.isSpanColumn);
/**
* @hidden
*
* Calculates the column index. If the column is stated in `SpanColumn`,
* the index for all child columns equals the index of the first child.
*/
const indexOf = (target, list) => {
let index = 0;
let ignore = 0;
let skip = 0;
while (index < list.length) {
const current = list[index];
const isParentSpanColumn = isInSpanColumn(current);
if (current === target) {
break;
}
if ((ignore-- <= 0) && isParentSpanColumn) {
ignore = current.parent.childColumns.length - 1;
skip += ignore;
}
index++;
}
return index - skip;
};
/**
* @hidden
*/
export class ColumnHandleDirective {
draggable;
element;
service;
zone;
cdr;
localization;
columns = [];
column;
get visible() {
return this.column.resizable ? 'block' : 'none';
}
get leftStyle() {
return isTruthy(this.rtl) ? 0 : null;
}
get rightStyle() {
return isTruthy(this.rtl) ? null : 0;
}
subscriptions = new Subscription();
rtl = false;
autoFit() {
const allLeafs = allLeafColumns(this.columns);
const currentLeafs = leafColumns([this.column]).filter(column => isTruthy(column.resizable));
const columnInfo = currentLeafs.map(column => {
const isParentSpan = isInSpanColumn(column);
const isLastInSpan = isParentSpan ? column.parent.childColumns.last === column : false;
const index = indexOf(column, allLeafs);
return {
column,
headerIndex: this.columnsForLevel(column.level).indexOf(column),
index,
isLastInSpan,
isParentSpan,
level: column.level
};
});
currentLeafs.forEach(column => column.width = 0);
this.service.measureColumns(columnInfo);
}
constructor(draggable, element, service, zone, cdr, localization) {
this.draggable = draggable;
this.element = element;
this.service = service;
this.zone = zone;
this.cdr = cdr;
this.localization = localization;
}
ngOnInit() {
const service = this.service.changes.pipe(filter(() => this.column.resizable), filter(e => isPresent(e.columns.find(column => column === this.column))));
this.subscriptions.add(service.pipe(filter(e => e.type === 'start'))
.subscribe(this.initState.bind(this)));
this.subscriptions.add(service.pipe(filter(e => e.type === 'resizeColumn'))
.subscribe(this.resize.bind(this)));
this.subscriptions.add(this.service.changes.pipe(filter(e => e.type === 'start'), filter(this.shouldUpdate.bind(this)), take(1) //on first resize only
).subscribe(this.initColumnWidth.bind(this)));
this.subscriptions.add(this.zone.runOutsideAngular(() => this.draggable.kendoPress.pipe(tap(stopPropagation), tap(() => this.service.start(this.column)), switchMap(preventOnDblClick(this.draggable.kendoRelease)), switchMap(createMoveStream(this.service, this.draggable)))
.subscribe(({ pageX, originalX }) => {
const delta = pageX - originalX;
const percent = toPercentage(delta, this.column.resizeStartWidth || this.column.width);
this.service.resizeColumns(percent);
})));
this.subscriptions.add(service.pipe(filter(e => e.type === 'autoFitComplete'))
.subscribe(this.sizeToFit.bind(this)));
this.subscriptions.add(service.pipe(filter(e => e.type === 'triggerAutoFit'))
.subscribe(this.autoFit.bind(this)));
this.subscriptions.add(this.localization.changes.subscribe(({ rtl }) => this.rtl = rtl));
}
ngOnDestroy() {
if (this.subscriptions) {
this.subscriptions.unsubscribe();
}
}
shouldUpdate() {
return !allLeafColumns(this.columns)
.map(column => column.width)
.some(isBlank);
}
initColumnWidth() {
this.column.width = headerWidth(this.element);
}
initState() {
this.column.resizeStartWidth = headerWidth(this.element);
this.service.resizedColumn({
column: this.column,
oldWidth: this.column.resizeStartWidth
});
}
resize({ deltaPercent }) {
let delta = fromPercentage(this.column.resizeStartWidth, deltaPercent);
if (isTruthy(this.rtl)) {
delta *= -1;
}
const newWidth = Math.max(this.column.resizeStartWidth + delta, this.column.minResizableWidth);
const tableDelta = newWidth > this.column.minResizableWidth ?
delta : this.column.minResizableWidth - this.column.resizeStartWidth;
this.updateWidth(this.column, newWidth);
this.service.resizeTable(this.column, tableDelta);
}
sizeToFit({ columns, widths }) {
const index = columns.indexOf(this.column);
const width = Math.max(...widths.map(w => w[index])) + 1; //add 1px for IE
const tableDelta = width - this.column.resizeStartWidth;
this.updateWidth(this.column, width);
this.service.resizeTable(this.column, tableDelta);
}
updateWidth(column, width) {
column.width = width;
this.cdr.markForCheck(); //force CD cycle
}
columnsForLevel(level) {
return columnsToRender(this.columns ? this.columns.filter(column => column.level === level) : []);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColumnHandleDirective, deps: [{ token: i1.DraggableDirective, host: true }, { token: i0.ElementRef }, { token: i2.ColumnResizingService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: i3.LocalizationService }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ColumnHandleDirective, isStandalone: true, selector: "[kendoTreeListColumnHandle]", inputs: { columns: "columns", column: "column" }, host: { listeners: { "dblclick": "autoFit()" }, properties: { "style.display": "this.visible", "style.left": "this.leftStyle", "style.right": "this.rightStyle" } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColumnHandleDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoTreeListColumnHandle]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i1.DraggableDirective, decorators: [{
type: Host
}] }, { type: i0.ElementRef }, { type: i2.ColumnResizingService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }, { type: i3.LocalizationService }]; }, propDecorators: { columns: [{
type: Input
}], column: [{
type: Input
}], visible: [{
type: HostBinding,
args: ['style.display']
}], leftStyle: [{
type: HostBinding,
args: ['style.left']
}], rightStyle: [{
type: HostBinding,
args: ['style.right']
}], autoFit: [{
type: HostListener,
args: ['dblclick']
}] } });