UNPKG

@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
/**----------------------------------------------------------------------------------------- * 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'] }] } });