@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
987 lines (986 loc) • 45.5 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, Input, Output, EventEmitter, HostBinding, ViewChild, ElementRef, Inject, InjectionToken, QueryList, NgZone, Renderer2, ViewChildren, ChangeDetectorRef, TemplateRef } from '@angular/core';
import { Subject, fromEvent, merge } from 'rxjs';
import { delay, map, filter, tap, take, switchMapTo } from 'rxjs/operators';
import { RowHeightService } from '../scrolling/row-height.service';
import { ScrollerService, PageAction, ScrollAction, ScrollBottomAction } from '../scrolling/scroller.service';
import { ScrollRequestService } from '../scrolling/scroll-request.service';
import { ColumnBase } from '../columns/column-base';
import { DetailTemplateDirective } from './details/detail-template.directive';
import { isChanged, isPresent, isUniversal, anyChanged, isNumber, requestAnimationFrame, cancelAnimationFrame, recursiveFlatMap } from '../utils';
import { DetailsService } from './details/details.service';
import { ColumnsContainer } from '../columns/columns-container';
import { ChangeNotificationService } from '../data/change-notification.service';
import { syncRowsHeight } from '../layout/row-sync';
import { NoRecordsTemplateDirective } from './no-records-template.directive';
import { SuspendService } from '../scrolling/suspend.service';
import { GroupsService } from "../grouping/groups.service";
import { expandColumns, sumColumnWidths } from "../columns/column-common";
import { ScrollSyncService } from "../scrolling/scroll-sync.service";
import { ResizeService } from "../layout/resize.service";
import { EventsOutsideAngularDirective, isDocumentAvailable, ResizeSensorComponent } from "@progress/kendo-angular-common";
import { BrowserSupportService } from "../layout/browser-support.service";
import { EditService } from '../editing/edit.service';
import { NavigationService } from '../navigation/navigation.service';
import { Keys } from '@progress/kendo-angular-common';
import { ColumnResizingService } from "../column-resizing/column-resizing.service";
import { GROUP_CELL_WIDTH } from '../constants';
import { defaultTrackBy } from '../common/default-track-by';
import { hasClasses, rtlScrollPosition } from './common/dom-queries';
import { PDFService } from '../pdf/pdf.service';
import { ColumnInfoService } from '../common/column-info.service';
import { NON_DATA_CELL_CLASSES } from './constants';
import { ContextService } from '../common/provider.service';
import { ResizableContainerDirective } from '../layout/resizable.directive';
import { TableBodyComponent } from './table-body.component';
import { ColGroupComponent } from './common/col-group.component';
import { GridTableDirective } from './grid-table.directive';
import { TableDirective } from '../column-resizing/table.directive';
import { NgIf } from '@angular/common';
import { RowspanService } from './rowspan.service';
import * as i0 from "@angular/core";
import * as i1 from "./details/details.service";
import * as i2 from "../data/change-notification.service";
import * as i3 from "../scrolling/suspend.service";
import * as i4 from "../grouping/groups.service";
import * as i5 from "../scrolling/scroll-sync.service";
import * as i6 from "../layout/resize.service";
import * as i7 from "../editing/edit.service";
import * as i8 from "../layout/browser-support.service";
import * as i9 from "../navigation/navigation.service";
import * as i10 from "../scrolling/scroll-request.service";
import * as i11 from "../common/provider.service";
import * as i12 from "../column-resizing/column-resizing.service";
import * as i13 from "../pdf/pdf.service";
import * as i14 from "../common/column-info.service";
import * as i15 from "./rowspan.service";
const elementAt = (index, elements, elementOffset) => {
for (let idx = 0, elementIdx = 0; idx < elements.length; idx++) {
const offset = elementOffset(elements[idx]);
if (elementIdx <= index && index <= elementIdx + offset - 1) {
return elements[idx];
}
elementIdx += offset;
}
};
const rowAt = (index, rows) => elementAt(index, rows, () => 1);
const cellAt = (index, cells) => elementAt(index, cells, cell => !hasClasses(cell, NON_DATA_CELL_CLASSES) ? parseInt(cell.getAttribute('colSpan'), 10) || 1 : 0);
const EMPTY_OBJECT = {};
/**
* @hidden
*/
export const SCROLLER_FACTORY_TOKEN = new InjectionToken('grid-scroll-service-factory');
/**
* @hidden
*/
export function DEFAULT_SCROLLER_FACTORY(observable) {
return new ScrollerService(observable);
}
const wheelDeltaY = (e) => {
const deltaY = e.wheelDeltaY;
if (e.wheelDelta && (deltaY === undefined || deltaY)) {
return e.wheelDelta;
}
else if (e.detail && e.axis === e.VERTICAL_AXIS) {
return (-e.detail) * 10;
}
return 0;
};
const preventLockedScroll = (args, element) => {
const delta = wheelDeltaY(args);
const scrollTop = element.scrollTop;
const allowScroll = (scrollTop === 0 && 0 < delta) || (element.scrollHeight <= element.offsetHeight + scrollTop && delta < 0);
if (!allowScroll) {
event.preventDefault();
}
};
const translateY = (renderer, value) => el => renderer.setStyle(el, "transform", `translateY(${value}px)`);
const maybeNativeElement = el => el ? el.nativeElement : null;
const hasScrollbar = (el, parent) => el.nativeElement.offsetWidth > parent.nativeElement.clientWidth;
const setHeight = renderer => ({ el, height }) => renderer.setStyle(el, "height", `${height}px`);
const bufferSize = 1;
/**
* @hidden
*/
export class ListComponent {
changeNotification;
suspendService;
groupsService;
ngZone;
renderer;
scrollSyncService;
resizeService;
editService;
supportService;
navigationService;
ctx;
columnResizingService;
changeDetector;
pdfService;
columnInfo;
rowspanService;
hostClass = true;
hostRole = 'presentation';
data;
groups = [];
total;
rowHeight;
stickyRowHeight;
detailRowHeight;
take;
skip = 0;
columns = new ColumnsContainer(() => []);
detailTemplate;
noRecordsTemplate;
selectable = false;
groupable = false;
filterable;
rowClass;
rowSticky;
loading;
trackBy = defaultTrackBy;
virtualColumns;
isVirtual;
cellLoadingTemplate;
loadingTemplate;
sort = new Array();
size = 'medium';
contentScroll = new EventEmitter();
pageChange = new EventEmitter();
scrollBottom = new EventEmitter();
totalHeight;
columnsStartIdx = 0;
get showFooter() {
return this.groupable && this.groupable.showFooter;
}
get totalWidth() {
if (this.virtualColumns && this.columns.unlockedWidth) {
return this.columns.unlockedWidth;
}
}
container;
lockedContainer;
lockedTable;
table;
resizeSensors = new QueryList();
scroller;
subscriptions;
scrollerSubscription;
dispatcher = new Subject();
rowHeightService;
skipScroll;
rebind;
containerScrollTop = 0;
viewportColumns;
columnsEndIdx;
viewportColumnsWidth;
scrollLeft = 0;
observer;
get lockedLeafColumns() {
return this.columns.lockedLeafColumns;
}
get nonLockedLeafColumns() {
return this.columns.nonLockedLeafColumns;
}
get nonLockedColumnsToRender() {
if (this.virtualColumns && !this.pdfService.exporting) {
return this.viewportColumns;
}
return this.nonLockedLeafColumns;
}
get leafColumns() {
return this.columns.leafColumnsToRender;
}
get lockedWidth() {
const groupCellsWidth = this.groups.length * GROUP_CELL_WIDTH;
return expandColumns(this.lockedLeafColumns.toArray()).reduce((prev, curr) => prev + (curr.width || 0), groupCellsWidth);
}
get nonLockedWidth() {
if ((!this.rtl && this.lockedLeafColumns.length) || this.virtualColumns) {
return sumColumnWidths(expandColumns(this.nonLockedColumnsToRender.toArray()));
}
return undefined;
}
get isLocked() {
return this.lockedLeafColumns.length > 0;
}
rtl = false;
columnUpdateFrame;
hasLockedContainer;
constructor(scrollerFactory, detailsService, changeNotification, suspendService, groupsService, ngZone, renderer, scrollSyncService, resizeService, editService, supportService, navigationService, scrollRequestService, ctx, columnResizingService, changeDetector, pdfService, columnInfo, rowspanService) {
this.changeNotification = changeNotification;
this.suspendService = suspendService;
this.groupsService = groupsService;
this.ngZone = ngZone;
this.renderer = renderer;
this.scrollSyncService = scrollSyncService;
this.resizeService = resizeService;
this.editService = editService;
this.supportService = supportService;
this.navigationService = navigationService;
this.ctx = ctx;
this.columnResizingService = columnResizingService;
this.changeDetector = changeDetector;
this.pdfService = pdfService;
this.columnInfo = columnInfo;
this.rowspanService = rowspanService;
this.scroller = scrollerFactory(this.dispatcher);
this.subscriptions = detailsService.changes.subscribe(x => this.detailExpand(x));
this.subscriptions.add(scrollRequestService.requests.subscribe(req => isPresent(req.adjustIndex) ? this.scrollTo(req.request, req.adjustIndex) : this.scrollToItem(req.request)));
}
ngOnInit() {
this.init();
this.subscriptions.add(this.ngZone.runOutsideAngular(this.handleRowSync.bind(this)));
this.subscriptions.add(this.ngZone.runOutsideAngular(this.handleRowNavigationLocked.bind(this)));
this.subscriptions.add(merge(this.columns.changes, this.resizeService.changes).subscribe(() => {
if (this.virtualColumns) {
this.ngZone.run(() => {
this.updateViewportColumns();
this.changeDetector.markForCheck();
});
}
}));
this.subscriptions.add(this.ctx.localization.changes.subscribe(({ rtl }) => this.rtl = rtl));
}
ngOnChanges(changes) {
if (!isDocumentAvailable()) {
return;
}
const changesInSkip = changes['skip'];
const hasInitialSkip = changesInSkip && changesInSkip.firstChange && changesInSkip.currentValue > 0;
if (hasInitialSkip) {
this.handleInitialScrollToSkip();
}
if (isChanged("skip", changes) && !this.rebind) {
this.skipScroll = true;
this.container.nativeElement.scrollTop = this.rowHeightService.offset(this.skip);
}
if (anyChanged(['total', 'take'], changes)) {
this.init();
}
this.rebind = false;
}
ngDoCheck() {
if (this.virtualColumns && (!this.viewportColumns || this.viewportWidthChange())) {
this.updateViewportColumns();
}
}
ngAfterViewInit() {
if (!isDocumentAvailable()) {
return;
}
if (this.skip && this.isVirtual) {
this.container.nativeElement.scrollTop = this.rowHeightService.offset(this.skip);
}
this.resetNavigationViewport();
this.attachContainerScroll();
this.initResizeService();
}
ngAfterViewChecked() {
const isLocked = this.isLocked;
if (isLocked && !this.hasLockedContainer) {
this.syncRowsHeight();
}
this.hasLockedContainer = isLocked;
}
syncRowsHeight() {
if (this.lockedContainer) {
syncRowsHeight(this.lockedTable.nativeElement, this.table.nativeElement);
}
}
ngOnDestroy() {
if (this.subscriptions) {
this.subscriptions.unsubscribe();
}
if (this.resizeService) {
this.resizeService.destroy();
}
this.observer?.disconnect();
this.cleanupScroller();
}
init() {
if (this.suspendService.scroll) {
return;
}
this.rowHeightService = new RowHeightService(this.total, this.rowHeight, this.detailRowHeight);
this.totalHeight = this.rowHeightService.totalHeight();
if (!isUniversal()) {
this.ngZone.runOutsideAngular(this.createScroller.bind(this));
}
}
lockedScroll() {
if (!this.suspendService.scroll) {
const lockedScrollTop = this.lockedContainer.nativeElement.scrollTop;
if (lockedScrollTop !== this.containerScrollTop) {
this.container.nativeElement.scrollTop = this.containerScrollTop = lockedScrollTop;
}
}
}
lockedMousewheel(args) {
if (!args.ctrlKey) {
preventLockedScroll(args, this.container.nativeElement);
const scrollDelta = wheelDeltaY(args);
this.container.nativeElement.scrollTop -= scrollDelta;
}
}
lockedKeydown(args) {
if (args.keyCode === Keys.PageDown || args.keyCode === Keys.PageUp) {
const dir = args.keyCode === Keys.PageDown ? 1 : -1;
const element = this.container.nativeElement;
element.scrollTop += element.offsetHeight * dir * 0.8;
args.preventDefault();
}
}
detailExpand({ index, expand }) {
if (expand) {
this.rowHeightService.expandDetail(index);
}
else {
this.rowHeightService.collapseDetail(index);
}
this.totalHeight = this.rowHeightService.totalHeight();
this.resetNavigationViewport();
}
attachContainerScroll() {
if (isUniversal()) {
return;
}
this.ngZone.runOutsideAngular(() => {
this.subscriptions.add(fromEvent(this.container.nativeElement, 'scroll').pipe(map((event) => event.target), filter(() => !this.suspendService.scroll), tap((target) => {
this.onContainerScroll(target);
this.resetNavigationViewport();
if (this.virtualColumns || this.isVirtual) {
this.handleColumnScroll();
}
const rowViewport = this.navigationService.viewport || EMPTY_OBJECT;
const columnViewport = this.navigationService.columnViewport || EMPTY_OBJECT;
this.contentScroll.emit({
scrollLeft: target.scrollLeft,
scrollTop: target.scrollTop,
startRow: rowViewport.firstItemIndex,
endRow: rowViewport.lastItemIndex,
startColumn: columnViewport.firstItemIndex,
endColumn: columnViewport.lastItemIndex
});
})).subscribe(this.dispatcher));
});
this.scrollSyncService.registerEmitter(this.container.nativeElement, 'body');
}
createScroller() {
this.cleanupScroller();
const observable = this.scroller
.create(this.rowHeightService, this.skip, this.take, this.total);
this.skipScroll = false;
this.scrollerSubscription = observable.pipe(filter((x) => x instanceof PageAction), filter(() => {
const temp = this.skipScroll;
this.skipScroll = false;
return !temp;
}), tap(() => this.rebind = true))
.subscribe((x) => this.ngZone.run(() => this.pageChange.emit(x)));
this.scrollerSubscription.add(observable.pipe(filter((x) => x instanceof ScrollAction))
.subscribe(this.scroll.bind(this)));
this.scrollerSubscription.add(observable.pipe(filter((x) => x instanceof ScrollBottomAction))
.subscribe(() => this.scrollBottom.emit()));
}
scroll({ offset = 0 }) {
if (this.isVirtual) {
[
maybeNativeElement(this.table),
maybeNativeElement(this.lockedTable)
].filter(isPresent).forEach(translateY(this.renderer, offset));
}
this.resetNavigationViewport();
}
onContainerScroll({ scrollTop }) {
this.containerScrollTop = scrollTop;
if (this.lockedContainer) {
this.lockedContainer.nativeElement.scrollTop = scrollTop;
}
}
handleInitialScrollToSkip() {
const shouldScroll = () => this.isVirtual && this.skip > 0 && this.total > 0;
const sub = this.changeNotification.changes
.pipe(filter(shouldScroll))
.subscribe(() => {
this.scrollTo({ row: this.skip });
sub.unsubscribe();
});
}
handleRowSync() {
const isLocked = () => isPresent(this.lockedContainer);
const onStable = () => this.ngZone.onStable.asObservable().pipe(take(1));
return merge(this.changeNotification.changes, this.groupsService.changes
.pipe(filter(isLocked), switchMapTo(onStable())), this.editService.changed, this.resizeService.changes, this.columnResizingService.changes
.pipe(filter(change => change.type === 'end')), this.supportService.changes)
.pipe(tap(() => {
this.ngZone.run(() => this.rowspanService.reset());
this.resetNavigationViewport();
}), filter(isLocked))
.subscribe(() => {
const scrollTop = this.container.nativeElement.scrollTop;
const scrollLeft = this.container.nativeElement.scrollLeft;
this.syncRowsHeight();
this.syncContainerHeight();
this.lockedContainer.nativeElement.scrollTop = this.container.nativeElement.scrollTop = scrollTop;
// fixes scroll left position in IE when editing
this.container.nativeElement.scrollLeft = scrollLeft;
this.resizeSensors.forEach(sensor => sensor.acceptSize());
});
}
handleRowNavigationLocked() {
return this.navigationService.changes.pipe(filter(() => isPresent(this.lockedContainer)), delay(10)).subscribe((args) => {
if (this.lockedLeafColumns.length <= args.prevColIndex && args.colIndex < this.lockedLeafColumns.length) {
const cell = this.navigationService.activeCell;
if (cell && cell.colIndex + cell.colSpan < args.prevColIndex) {
this.container.nativeElement.scrollLeft = 0;
}
}
});
}
scrollToVirtualRow(itemIndex, adjustIndexForDetailTemplate = true) {
if (!isDocumentAvailable()) {
return;
}
if (isPresent(this.detailTemplate) && adjustIndexForDetailTemplate) {
itemIndex = Math.floor(itemIndex / 2);
}
const offset = this.rowHeightService.offset(itemIndex, !adjustIndexForDetailTemplate);
this.container.nativeElement.scrollTop = offset;
this.resetNavigationViewport();
}
scrollTo({ row, column }, adjustIndex = false) {
if (isNumber(row)) {
if (this.isVirtual) {
this.scrollToVirtualRow(row, adjustIndex);
}
else {
const element = rowAt(row, this.table.nativeElement.rows);
if (element) {
this.container.nativeElement.scrollTop = element.offsetTop;
}
}
}
if (isNumber(column)) {
column -= this.lockedLeafColumns.length;
if (this.virtualColumns) {
const columns = this.columns.leafColumnsToRender;
let offset = 0;
for (let idx = 0; idx < column; idx++) {
offset += columns[idx].width || 0;
}
const startOffset = this.lockedLeafColumns.length ? 0 : this.groups.length * GROUP_CELL_WIDTH + (this.detailTemplate && column > 0 ? GROUP_CELL_WIDTH : 0);
this.container.nativeElement.scrollLeft = this.normalizeScrollLeft(offset + startOffset);
}
else if (column === 0 && this.detailTemplate) {
this.container.nativeElement.scrollLeft = this.normalizeScrollLeft(0);
}
else {
const firstRow = rowAt(0, this.table.nativeElement.rows);
if (firstRow) {
const element = cellAt(column, firstRow.cells);
if (element) {
this.container.nativeElement.scrollLeft = this.elementScrollLeft(element);
}
}
}
}
}
scrollToItem(item) {
if (!isDocumentAvailable()) {
return;
}
const data = this.ctx.grid.data;
const gridData = Array.isArray(data) ? data : data.data;
const gridDataItems = gridData.flatMap(recursiveFlatMap);
const dataItemIndex = gridDataItems.findIndex(dataItem => dataItem[item.idField] === item.id);
if (dataItemIndex !== -1) {
const row = Array.from(this.table.nativeElement.rows).find((r) => {
const dataAttribute = r.getAttribute('data-kendo-grid-item-index');
return dataAttribute && +dataAttribute === this.ctx.grid.skip + dataItemIndex;
});
row && row.scrollIntoView();
this.isVirtual && this.resetNavigationViewport();
}
}
resetNavigationViewport() {
if (!isDocumentAvailable) {
return;
}
if (!this.container || !this.navigationService.tableEnabled ||
!this.navigationService.needsViewport() || this.data.length === 0) {
return;
}
const { scrollTop, offsetHeight } = this.container.nativeElement;
const scrollBottom = scrollTop + offsetHeight;
const firstItemIndex = this.rowHeightService.index(scrollTop);
const lastItemIndex = this.rowHeightService.index(scrollBottom);
const lastItemOffset = this.rowHeightService.offset(lastItemIndex);
let viewportStart = firstItemIndex;
let viewportEnd = lastItemIndex;
if (isPresent(this.detailTemplate)) {
viewportStart *= 2;
viewportEnd *= 2;
const firstItemHeight = this.rowHeightService.offset(firstItemIndex);
if (firstItemHeight + this.rowHeight < scrollTop) {
viewportStart++;
}
const lastItemHeight = this.rowHeightService.height(lastItemIndex);
const lastItemExpanded = this.rowHeightService.isExpanded(lastItemIndex);
const lastItemDetailOverflows = lastItemOffset + lastItemHeight > scrollBottom;
if (lastItemExpanded && !lastItemDetailOverflows) {
viewportEnd++;
}
}
this.navigationService.setViewport(viewportStart, viewportEnd);
}
cleanupScroller() {
if (this.scrollerSubscription) {
this.scrollerSubscription.unsubscribe();
}
if (this.scroller) {
this.scroller.destroy();
}
}
initResizeService() {
this.resizeService.connect(merge(...this.resizeSensors.map(sensor => sensor.resize)));
}
syncContainerHeight() {
[maybeNativeElement(this.lockedContainer)]
.filter(isPresent)
.map(el => {
el.style.height = '';
let height = this.container.nativeElement.offsetHeight;
if (hasScrollbar(this.table, this.container)) {
height -= this.supportService.scrollbarWidth;
}
return { el, height };
})
.forEach(setHeight(this.renderer));
}
updateViewportColumns(range) {
const columns = this.columns.nonLockedLeafColumns.toArray();
// eslint-disable-next-line prefer-const
let { startIdx, endIdx, offset } = range || this.calculateViewportColumns();
const start = Math.max(0, startIdx - bufferSize);
const end = Math.min(endIdx + bufferSize, columns.length - 1);
if (start < startIdx) {
for (let idx = startIdx - 1; idx >= start; idx--) {
offset -= columns[idx].width;
}
}
let currentColumns = columns.slice(start, end + 1);
this.viewportColumnsWidth = currentColumns.reduce((total, column) => total + column.width, 0);
const stickyBeforeStart = columns.slice(0, start).filter(c => c.sticky && !currentColumns.some(col => col === c));
const stickyAfterEnd = columns.slice(end, columns.length).filter(c => c.sticky && !currentColumns.some(col => col === c));
currentColumns = [...stickyBeforeStart, ...currentColumns, ...stickyAfterEnd];
if (start > 0) {
const offsetColumn = new ColumnBase();
offsetColumn.width = offset;
currentColumns.unshift(offsetColumn);
}
this.viewportColumns = new QueryList();
this.viewportColumns.reset(currentColumns);
this.columnsStartIdx = start;
this.columnsEndIdx = end;
this.columnInfo.columnRangeChange.emit({ start, end, offset });
if (!range) {
this.updateColumnViewport(startIdx, endIdx);
}
}
handleColumnScroll() {
const container = this.container.nativeElement;
const scrollLeft = container.scrollLeft;
if (this.scrollLeft !== scrollLeft) {
this.scrollLeft = scrollLeft;
const range = this.calculateViewportColumns();
this.updateColumnViewport(range.startIdx, range.endIdx);
if (range.startIdx < this.columnsStartIdx || this.columnsEndIdx < range.endIdx) {
cancelAnimationFrame(this.columnUpdateFrame);
this.columnUpdateFrame = requestAnimationFrame(() => {
this.ngZone.run(() => {
this.updateViewportColumns(range);
this.changeDetector.markForCheck();
});
});
}
}
}
updateColumnViewport(startIdx, endIdx) {
const lockedCount = this.lockedLeafColumns.length;
const leafColumns = this.nonLockedLeafColumns.toArray();
const viewportStart = lockedCount + startIdx + (this.detailTemplate && startIdx > 0 ? 1 : 0);
let viewportEnd = lockedCount + endIdx + (this.detailTemplate ? 1 : 0);
for (let idx = 0; idx < leafColumns.length; idx++) {
const column = leafColumns[idx];
if (column.isSpanColumn) {
viewportEnd += column.childColumns.length;
}
}
this.navigationService.setColumnViewport(viewportStart, viewportEnd);
}
calculateViewportColumns() {
const { scrollLeft, clientWidth } = this.container.nativeElement;
const columns = this.columns.nonLockedLeafColumns.toArray();
const normalizedScrollLeft = this.normalizeScrollLeft(scrollLeft);
const viewportEnd = normalizedScrollLeft + clientWidth;
let startIdx;
let endIdx = 0;
let current = 0;
let offset = 0;
let idx;
for (idx = 0; idx < columns.length; idx++) {
const column = columns[idx];
current += column.width || 0;
if (startIdx === undefined && current > normalizedScrollLeft) {
startIdx = idx;
offset = current - (column.width || 0);
}
if (current >= viewportEnd) {
endIdx = idx;
break;
}
}
if (!endIdx && idx > 0) {
endIdx = columns.length - 1;
}
return { startIdx, endIdx, offset };
}
viewportWidthChange() {
const currentWidth = this.viewportColumns.toArray().reduce((total, column) => total + column.width, 0);
return currentWidth !== this.viewportColumnsWidth;
}
normalizeScrollLeft(position) {
return this.rtl ? rtlScrollPosition(position, this.container.nativeElement, this.supportService.rtlScrollLeft) : position;
}
elementScrollLeft(element) {
if (this.rtl) {
return this.normalizeScrollLeft(this.container.nativeElement.scrollWidth - element.offsetLeft - element.offsetWidth);
}
return element.offsetLeft;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListComponent, deps: [{ token: SCROLLER_FACTORY_TOKEN }, { token: i1.DetailsService }, { token: i2.ChangeNotificationService }, { token: i3.SuspendService }, { token: i4.GroupsService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i5.ScrollSyncService }, { token: i6.ResizeService }, { token: i7.EditService }, { token: i8.BrowserSupportService }, { token: i9.NavigationService }, { token: i10.ScrollRequestService }, { token: i11.ContextService }, { token: i12.ColumnResizingService }, { token: i0.ChangeDetectorRef }, { token: i13.PDFService }, { token: i14.ColumnInfoService }, { token: i15.RowspanService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ListComponent, isStandalone: true, selector: "kendo-grid-list", inputs: { data: "data", groups: "groups", total: "total", rowHeight: "rowHeight", stickyRowHeight: "stickyRowHeight", detailRowHeight: "detailRowHeight", take: "take", skip: "skip", columns: "columns", detailTemplate: "detailTemplate", noRecordsTemplate: "noRecordsTemplate", selectable: "selectable", groupable: "groupable", filterable: "filterable", rowClass: "rowClass", rowSticky: "rowSticky", loading: "loading", trackBy: "trackBy", virtualColumns: "virtualColumns", isVirtual: "isVirtual", cellLoadingTemplate: "cellLoadingTemplate", loadingTemplate: "loadingTemplate", sort: "sort", size: "size" }, outputs: { contentScroll: "contentScroll", pageChange: "pageChange", scrollBottom: "scrollBottom" }, host: { properties: { "class.k-grid-container": "this.hostClass", "attr.role": "this.hostRole" } }, providers: [
{
provide: SCROLLER_FACTORY_TOKEN,
useValue: DEFAULT_SCROLLER_FACTORY
}
], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "lockedContainer", first: true, predicate: ["lockedContainer"], descendants: true }, { propertyName: "lockedTable", first: true, predicate: ["lockedTable"], descendants: true }, { propertyName: "table", first: true, predicate: ["table"], descendants: true, static: true }, { propertyName: "resizeSensors", predicate: ResizeSensorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
<div #lockedContainer class="k-grid-content-locked" role="presentation"
*ngIf="isLocked" [style.width.px]="lockedWidth" tabindex="-1"
[kendoEventsOutsideAngular]="{
keydown: lockedKeydown,
scroll: lockedScroll,
mousewheel: lockedMousewheel,
DOMMouseScroll: lockedMousewheel
}"
[scope]="this"
>
<div role="presentation">
<table
kendoGridResizableTable
[locked]="true"
#lockedTable
class="k-grid-table"
role="presentation"
kendoGridTable
[size]="size"
[style.width.px]="lockedWidth">
<colgroup kendoGridColGroup
[groups]="groups"
[columns]="$any(lockedLeafColumns)"
[detailTemplate]="detailTemplate"
[sort]="sort">
</colgroup>
<tbody kendoGridTableBody
role="presentation"
[groups]="groups"
[isLocked]="true"
[data]="data"
[noRecordsText]="''"
[columns]="$any(lockedLeafColumns)"
[totalColumnsCount]="leafColumns.length"
[totalColumns]="columns"
[detailTemplate]="detailTemplate"
[showGroupFooters]="showFooter"
[skip]="skip"
[selectable]="selectable"
[trackBy]="trackBy"
[filterable]="filterable"
[rowClass]="rowClass"
[isLoading]="loading"
[isVirtual]="isVirtual"
[cellLoadingTemplate]="cellLoadingTemplate">
</tbody>
</table>
<kendo-resize-sensor></kendo-resize-sensor>
</div>
<div *ngIf="isVirtual" class="k-height-container" role="presentation">
<div [style.height.px]="totalHeight"></div>
</div>
</div>
<div
#container
class="k-grid-content k-virtual-content"
[kendoGridResizableContainer]="lockedLeafColumns.length > 0"
[lockedWidth]="lockedWidth + 1"
>
<div role="presentation">
<table
[style.width.px]="nonLockedWidth"
#table
kendoGridResizableTable
[virtualColumns]="virtualColumns"
class="k-grid-table"
kendoGridTable
[size]="size"
role="presentation">
<colgroup kendoGridColGroup
[groups]="isLocked ? [] : groups"
[columns]="$any(nonLockedColumnsToRender)"
[detailTemplate]="detailTemplate"
[sort]="sort">
</colgroup>
<tbody kendoGridTableBody
role="rowgroup"
[skipGroupDecoration]="isLocked"
[data]="data"
[groups]="groups"
[showGroupFooters]="showFooter"
[columns]="$any(nonLockedColumnsToRender)"
[allColumns]="$any(nonLockedLeafColumns)"
[detailTemplate]="detailTemplate"
[noRecordsTemplate]="noRecordsTemplate"
[lockedColumnsCount]="lockedLeafColumns.length"
[totalColumnsCount]="leafColumns.length"
[totalColumns]="columns"
[skip]="skip"
[selectable]="selectable"
[trackBy]="trackBy"
[filterable]="filterable"
[rowClass]="rowClass"
[rowSticky]="rowSticky"
[virtualColumns]="virtualColumns"
[isLoading]="loading"
[isVirtual]="isVirtual"
[cellLoadingTemplate]="cellLoadingTemplate">
</tbody>
</table>
<kendo-resize-sensor *ngIf="isLocked"></kendo-resize-sensor>
</div>
<kendo-resize-sensor *ngIf="isLocked || virtualColumns"></kendo-resize-sensor>
<div *ngIf="isVirtual" class="k-height-container" role="presentation">
<div [style.height.px]="totalHeight"></div>
</div>
<div *ngIf="virtualColumns" class="k-width-container" role="presentation">
<div [style.width.px]="totalWidth"></div>
</div>
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "directive", type: TableDirective, selector: "[kendoGridResizableTable]", inputs: ["locked", "virtualColumns"] }, { kind: "directive", type: GridTableDirective, selector: "[kendoGridTable]", inputs: ["size"] }, { kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: TableBodyComponent, selector: "[kendoGridTableBody]", inputs: ["columns", "allColumns", "groups", "detailTemplate", "noRecordsTemplate", "data", "skip", "selectable", "filterable", "noRecordsText", "isLocked", "isLoading", "isVirtual", "cellLoadingTemplate", "skipGroupDecoration", "showGroupFooters", "lockedColumnsCount", "totalColumnsCount", "virtualColumns", "trackBy", "rowSticky", "totalColumns", "rowClass"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }, { kind: "directive", type: ResizableContainerDirective, selector: "[kendoGridResizableContainer]", inputs: ["lockedWidth", "kendoGridResizableContainer"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListComponent, decorators: [{
type: Component,
args: [{
providers: [
{
provide: SCROLLER_FACTORY_TOKEN,
useValue: DEFAULT_SCROLLER_FACTORY
}
],
selector: 'kendo-grid-list',
template: `
<div #lockedContainer class="k-grid-content-locked" role="presentation"
*ngIf="isLocked" [style.width.px]="lockedWidth" tabindex="-1"
[kendoEventsOutsideAngular]="{
keydown: lockedKeydown,
scroll: lockedScroll,
mousewheel: lockedMousewheel,
DOMMouseScroll: lockedMousewheel
}"
[scope]="this"
>
<div role="presentation">
<table
kendoGridResizableTable
[locked]="true"
#lockedTable
class="k-grid-table"
role="presentation"
kendoGridTable
[size]="size"
[style.width.px]="lockedWidth">
<colgroup kendoGridColGroup
[groups]="groups"
[columns]="$any(lockedLeafColumns)"
[detailTemplate]="detailTemplate"
[sort]="sort">
</colgroup>
<tbody kendoGridTableBody
role="presentation"
[groups]="groups"
[isLocked]="true"
[data]="data"
[noRecordsText]="''"
[columns]="$any(lockedLeafColumns)"
[totalColumnsCount]="leafColumns.length"
[totalColumns]="columns"
[detailTemplate]="detailTemplate"
[showGroupFooters]="showFooter"
[skip]="skip"
[selectable]="selectable"
[trackBy]="trackBy"
[filterable]="filterable"
[rowClass]="rowClass"
[isLoading]="loading"
[isVirtual]="isVirtual"
[cellLoadingTemplate]="cellLoadingTemplate">
</tbody>
</table>
<kendo-resize-sensor></kendo-resize-sensor>
</div>
<div *ngIf="isVirtual" class="k-height-container" role="presentation">
<div [style.height.px]="totalHeight"></div>
</div>
</div>
<div
#container
class="k-grid-content k-virtual-content"
[kendoGridResizableContainer]="lockedLeafColumns.length > 0"
[lockedWidth]="lockedWidth + 1"
>
<div role="presentation">
<table
[style.width.px]="nonLockedWidth"
#table
kendoGridResizableTable
[virtualColumns]="virtualColumns"
class="k-grid-table"
kendoGridTable
[size]="size"
role="presentation">
<colgroup kendoGridColGroup
[groups]="isLocked ? [] : groups"
[columns]="$any(nonLockedColumnsToRender)"
[detailTemplate]="detailTemplate"
[sort]="sort">
</colgroup>
<tbody kendoGridTableBody
role="rowgroup"
[skipGroupDecoration]="isLocked"
[data]="data"
[groups]="groups"
[showGroupFooters]="showFooter"
[columns]="$any(nonLockedColumnsToRender)"
[allColumns]="$any(nonLockedLeafColumns)"
[detailTemplate]="detailTemplate"
[noRecordsTemplate]="noRecordsTemplate"
[lockedColumnsCount]="lockedLeafColumns.length"
[totalColumnsCount]="leafColumns.length"
[totalColumns]="columns"
[skip]="skip"
[selectable]="selectable"
[trackBy]="trackBy"
[filterable]="filterable"
[rowClass]="rowClass"
[rowSticky]="rowSticky"
[virtualColumns]="virtualColumns"
[isLoading]="loading"
[isVirtual]="isVirtual"
[cellLoadingTemplate]="cellLoadingTemplate">
</tbody>
</table>
<kendo-resize-sensor *ngIf="isLocked"></kendo-resize-sensor>
</div>
<kendo-resize-sensor *ngIf="isLocked || virtualColumns"></kendo-resize-sensor>
<div *ngIf="isVirtual" class="k-height-container" role="presentation">
<div [style.height.px]="totalHeight"></div>
</div>
<div *ngIf="virtualColumns" class="k-width-container" role="presentation">
<div [style.width.px]="totalWidth"></div>
</div>
</div>
`,
standalone: true,
imports: [NgIf, EventsOutsideAngularDirective, TableDirective, GridTableDirective, ColGroupComponent, TableBodyComponent, ResizeSensorComponent, ResizableContainerDirective]
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [SCROLLER_FACTORY_TOKEN]
}] }, { type: i1.DetailsService }, { type: i2.ChangeNotificationService }, { type: i3.SuspendService }, { type: i4.GroupsService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i5.ScrollSyncService }, { type: i6.ResizeService }, { type: i7.EditService }, { type: i8.BrowserSupportService }, { type: i9.NavigationService }, { type: i10.ScrollRequestService }, { type: i11.ContextService }, { type: i12.ColumnResizingService }, { type: i0.ChangeDetectorRef }, { type: i13.PDFService }, { type: i14.ColumnInfoService }, { type: i15.RowspanService }]; }, propDecorators: { hostClass: [{
type: HostBinding,
args: ['class.k-grid-container']
}], hostRole: [{
type: HostBinding,
args: ['attr.role']
}], data: [{
type: Input
}], groups: [{
type: Input
}], total: [{
type: Input
}], rowHeight: [{
type: Input
}], stickyRowHeight: [{
type: Input
}], detailRowHeight: [{
type: Input
}], take: [{
type: Input
}], skip: [{
type: Input
}], columns: [{
type: Input
}], detailTemplate: [{
type: Input
}], noRecordsTemplate: [{
type: Input
}], selectable: [{
type: Input
}], groupable: [{
type: Input
}], filterable: [{
type: Input
}], rowClass: [{
type: Input
}], rowSticky: [{
type: Input
}], loading: [{
type: Input
}], trackBy: [{
type: Input
}], virtualColumns: [{
type: Input
}], isVirtual: [{
type: Input
}], cellLoadingTemplate: [{
type: Input
}], loadingTemplate: [{
type: Input
}], sort: [{
type: Input
}], size: [{
type: Input
}], contentScroll: [{
type: Output
}], pageChange: [{
type: Output
}], scrollBottom: [{
type: Output
}], container: [{
type: ViewChild,
args: ['container', { static: true }]
}], lockedContainer: [{
type: ViewChild,
args: ['lockedContainer', { static: false }]
}], lockedTable: [{
type: ViewChild,
args: ['lockedTable', { static: false }]
}], table: [{
type: ViewChild,
args: ['table', { static: true }]
}], resizeSensors: [{
type: ViewChildren,
args: [ResizeSensorComponent]
}] } });