UNPKG

@clr/angular

Version:

Angular components for Clarity

1 lines 499 kB
{"version":3,"file":"clr-angular-data-datagrid.mjs","sources":["../../../projects/angular/data/datagrid/built-in/nested-property.ts","../../../projects/angular/data/datagrid/built-in/comparators/datagrid-property-comparator.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-property-numeric-filter.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-numeric-filter-impl.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-property-string-filter.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts","../../../projects/angular/data/datagrid/enums/sort-order.enum.ts","../../../projects/angular/data/datagrid/providers/custom-filter.ts","../../../projects/angular/data/datagrid/render/constants.ts","../../../projects/angular/data/datagrid/providers/state-debouncer.provider.ts","../../../projects/angular/data/datagrid/providers/page.ts","../../../projects/angular/data/datagrid/providers/filters.ts","../../../projects/angular/data/datagrid/utils/datagrid-filter-registrar.ts","../../../projects/angular/data/datagrid/wrapped-column.ts","../../../projects/angular/data/datagrid/providers/sort.ts","../../../projects/angular/data/datagrid/providers/detail.service.ts","../../../projects/angular/data/datagrid/enums/render-step.enum.ts","../../../projects/angular/data/datagrid/render/render-organizer.ts","../../../projects/angular/data/datagrid/providers/column-resizer.service.ts","../../../projects/angular/data/datagrid/providers/table-size.service.ts","../../../projects/angular/data/datagrid/datagrid-column-separator.ts","../../../projects/angular/data/datagrid/utils/key-navigation-strategies/default.ts","../../../projects/angular/data/datagrid/utils/key-navigation-strategies/expanded-row.ts","../../../projects/angular/data/datagrid/utils/key-navigation-strategies/expanded-columns-row.ts","../../../projects/angular/data/datagrid/utils/key-navigation-utils.ts","../../../projects/angular/data/datagrid/utils/key-navigation-grid.controller.ts","../../../projects/angular/data/datagrid/datagrid-filter.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-numeric-filter.ts","../../../projects/angular/data/datagrid/built-in/filters/datagrid-string-filter.ts","../../../projects/angular/data/datagrid/datagrid-column.ts","../../../projects/angular/data/datagrid/providers/items.ts","../../../projects/angular/data/datagrid/datagrid-items.ts","../../../projects/angular/data/datagrid/datagrid-placeholder.ts","../../../projects/angular/data/datagrid/wrapped-cell.ts","../../../projects/angular/data/datagrid/datagrid-cell.ts","../../../projects/angular/data/datagrid/datagrid-if-expanded.service.ts","../../../projects/angular/data/datagrid/enums/display-mode.enum.ts","../../../projects/angular/data/datagrid/enums/selection-type.ts","../../../projects/angular/data/datagrid/wrapped-row.ts","../../../projects/angular/data/datagrid/providers/selection.ts","../../../projects/angular/data/datagrid/providers/row-action-service.ts","../../../projects/angular/data/datagrid/providers/global-expandable-rows.ts","../../../projects/angular/data/datagrid/providers/display-mode.service.ts","../../../projects/angular/data/datagrid/datagrid-single-selection.directive.ts","../../../projects/angular/data/datagrid/datagrid-selection-cell.directive.ts","../../../projects/angular/data/datagrid/datagrid-row.ts","../../../projects/angular/data/datagrid/datagrid-row.html","../../../projects/angular/data/datagrid/datagrid-virtual-scroll.directive.ts","../../../projects/angular/data/datagrid/enums/column-changes.enum.ts","../../../projects/angular/data/datagrid/providers/columns.service.ts","../../../projects/angular/data/datagrid/providers/state.provider.ts","../../../projects/angular/data/datagrid/render/cell-renderer.ts","../../../projects/angular/data/datagrid/render/row-renderer.ts","../../../projects/angular/data/datagrid/chocolate/datagrid-willy-wonka.ts","../../../projects/angular/data/datagrid/chocolate/actionable-oompa-loompa.ts","../../../projects/angular/data/datagrid/chocolate/expandable-oompa-loompa.ts","../../../projects/angular/data/datagrid/datagrid.ts","../../../projects/angular/data/datagrid/datagrid.html","../../../projects/angular/data/datagrid/datagrid-action-bar.ts","../../../projects/angular/data/datagrid/datagrid-action-overflow.ts","../../../projects/angular/data/datagrid/datagrid-column-toggle-trackby.ts","../../../projects/angular/data/datagrid/datagrid-column-toggle-button.ts","../../../projects/angular/data/datagrid/datagrid-column-toggle.ts","../../../projects/angular/data/datagrid/datagrid-detail-header.ts","../../../projects/angular/data/datagrid/datagrid-detail.ts","../../../projects/angular/data/datagrid/datagrid-detail-body.ts","../../../projects/angular/data/datagrid/providers/column-state.provider.ts","../../../projects/angular/data/datagrid/datagrid-hideable-column.ts","../../../projects/angular/data/datagrid/datagrid-if-detail.ts","../../../projects/angular/data/datagrid/datagrid-row-detail.ts","../../../projects/angular/data/datagrid/datagrid-footer.ts","../../../projects/angular/data/datagrid/datagrid-page-size.ts","../../../projects/angular/data/datagrid/datagrid-pagination.ts","../../../projects/angular/data/datagrid/interfaces/state.interface.ts","../../../projects/angular/data/datagrid/interfaces/filter.interface.ts","../../../projects/angular/data/datagrid/interfaces/string-filter.interface.ts","../../../projects/angular/data/datagrid/interfaces/numeric-filter.interface.ts","../../../projects/angular/data/datagrid/interfaces/comparator.interface.ts","../../../projects/angular/data/datagrid/interfaces/virtual-scroll-data-range.interface.ts","../../../projects/angular/data/datagrid/datagrid-detail-registerer.ts","../../../projects/angular/data/datagrid/render/header-renderer.ts","../../../projects/angular/data/datagrid/render/noop-dom-adapter.ts","../../../projects/angular/data/datagrid/render/main-renderer.ts","../../../projects/angular/data/datagrid/render/row-detail-renderer.ts","../../../projects/angular/data/datagrid/datagrid.module.ts","../../../projects/angular/data/datagrid/index.ts","../../../projects/angular/data/datagrid/clr-angular-data-datagrid.ts"],"sourcesContent":["/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\n/**\n * Generic accessor for deep object properties\n * that can be specified as simple dot-separated strings.\n */\nexport class NestedProperty<T = any> {\n private splitProp: string[];\n\n constructor(private prop: string) {\n if (prop.indexOf('.') >= 0) {\n this.splitProp = prop.split('.');\n }\n }\n\n // Safe getter for a deep object property, will not throw an error but return\n // undefined if one of the intermediate properties is null or undefined.\n getPropValue(item: T): any {\n if (this.splitProp) {\n let value = item;\n for (const nestedProp of this.splitProp) {\n if (\n value === null ||\n typeof value === 'undefined' ||\n typeof (value as Record<string, any>)[nestedProp] === 'undefined'\n ) {\n return undefined;\n }\n value = (value as Record<string, any>)[nestedProp];\n }\n return value;\n } else {\n return (item as Record<string, any>)[this.prop];\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { ClrDatagridComparatorInterface } from '../../interfaces/comparator.interface';\nimport { NestedProperty } from '../nested-property';\n\nexport class DatagridPropertyComparator<T = any> implements ClrDatagridComparatorInterface<T> {\n private nestedProp: NestedProperty<T>;\n\n constructor(public prop: string) {\n this.nestedProp = new NestedProperty(prop);\n }\n\n compare(a: T, b: T): number {\n let propA = this.nestedProp.getPropValue(a);\n let propB = this.nestedProp.getPropValue(b);\n\n if (typeof propA === 'string') {\n propA = propA.toLowerCase();\n }\n\n if (typeof propB === 'string') {\n propB = propB.toLowerCase();\n }\n\n if (typeof propA === 'undefined' || propA === null) {\n if (typeof propB === 'undefined' || propB === null) {\n return 0;\n } else {\n return 1;\n }\n } else {\n if (typeof propB === 'undefined' || propB === null) {\n return -1;\n } else if (propA < propB) {\n return -1;\n } else if (propA > propB) {\n return 1;\n } else {\n return 0;\n }\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { ClrDatagridNumericFilterInterface } from '../../interfaces/numeric-filter.interface';\nimport { NestedProperty } from '../nested-property';\n\nexport class DatagridPropertyNumericFilter<T = any> implements ClrDatagridNumericFilterInterface<T> {\n private nestedProp: NestedProperty<T>;\n\n constructor(\n public prop: string,\n public exact = false\n ) {\n this.nestedProp = new NestedProperty(prop);\n }\n\n accepts(item: T, low: number, high: number): boolean {\n const propValue = this.nestedProp.getPropValue(item);\n if (propValue === undefined) {\n return false;\n }\n if (low !== null && (typeof propValue !== 'number' || propValue < low)) {\n return false;\n }\n if (high !== null && (typeof propValue !== 'number' || propValue > high)) {\n return false;\n }\n return true;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Observable, Subject } from 'rxjs';\n\nimport { DatagridPropertyNumericFilter } from './datagrid-property-numeric-filter';\nimport { ClrDatagridFilterInterface } from '../../interfaces/filter.interface';\nimport { ClrDatagridNumericFilterInterface } from '../../interfaces/numeric-filter.interface';\n\nexport class DatagridNumericFilterImpl<T = any> implements ClrDatagridFilterInterface<T> {\n /**\n * The Observable required as part of the Filter interface\n */\n private _changes = new Subject<[number, number]>();\n\n /**\n * Internal values and accessor\n */\n private _low: number | null = null;\n private _high: number | null = null;\n\n constructor(public filterFn: ClrDatagridNumericFilterInterface<T>) {}\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get changes(): Observable<[number, number]> {\n return this._changes.asObservable();\n }\n\n get value(): [number, number] {\n return [this._low, this._high];\n }\n set value(vals: [number, number]) {\n const low = vals[0];\n const high = vals[1];\n if (low !== this._low || high !== this._high) {\n this._low = low;\n this._high = high;\n this._changes.next([this._low, this._high]);\n }\n }\n\n get low() {\n return this._low;\n }\n set low(low: number) {\n if (low !== this._low) {\n this._low = low;\n this._changes.next([this._low, this._high]);\n }\n }\n\n get high() {\n return this._high;\n }\n set high(high: number) {\n if (high !== this._high) {\n this._high = high;\n this._changes.next([this._low, this._high]);\n }\n }\n\n get state() {\n if (this.filterFn instanceof DatagridPropertyNumericFilter) {\n return {\n property: this.filterFn.prop,\n low: this._low,\n high: this._high,\n };\n }\n return this;\n }\n\n /**\n * Indicates if the filter is currently active, (at least one input is set)\n */\n isActive(): boolean {\n return this._low !== null || this.high !== null;\n }\n\n /**\n * Tests if an item matches a search text\n */\n accepts(item: T): boolean {\n // We have a filter function in case someone wants to implement a numeric\n // filter that always passes nulls or similar\n return this.filterFn.accepts(item, this._low, this._high);\n }\n\n equals(other: ClrDatagridFilterInterface<T, any>): boolean {\n if (other instanceof DatagridNumericFilterImpl) {\n if (other.filterFn instanceof DatagridPropertyNumericFilter) {\n return (\n this.filterFn instanceof DatagridPropertyNumericFilter &&\n other.filterFn.prop === this.filterFn.prop &&\n other.low === this._low &&\n other.high === this._high\n );\n }\n return other === this;\n }\n return false;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { ClrDatagridStringFilterInterface } from '../../interfaces/string-filter.interface';\nimport { NestedProperty } from '../nested-property';\n\nexport class DatagridPropertyStringFilter<T = any> implements ClrDatagridStringFilterInterface<T> {\n private nestedProp: NestedProperty<T>;\n\n constructor(\n public prop: string,\n public exact = false\n ) {\n this.nestedProp = new NestedProperty(prop);\n }\n\n accepts(item: T, search: string): boolean {\n const propValue = this.nestedProp.getPropValue(item);\n if (typeof propValue === 'undefined') {\n return false;\n } else if (this.exact) {\n return ('' + propValue).toLowerCase() === search;\n } else {\n return ('' + propValue).toLowerCase().indexOf(search) >= 0;\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Observable, Subject } from 'rxjs';\n\nimport { DatagridPropertyStringFilter } from './datagrid-property-string-filter';\nimport { ClrDatagridFilterInterface } from '../../interfaces/filter.interface';\nimport { ClrDatagridStringFilterInterface } from '../../interfaces/string-filter.interface';\n\nexport class DatagridStringFilterImpl<T = any> implements ClrDatagridFilterInterface<T> {\n /**\n * The Observable required as part of the Filter interface\n */\n private _changes = new Subject<string>();\n\n /**\n * Input value converted to lowercase\n */\n private _lowerCaseValue = '';\n\n /**\n * Raw input value\n */\n private _rawValue = '';\n\n constructor(public filterFn: ClrDatagridStringFilterInterface<T>) {}\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get changes(): Observable<string> {\n return this._changes.asObservable();\n }\n\n get lowerCaseValue() {\n return this._lowerCaseValue;\n }\n\n get state() {\n if (this.filterFn instanceof DatagridPropertyStringFilter) {\n return {\n property: this.filterFn.prop,\n value: this.value,\n };\n }\n return this;\n }\n\n get value(): string {\n return this._rawValue;\n }\n /**\n * Common setter for the input value\n */\n set value(value: string) {\n if (!value) {\n value = '';\n }\n if (value !== this._rawValue) {\n this._rawValue = value;\n this._lowerCaseValue = value.toLowerCase().trim();\n this._changes.next(value);\n }\n }\n\n /**\n * Indicates if the filter is currently active, meaning the input is not empty\n */\n isActive(): boolean {\n return !!this.value;\n }\n\n /**\n * Tests if an item matches a search text\n */\n accepts(item: T): boolean {\n // We always test with the lowercase value of the input, to stay case insensitive\n return this.filterFn.accepts(item, this.lowerCaseValue);\n }\n\n equals(other: ClrDatagridFilterInterface<T, any>): boolean {\n if (other instanceof DatagridStringFilterImpl) {\n if (other.filterFn instanceof DatagridPropertyStringFilter) {\n return (\n this.filterFn instanceof DatagridPropertyStringFilter &&\n other.filterFn.prop === this.filterFn.prop &&\n other.value === this.value\n );\n }\n return other === this;\n }\n return false;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\n/**\n * Enumeration representing the sorting order of a datagrid column. It is a constant Enum,\n * i.e. each value needs to be treated as a `number`, starting at index 0.\n *\n * @export\n * @enum {number}\n */\nexport enum ClrDatagridSortOrder {\n UNSORTED = 0,\n ASC = 1,\n DESC = -1,\n}\n\nexport enum ClrDatagridAriaSortOrder {\n UNSORTED = 'none',\n ASC = 'ascending',\n DESC = 'descending',\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nexport abstract class CustomFilter {}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nexport const STRICT_WIDTH_CLASS = 'datagrid-fixed-width';\nexport const HIDDEN_COLUMN_CLASS = 'datagrid-hidden-column';\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\n\n/*\n * This provider implements some form of synchronous debouncing through a lock pattern\n * to avoid emitting multiple state changes for a single user action.\n */\n@Injectable()\nexport class StateDebouncer {\n /*\n * This is the lock, to only emit once all the changes have finished processing\n */\n private nbChanges = 0;\n\n /**\n * The Observable that lets other classes subscribe to global state changes\n */\n private _change = new Subject<void>();\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get change(): Observable<void> {\n return this._change.asObservable();\n }\n\n changeStart() {\n this.nbChanges++;\n }\n\n changeDone() {\n if (--this.nbChanges === 0) {\n this._change.next();\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\n\nimport { StateDebouncer } from './state-debouncer.provider';\n\n@Injectable()\nexport class Page {\n activated = false;\n\n /**\n * Page size, a value of 0 means no pagination\n */\n private _size = 0;\n\n /**\n * Total items (needed to guess the last page)\n */\n private _totalItems?: number;\n\n /**\n * Last page\n */\n private _last: number;\n\n /**\n * Current page\n */\n private _current = 1;\n\n /**\n * The Observable that lets other classes subscribe to page changes\n */\n private _change = new Subject<number>();\n\n private preventEmit = false;\n private _sizeChange = new Subject<number>();\n\n constructor(private stateDebouncer: StateDebouncer) {}\n\n get size(): number {\n return this._size;\n }\n set size(size: number) {\n const oldSize = this._size;\n if (size !== oldSize) {\n if (!this.preventEmit) {\n this.stateDebouncer.changeStart();\n }\n this._size = size;\n if (size === 0) {\n this._current = 1;\n } else {\n // Yeap. That's the formula to keep the first item from the old page still\n // displayed in the new one.\n this._current = Math.floor((oldSize / size) * (this._current - 1)) + 1;\n }\n // We always emit an event even if the current page index didn't change, because\n // the size changing means the items inside the page are different\n if (!this.preventEmit) {\n this._change.next(this._current);\n this._sizeChange.next(this._size);\n this.stateDebouncer.changeDone();\n }\n }\n this.preventEmit = false;\n }\n\n get totalItems(): number {\n return this._totalItems || 0; // remains 0 if not set to avoid breaking change\n }\n set totalItems(total: number) {\n this._totalItems = total;\n // If we have less items than before, we might need to change the current page\n if (this.current > this.last) {\n this.current = this.last;\n }\n }\n\n get last(): number {\n if (this._last) {\n return this._last;\n }\n // If the last page isn't known, we compute it from the last item's index\n if (this.size > 0 && this.totalItems) {\n return Math.ceil(this.totalItems / this.size);\n }\n return 1;\n }\n set last(page: number) {\n this._last = page;\n }\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get change(): Observable<number> {\n return this._change.asObservable();\n }\n\n get sizeChange(): Observable<number> {\n return this._sizeChange.asObservable();\n }\n\n get current(): number {\n return this._current;\n }\n set current(page: number) {\n if (page !== this._current) {\n this.stateDebouncer.changeStart();\n this._current = page;\n this._change.next(page);\n this.stateDebouncer.changeDone();\n }\n }\n\n /**\n * Index of the first item displayed on the current page, starting at 0, -1 if none displayed\n */\n get firstItem(): number {\n if (this._totalItems === 0) {\n return -1;\n }\n\n if (this.size === 0) {\n return 0;\n }\n return (this.current - 1) * this.size;\n }\n\n /**\n * Index of the last item displayed on the current page, starting at 0, -1 if none displayed\n */\n get lastItem(): number {\n if (this._totalItems === 0) {\n return -1;\n }\n\n if (this.size === 0) {\n return this.totalItems - 1;\n }\n let lastInPage = this.current * this.size - 1;\n if (this.totalItems) {\n lastInPage = Math.min(lastInPage, this.totalItems - 1);\n }\n return lastInPage;\n }\n\n /**\n * Moves to the previous page if it exists\n */\n previous() {\n if (this.current > 1) {\n this.current--;\n }\n }\n\n /**\n * Moves to the next page if it exists\n */\n next() {\n if (this.current < this.last) {\n this.current++;\n }\n }\n\n /**\n * Resets the page size to 0\n */\n resetPageSize(preventEmit = false): void {\n this.preventEmit = preventEmit;\n this.size = 0;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\n\nimport { Page } from './page';\nimport { StateDebouncer } from './state-debouncer.provider';\nimport { ClrDatagridFilterInterface } from '../interfaces/filter.interface';\n\n@Injectable()\nexport class FiltersProvider<T = any> {\n /**\n * This subject is the list of filters that changed last, not the whole list.\n * We emit a list rather than just one filter to allow batch changes to several at once.\n */\n private _change = new Subject<ClrDatagridFilterInterface<T>[]>();\n\n /**\n * List of all filters, whether they're active or not\n */\n private _all: RegisteredFilter<T, ClrDatagridFilterInterface<T>>[] = [];\n\n constructor(\n private _page: Page,\n private stateDebouncer: StateDebouncer\n ) {}\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get change(): Observable<ClrDatagridFilterInterface<T>[]> {\n return this._change.asObservable();\n }\n\n /**\n * Tests if at least one filter is currently active\n */\n hasActiveFilters(): boolean {\n // We do not use getActiveFilters() because this function will be called much more often\n // and stopping the loop early might be relevant.\n for (const { filter } of this._all) {\n if (filter && filter.isActive()) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Returns a list of all currently active filters\n */\n getActiveFilters(): ClrDatagridFilterInterface<T>[] {\n const ret: ClrDatagridFilterInterface<T>[] = [];\n for (const { filter } of this._all) {\n if (filter && filter.isActive()) {\n ret.push(filter);\n }\n }\n return ret;\n }\n\n /**\n * Registers a filter, and returns a deregistration function\n */\n add<F extends ClrDatagridFilterInterface<T>>(filter: F): RegisteredFilter<T, F> {\n const subscription = filter.changes.subscribe(() => this.resetPageAndEmitFilterChange([filter]));\n let hasUnregistered = false;\n const registered = new RegisteredFilter(filter, () => {\n if (hasUnregistered) {\n return;\n }\n subscription.unsubscribe();\n const matchIndex = this._all.findIndex(item => item.filter === filter);\n if (matchIndex >= 0) {\n this._all.splice(matchIndex, 1);\n }\n if (filter.isActive()) {\n this.resetPageAndEmitFilterChange([]);\n }\n hasUnregistered = true;\n });\n this._all.push(registered);\n if (filter.isActive()) {\n this.resetPageAndEmitFilterChange([filter]);\n }\n return registered;\n }\n\n /**\n * Accepts an item if it is accepted by all currently active filters\n */\n accepts(item: T): boolean {\n for (const { filter } of this._all) {\n if (filter && filter.isActive() && !filter.accepts(item)) {\n return false;\n }\n }\n return true;\n }\n\n private resetPageAndEmitFilterChange(filters: ClrDatagridFilterInterface<T>[]) {\n this.stateDebouncer.changeStart();\n // filtering may change the page number such that current page number doesn't exist in the filtered dataset.\n // So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.\n this._page.current = 1;\n this._change.next(filters);\n this.stateDebouncer.changeDone();\n }\n}\n\nexport class RegisteredFilter<T, F extends ClrDatagridFilterInterface<T>> {\n constructor(\n public filter: F,\n public unregister: () => void\n ) {}\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Directive, OnDestroy } from '@angular/core';\n\nimport { ClrDatagridFilterInterface } from '../interfaces/filter.interface';\nimport { FiltersProvider, RegisteredFilter } from '../providers/filters';\n\n@Directive()\nexport abstract class DatagridFilterRegistrar<T, F extends ClrDatagridFilterInterface<T>> implements OnDestroy {\n /**\n * @NOTEe Type `any` is set here to be able to pass templateStrictMode\n */\n registered: any;\n\n protected constructor(private filters: FiltersProvider<T>) {}\n\n get filter(): F {\n return this.registered && this.registered.filter;\n }\n\n ngOnDestroy(): void {\n this.deleteFilter();\n }\n\n setFilter(filter: F | RegisteredFilter<T, F>) {\n // If we previously had another filter, we unregister it\n this.deleteFilter();\n if (filter instanceof RegisteredFilter) {\n this.registered = filter;\n } else if (filter) {\n this.registered = this.filters.add(filter);\n }\n }\n\n deleteFilter() {\n if (this.registered) {\n this.registered.unregister();\n delete this.registered;\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { AfterViewInit, Component, EmbeddedViewRef, OnDestroy, TemplateRef, ViewChild } from '@angular/core';\n\n@Component({\n selector: 'dg-wrapped-column',\n template: `\n <ng-template #columnPortal>\n <ng-content></ng-content>\n </ng-template>\n `,\n standalone: false,\n})\nexport class WrappedColumn implements AfterViewInit, OnDestroy {\n @ViewChild('columnPortal') templateRef: TemplateRef<void>;\n columnView: EmbeddedViewRef<void>; // the columns projected view (in memory)\n\n ngAfterViewInit() {\n // Create the cells view in memory, not the DOM.\n this.columnView = this.templateRef.createEmbeddedView(null);\n }\n\n ngOnDestroy() {\n this.columnView.destroy();\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\n\nimport { StateDebouncer } from './state-debouncer.provider';\nimport { ClrDatagridComparatorInterface } from '../interfaces/comparator.interface';\n\n@Injectable()\nexport class Sort<T = any> {\n /**\n * Currently active comparator\n */\n private _comparator: ClrDatagridComparatorInterface<T>;\n\n /**\n * Ascending order if false, descending if true\n */\n private _reverse = false;\n\n /**\n * The Observable that lets other classes subscribe to sort changes\n */\n private _change = new Subject<Sort<T>>();\n\n constructor(private stateDebouncer: StateDebouncer) {}\n\n get comparator(): ClrDatagridComparatorInterface<T> {\n return this._comparator;\n }\n set comparator(value: ClrDatagridComparatorInterface<T>) {\n this.stateDebouncer.changeStart();\n this._comparator = value;\n this.emitChange();\n this.stateDebouncer.changeDone();\n }\n\n get reverse(): boolean {\n return this._reverse;\n }\n set reverse(value: boolean) {\n this.stateDebouncer.changeStart();\n this._reverse = value;\n this.emitChange();\n this.stateDebouncer.changeDone();\n }\n\n // We do not want to expose the Subject itself, but the Observable which is read-only\n get change(): Observable<Sort<T>> {\n return this._change.asObservable();\n }\n\n /**\n * Sets a comparator as the current one, or toggles reverse if the comparator is already used. The\n * optional forceReverse input parameter allows to override that toggling behavior by sorting in\n * reverse order if `true`.\n *\n * @memberof Sort\n */\n toggle(sortBy: ClrDatagridComparatorInterface<T>, forceReverse?: boolean) {\n this.stateDebouncer.changeStart();\n // We modify private properties directly, to batch the change event\n if (this.comparator === sortBy) {\n this._reverse = typeof forceReverse !== 'undefined' ? forceReverse || !this._reverse : !this._reverse;\n } else {\n this._comparator = sortBy;\n this._reverse = typeof forceReverse !== 'undefined' ? forceReverse : false;\n }\n this.emitChange();\n this.stateDebouncer.changeDone();\n }\n\n /**\n * Clears the current sorting order\n */\n clear() {\n this.comparator = null;\n }\n\n /**\n * Compares two objects according to the current comparator\n */\n compare(a: T, b: T): number {\n return (this.reverse ? -1 : 1) * this.comparator.compare(a, b);\n }\n\n private emitChange() {\n this._change.next(this);\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { ModalStackService } from '@clr/angular/modal';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n@Injectable()\nexport class DetailService {\n id: string;\n\n private preventScroll = false;\n private toggleState = false;\n private cache: any;\n private button: HTMLButtonElement;\n private _enabled = false;\n private _state = new BehaviorSubject<boolean | null>(this.toggleState);\n\n constructor(private readonly modalStackService: ModalStackService) {}\n\n get enabled(): boolean {\n return this._enabled;\n }\n set enabled(state: boolean) {\n this._enabled = state;\n }\n\n get preventFocusScroll(): boolean {\n return this.preventScroll;\n }\n set preventFocusScroll(preventScroll: boolean) {\n this.preventScroll = preventScroll;\n }\n\n get state() {\n return this.cache;\n }\n\n get stateChange(): Observable<boolean | null> {\n return this._state.asObservable();\n }\n\n get isOpen() {\n return this.toggleState === true;\n }\n\n open(item: any, button?: HTMLButtonElement) {\n this.cache = item;\n this.button = button;\n this.toggleState = true;\n this._state.next(this.toggleState);\n this.modalStackService.trackModalOpen(this);\n }\n\n close() {\n this.toggleState = false;\n this.returnFocus();\n this._state.next(this.toggleState);\n this.modalStackService.trackModalClose(this);\n }\n\n returnFocus() {\n if (this.button) {\n this.button.focus({ preventScroll: this.preventFocusScroll });\n this.button = null;\n }\n }\n\n toggle(item: any, button?: HTMLButtonElement) {\n if (this.isRowOpen(item) || !item) {\n this.close();\n } else {\n this.open(item, button);\n }\n }\n\n isRowOpen(item: any) {\n return !!(this.toggleState && this.cache === item);\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nexport enum DatagridRenderStep {\n ALIGN_COLUMNS,\n CALCULATE_MODE_ON,\n CALCULATE_MODE_OFF,\n CLEAR_WIDTHS, // Note this is listened to by both cells and columns\n COMPUTE_COLUMN_WIDTHS,\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\nimport { DatagridRenderStep } from '../enums/render-step.enum';\n\n@Injectable()\nexport class DatagridRenderOrganizer {\n protected _renderStep = new Subject<DatagridRenderStep>();\n\n private alreadySized = false;\n\n get renderStep(): Observable<DatagridRenderStep> {\n return this._renderStep.asObservable();\n }\n\n filterRenderSteps(step: DatagridRenderStep) {\n return this.renderStep.pipe(filter(testStep => step === testStep));\n }\n\n resize() {\n this._renderStep.next(DatagridRenderStep.CALCULATE_MODE_ON);\n if (this.alreadySized) {\n this._renderStep.next(DatagridRenderStep.CLEAR_WIDTHS);\n }\n this._renderStep.next(DatagridRenderStep.COMPUTE_COLUMN_WIDTHS);\n this._renderStep.next(DatagridRenderStep.ALIGN_COLUMNS); // NOT USED\n this.alreadySized = true;\n this._renderStep.next(DatagridRenderStep.CALCULATE_MODE_OFF);\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { ElementRef, Injectable } from '@angular/core';\nimport { DomAdapter } from '@clr/angular/utils';\n\nimport { DatagridRenderOrganizer } from '../render/render-organizer';\n\nconst MIN_COLUMN_WIDTH = 96;\n\n// This service allows DatagridHeaderRenderer and ClrDatagridColumnSeparator\n// to share column resize data with each other.\n\n@Injectable()\nexport class ColumnResizerService {\n // is it within the maximum resize range to the left\n isWithinMaxResizeRange: boolean;\n\n private widthBeforeResize: number;\n private _resizedBy = 0;\n\n constructor(\n private el: ElementRef<HTMLElement>,\n private domAdapter: DomAdapter,\n private organizer: DatagridRenderOrganizer\n ) {}\n\n get resizedBy() {\n return this._resizedBy;\n }\n\n get minColumnWidth() {\n return this.domAdapter.minWidth(this.el.nativeElement) || MIN_COLUMN_WIDTH;\n }\n\n get maxResizeRange() {\n return this.widthBeforeResize - this.minColumnWidth;\n }\n\n get widthAfterResize(): number {\n return this.widthBeforeResize + this._resizedBy;\n }\n\n startResize(): void {\n this._resizedBy = 0;\n this.isWithinMaxResizeRange = true;\n this.widthBeforeResize = this.domAdapter.clientRect(this.el.nativeElement).width;\n }\n\n endResize(): void {\n this.organizer.resize();\n }\n\n calculateResize(resizedBy: number): void {\n // calculates the resize amount within the allowed range\n if (resizedBy < -this.maxResizeRange) {\n this._resizedBy = -this.maxResizeRange;\n this.isWithinMaxResizeRange = false;\n } else {\n this._resizedBy = resizedBy;\n this.isWithinMaxResizeRange = true;\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport { ElementRef, Inject, Injectable, PLATFORM_ID } from '@angular/core';\n\n/**\n * @description\n * Internal datagrid service that holds a reference to the clr-dg-table element and exposes a method to get height.\n */\n@Injectable()\nexport class TableSizeService {\n private _tableRef: HTMLElement;\n\n constructor(@Inject(PLATFORM_ID) private platformId: any) {}\n\n get tableRef(): HTMLElement {\n return this._tableRef;\n }\n set tableRef(element: HTMLElement) {\n this._tableRef = element;\n }\n\n set table(table: ElementRef<HTMLElement>) {\n if (isPlatformBrowser(this.platformId) && table.nativeElement) {\n this.tableRef = table.nativeElement.querySelector('.datagrid-table');\n }\n }\n\n // Used when resizing columns to show the column border being dragged.\n getColumnDragHeight(): string {\n if (!this.tableRef) {\n return null;\n }\n return `${this.tableRef.clientHeight}px`;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport {\n AfterViewInit,\n Component,\n DOCUMENT,\n ElementRef,\n Inject,\n NgZone,\n OnDestroy,\n Renderer2,\n ViewChild,\n} from '@angular/core';\nimport { ClrCommonStringsService, Keys, uniqueIdFactory } from '@clr/angular/utils';\n\nimport { ColumnResizerService } from './providers/column-resizer.service';\nimport { TableSizeService } from './providers/table-size.service';\n\n// Default resize length on each keyboard move event\nconst KEYBOARD_RESIZE_LENGTH = 12;\n\n@Component({\n selector: 'clr-dg-column-separator',\n template: `\n <button\n type=\"button\"\n class=\"datagrid-column-handle\"\n [attr.aria-label]=\"commonString.keys.columnSeparatorAriaLabel\"\n [attr.aria-describedby]=\"descriptionId\"\n cdkDrag\n cdkDragLockAxis=\"x\"\n (cdkDragStarted)=\"showTracker()\"\n (cdkDragMoved)=\"moveTracker($event.distance.x)\"\n (cdkDragEnded)=\"hideTracker(); $event.source._dragRef.reset()\"\n #columnHandle\n ></button>\n <span class=\"clr-sr-only\" [attr.id]=\"descriptionId\">\n {{ commonString.keys.columnSeparatorDescription }}\n </span>\n <div class=\"datagrid-column-resize-tracker\" #resizeTracker></div>\n `,\n host: {\n '[class.datagrid-column-separator]': 'true',\n },\n standalone: false,\n})\nexport class ClrDatagridColumnSeparator implements AfterViewInit, OnDestroy {\n columnSeparatorId = uniqueIdFactory();\n\n private resizeStartedOnKeyDown = false;\n private isWithinMaxResizeRange: boolean;\n private unlisteners: (() => void)[] = [];\n\n @ViewChild('resizeTracker') private resizeTrackerRef: ElementRef<HTMLElement>;\n @ViewChild('columnHandle') private columnHandleRef: ElementRef<HTMLElement>;\n\n constructor(\n private columnResizerService: ColumnResizerService,\n private renderer: Renderer2,\n private ngZone: NgZone,\n private tableSizeService: TableSizeService,\n public commonString: ClrCommonStringsService,\n @Inject(DOCUMENT) private document: any\n ) {}\n\n get descriptionId(): string {\n return `${this.columnSeparatorId}-aria-describedby`;\n }\n\n private get resizeTrackerEl() {\n return this.resizeTrackerRef.nativeElement;\n }\n\n private get columnHandleEl() {\n return this.columnHandleRef.nativeElement;\n }\n\n ngAfterViewInit() {\n this.ngZone.runOutsideAngular(() => {\n this.unlisteners.push(\n this.renderer.listen(this.columnHandleEl, 'keydown', event => {\n this.showTrackerOnFirstKeyDown(event);\n this.moveTrackerOnKeyDown(event);\n })\n );\n this.unlisteners.push(\n this.renderer.listen(this.columnHandleEl, 'keyup', event => {\n this.hideTrackerOnKeyUp(event);\n })\n );\n });\n }\n\n ngOnDestroy() {\n this.unlisteners.forEach(unlistener => unlistener());\n }\n\n showTracker(): void {\n this.columnResizerService.startResize();\n const tableHeight = this.tableSizeService.getColumnDragHeight();\n this.renderer.setStyle(this.resizeTrackerEl, 'height', tableHeight);\n this.renderer.setStyle(this.resizeTrackerEl, 'display', 'block');\n }\n\n moveTracker(movedBy: number): void {\n this.columnResizerService.calculateResize(movedBy);\n this.renderer.setStyle(this.resizeTrackerEl, 'transform', `translateX(${this.columnResizerService.resizedBy}px)`);\n this.renderer.setStyle(this.document.body, 'cursor', 'col-resize');\n this.redFlagTracker();\n }\n\n hideTracker(): void {\n this.columnResizerService.endResize();\n this.renderer.setStyle(this.resizeTrackerEl, 'display', 'none');\n this.renderer.setStyle(this.resizeTrackerEl, 'transform', `translateX(0px)`);\n this.renderer.setStyle(this.columnHandleEl, 'transform', `translateX(0px)`);\n this.renderer.setStyle(this.document.body, 'cursor', 'auto');\n }\n\n private showTrackerOnFirstKeyDown(event: KeyboardEvent): void {\n if (!this.resizeStartedOnKeyDown && (this.isArrowLeftKeyEvent(event) || this.isArrowRightKeyEvent(event))) {\n this.resizeStartedOnKeyDown = true;\n this.renderer.addClass(this.resizeTrackerEl, 'on-arrow-key-resize');\n this.showTracker();\n }\n }\n\n private moveTrackerOnKeyDown(event: KeyboardEvent): void {\n if (this.isArrowLeftKeyEvent(event)) {\n event.stopPropagation();\n this.moveTracker(this.columnResizerService.resizedBy - KEYBOARD_RESIZE_LENGTH);\n } else if (this.isArrowRightKeyEvent(event)) {\n event.stopPropagation();\n this.moveTracker(this.columnResizerService.resizedBy + KEYBOARD_RESIZE_LENGTH);\n }\n }\n\n private hideTrackerOnKeyUp(event: KeyboardEvent): void {\n if (this.resizeStartedOnKeyDown && (this.isArrowLeftKeyEvent(event) || this.isArrowRightKeyEvent(event))) {\n this.resizeStartedOnKeyDown = false;\n this.renderer.removeClass(this.resizeTrackerEl, 'on-arrow-key-resize');\n this.hideTracker();\n this.columnHandleEl.focus();\n }\n }\n\n private redFlagTracker(): void {\n if (this.isWithinMaxResizeRange !== this.columnResizerService.isWithinMaxResizeRange) {\n this.isWithinMaxResizeRange = this.columnResizerService.isWithinMaxResizeRange;\n if (!this.isWithinMaxResizeRange) {\n this.renderer.addClass(this.resizeTrackerEl, 'exceeded-max');\n } else {\n this.renderer.removeClass(this.resizeTrackerEl, 'exceeded-max');\n }\n }\n }\n\n private isArrowLeftKeyEvent(event: KeyboardEvent) {\n return event.key === Keys.ArrowLeft;\n }\n\n private isArrowRightKeyEvent(event: KeyboardEvent) {\n return event.key === Keys.ArrowRight;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { KeyNavigationGridStrategyInterface } from '../../interfaces/key-nav-grid-strategy.interface';\nimport { CellCoordinates } from '../key-navigation-grid.controller';\nimport { KeyNavigationUtils } from '../key-navigation-utils';\n\nexport class DefaultKeyNavigationStrategy implements KeyNavigationGridStrategyInterface {\n constructor(protected utils: KeyNavigationUtils) {}\n\n keyUp(currentCellCoords: CellCoordinates) {\n const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);\n\n if (currentCellCoords.y === 0) {\n return nextCellCoords;\n }\n\n nextCellCoords.y = currentCellCoords.y - 1;\n\n const isActionCell = this.utils.isActionCell(currentCellCoords);\n\n if (\n this.utils.isSingleCellExpandedRow(nextCellCoords.y) &&\n !isActionCell &&\n this.utils.isDetailsRow(nextCellCoords.y)\n ) {\n nextCellCoords.x = 0;\n } else if (this.utils.isDetailsRow(nextCellCoords.y)) {\n if (isActionCell) {\n nextCellCoords.y = nextCellCoords.y - 1;\n } else {\n nextCellCoords.x = nextCellCoords.x - this.utils.actionCellCount(currentCellCoords.y);\n }\n }\n\n return nextCellCoords;\n }\n\n keyDown(currentCellCoords: CellCoordinates) {\n const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);\n\n const numOfRows = this.utils.rows ? this.utils.rows.length - 1 : 0;\n if (currentCellCoords.y >= numOfRows) {\n return nextCellCoords;\n }\n\n const isActionCell = this.utils.isActionCell(currentCellCoords);\n nextCellCoords.y = currentCellCoords.y + 1;\n\n if (!isActionCell && this.utils.isRowReplaced(nextCellCoords.y)) {\n nextCellCoords.y = nextCellCoords.y + 1;\n\n nextCellCoords.x = this.utils.isSingleCellExpandedRow(nextCellCoords.y)\n ? 0\n : nextCellCoords.x - this.utils.actionCellCount(currentCellCoords.y);\n }\n\n return nextCellCoords;\n }\n\n keyLeft(currentCellCoords: CellCoordinates) {\n const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);\n\n if (currentCellCoords.x === 0) {\n return nextCellCoords;\n }\n\n nextCellCoords.x = currentCellCoords.x - 1;\n\n return nextCellCoords;\n }\n\n keyRight(currentCellCoords: CellCoordinates) {\n const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);\n\n // calculate numOfColumns based on header cells.\n const numOfColumns = this.utils.rows?.length - 1 ? this.utils.getCellsForRow(0).length - 1 : 0;\n\n nextCellCoords.x = currentCellCoords.x < numOfColumns ? nextCellCoords.x + 1 : nextCellCoords.x;\n\n return nextCellCoords;\n }\n\n keyEnd(currentCellCoords: CellCoordinates, ctrlKey: boolean) {\n const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);\n const numOfRows = this.utils.rows ? this.utils.rows.length - 1 : 0;\n\n // calculate X based on header cells.\n nextCellCoords.x = numOfRows ? this.utils.getCellsForRow(0).length - 1 : 0;\n\n if (ctrlKey) {\n nextCellCoords.y = numOfRows;\n\n if (this.utils.isDetailsRow(nextCellCoords.y)) {\n nextCellCoords.x = this.utils.getCellsForRow(nextCellCoords.y).length - 1;\n }\n }\n\n return nextCellCoords;\n }\n\n keyHome(currentCellCoords: CellCoordinates, ctrlKey: boole