UNPKG

@swimlane/ngx-datatable

Version:

ngx-datatable is an Angular table grid component for presenting large and complex data.

1,296 lines (1,276 loc) 264 kB
import * as i0 from '@angular/core'; import { Directive, EventEmitter, TemplateRef, Output, ContentChild, Input, inject, Injectable, booleanAttribute, numberAttribute, Renderer2, ElementRef, HostBinding, ChangeDetectionStrategy, Component, ChangeDetectorRef, HostListener, KeyValueDiffers, InjectionToken, ViewContainerRef, Injector, signal, IterableDiffers, ViewChild, computed, DOCUMENT, ContentChildren, NgZone, input, effect, NgModule } from '@angular/core'; import { __decorate } from 'tslib'; import { Subject, fromEvent, takeUntil as takeUntil$1 } from 'rxjs'; import { NgTemplateOutlet, NgStyle, NgClass } from '@angular/common'; import { takeUntil } from 'rxjs/operators'; class DataTableFooterTemplateDirective { static ngTemplateContextGuard(directive, context) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableFooterTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DataTableFooterTemplateDirective, isStandalone: true, selector: "[ngx-datatable-footer-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableFooterTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-footer-template]' }] }] }); class DatatableGroupHeaderTemplateDirective { static ngTemplateContextGuard(directive, context) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableGroupHeaderTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DatatableGroupHeaderTemplateDirective, isStandalone: true, selector: "[ngx-datatable-group-header-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableGroupHeaderTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-group-header-template]' }] }] }); class DatatableGroupHeaderDirective { constructor() { /** * Row height is required when virtual scroll is enabled. */ this.rowHeight = 0; /** * Show checkbox at group header to select all rows of the group. */ this.checkboxable = false; /** * Track toggling of group visibility */ this.toggle = new EventEmitter(); } get template() { return this._templateInput || this._templateQuery; } /** * Toggle the expansion of a group */ toggleExpandGroup(group) { this.toggle.emit({ type: 'group', value: group }); } /** * Expand all groups */ expandAllGroups() { this.toggle.emit({ type: 'all', value: true }); } /** * Collapse all groups */ collapseAllGroups() { this.toggle.emit({ type: 'all', value: false }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableGroupHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DatatableGroupHeaderDirective, isStandalone: true, selector: "ngx-datatable-group-header", inputs: { rowHeight: "rowHeight", checkboxable: "checkboxable", _templateInput: ["template", "_templateInput"] }, outputs: { toggle: "toggle" }, queries: [{ propertyName: "_templateQuery", first: true, predicate: DatatableGroupHeaderTemplateDirective, descendants: true, read: TemplateRef, static: true }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableGroupHeaderDirective, decorators: [{ type: Directive, args: [{ selector: 'ngx-datatable-group-header' }] }], propDecorators: { rowHeight: [{ type: Input }], checkboxable: [{ type: Input }], _templateInput: [{ type: Input, args: ['template'] }], _templateQuery: [{ type: ContentChild, args: [DatatableGroupHeaderTemplateDirective, { read: TemplateRef, static: true }] }], toggle: [{ type: Output }] } }); /** * Always returns the empty string '' */ function emptyStringGetter() { return ''; } /** * Returns the appropriate getter function for this kind of prop. * If prop == null, returns the emptyStringGetter. */ function getterForProp(prop) { // TODO requires better typing which will also involve adjust TableColum. So postponing it. if (prop == null) { return emptyStringGetter; } if (typeof prop === 'number') { return numericIndexGetter; } else { // deep or simple if (prop.indexOf('.') !== -1) { return deepValueGetter; } else { return shallowValueGetter; } } } /** * Returns the value at this numeric index. * @param row array of values * @param index numeric index * @returns any or '' if invalid index */ function numericIndexGetter(row, index) { if (row == null) { return ''; } // mimic behavior of deepValueGetter if (!row || index == null) { return row; } const value = row[index]; if (value == null) { return ''; } return value; } /** * Returns the value of a field. * (more efficient than deepValueGetter) * @param obj object containing the field * @param fieldName field name string */ function shallowValueGetter(obj, fieldName) { if (obj == null) { return ''; } if (!obj || !fieldName) { return obj; } const value = obj[fieldName]; if (value == null) { return ''; } return value; } /** * Returns a deep object given a string. zoo['animal.type'] */ function deepValueGetter(obj, path) { if (obj == null) { return ''; } if (!obj || !path) { return obj; } // check if path matches a root-level field // { "a.b.c": 123 } let current = obj[path]; if (current !== undefined) { return current; } current = obj; const split = path.split('.'); if (split.length) { for (let i = 0; i < split.length; i++) { current = current[split[i]]; // if found undefined, return empty string if (current === undefined || current === null) { return ''; } } } return current; } function optionalGetterForProp(prop) { return prop ? row => getterForProp(prop)(row, prop) : undefined; } /** * This functions rearrange items by their parents * Also sets the level value to each of the items * * Note: Expecting each item has a property called parentId * Note: This algorithm will fail if a list has two or more items with same ID * NOTE: This algorithm will fail if there is a deadlock of relationship * * For example, * * Input * * id -> parent * 1 -> 0 * 2 -> 0 * 3 -> 1 * 4 -> 1 * 5 -> 2 * 7 -> 8 * 6 -> 3 * * * Output * id -> level * 1 -> 0 * --3 -> 1 * ----6 -> 2 * --4 -> 1 * 2 -> 0 * --5 -> 1 * 7 -> 8 * * * @param rows * */ function groupRowsByParents(rows, from, to) { if (from && to) { const treeRows = rows.filter(row => !!row).map(row => new TreeNode(row)); const uniqIDs = new Map(treeRows.map(node => [to(node.row), node])); const rootNodes = treeRows.reduce((root, node) => { const fromValue = from(node.row); const parent = uniqIDs.get(fromValue); if (parent) { node.row.level = parent.row.level + 1; // TODO: should be reflected by type, that level is defined node.parent = parent; parent.children.push(node); } else { node.row.level = 0; root.push(node); } return root; }, []); return rootNodes.flatMap(child => child.flatten()); } else { return rows; } } class TreeNode { constructor(row) { this.row = row; this.children = []; } flatten() { if (this.row.treeStatus === 'expanded') { return [this.row, ...this.children.flatMap(child => child.flatten())]; } else { return [this.row]; } } } class DataTableColumnHeaderDirective { static ngTemplateContextGuard(directive, context) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DataTableColumnHeaderDirective, isStandalone: true, selector: "[ngx-datatable-header-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnHeaderDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-header-template]' }] }] }); class DataTableColumnCellDirective { constructor() { this.template = inject(TemplateRef); } static ngTemplateContextGuard(dir, ctx) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnCellDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DataTableColumnCellDirective, isStandalone: true, selector: "[ngx-datatable-cell-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnCellDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-cell-template]' }] }] }); class DataTableColumnCellTreeToggle { constructor() { this.template = inject(TemplateRef); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnCellTreeToggle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DataTableColumnCellTreeToggle, isStandalone: true, selector: "[ngx-datatable-tree-toggle]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnCellTreeToggle, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-tree-toggle]' }] }] }); /** * service to make DatatableComponent aware of changes to * input bindings of DataTableColumnDirective */ class ColumnChangesService { constructor() { this.columnInputChanges = new Subject(); } get columnInputChanges$() { return this.columnInputChanges.asObservable(); } onInputChange() { this.columnInputChanges.next(undefined); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: ColumnChangesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: ColumnChangesService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: ColumnChangesService, decorators: [{ type: Injectable }] }); class DataTableColumnGhostCellDirective { static ngTemplateContextGuard(directive, context) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnGhostCellDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DataTableColumnGhostCellDirective, isStandalone: true, selector: "[ngx-datatable-ghost-cell-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnGhostCellDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-ghost-cell-template]' }] }] }); class DataTableColumnDirective { constructor() { this.columnChangesService = inject(ColumnChangesService); this.isFirstChange = true; } get cellTemplate() { return this._cellTemplateInput || this._cellTemplateQuery; } get headerTemplate() { return this._headerTemplateInput || this._headerTemplateQuery; } get treeToggleTemplate() { return this._treeToggleTemplateInput || this._treeToggleTemplateQuery; } get ghostCellTemplate() { return this._ghostCellTemplateInput || this._ghostCellTemplateQuery; } ngOnChanges() { if (this.isFirstChange) { this.isFirstChange = false; } else { this.columnChangesService.onInputChange(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.0.5", type: DataTableColumnDirective, isStandalone: true, selector: "ngx-datatable-column", inputs: { name: "name", prop: "prop", bindAsUnsafeHtml: ["bindAsUnsafeHtml", "bindAsUnsafeHtml", booleanAttribute], frozenLeft: ["frozenLeft", "frozenLeft", booleanAttribute], frozenRight: ["frozenRight", "frozenRight", booleanAttribute], flexGrow: ["flexGrow", "flexGrow", numberAttribute], resizeable: ["resizeable", "resizeable", booleanAttribute], comparator: "comparator", pipe: "pipe", sortable: ["sortable", "sortable", booleanAttribute], draggable: ["draggable", "draggable", booleanAttribute], canAutoResize: ["canAutoResize", "canAutoResize", booleanAttribute], minWidth: ["minWidth", "minWidth", numberAttribute], width: ["width", "width", numberAttribute], maxWidth: ["maxWidth", "maxWidth", numberAttribute], checkboxable: ["checkboxable", "checkboxable", booleanAttribute], headerCheckboxable: ["headerCheckboxable", "headerCheckboxable", booleanAttribute], headerClass: "headerClass", cellClass: "cellClass", isTreeColumn: ["isTreeColumn", "isTreeColumn", booleanAttribute], treeLevelIndent: "treeLevelIndent", summaryFunc: "summaryFunc", summaryTemplate: "summaryTemplate", _cellTemplateInput: ["cellTemplate", "_cellTemplateInput"], _headerTemplateInput: ["headerTemplate", "_headerTemplateInput"], _treeToggleTemplateInput: ["treeToggleTemplate", "_treeToggleTemplateInput"], _ghostCellTemplateInput: ["ghostCellTemplate", "_ghostCellTemplateInput"] }, queries: [{ propertyName: "_cellTemplateQuery", first: true, predicate: DataTableColumnCellDirective, descendants: true, read: TemplateRef, static: true }, { propertyName: "_headerTemplateQuery", first: true, predicate: DataTableColumnHeaderDirective, descendants: true, read: TemplateRef, static: true }, { propertyName: "_treeToggleTemplateQuery", first: true, predicate: DataTableColumnCellTreeToggle, descendants: true, read: TemplateRef, static: true }, { propertyName: "_ghostCellTemplateQuery", first: true, predicate: DataTableColumnGhostCellDirective, descendants: true, read: TemplateRef, static: true }], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableColumnDirective, decorators: [{ type: Directive, args: [{ selector: 'ngx-datatable-column' }] }], propDecorators: { name: [{ type: Input }], prop: [{ type: Input }], bindAsUnsafeHtml: [{ type: Input, args: [{ transform: booleanAttribute }] }], frozenLeft: [{ type: Input, args: [{ transform: booleanAttribute }] }], frozenRight: [{ type: Input, args: [{ transform: booleanAttribute }] }], flexGrow: [{ type: Input, args: [{ transform: numberAttribute }] }], resizeable: [{ type: Input, args: [{ transform: booleanAttribute }] }], comparator: [{ type: Input }], pipe: [{ type: Input }], sortable: [{ type: Input, args: [{ transform: booleanAttribute }] }], draggable: [{ type: Input, args: [{ transform: booleanAttribute }] }], canAutoResize: [{ type: Input, args: [{ transform: booleanAttribute }] }], minWidth: [{ type: Input, args: [{ transform: numberAttribute }] }], width: [{ type: Input, args: [{ transform: numberAttribute }] }], maxWidth: [{ type: Input, args: [{ transform: numberAttribute }] }], checkboxable: [{ type: Input, args: [{ transform: booleanAttribute }] }], headerCheckboxable: [{ type: Input, args: [{ transform: booleanAttribute }] }], headerClass: [{ type: Input }], cellClass: [{ type: Input }], isTreeColumn: [{ type: Input, args: [{ transform: booleanAttribute }] }], treeLevelIndent: [{ type: Input }], summaryFunc: [{ type: Input }], summaryTemplate: [{ type: Input }], _cellTemplateInput: [{ type: Input, args: ['cellTemplate'] }], _cellTemplateQuery: [{ type: ContentChild, args: [DataTableColumnCellDirective, { read: TemplateRef, static: true }] }], _headerTemplateInput: [{ type: Input, args: ['headerTemplate'] }], _headerTemplateQuery: [{ type: ContentChild, args: [DataTableColumnHeaderDirective, { read: TemplateRef, static: true }] }], _treeToggleTemplateInput: [{ type: Input, args: ['treeToggleTemplate'] }], _treeToggleTemplateQuery: [{ type: ContentChild, args: [DataTableColumnCellTreeToggle, { read: TemplateRef, static: true }] }], _ghostCellTemplateInput: [{ type: Input, args: ['ghostCellTemplate'] }], _ghostCellTemplateQuery: [{ type: ContentChild, args: [DataTableColumnGhostCellDirective, { read: TemplateRef, static: true }] }] } }); class DatatableRowDetailTemplateDirective { static ngTemplateContextGuard(directive, context) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableRowDetailTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DatatableRowDetailTemplateDirective, isStandalone: true, selector: "[ngx-datatable-row-detail-template]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableRowDetailTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[ngx-datatable-row-detail-template]' }] }] }); class DatatableRowDetailDirective { constructor() { /** * The detail row height is required especially * when virtual scroll is enabled. */ this.rowHeight = 0; /** * Row detail row visbility was toggled. */ this.toggle = new EventEmitter(); } get template() { return this._templateInput || this._templateQuery; } /** * Toggle the expansion of the row */ toggleExpandRow(row) { this.toggle.emit({ type: 'row', value: row }); } /** * API method to expand all the rows. */ expandAllRows() { this.toggle.emit({ type: 'all', value: true }); } /** * API method to collapse all the rows. */ collapseAllRows() { this.toggle.emit({ type: 'all', value: false }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableRowDetailDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DatatableRowDetailDirective, isStandalone: true, selector: "ngx-datatable-row-detail", inputs: { rowHeight: "rowHeight", _templateInput: ["template", "_templateInput"] }, outputs: { toggle: "toggle" }, queries: [{ propertyName: "_templateQuery", first: true, predicate: DatatableRowDetailTemplateDirective, descendants: true, read: TemplateRef, static: true }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableRowDetailDirective, decorators: [{ type: Directive, args: [{ selector: 'ngx-datatable-row-detail' }] }], propDecorators: { rowHeight: [{ type: Input }], _templateInput: [{ type: Input, args: ['template'] }], _templateQuery: [{ type: ContentChild, args: [DatatableRowDetailTemplateDirective, { read: TemplateRef, static: true }] }], toggle: [{ type: Output }] } }); class DatatableFooterDirective { get template() { return this._templateInput || this._templateQuery; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.5", type: DatatableFooterDirective, isStandalone: true, selector: "ngx-datatable-footer", inputs: { _templateInput: ["template", "_templateInput"] }, queries: [{ propertyName: "_templateQuery", first: true, predicate: DataTableFooterTemplateDirective, descendants: true, read: TemplateRef }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DatatableFooterDirective, decorators: [{ type: Directive, args: [{ selector: 'ngx-datatable-footer' }] }], propDecorators: { _templateInput: [{ type: Input, args: ['template'] }], _templateQuery: [{ type: ContentChild, args: [DataTableFooterTemplateDirective, { read: TemplateRef }] }] } }); class ScrollerComponent { constructor() { this.renderer = inject(Renderer2); this.scroll = new EventEmitter(); this.scrollYPos = 0; this.scrollXPos = 0; this.prevScrollYPos = 0; this.prevScrollXPos = 0; this.element = inject(ElementRef).nativeElement; this._scrollEventListener = null; } ngOnInit() { // manual bind so we don't always listen if (this.scrollbarV || this.scrollbarH) { const renderer = this.renderer; this.parentElement = renderer.parentNode(this.element); this._scrollEventListener = this.onScrolled.bind(this); this.parentElement?.addEventListener('scroll', this._scrollEventListener); } } ngOnDestroy() { if (this._scrollEventListener) { this.parentElement?.removeEventListener('scroll', this._scrollEventListener); this._scrollEventListener = null; } } setOffset(offsetY) { if (this.parentElement) { this.parentElement.scrollTop = offsetY; } } onScrolled(event) { const dom = event.currentTarget; requestAnimationFrame(() => { this.scrollYPos = dom.scrollTop; this.scrollXPos = dom.scrollLeft; this.updateOffset(); }); } updateOffset() { let direction; if (this.scrollYPos < this.prevScrollYPos) { direction = 'down'; } else { direction = 'up'; } this.scroll.emit({ direction, scrollYPos: this.scrollYPos, scrollXPos: this.scrollXPos }); this.prevScrollYPos = this.scrollYPos; this.prevScrollXPos = this.scrollXPos; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: ScrollerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.5", type: ScrollerComponent, isStandalone: true, selector: "datatable-scroller", inputs: { scrollbarV: "scrollbarV", scrollbarH: "scrollbarH", scrollHeight: "scrollHeight", scrollWidth: "scrollWidth" }, outputs: { scroll: "scroll" }, host: { properties: { "style.height.px": "this.scrollHeight", "style.width.px": "this.scrollWidth" }, classAttribute: "datatable-scroll" }, ngImport: i0, template: ` <ng-content></ng-content> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: ScrollerComponent, decorators: [{ type: Component, args: [{ selector: 'datatable-scroller', template: ` <ng-content></ng-content> `, host: { class: 'datatable-scroll' }, changeDetection: ChangeDetectionStrategy.OnPush }] }], propDecorators: { scrollbarV: [{ type: Input }], scrollbarH: [{ type: Input }], scrollHeight: [{ type: HostBinding, args: ['style.height.px'] }, { type: Input }], scrollWidth: [{ type: HostBinding, args: ['style.width.px'] }, { type: Input }], scroll: [{ type: Output }] } }); /** * Returns the columns by pin. */ function columnsByPin(cols) { const ret = { left: [], center: [], right: [] }; if (cols) { for (const col of cols) { if (col.frozenLeft) { ret.left.push(col); } else if (col.frozenRight) { ret.right.push(col); } else { ret.center.push(col); } } } return ret; } /** * Returns the widths of all group sets of a column */ function columnGroupWidths(groups, all) { return { left: columnTotalWidth(groups.left), center: columnTotalWidth(groups.center), right: columnTotalWidth(groups.right), total: Math.floor(columnTotalWidth(all)) }; } /** * Calculates the total width of all columns */ function columnTotalWidth(columns) { return columns?.reduce((total, column) => total + column.width, 0) ?? 0; } function columnsByPinArr(val) { const colsByPin = columnsByPin(val); return [ { type: 'left', columns: colsByPin.left }, { type: 'center', columns: colsByPin.center }, { type: 'right', columns: colsByPin.right } ]; } /** * This object contains the cache of the various row heights that are present inside * the data table. Its based on Fenwick tree data structure that helps with * querying sums that have time complexity of log n. * * Fenwick Tree Credits: http://petr-mitrichev.blogspot.com/2013/05/fenwick-tree-range-updates.html * https://github.com/mikolalysenko/fenwick-tree * */ class RowHeightCache { constructor() { /** * Tree Array stores the cumulative information of the row heights to perform efficient * range queries and updates. Currently the tree is initialized to the base row * height instead of the detail row height. */ this.treeArray = []; } /** * Clear the Tree array. */ clearCache() { this.treeArray = []; } /** * Initialize the Fenwick tree with row Heights. * * @param rows The array of rows which contain the expanded status. * @param rowHeight The row height. * @param detailRowHeight The detail row height. */ initCache(details) { const { rows, rowHeight, detailRowHeight, externalVirtual, indexOffset, rowCount, rowExpansions } = details; const isFn = typeof rowHeight === 'function'; const isDetailFn = typeof detailRowHeight === 'function'; if (!isFn && isNaN(rowHeight)) { throw new Error(`Row Height cache initialization failed. Please ensure that 'rowHeight' is a valid number or function value: (${rowHeight}) when 'scrollbarV' is enabled.`); } // Add this additional guard in case detailRowHeight is set to 'auto' as it wont work. if (!isDetailFn && isNaN(detailRowHeight)) { throw new Error(`Row Height cache initialization failed. Please ensure that 'detailRowHeight' is a valid number or function value: (${detailRowHeight}) when 'scrollbarV' is enabled.`); } const n = externalVirtual ? rowCount : rows.length; this.treeArray = new Array(n); for (let i = 0; i < n; ++i) { this.treeArray[i] = 0; } for (let i = 0; i < n; ++i) { const row = rows[i]; let currentRowHeight = rowHeight; if (isFn) { currentRowHeight = rowHeight(row); } // Add the detail row height to the already expanded rows. // This is useful for the table that goes through a filter or sort. const expanded = rowExpansions.has(row); if (row && expanded) { if (isDetailFn) { const index = indexOffset + i; currentRowHeight += detailRowHeight(row, index); } else { currentRowHeight += detailRowHeight; } } this.update(i, currentRowHeight); } } /** * Given the ScrollY position i.e. sum, provide the rowIndex * that is present in the current view port. Below handles edge cases. */ getRowIndex(scrollY) { if (scrollY === 0) { return 0; } return this.calcRowIndex(scrollY); } /** * When a row is expanded or rowHeight is changed, update the height. This can * be utilized in future when Angular Data table supports dynamic row heights. */ update(atRowIndex, byRowHeight) { if (!this.treeArray.length) { throw new Error(`Update at index ${atRowIndex} with value ${byRowHeight} failed: Row Height cache not initialized.`); } const n = this.treeArray.length; atRowIndex |= 0; while (atRowIndex < n) { this.treeArray[atRowIndex] += byRowHeight; atRowIndex |= atRowIndex + 1; } } /** * Range Sum query from 1 to the rowIndex */ query(atIndex) { if (!this.treeArray.length) { throw new Error(`query at index ${atIndex} failed: Fenwick tree array not initialized.`); } let sum = 0; atIndex |= 0; while (atIndex >= 0) { sum += this.treeArray[atIndex]; atIndex = (atIndex & (atIndex + 1)) - 1; } return sum; } /** * Find the total height between 2 row indexes */ queryBetween(atIndexA, atIndexB) { return this.query(atIndexB) - this.query(atIndexA - 1); } /** * Given the ScrollY position i.e. sum, provide the rowIndex * that is present in the current view port. */ calcRowIndex(sum) { if (!this.treeArray.length) { return 0; } let pos = -1; const dataLength = this.treeArray.length; // Get the highest bit for the block size. const highestBit = Math.pow(2, dataLength.toString(2).length - 1); for (let blockSize = highestBit; blockSize !== 0; blockSize >>= 1) { const nextPos = pos + blockSize; if (nextPos < dataLength && sum >= this.treeArray[nextPos]) { sum -= this.treeArray[nextPos]; pos = nextPos; } } return pos + 1; } } var Keys; (function (Keys) { Keys["up"] = "ArrowUp"; Keys["down"] = "ArrowDown"; Keys["return"] = "Enter"; Keys["escape"] = "Escape"; Keys["left"] = "ArrowLeft"; Keys["right"] = "ArrowRight"; })(Keys || (Keys = {})); var SortDirection; (function (SortDirection) { SortDirection["asc"] = "asc"; SortDirection["desc"] = "desc"; })(SortDirection || (SortDirection = {})); var SortType; (function (SortType) { SortType["single"] = "single"; SortType["multi"] = "multi"; })(SortType || (SortType = {})); var ColumnMode; (function (ColumnMode) { ColumnMode["standard"] = "standard"; ColumnMode["flex"] = "flex"; ColumnMode["force"] = "force"; })(ColumnMode || (ColumnMode = {})); var ContextmenuType; (function (ContextmenuType) { ContextmenuType["header"] = "header"; ContextmenuType["body"] = "body"; })(ContextmenuType || (ContextmenuType = {})); var SelectionType; (function (SelectionType) { SelectionType["single"] = "single"; SelectionType["multi"] = "multi"; SelectionType["multiClick"] = "multiClick"; SelectionType["cell"] = "cell"; SelectionType["checkbox"] = "checkbox"; })(SelectionType || (SelectionType = {})); class DataTableBodyCellComponent { set disabled(value) { this.cellContext.disabled = value; this._disabled = value; } get disabled() { return this._disabled; } set group(group) { this._group = group; this.cellContext.group = group; this.checkValueUpdates(); this.cd.markForCheck(); } get group() { return this._group; } set rowHeight(val) { this._rowHeight = val; this.cellContext.rowHeight = val; this.checkValueUpdates(); this.cd.markForCheck(); } get rowHeight() { return this._rowHeight; } set isSelected(val) { this._isSelected = val; this.cellContext.isSelected = val; this.cd.markForCheck(); } get isSelected() { return this._isSelected; } set expanded(val) { this._expanded = val; this.cellContext.expanded = val; this.cd.markForCheck(); } get expanded() { return this._expanded; } set rowIndex(val) { this._rowIndex = val; this.cellContext.rowIndex = val?.index; this.cellContext.rowInGroupIndex = val?.indexInGroup; this.checkValueUpdates(); this.cd.markForCheck(); } get rowIndex() { return this._rowIndex; } set column(column) { this._column = column; this.cellContext.column = column; this.checkValueUpdates(); this.cd.markForCheck(); } get column() { return this._column; } set row(row) { this._row = row; this.cellContext.row = row; this.checkValueUpdates(); this.cd.markForCheck(); } get row() { return this._row; } set sorts(val) { this._sorts = val; this.sortDir = this.calcSortDir(val); } get sorts() { return this._sorts; } set treeStatus(status) { if (status !== 'collapsed' && status !== 'expanded' && status !== 'loading' && status !== 'disabled') { this._treeStatus = 'collapsed'; } else { this._treeStatus = status; } this.cellContext.treeStatus = this._treeStatus; this.checkValueUpdates(); this.cd.markForCheck(); } get treeStatus() { return this._treeStatus; } get columnCssClasses() { let cls = 'datatable-body-cell'; if (this.column.cellClass) { if (typeof this.column.cellClass === 'string') { cls += ' ' + this.column.cellClass; } else if (typeof this.column.cellClass === 'function') { const res = this.column.cellClass({ row: this.row, group: this.group, column: this.column, value: this.value, rowHeight: this.rowHeight }); if (typeof res === 'string') { cls += ' ' + res; } else if (typeof res === 'object') { const keys = Object.keys(res); for (const k of keys) { if (res[k] === true) { cls += ` ${k}`; } } } } } if (!this.sortDir) { cls += ' sort-active'; } if (this.isFocused && !this._disabled) { cls += ' active'; } if (this.sortDir === SortDirection.asc) { cls += ' sort-asc'; } if (this.sortDir === SortDirection.desc) { cls += ' sort-desc'; } if (this._disabled) { cls += ' row-disabled'; } return cls; } get width() { return this.column.width; } get minWidth() { return this.column.minWidth; } get maxWidth() { return this.column.maxWidth; } get height() { const height = this.rowHeight; if (isNaN(height)) { return height; } return height + 'px'; } constructor() { this.cd = inject(ChangeDetectorRef); this.activate = new EventEmitter(); this.treeAction = new EventEmitter(); this.isFocused = false; this._element = inject(ElementRef).nativeElement; this.cellContext = { onCheckboxChangeFn: (event) => this.onCheckboxChange(event), activateFn: (event) => this.activate.emit(event), row: this.row, group: this.group, value: this.value, column: this.column, rowHeight: this.rowHeight, isSelected: this.isSelected, rowIndex: this.rowIndex?.index, rowInGroupIndex: this.rowIndex?.indexInGroup, treeStatus: this.treeStatus, disabled: this._disabled, onTreeAction: () => this.onTreeAction() }; } ngDoCheck() { this.checkValueUpdates(); } checkValueUpdates() { let value = ''; if (!this.row || !this.column || this.column.prop == undefined) { value = ''; } else { const val = this.column.$$valueGetter(this.row, this.column.prop); const userPipe = this.column.pipe; if (userPipe) { value = userPipe.transform(val); } else if (value !== undefined) { value = val; } } if (this.value !== value) { this.value = value; this.cellContext.value = value; this.cellContext.disabled = this._disabled; this.sanitizedValue = value !== null && value !== undefined ? this.stripHtml(value) : value; this.cd.markForCheck(); } } onFocus() { this.isFocused = true; } onBlur() { this.isFocused = false; } onClick(event) { this.activate.emit({ type: 'click', event, row: this.row, group: this.group, rowHeight: this.rowHeight, column: this.column, value: this.value, cellElement: this._element }); } onDblClick(event) { this.activate.emit({ type: 'dblclick', event, row: this.row, group: this.group, rowHeight: this.rowHeight, column: this.column, value: this.value, cellElement: this._element }); } onKeyDown(event) { const key = event.key; const isTargetCell = event.target === this._element; const isAction = key === Keys.return || key === Keys.down || key === Keys.up || key === Keys.left || key === Keys.right; if (isAction && isTargetCell) { event.preventDefault(); event.stopPropagation(); this.activate.emit({ type: 'keydown', event, row: this.row, group: this.group, rowHeight: this.rowHeight, column: this.column, value: this.value, cellElement: this._element }); } } onCheckboxChange(event) { this.activate.emit({ type: 'checkbox', event, row: this.row, group: this.group, rowHeight: this.rowHeight, column: this.column, value: this.value, cellElement: this._element, treeStatus: 'collapsed' }); } calcSortDir(sorts) { if (!sorts) { return undefined; } const sort = sorts.find(s => s.prop === this.column.prop); return sort?.dir; } stripHtml(html) { if (!html.replace) { return html; } return html.replace(/<\/?[^>]+(>|$)/g, ''); } onTreeAction() { this.treeAction.emit(this.row); } calcLeftMargin(column, row) { const levelIndent = column.treeLevelIndent != null ? column.treeLevelIndent : 50; return column.isTreeColumn ? row.level * levelIndent : 0; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableBodyCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.5", type: DataTableBodyCellComponent, isStandalone: true, selector: "datatable-body-cell", inputs: { displayCheck: "displayCheck", disabled: "disabled", group: "group", rowHeight: "rowHeight", isSelected: "isSelected", expanded: "expanded", rowIndex: "rowIndex", column: "column", row: "row", sorts: "sorts", treeStatus: "treeStatus" }, outputs: { activate: "activate", treeAction: "treeAction" }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "click": "onClick($event)", "dblclick": "onDblClick($event)", "keydown": "onKeyDown($event)" }, properties: { "class": "this.columnCssClasses", "style.width.px": "this.width", "style.minWidth.px": "this.minWidth", "style.maxWidth.px": "this.maxWidth", "style.height": "this.height" } }, ngImport: i0, template: ` <div class="datatable-body-cell-label" [style.margin-left.px]="calcLeftMargin(column, row)"> @if (column.checkboxable && (!displayCheck || displayCheck(row, column, value))) { <label class="datatable-checkbox"> <input type="checkbox" [disabled]="disabled" [checked]="isSelected" (click)="onCheckboxChange($event)" /> </label> } @if (column.isTreeColumn) { @if (!column.treeToggleTemplate) { <button class="datatable-tree-button" [disabled]="treeStatus === 'disabled'" (click)="onTreeAction()" [attr.aria-label]="treeStatus" > <span> @if (treeStatus === 'loading') { <i class="icon datatable-icon-collapse"></i> } @if (treeStatus === 'collapsed') { <i class="icon datatable-icon-up"></i> } @if (treeStatus === 'expanded' || treeStatus === 'disabled') { <i class="icon datatable-icon-down"></i> } </span> </button> } @else { <ng-template [ngTemplateOutlet]="column.treeToggleTemplate" [ngTemplateOutletContext]="{ cellContext: cellContext }" > </ng-template> } } @if (!column.cellTemplate) { @if (column.bindAsUnsafeHtml) { <span [title]="sanitizedValue" [innerHTML]="value"> </span> } @else { <span [title]="sanitizedValue">{{ value }}</span> } } @else { <ng-template [ngTemplateOutlet]="column.cellTemplate" [ngTemplateOutletContext]="cellContext"> </ng-template> } </div> `, isInline: true, styles: [":host{overflow-x:hidden;vertical-align:top;display:inline-block;line-height:1.625}:host:focus{outline:none}:host-context(ngx-datatable.fixed-row) :host{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: DataTableBodyCellComponent, decorators: [{ type: Component, args: [{ selector: 'datatable-body-cell', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <div class="datatable-body-cell-label" [style.margin-left.px]="calcLeftMargin(column, row)"> @if (column.checkboxable && (!displayCheck || displayCheck(row, column, value))) { <label class="datatable-checkbox"> <input type="checkbox" [disabled]="disabled" [checked]="isSelected" (click)="onCheckboxChange($event)" /> </label> } @if (column.isTreeColumn) { @if (!column.treeToggleTemplate) { <button class="datatable-tree-button" [disabled]="treeStatus === 'disabled'" (click)="onTreeAction()" [attr.aria-label]="treeStatus" > <span> @if (treeStatus === 'loading') { <i class="icon datatable-icon-collapse"></i> } @if (treeStatus === 'collapsed') { <i class="icon datatable-icon-up"></i> } @if (treeStatus === 'expanded' || treeStatus === 'disabled') { <i class="icon datatable-icon-down"></i> } </span> </button> } @else { <ng-template [ngTemplateOutlet]="column.treeToggleTemplate" [ngTemplateOutletContext]="{ cellContext: cellContext }" > </ng-template> } } @if (!column.cellTemplate) { @if (column.bindAsUnsafeHtml) { <span [title]="sanitizedValue" [innerHTML]="value"> </span> } @else { <span [title]="sanitizedValue">{{ value }}</span> } } @else { <ng-template [ngTemplateOutlet]="column.cellTemplate" [ngTemplateOutletContext]="cellContext"> </ng-template> } </div> `, imports: [NgTemplateOutlet], styles: [":host{overflow-x:hidden;vertical-align:top;display:inline-block;line-height:1.625}:host:focus{outline:none}:host-context(ngx-datatable.fixed-row) :host{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n"] }] }], ctorParameters: () => [], propDecorators: { displayCheck: [{ type: Input }], disabled: [{ type: Input }], group: [{ type: Input }], rowHeight: [{ type: Input }], isSelected: [{ type: Input }], expanded: [{ type: Input }], rowIndex: [{ type: Input }], column: [{ type: Input }], row: [{ type: Input }], sorts: [{ type: Input }], treeStatus: [{ type: Input }], activate: [{ type: Output }], treeAction: [{ type: Output }], columnCssClasses: [{ type: