UNPKG

@swimlane/ngx-datatable

Version:

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

1 lines 390 kB
{"version":3,"file":"swimlane-ngx-datatable.mjs","sources":["../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-group-header-template.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-group-header.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-row-def.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/columns/column-cell.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/columns/column-ghost-cell.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/columns/column-header.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/columns/tree.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/columns/column.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/directives/visibility.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/ngx-datatable.config.ts","../../../../projects/swimlane/ngx-datatable/src/lib/services/scrollbar-helper.service.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/camel-case.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/column-prop-getters.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/id.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/sort.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/column-helper.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/column.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/math.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/number-or-undefined-attribute.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/table-token.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/throttle.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/tree.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/keys.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/row-height-cache.ts","../../../../projects/swimlane/ngx-datatable/src/lib/utils/selection.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-group-wrapper.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-cell.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-row.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body-row.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/ghost-loader/ghost-loader.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/ghost-loader/ghost-loader.component.html","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/scroller.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/summary/summary-row.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/body.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/body/progress-bar.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/footer/pager.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/footer/footer.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/footer/footer-template.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/footer/footer.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/directives/datatable-draggable.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/directives/orderable.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/header/header-cell.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/header/header.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/row-detail/row-detail-template.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/row-detail/row-detail.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/datatable.component.ts","../../../../projects/swimlane/ngx-datatable/src/lib/components/datatable.component.html","../../../../projects/swimlane/ngx-datatable/src/lib/directives/disable-row.directive.ts","../../../../projects/swimlane/ngx-datatable/src/lib/ngx-datatable.module.ts","../../../../projects/swimlane/ngx-datatable/src/lib/types/public.types.ts","../../../../projects/swimlane/ngx-datatable/src/public-api.ts","../../../../projects/swimlane/ngx-datatable/src/swimlane-ngx-datatable.ts"],"sourcesContent":["import { Directive } from '@angular/core';\n\nimport { GroupContext } from '../../types/public.types';\n\n@Directive({\n selector: '[ngx-datatable-group-header-template]'\n})\nexport class DatatableGroupHeaderTemplateDirective {\n static ngTemplateContextGuard(\n directive: DatatableGroupHeaderTemplateDirective,\n context: unknown\n ): context is GroupContext {\n return true;\n }\n}\n","import { computed, contentChild, Directive, input, output, TemplateRef } from '@angular/core';\n\nimport { Group, GroupContext, GroupToggleEvents, Row } from '../../types/public.types';\nimport { DatatableGroupHeaderTemplateDirective } from './body-group-header-template.directive';\n\n@Directive({\n selector: 'ngx-datatable-group-header'\n})\nexport class DatatableGroupHeaderDirective<TRow extends Row = any> {\n /**\n * Row height is required when virtual scroll is enabled.\n */\n readonly rowHeight = input<number | ((group?: Group<TRow>, index?: number) => number)>(0);\n\n /**\n * Show checkbox at group header to select all rows of the group.\n */\n readonly checkboxable = input(false);\n\n readonly _templateInput = input<TemplateRef<GroupContext<TRow>>>(undefined, {\n alias: 'template'\n });\n\n readonly _templateQuery = contentChild(DatatableGroupHeaderTemplateDirective, {\n read: TemplateRef\n });\n\n readonly template = computed(() => this._templateInput() ?? this._templateQuery() ?? null);\n\n /**\n * Track toggling of group visibility\n */\n readonly toggle = output<GroupToggleEvents<TRow>>();\n\n /**\n * Toggle the expansion of a group\n */\n toggleExpandGroup(group: Group<TRow>): void {\n this.toggle.emit({\n type: 'group',\n value: group\n });\n }\n\n /**\n * Expand all groups\n */\n expandAllGroups(): void {\n this.toggle.emit({\n type: 'all',\n value: true\n });\n }\n\n /**\n * Collapse all groups\n */\n collapseAllGroups(): void {\n this.toggle.emit({\n type: 'all',\n value: false\n });\n }\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n Directive,\n inject,\n InjectionToken,\n Injector,\n input,\n OnInit,\n TemplateRef,\n ViewContainerRef\n} from '@angular/core';\n\nimport { RowOrGroup } from '../../types/public.types';\n\n/**\n * This component is passed as ng-template and rendered by BodyComponent.\n * BodyComponent uses rowDefInternal to first inject actual row template.\n * This component will render that actual row template.\n */\n@Component({\n selector: 'datatable-row-def',\n imports: [NgTemplateOutlet],\n template: `@if (rowDef.rowDefInternal().rowTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"rowDef.rowDefInternal().rowTemplate\"\n [ngTemplateOutletContext]=\"rowContext\"\n />\n }`\n})\nexport class DatatableRowDefComponent {\n rowDef = inject(ROW_DEF_TOKEN);\n rowContext = {\n ...this.rowDef.rowDefInternal(),\n disabled: this.rowDef.rowDefInternalDisabled()\n };\n}\n\n@Directive({\n selector: '[rowDef]'\n})\nexport class DatatableRowDefDirective {\n static ngTemplateContextGuard(\n _dir: DatatableRowDefDirective,\n ctx: unknown\n ): ctx is RowDefContext {\n return true;\n }\n}\n\n/**\n * @internal To be used internally by ngx-datatable.\n */\n@Directive({\n selector: '[rowDefInternal]'\n})\nexport class DatatableRowDefInternalDirective implements OnInit {\n vc = inject(ViewContainerRef);\n\n readonly rowDefInternal = input.required<RowDefContext>();\n readonly rowDefInternalDisabled = input<boolean>();\n\n ngOnInit(): void {\n this.vc.createEmbeddedView(\n this.rowDefInternal().template,\n {\n ...this.rowDefInternal()\n },\n {\n injector: Injector.create({\n providers: [\n {\n provide: ROW_DEF_TOKEN,\n useValue: this\n }\n ]\n })\n }\n );\n }\n}\nconst ROW_DEF_TOKEN = new InjectionToken<DatatableRowDefInternalDirective>('RowDef');\ninterface RowDefContext {\n template: TemplateRef<unknown>;\n rowTemplate: TemplateRef<unknown>;\n row: RowOrGroup<unknown>;\n index: number;\n}\n","import { Directive, inject, TemplateRef } from '@angular/core';\n\nimport { CellContext } from '../../types/public.types';\n\n@Directive({\n selector: '[ngx-datatable-cell-template]'\n})\nexport class DataTableColumnCellDirective {\n template = inject<TemplateRef<CellContext>>(TemplateRef);\n\n static ngTemplateContextGuard(dir: DataTableColumnCellDirective, ctx: any): ctx is CellContext {\n return true;\n }\n}\n","import { Directive } from '@angular/core';\n\n@Directive({\n selector: '[ngx-datatable-ghost-cell-template]'\n})\nexport class DataTableColumnGhostCellDirective {\n static ngTemplateContextGuard(\n directive: DataTableColumnGhostCellDirective,\n context: unknown\n ): context is void {\n return true;\n }\n}\n","import { Directive } from '@angular/core';\n\nimport { HeaderCellContext } from '../../types/public.types';\n\n@Directive({\n selector: '[ngx-datatable-header-template]'\n})\nexport class DataTableColumnHeaderDirective {\n static ngTemplateContextGuard(\n directive: DataTableColumnHeaderDirective,\n context: unknown\n ): context is HeaderCellContext {\n return true;\n }\n}\n","import { Directive, inject, TemplateRef } from '@angular/core';\n\n@Directive({\n selector: '[ngx-datatable-tree-toggle]'\n})\nexport class DataTableColumnCellTreeToggle {\n template = inject<TemplateRef<any>>(TemplateRef);\n}\n","import {\n booleanAttribute,\n computed,\n contentChild,\n Directive,\n input,\n numberAttribute,\n PipeTransform,\n Signal,\n TemplateRef\n} from '@angular/core';\n\nimport { CellContext, HeaderCellContext, Row } from '../../types/public.types';\nimport { TableColumn, TableColumnProp } from '../../types/table-column.type';\nimport { DataTableColumnCellDirective } from './column-cell.directive';\nimport { DataTableColumnGhostCellDirective } from './column-ghost-cell.directive';\nimport { DataTableColumnHeaderDirective } from './column-header.directive';\nimport { DataTableColumnCellTreeToggle } from './tree.directive';\n\n@Directive({\n selector: 'ngx-datatable-column'\n})\nexport class DataTableColumnDirective<TRow extends Row> {\n readonly name = input<string>();\n readonly prop = input<TableColumnProp>();\n readonly bindAsUnsafeHtml = input(false, { transform: booleanAttribute });\n readonly frozenLeft = input(false, { transform: booleanAttribute });\n readonly frozenRight = input(false, { transform: booleanAttribute });\n readonly flexGrow = input<number, number | string | undefined>(undefined, {\n transform: numberAttribute\n });\n readonly resizeable = input<boolean, boolean | string | undefined>(undefined, {\n transform: booleanAttribute\n });\n readonly comparator = input<\n ((valueA: any, valueB: any, rowA: TRow, rowB: TRow) => number) | undefined\n >();\n readonly pipe = input<PipeTransform | undefined>();\n readonly sortable = input<boolean, boolean | string | undefined>(undefined, {\n transform: booleanAttribute\n });\n readonly draggable = input<boolean, boolean | string | undefined>(undefined, {\n transform: booleanAttribute\n });\n readonly canAutoResize = input<boolean, boolean | string | undefined>(undefined, {\n transform: booleanAttribute\n });\n readonly minWidth = input<number, number | string | undefined>(undefined, {\n transform: numberAttribute\n });\n readonly width = input<number, number | string | undefined>(undefined, {\n transform: numberAttribute\n });\n readonly maxWidth = input<number, number | string | undefined>(undefined, {\n transform: numberAttribute\n });\n readonly checkboxable = input(false, { transform: booleanAttribute });\n readonly headerCheckboxable = input(false, { transform: booleanAttribute });\n readonly headerClass = input<\n string | ((data: { column: TableColumn }) => string | Record<string, boolean>) | undefined\n >();\n readonly cellClass = input<\n | string\n | ((data: {\n row: TRow;\n group?: TRow[];\n column: TableColumn<TRow>;\n value: any;\n rowHeight: number;\n }) => string | Record<string, boolean>)\n | undefined\n >();\n readonly isTreeColumn = input(false, { transform: booleanAttribute });\n readonly treeLevelIndent = input<number | undefined>();\n readonly summaryFunc = input<((cells: any[]) => any) | undefined>();\n readonly summaryTemplate = input<TemplateRef<any> | undefined>();\n\n readonly cellTemplateInput = input<TemplateRef<CellContext<TRow>> | undefined>(undefined, {\n alias: 'cellTemplate'\n });\n readonly cellTemplateQuery = contentChild(DataTableColumnCellDirective, { read: TemplateRef });\n\n readonly headerTemplateInput = input<TemplateRef<HeaderCellContext> | undefined>(undefined, {\n alias: 'headerTemplate'\n });\n readonly headerTemplateQuery = contentChild(DataTableColumnHeaderDirective, {\n read: TemplateRef\n });\n\n readonly treeToggleTemplateInput = input<TemplateRef<any> | undefined>(undefined, {\n alias: 'treeToggleTemplate'\n });\n readonly treeToggleTemplateQuery = contentChild(DataTableColumnCellTreeToggle, {\n read: TemplateRef\n });\n\n readonly ghostCellTemplateInput = input<TemplateRef<void> | undefined>(undefined, {\n alias: 'ghostCellTemplate'\n });\n readonly ghostCellTemplateQuery = contentChild(DataTableColumnGhostCellDirective, {\n read: TemplateRef\n });\n\n /**\n * Computed property that returns the column configuration as a TableColumn object\n */\n readonly column: Signal<TableColumn<TRow>> = computed(() => ({\n name: this.name(),\n prop: this.prop(),\n bindAsUnsafeHtml: this.bindAsUnsafeHtml(),\n frozenLeft: this.frozenLeft(),\n frozenRight: this.frozenRight(),\n flexGrow: this.flexGrow(),\n resizeable: this.resizeable(),\n comparator: this.comparator(),\n pipe: this.pipe(),\n sortable: this.sortable(),\n draggable: this.draggable(),\n canAutoResize: this.canAutoResize(),\n minWidth: this.minWidth(),\n width: this.width(),\n maxWidth: this.maxWidth(),\n checkboxable: this.checkboxable(),\n headerCheckboxable: this.headerCheckboxable(),\n headerClass: this.headerClass(),\n cellClass: this.cellClass(),\n isTreeColumn: this.isTreeColumn(),\n treeLevelIndent: this.treeLevelIndent(),\n summaryFunc: this.summaryFunc(),\n summaryTemplate: this.summaryTemplate(),\n cellTemplate: this.cellTemplateInput() ?? this.cellTemplateQuery(),\n headerTemplate: this.headerTemplateInput() ?? this.headerTemplateQuery(),\n treeToggleTemplate: this.treeToggleTemplateInput() ?? this.treeToggleTemplateQuery(),\n ghostCellTemplate: this.ghostCellTemplateInput() ?? this.ghostCellTemplateQuery()\n }));\n}\n","import {\n Directive,\n ElementRef,\n inject,\n NgZone,\n OnDestroy,\n OnInit,\n output,\n signal\n} from '@angular/core';\n\n/**\n * Visibility Observer Directive\n *\n * Usage:\n *\n * \t\t<div\n * \t\t\tvisibilityObserver\n * \t\t\t(visible)=\"onVisible($event)\">\n * \t\t</div>\n *\n */\n@Directive({\n selector: '[visibilityObserver]',\n host: {\n '[class.visible]': 'isVisible()'\n }\n})\nexport class VisibilityDirective implements OnInit, OnDestroy {\n private element = inject(ElementRef);\n private zone = inject(NgZone);\n\n readonly isVisible = signal(false);\n\n readonly visible = output<boolean>();\n\n timeout?: number;\n\n ngOnInit(): void {\n this.runCheck();\n }\n\n ngOnDestroy(): void {\n clearTimeout(this.timeout);\n }\n\n onVisibilityChange(): void {\n // trigger zone recalc for columns\n this.zone.run(() => {\n this.isVisible.set(true);\n this.visible.emit(true);\n });\n }\n\n runCheck(): void {\n const check = (): void => {\n // https://davidwalsh.name/offsetheight-visibility\n const { offsetHeight, offsetWidth } = this.element.nativeElement;\n\n if (offsetHeight && offsetWidth) {\n clearTimeout(this.timeout);\n this.onVisibilityChange();\n } else {\n clearTimeout(this.timeout);\n this.zone.runOutsideAngular(() => {\n this.timeout = window.setTimeout(() => check(), 50);\n });\n }\n };\n\n this.timeout = window.setTimeout(() => check());\n }\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/** Interface for messages to override default table texts. */\nexport interface NgxDatatableMessages {\n /** Message to show when the array is present but empty */\n emptyMessage: string;\n /** Footer total message */\n totalMessage: string;\n /** Footer selected message */\n selectedMessage: string;\n /** Pager screen reader message for the first page button */\n ariaFirstPageMessage: string;\n /**\n * Pager screen reader message for the n-th page button.\n * It will be rendered as: `{{ariaPageNMessage}} {{n}}`.\n */\n ariaPageNMessage: string;\n /** Pager screen reader message for the previous page button */\n ariaPreviousPageMessage: string;\n /** Pager screen reader message for the next page button */\n ariaNextPageMessage: string;\n /** Pager screen reader message for the last page button */\n ariaLastPageMessage: string;\n /** Row checkbox aria label */\n ariaRowCheckboxMessage: string;\n /** Header checkbox aria label */\n ariaHeaderCheckboxMessage: string;\n /** Group header checkbox aria label */\n ariaGroupHeaderCheckboxMessage: string;\n}\n\n/** CSS classes for icons that override the default table icons. */\nexport interface NgxDatatableCssClasses {\n sortAscending: string;\n sortDescending: string;\n sortUnset: string;\n pagerLeftArrow: string;\n pagerRightArrow: string;\n pagerPrevious: string;\n pagerNext: string;\n treeStatusLoading: string;\n treeStatusExpanded: string;\n treeStatusCollapsed: string;\n}\n\n/**\n * Interface definition for ngx-datatable global configuration\n */\n// TODO those properties should all be required in the interface. Should be changed with signal migration.\nexport interface NgxDatatableConfig {\n messages?: NgxDatatableMessages;\n cssClasses?: NgxDatatableCssClasses;\n headerHeight?: number;\n footerHeight?: number;\n rowHeight?: number;\n defaultColumnWidth?: number;\n}\n\nexport const NGX_DATATABLE_CONFIG = new InjectionToken<NgxDatatableConfig>('ngx-datatable.config');\n\n/**\n * This makes all properties recursively optional.\n *\n * @internal\n */\nexport type AllPartial<T> = { [K in keyof T]?: AllPartial<T[K]> };\n\n/**\n * Interface definition for INgxDatatableConfig global configuration.\n *\n * @deprecated Use {@link NgxDatatableConfig} instead.\n */\nexport type INgxDatatableConfig = NgxDatatableConfig;\n\n/**\n * Provides a global configuration for ngx-datatable.\n *\n * @param overrides The overrides of the table configuration.\n */\nexport const providedNgxDatatableConfig = (overrides: AllPartial<NgxDatatableConfig>): Provider => {\n return {\n provide: NGX_DATATABLE_CONFIG,\n useValue: overrides\n };\n};\n","import { DOCUMENT, inject, Injectable } from '@angular/core';\n\n/**\n * Gets the width of the scrollbar. Nesc for windows\n * http://stackoverflow.com/a/13382873/888165\n */\n@Injectable({ providedIn: 'root' })\nexport class ScrollbarHelper {\n private document = inject(DOCUMENT);\n\n width: number = this.getWidth();\n\n getWidth(): number {\n const outer = this.document.createElement('div');\n outer.style.visibility = 'hidden';\n outer.style.width = '100px';\n this.document.body.appendChild(outer);\n\n const widthNoScroll = outer.offsetWidth;\n outer.style.overflow = 'scroll';\n\n const inner = this.document.createElement('div');\n inner.style.width = '100%';\n outer.appendChild(inner);\n\n const widthWithScroll = inner.offsetWidth;\n this.document.body.removeChild(outer);\n\n return widthNoScroll - widthWithScroll;\n }\n}\n","/**\n * Converts strings from something to camel case\n * http://stackoverflow.com/questions/10425287/convert-dash-separated-string-to-camelcase\n */\nexport const camelCase = (str: string): string => {\n // Replace special characters with a space\n str = str.replace(/[^a-zA-Z0-9 ]/g, ' ');\n // put a space before an uppercase letter\n str = str.replace(/([a-z](?=[A-Z]))/g, '$1 ');\n\n // Lower case first character and some other stuff\n str = str\n .replace(/([^a-zA-Z0-9 ])|^[0-9]+/g, '')\n .trim()\n .toLowerCase();\n\n // uppercase characters preceded by a space or number\n str = str.replace(/([ 0-9]+)([a-zA-Z])/g, (a, b, c) => {\n return b.trim() + c.toUpperCase();\n });\n\n return str;\n};\n\n/**\n * Converts strings from camel case to words\n * http://stackoverflow.com/questions/7225407/convert-camelcasetext-to-camel-case-text\n */\nexport const deCamelCase = (str: string): string => {\n return str.replace(/([A-Z])/g, match => ` ${match}`).replace(/^./, match => match.toUpperCase());\n};\n","import { TableColumnProp } from '../types/table-column.type';\n\n// maybe rename this file to prop-getters.ts\n\nexport type ValueGetter = (obj: any, prop: TableColumnProp) => any;\n\n/**\n * Always returns the empty string ''\n */\nexport const emptyStringGetter = (): string => {\n return '';\n};\n\n/**\n * Returns the appropriate getter function for this kind of prop.\n * If prop == null, returns the emptyStringGetter.\n */\nexport const getterForProp = (prop: TableColumnProp | undefined): ValueGetter => {\n // TODO requires better typing which will also involve adjust TableColum. So postponing it.\n if (prop == null) {\n return emptyStringGetter;\n }\n\n if (typeof prop === 'number') {\n return numericIndexGetter as ValueGetter;\n } else {\n // deep or simple\n if (prop.includes('.')) {\n return deepValueGetter as ValueGetter;\n } else {\n return shallowValueGetter as ValueGetter;\n }\n }\n};\n\n/**\n * Returns the value at this numeric index.\n * @param row array of values\n * @param index numeric index\n * @returns any or '' if invalid index\n */\nexport const numericIndexGetter = (row: any[], index: number): any => {\n if (row == null) {\n return '';\n }\n // mimic behavior of deepValueGetter\n if (!row || index == null) {\n return row;\n }\n\n const value = row[index];\n if (value == null) {\n return '';\n }\n return value;\n};\n\n/**\n * Returns the value of a field.\n * (more efficient than deepValueGetter)\n * @param obj object containing the field\n * @param fieldName field name string\n */\nexport const shallowValueGetter = (obj: any, fieldName: string): any => {\n if (obj == null) {\n return '';\n }\n if (!obj || !fieldName) {\n return obj;\n }\n\n const value = obj[fieldName];\n if (value == null) {\n return '';\n }\n return value;\n};\n\n/**\n * Returns a deep object given a string. zoo['animal.type']\n */\nexport const deepValueGetter = (obj: any, path: string): any => {\n if (obj == null) {\n return '';\n }\n if (!obj || !path) {\n return obj;\n }\n\n // check if path matches a root-level field\n // { \"a.b.c\": 123 }\n let current = obj[path];\n if (current !== undefined) {\n return current;\n }\n\n current = obj;\n const split = path.split('.');\n\n if (split.length) {\n for (let i = 0; i < split.length; i++) {\n current = current[split[i]];\n\n // if found undefined, return empty string\n if (current === undefined || current === null) {\n return '';\n }\n }\n }\n\n return current;\n};\n","/**\n * Creates a unique object id.\n * http://stackoverflow.com/questions/6248666/how-to-generate-short-uid-like-ax4j9z-in-js\n */\nexport const id = () => {\n return ('0000' + ((Math.random() * Math.pow(36, 4)) << 0).toString(36)).slice(-4);\n};\n","import { SortableTableColumnInternal, TableColumnInternal } from '../types/internal.types';\nimport { Group, SortDirection, SortPropDir, SortType } from '../types/public.types';\nimport { TableColumnProp } from '../types/table-column.type';\nimport { getterForProp } from './column-prop-getters';\n\n/**\n * Gets the next sort direction\n */\nexport const nextSortDir = (\n sortType: SortType,\n current: SortDirection | 'desc' | 'asc' | undefined\n): SortDirection | undefined => {\n if (sortType === 'single') {\n if (current === 'asc') {\n return 'desc';\n } else {\n return 'asc';\n }\n } else {\n if (!current) {\n return 'asc';\n } else if (current === 'asc') {\n return 'desc';\n } else if (current === 'desc') {\n return undefined;\n }\n // avoid TS7030: Not all code paths return a value.\n return undefined;\n }\n};\n\n/**\n * Adapted from fueld-ui on 6/216\n * https://github.com/FuelInteractive/fuel-ui/tree/master/src/pipes/OrderBy\n */\nexport const orderByComparator = (a: any, b: any): number => {\n if (a === null || typeof a === 'undefined') {\n a = 0;\n }\n if (b === null || typeof b === 'undefined') {\n b = 0;\n }\n if (a instanceof Date && b instanceof Date) {\n if (a < b) {\n return -1;\n }\n if (a > b) {\n return 1;\n }\n } else if (isNaN(parseFloat(a)) || !isFinite(a) || isNaN(parseFloat(b)) || !isFinite(b)) {\n // Convert to string in case of a=0 or b=0\n a = String(a);\n b = String(b);\n // Isn't a number so lowercase the string to properly compare\n if (a.toLowerCase() < b.toLowerCase()) {\n return -1;\n }\n if (a.toLowerCase() > b.toLowerCase()) {\n return 1;\n }\n } else {\n // Parse strings as numbers to compare properly\n if (parseFloat(a) < parseFloat(b)) {\n return -1;\n }\n if (parseFloat(a) > parseFloat(b)) {\n return 1;\n }\n }\n\n // equal each other\n return 0;\n};\n\n/**\n * creates a shallow copy of the `rows` input and returns the sorted copy. this function\n * does not sort the `rows` argument in place\n */\nexport const sortRows = <TRow>(\n rows: TRow[],\n columns: TableColumnInternal[],\n dirs: SortPropDir[],\n sortOnGroupHeader?: SortPropDir\n): TRow[] => {\n if (!rows) {\n return [];\n }\n if (!dirs?.length || !columns) {\n return [...rows];\n }\n\n const temp = [...rows];\n const cols = columns.reduce(\n (obj, col) => {\n if (col.sortable) {\n obj[col.prop] = col.comparator;\n }\n return obj;\n },\n {} as Record<TableColumnProp, SortableTableColumnInternal['comparator'] | undefined>\n );\n\n // cache valueGetter and compareFn so that they\n // do not need to be looked-up in the sort function body\n const cachedDirs = dirs.map(dir => {\n // When sorting on group header, override prop to 'key'\n const prop = sortOnGroupHeader?.prop === dir.prop ? 'key' : dir.prop;\n // SortDirs may contain columns that are not sortable, so compareFn would be undefined. In that case just return a comparator that returns 0.\n const compareFn = cols[dir.prop] ?? (() => 0);\n return {\n prop,\n dir: dir.dir,\n valueGetter: getterForProp(prop),\n compareFn\n };\n });\n\n return temp.sort((rowA: TRow, rowB: TRow) => {\n for (const cachedDir of cachedDirs) {\n // Get property and valuegetters for column to be sorted\n const { prop, valueGetter } = cachedDir;\n // Get A and B cell values from rows based on properties of the columns\n const propA = valueGetter(rowA, prop);\n const propB = valueGetter(rowB, prop);\n\n // Compare function gets five parameters:\n // Two cell values to be compared as propA and propB\n // Two rows corresponding to the cells as rowA and rowB\n // Direction of the sort for this column as SortDirection\n // Compare can be a standard JS comparison function (a,b) => -1|0|1\n // as additional parameters are silently ignored. The whole row and sort\n // direction enable more complex sort logic.\n const comparison =\n cachedDir.dir !== 'desc'\n ? cachedDir.compareFn(propA, propB, rowA, rowB)\n : -cachedDir.compareFn(propA, propB, rowA, rowB);\n\n // Don't return 0 yet in case of needing to sort by next property\n if (comparison !== 0) {\n return comparison;\n }\n }\n\n return 0;\n });\n};\n\nexport const sortGroupedRows = <TRow>(\n groupedRows: Group<TRow>[],\n columns: TableColumnInternal[],\n dirs: SortPropDir[],\n sortOnGroupHeader: SortPropDir | undefined\n): Group<TRow>[] => {\n if (sortOnGroupHeader) {\n groupedRows = sortRows(groupedRows, columns, dirs, sortOnGroupHeader);\n }\n return groupedRows.map(group => ({ ...group, value: sortRows(group.value, columns, dirs) }));\n};\n","import { signal } from '@angular/core';\n\nimport { TableColumnInternal } from '../types/internal.types';\nimport { Row } from '../types/public.types';\nimport { TableColumn } from '../types/table-column.type';\nimport { camelCase, deCamelCase } from './camel-case';\nimport { getterForProp } from './column-prop-getters';\nimport { id } from './id';\nimport { orderByComparator } from './sort';\n\nexport const toInternalColumn = <T extends Row>(\n columns: TableColumn<T>[],\n defaultColumnWidth = 150\n): TableColumnInternal<T>[] => {\n let hasTreeColumn = false;\n // TS fails to infer the type here.\n return (columns as TableColumn<T>[]).map(column => {\n const prop = column.prop ?? (column.name ? camelCase(column.name) : undefined);\n // Only one column should hold the tree view,\n // Thus if multiple columns are provided with\n // isTreeColumn as true, we take only the first one\n const isTreeColumn = !!column.isTreeColumn && !hasTreeColumn;\n hasTreeColumn = hasTreeColumn || isTreeColumn;\n // TODO: add check if prop or name is provided if sorting is enabled.\n\n return {\n ...column,\n $$id: id(),\n $$originalColumn: column,\n $$valueGetter: getterForProp(prop),\n prop,\n name: column.name ?? (prop ? deCamelCase(String(prop)) : ''),\n resizeable: column.resizeable ?? true,\n sortable: column.sortable ?? true,\n comparator: column.comparator ?? orderByComparator,\n draggable: column.draggable ?? true,\n canAutoResize: column.canAutoResize ?? true,\n width: signal(column.width ?? defaultColumnWidth),\n isTreeColumn,\n // in case of the directive, those are getters, so call them explicitly.\n headerTemplate: column.headerTemplate,\n cellTemplate: column.cellTemplate,\n summaryTemplate: column.summaryTemplate,\n ghostCellTemplate: column.ghostCellTemplate,\n treeToggleTemplate: column.treeToggleTemplate\n } as TableColumnInternal; // TS cannot cast here\n });\n};\n\nexport const toPublicColumn = (column: TableColumnInternal): TableColumn => {\n return {\n ...column.$$originalColumn,\n checkboxable: column.checkboxable,\n frozenLeft: column.frozenLeft,\n frozenRight: column.frozenRight,\n flexGrow: column.flexGrow,\n minWidth: column.minWidth,\n maxWidth: column.maxWidth,\n width: column.width(),\n resizeable: column.resizeable,\n comparator: column.comparator,\n pipe: column.pipe,\n sortable: column.sortable,\n draggable: column.draggable,\n canAutoResize: column.canAutoResize,\n name: column.name,\n prop: column.prop,\n bindAsUnsafeHtml: column.bindAsUnsafeHtml,\n cellTemplate: column.cellTemplate,\n ghostCellTemplate: column.ghostCellTemplate,\n headerTemplate: column.headerTemplate,\n treeToggleTemplate: column.treeToggleTemplate,\n cellClass: column.cellClass,\n headerClass: column.headerClass,\n headerCheckboxable: column.headerCheckboxable,\n isTreeColumn: column.isTreeColumn,\n treeLevelIndent: column.treeLevelIndent,\n summaryFunc: column.summaryFunc,\n summaryTemplate: column.summaryTemplate\n };\n};\n","import {\n ColumnGroupWidth,\n PinnedColumns,\n TableColumnGroup,\n TableColumnInternal\n} from '../types/internal.types';\n\n/**\n * Returns the columns by pin.\n */\nexport const columnsByPin = (cols: TableColumnInternal[]) => {\n const ret: TableColumnGroup = {\n left: [],\n center: [],\n right: []\n };\n\n if (cols) {\n for (const col of cols) {\n if (col.frozenLeft) {\n ret.left.push(col);\n } else if (col.frozenRight) {\n ret.right.push(col);\n } else {\n ret.center.push(col);\n }\n }\n }\n\n return ret;\n};\n\n/**\n * Returns the widths of all group sets of a column\n */\nexport const columnGroupWidths = (\n groups: TableColumnGroup,\n all: TableColumnInternal[]\n): ColumnGroupWidth => {\n return {\n left: columnTotalWidth(groups.left),\n center: columnTotalWidth(groups.center),\n right: columnTotalWidth(groups.right),\n total: Math.floor(columnTotalWidth(all))\n };\n};\n\n/**\n * Calculates the total width of all columns\n */\nexport const columnTotalWidth = (columns?: TableColumnInternal[]) => {\n return columns?.reduce((total, column) => total + column.width(), 0) ?? 0;\n};\n\nexport const columnsByPinArr = (val: TableColumnInternal[]): PinnedColumns[] => {\n const colsByPin = columnsByPin(val);\n return [\n { type: 'left' as const, columns: colsByPin.left },\n { type: 'center' as const, columns: colsByPin.center },\n { type: 'right' as const, columns: colsByPin.right }\n ];\n};\n","import { TableColumnGroup, TableColumnInternal } from '../types/internal.types';\nimport { TableColumnProp } from '../types/table-column.type';\nimport { columnsByPin, columnTotalWidth } from './column';\n\n/**\n * Calculates the Total Flex Grow\n */\nexport const getTotalFlexGrow = (columns: TableColumnInternal[]) => {\n let totalFlexGrow = 0;\n\n for (const c of columns) {\n totalFlexGrow += c.flexGrow ?? 0;\n }\n\n return totalFlexGrow;\n};\n\n/**\n * Adjusts the column widths.\n * Inspired by: https://github.com/facebook/fixed-data-table/blob/master/src/FixedDataTableWidthHelper.js\n */\nexport const adjustColumnWidths = (allColumns: TableColumnInternal[], expectedWidth: number) => {\n const columnsWidth = columnTotalWidth(allColumns);\n const totalFlexGrow = getTotalFlexGrow(allColumns);\n const colsByGroup = columnsByPin(allColumns);\n\n if (columnsWidth !== expectedWidth) {\n scaleColumns(colsByGroup, expectedWidth, totalFlexGrow);\n }\n};\n\n/**\n * Resizes columns based on the flexGrow property, while respecting manually set widths\n */\nconst scaleColumns = (colsByGroup: TableColumnGroup, maxWidth: number, totalFlexGrow: number) => {\n const columns: TableColumnInternal[] = Object.values(colsByGroup).flat();\n let remainingWidth = maxWidth;\n\n // calculate total width and flexgrow points for columns that can be resized\n for (const column of columns) {\n if (column.$$oldWidth) {\n // when manually resized, switch off auto-resize\n column.canAutoResize = false;\n }\n if (!column.canAutoResize) {\n remainingWidth -= column.width();\n totalFlexGrow -= column.flexGrow ?? 0;\n } else {\n column.width.set(0);\n }\n }\n\n const hasMinWidth: Record<TableColumnProp, boolean> = {};\n\n // resize columns until no width is left to be distributed\n do {\n const widthPerFlexPoint = remainingWidth / totalFlexGrow;\n remainingWidth = 0;\n\n for (const column of columns) {\n // if the column can be resize and it hasn't reached its minimum width yet\n if (column.canAutoResize && !hasMinWidth[column.prop]) {\n const newWidth = column.width() + (column.flexGrow ?? 0) * widthPerFlexPoint;\n if (column.minWidth !== undefined && newWidth < column.minWidth) {\n remainingWidth += newWidth - column.minWidth;\n column.width.set(column.minWidth);\n hasMinWidth[column.prop] = true;\n } else {\n column.width.set(newWidth);\n }\n }\n }\n } while (remainingWidth !== 0);\n\n // Adjust for any remaining offset in computed widths vs maxWidth\n const totalWidthAchieved = columns.reduce((acc, col) => acc + col.width(), 0);\n const delta = maxWidth - totalWidthAchieved;\n\n if (delta === 0) {\n return;\n }\n\n // adjust the first column that can be auto-resized respecting the min/max widths\n for (const col of columns.filter(c => c.canAutoResize).sort((a, b) => a.width() - b.width())) {\n if (\n (delta > 0 && (!col.maxWidth || col.width() + delta <= col.maxWidth)) ||\n (delta < 0 && (!col.minWidth || col.width() + delta >= col.minWidth))\n ) {\n col.width.update(value => value + delta);\n break;\n }\n }\n};\n\n/**\n * Forces the width of the columns to\n * distribute equally but overflowing when necessary\n *\n * Rules:\n *\n * - If combined withs are less than the total width of the grid,\n * proportion the widths given the min / max / normal widths to fill the width.\n *\n * - If the combined widths, exceed the total width of the grid,\n * use the standard widths.\n *\n * - If a column is resized, it should always use that width\n *\n * - The proportional widths should never fall below min size if specified.\n *\n * - If the grid starts off small but then becomes greater than the size ( + / - )\n * the width should use the original width; not the newly proportioned widths.\n */\nexport const forceFillColumnWidths = (\n allColumns: TableColumnInternal[],\n expectedWidth: number,\n startIdx: number,\n allowBleed: boolean,\n defaultColWidth: number = 150,\n verticalScrollWidth = 0\n) => {\n const columnsToResize = allColumns\n .slice(startIdx + 1, allColumns.length)\n .filter(c => c.canAutoResize !== false);\n\n for (const column of columnsToResize) {\n if (!column.$$oldWidth) {\n column.$$oldWidth = column.width();\n }\n }\n\n let additionWidthPerColumn = 0;\n let exceedsWindow = false;\n let contentWidth = getContentWidth(allColumns, defaultColWidth);\n let remainingWidth = expectedWidth - contentWidth;\n const initialRemainingWidth = remainingWidth;\n const columnsProcessed: any[] = [];\n const remainingWidthLimit = 1; // when to stop\n\n // This loop takes care of the\n do {\n additionWidthPerColumn = remainingWidth / columnsToResize.length;\n exceedsWindow = contentWidth >= expectedWidth;\n\n for (const column of columnsToResize) {\n // don't bleed if the initialRemainingWidth is same as verticalScrollWidth\n if (exceedsWindow && allowBleed && initialRemainingWidth !== -1 * verticalScrollWidth) {\n column.width.update(value => value || defaultColWidth);\n } else {\n const newSize = (column.width() || defaultColWidth) + additionWidthPerColumn;\n\n if (column.minWidth && newSize < column.minWidth) {\n column.width.set(column.minWidth);\n columnsProcessed.push(column);\n } else if (column.maxWidth && newSize > column.maxWidth) {\n column.width.set(column.maxWidth);\n columnsProcessed.push(column);\n } else {\n column.width.set(newSize);\n }\n }\n\n column.width.update(value => Math.max(0, value));\n }\n\n contentWidth = getContentWidth(allColumns, defaultColWidth);\n remainingWidth = expectedWidth - contentWidth;\n removeProcessedColumns(columnsToResize, columnsProcessed);\n } while (remainingWidth > remainingWidthLimit && columnsToResize.length !== 0);\n\n // reset so we don't have stale values\n for (const column of columnsToResize) {\n column.$$oldWidth = 0;\n }\n};\n\n/**\n * Remove the processed columns from the current active columns.\n */\nconst removeProcessedColumns = (\n columnsToResize: TableColumnInternal[],\n columnsProcessed: TableColumnInternal[]\n) => {\n for (const column of columnsProcessed) {\n const index = columnsToResize.indexOf(column);\n columnsToResize.splice(index, 1);\n }\n};\n\n/**\n * Gets the width of the columns\n */\nconst getContentWidth = (allColumns: TableColumnInternal[], defaultColWidth = 150): number => {\n let contentWidth = 0;\n\n for (const column of allColumns) {\n contentWidth += column.width() || defaultColWidth;\n }\n\n return contentWidth;\n};\n","import { numberAttribute } from '@angular/core';\n\n/**\n * Same as {@link numberAttribute} but returns `undefined` if the value is `undefined`.\n * {@link numberAttribute} would return `NaN` in that case.\n * @param value\n */\n// Must be a function.\nexport function numberOrUndefinedAttribute(value: unknown | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n return numberAttribute(value);\n}\n","import { InjectionToken } from '@angular/core';\n\nimport type { DatatableComponent } from '../components/datatable.component';\n\n/**\n * This token is created to break cycling import error which occurs when we import\n * DatatableComponent in DataTableRowWrapperComponent.\n */\nexport const DATATABLE_COMPONENT_TOKEN = new InjectionToken<DatatableComponent>(\n 'DatatableComponentToken'\n);\n","/**\n * Throttle a function\n */\nexport const throttle = (func: any, wait: number, options?: any) => {\n options ??= {};\n let args: any;\n let result: any;\n let timeout: any = null;\n let previous = 0;\n\n const later = () => {\n previous = options.leading === false ? 0 : +new Date();\n timeout = null;\n result = func(...args);\n };\n\n return (...argsNew: any[]) => {\n const now = +new Date();\n\n if (!previous && options.leading === false) {\n previous = now;\n }\n\n const remaining = wait - (now - previous);\n args = argsNew;\n\n if (remaining <= 0) {\n clearTimeout(timeout);\n timeout = null;\n previous = now;\n result = func(...args);\n } else if (!timeout && options.trailing !== false) {\n timeout = setTimeout(later, remaining);\n }\n\n return result;\n };\n};\n\n/**\n * Throttle decorator\n *\n * class MyClass {\n * throttleable(10)\n * myFn() { ... }\n * }\n */\nexport const throttleable = (duration: number, options?: any) => {\n return (target: any, key: PropertyKey, descriptor: PropertyDescriptor) => {\n return {\n configurable: true,\n enumerable: descriptor.enumerable,\n get: function () {\n Object.defineProperty(this, key, {\n configurable: true,\n enumerable: descriptor.enumerable,\n value: throttle(descriptor.value.bind(this), duration, options)\n });\n\n return (this as any)[key];\n }\n };\n };\n};\n","import { Row } from '../types/public.types';\nimport { TableColumnProp } from '../types/table-column.type';\nimport { getterForProp } from './column-prop-getters';\n\nexport type OptionalValueGetter = ((row: any) => any) | undefined;\nexport const optionalGetterForProp = (prop: TableColumnProp | undefined): OptionalValueGetter => {\n return prop ? row => getterForProp(prop)(row, prop) : undefined;\n};\n\n/**\n * This functions rearrange items by their parents\n * Also sets the level value to each of the items\n *\n * Note: Expecting each item has a property called parentId\n * Note: This algorithm will fail if a list has two or more items with same ID\n * NOTE: This algorithm will fail if there is a deadlock of relationship\n *\n * For example,\n *\n * Input\n *\n * id -> parent\n * 1 -> 0\n * 2 -> 0\n * 3 -> 1\n * 4 -> 1\n * 5 -> 2\n * 7 -> 8\n * 6 -> 3\n *\n *\n * Output\n * id -> level\n * 1 -> 0\n * --3 -> 1\n * ----6 -> 2\n * --4 -> 1\n * 2 -> 0\n * --5 -> 1\n * 7 -> 8\n *\n *\n * @param rows\n *\n */\nexport const groupRowsByParents = <TRow extends Row>(\n rows: (TRow | undefined)[],\n from?: OptionalValueGetter,\n to?: OptionalValueGetter\n): (TRow | undefined)[] => {\n if (from && to) {\n const treeRows = rows.filter(row => !!row).map(row => new TreeNode(row));\n const uniqIDs = new Map(treeRows.map(node => [to(node.row), node]));\n\n const rootNodes = treeRows.reduce((root, node) => {\n const fromValue = from(node.row);\n const parent = uniqIDs.get(fromValue);\n if (parent) {\n node.row.level = parent.row.level! + 1; // TODO: should be reflected by type, that level is defined\n node.parent = parent;\n parent.children.push(node);\n } else {\n node.row.level = 0;\n root.push(node);\n }\n return root;\n }, [] as TreeNode<TRow>[]);\n\n return rootNodes.flatMap(child => child.flatten());\n } else {\n return rows;\n }\n};\n\nclass TreeNode<TRow extends Row> {\n public row: TRow;\n public parent?: TreeNode<TRow>;\n public children: TreeNode<TRow>[];\n\n constructor(row: TRow) {\n this.row = row;\n this.children = [];\n }\n\n flatten(): TRow[] {\n if (this.row.treeStatus === 'expanded') {\n return [this.row, ...this.children.flatMap(child => child.flatten())];\n } else {\n return [this.row];\n }\n }\n}\n\nexport const expandToRow = <TRow extends Row>(\n targetRow: TRow,\n rows: (TRow | undefined)[],\n from?: OptionalValueGetter,\n to?: OptionalValueGetter\n) => {\n if (from && to) {\n const uniqIDs = new Map(rows.filter(row => !!row).map(node => [to(node), node]));\n const visitedRowIds = new Set<unknown>();\n let currentRow: TRow | undefined = targetRow;\n\n while (currentRow) {\n const currentRowId = to(currentRow);\n if (visitedRowIds.has(currentRowId)) {\n // cycle detected, abort to avoid an infinite loop\n break;\n }\n visitedRowIds.add(currentRowId);\n\n if (currentRow.treeStatus === 'collapsed') {\n currentRow.treeStatus = 'expanded';\n }\n currentRow = uniqIDs.get(from(currentRow));\n }\n }\n};\n","export const ARROW_UP = 'ArrowUp';\nexport const ARROW_DOWN = 'ArrowDown';\nexport const ENTER = 'Enter';\nexport const ESCAPE = 'Escape';\nexport const ARROW_LEFT = 'ArrowLeft';\nexport const ARROW_RIGHT = 'ArrowRight';\n","/**\n * This object contains the cache of the various row heights that are present inside\n * the data table. Its based on Fenwick tree data structure that helps with\n * querying sums that have time complexity of log n.\n *\n * Fenwick Tree Credits: http://petr-mitrichev.blogspot.com/2013/05/fenwick-tree-range-updates.html\n * https://github.com/mikolalysenko/fenwick-tree\n *\n */\nexport class RowHeightCache<TRow> {\n /**\n * Tree Array stores the cumulative information of the row heights to perform efficient\n * range queries and updates. Currently the tree is initialized to the base row\n * height instead of the detail row height.\n */\n private treeArray: number[] = [];\n\n /**\n * Clear the Tree array.\n */\n clearCache(): void {\n this.treeArray = [];\n }\n\n /**\n * Initialize the Fenwick tree with row Heights.\n */\n initCache(details: {\n rows: TRow[];\n rowHeight: number | 'auto' | ((row: TRow) => number);\n detailRowHeight: number | ((row: TRow, index: number) => number);\n externalVirtual: boolean | undefined;\n indexOffset: number;\n rowCount: number;\n rowExpansions: Set<TRow>;\n }): void {\n const {\n rows,\n rowHeight,\n detailRowHeight,\n externalVirtual,\n indexOffset,\n rowCount,\n rowExpansions\n } = details;\n const isFn = typeof rowHeight === 'function';\n const isDetailFn = typeof detailRowHeight === 'function';\n\n if (rowHeight === 'auto' || (!isFn && isNaN(rowHeight))) {\n throw new Error(`Row Height cache initialization failed. Please ensure that 'rowHeight' is a\n valid number or function value: (${rowHeight}) when 'scrollbarV' is enabled.`);\n }\n\n // Add this additional guard in case detailRowHeight is set to 'auto' as it wont work.\n if (!isDetailFn && isNaN(detailRowHeight)) {\n throw new Error(`Row Height cache initialization failed. Please ensure that 'detailRowHeight' is a\n valid number or function value: (${detailRowHeight}) when 'scrollbarV' is enabled.`);\n }\n\n const n = externalVirtual ? rowCount : rows.length;\n this.treeArray = new Array(n);\n\n for (let i = 0; i < n; ++i) {\n this.treeArray[i] = 0;\n }\n\n for (let i = 0; i < n; ++i) {\n const row = rows[i];\n let currentRowHeight = isFn ? rowHeight(row) : rowHeight;\n\n // Add the detail row height to the already expanded rows.\n // This is useful for the table that goes through a filter or sort.\n const expanded = rowExpansions.has(row);\n if (row && expanded) {\n currentRowHeight += isDetailFn ? detailRowHeight(row, indexOffset + i) : detailRowHeight;\n }\n\n this.update(i, currentRowHeight);\n }\n }\n\n /**\n * Given the ScrollY position i.e. sum, provide the rowIndex\n * that is present in the current view port. Below handles edge cases.\n */\n getRowIndex(scrollY: number): number {\n if (scrollY === 0) {\n return 0;\n }\n return this.calcRowIndex(scrollY);\n }\n\n /**\n * When a row is expanded or rowHeight is changed, update the height. This can\n * be utilized in future when Angular Data table supports dynamic row heights.\n */\n update(atR