@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
JavaScript
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: