@progress/kendo-angular-pivotgrid
Version:
PivotGrid package for Angular
325 lines (324 loc) • 20 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, ElementRef, Input, NgZone } from '@angular/core';
import { Subscription } from 'rxjs';
import { PivotGridDataService } from '../data-binding/pivotgrid-data.service';
import { CellTemplateDirective } from './templates/pivotgrid-cell-template.directive';
import { ValueCellTemplateDirective } from './templates/pivotgrid-value-cell-template.directive';
import { ColumnHeaderCellTemplateDirective } from './templates/pivotgrid-column-header-cell-template.directive';
import { RowHeaderCellTemplateDirective } from './templates/pivotgrid-row-header-cell-template.directive';
import { ScrollableTable } from '../virtual/scrollable-container';
import { PivotGridCellDirective } from './pivotgrid-cell.directive';
import { NgFor, NgIf, NgStyle } from '@angular/common';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { Keys, isDocumentAvailable } from '@progress/kendo-angular-common';
import { isVisible, matchAriaAttributes } from '../util';
import { PivotGridScrollService } from '../virtual/scroll.service';
import * as i0 from "@angular/core";
import * as i1 from "../data-binding/pivotgrid-data.service";
import * as i2 from "@progress/kendo-angular-l10n";
import * as i3 from "../virtual/scroll.service";
/**
* @hidden
*/
export class PivotGridTableComponent {
host;
dataService;
localization;
zone;
scrollService;
headerItems;
rows;
renderedRows;
renderedCols;
totalRows;
startRowIndex = 0;
startColIndex = 0;
rtl = false;
tableType;
colWidth;
customCellTemplate;
valueCellTemplate;
rowHeaderCellTemplate;
columnHeaderCellTemplate;
scrollableSettings;
get pivotGridId() {
return `kendo-pivotgrid-${this.dataService.pivotGridId}-`;
}
get columnVirtualization() {
return this.scrollableSettings?.type && this.scrollableSettings?.type !== 'row';
}
get rowVirtualization() {
return this.scrollableSettings?.type && this.scrollableSettings?.type !== 'column';
}
subs = new Subscription();
scrollable;
constructor(host, dataService, localization, zone, scrollService) {
this.host = host;
this.dataService = dataService;
this.localization = localization;
this.zone = zone;
this.scrollService = scrollService;
this.subs.add(this.localization.changes.subscribe(({ rtl }) => {
this.rtl = rtl;
this.scrollable && (this.scrollable.rtl = rtl);
}));
}
ngOnInit() {
this.subs.add(this.dataService[`${this.tableType}Rows`].subscribe(rows => {
this.rows = rows;
this.renderedRows = this.scrollableSettings ? rows.slice(this.startRowIndex, this.startRowIndex + this.scrollableSettings.rows) : rows;
this.totalRows && this.totalRows !== rows.length && this.scrollable && (this.scrollable.total = rows.length);
this.scrollable && this.scrollable.onNewData(this.totalRows && this.totalRows !== rows.length);
this.totalRows = rows.length;
}));
this.subs.add(this.tableType === 'values' ?
this.dataService.columnHeaderCols.subscribe(this.colsUpdateCallback) :
this.dataService[`${this.tableType}Cols`].subscribe(this.colsUpdateCallback));
}
ngAfterViewInit() {
if (isDocumentAvailable() && this.scrollService.virtualScrolling) {
this.initScrollableKeyboardNavigation();
}
}
ngOnDestroy() {
this.subs.unsubscribe();
this.scrollable?.destroy();
}
colsUpdateCallback = (cols) => {
this.renderedCols = Math.min(cols.length, this.scrollableSettings?.columns);
this.renderedCols && this.scrollable && (this.scrollable.totalCols = cols.length);
(this.scrollableSettings && this.scrollableSettings.type !== 'row') && this.scrollable?.onNewData(true);
this.headerItems = cols;
isDocumentAvailable() && !this.scrollable && this.tableType === 'values' && (this.columnVirtualization || this.rowVirtualization) && this.initScrollable();
};
initScrollable = () => {
this.scrollable = new ScrollableTable(this.host.nativeElement, {
onScroll: () => {
this.startRowIndex = this.scrollable.startRow;
this.startColIndex = this.scrollable.startCol;
this.renderedRows = this.rows.slice(this.startRowIndex, this.startRowIndex + this.scrollableSettings.rows);
this.scrollable.renderedRows = this.renderedRows.length;
this.scrollable.renderedCols = this.renderedCols;
},
onScrollEnd: () => {
matchAriaAttributes(this.host.nativeElement.closest('.k-pivotgrid'));
}
}, {
itemHeight: this.scrollableSettings.rowHeight,
itemWidth: this.colWidth || 200,
total: this.totalRows,
totalCols: this.headerItems.length,
renderedRows: this.scrollableSettings.rows,
renderedCols: this.scrollableSettings.columns,
columnVirtualization: this.columnVirtualization,
rowVirtualization: this.rowVirtualization,
rtl: this.rtl
});
};
initScrollableKeyboardNavigation() {
const pivotGrid = this.scrollService.pivotGrid;
this.host.nativeElement.addEventListener('keydown', (e) => {
if (this.tableType === 'values' && e.target.tagName === 'TD') {
e.stopImmediatePropagation();
e.preventDefault();
if (e.keyCode === Keys.ArrowLeft) {
const id = e.target.getAttribute('id');
if (id.split('-')[5] === '1') {
const target = document.querySelector(`tr[aria-owns*="${id}"]`);
pivotGrid.navigation.focusElement(target.lastElementChild, e.target);
}
else {
pivotGrid.navigation.focusElement(e.target.previousElementSibling, e.target);
if (!isVisible(e.target.previousElementSibling, this.host.nativeElement, this.scrollable.offsetFirst, this.rtl).visibleX) {
e.target.previousElementSibling.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'start' });
}
}
}
else if (e.keyCode === Keys.ArrowRight) {
const id = e.target.getAttribute('id');
if (id.split('-')[5] !== this.headerItems.length.toString()) {
pivotGrid.navigation.focusElement(e.target.nextElementSibling, e.target);
if (!isVisible(e.target.nextElementSibling, this.host.nativeElement, this.scrollable.offsetFirst, this.rtl).visibleX) {
e.target.nextElementSibling.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'start' });
}
}
}
else if (e.keyCode === Keys.ArrowUp) {
const id = e.target.getAttribute('id');
if (id.split('-')[4] === '1') {
const target = document.getElementById(e.target.getAttribute('aria-describedby').split(' ').pop());
pivotGrid.navigation.focusElement(target, e.target);
}
else {
const index = Array.from(e.target.parentElement.children).findIndex(el => el === e.target);
const elementToFocus = e.target.parentElement.previousElementSibling.children[index];
pivotGrid.navigation.focusElement(elementToFocus, e.target);
if (!isVisible(elementToFocus, this.host.nativeElement, this.scrollable.offsetFirst, this.rtl).visibleY) {
elementToFocus.scrollIntoView();
}
}
}
else if (e.keyCode === Keys.ArrowDown) {
const id = e.target.getAttribute('id');
if (id.split('-')[4] !== this.totalRows.toString()) {
const index = Array.from(e.target.parentElement.children).findIndex(el => el === e.target);
const elementToFocus = e.target.parentElement.nextElementSibling.children[index];
pivotGrid.navigation.focusElement(elementToFocus, e.target);
if (!isVisible(elementToFocus, this.host.nativeElement, this.scrollable.offsetFirst, this.rtl).visibleY) {
elementToFocus.scrollIntoView(false);
}
}
}
}
if (this.tableType === 'rowHeader' && e.target.tagName === 'TH' && e.keyCode === Keys.ArrowRight) {
if (e.target.matches(':last-child')) {
e.stopImmediatePropagation();
e.preventDefault();
const valuesContainer = this.host.nativeElement.nextElementSibling;
valuesContainer.scrollLeft = this.rtl ? valuesContainer.scrollWidth : 0;
this.zone.runOutsideAngular(() => setTimeout(() => {
const elementToFocusId = e.target.parentElement.getAttribute('aria-owns').split(' ')[0];
const elementToFocus = document.getElementById(elementToFocusId);
pivotGrid.navigation.focusElement(elementToFocus, e.target);
}));
}
}
if (this.tableType === 'columnHeader' && e.target.tagName === 'TH' && e.keyCode === Keys.ArrowDown) {
if (e.target.parentElement.matches(':last-child')) {
e.stopImmediatePropagation();
e.preventDefault();
const valuesContainer = this.host.nativeElement.nextElementSibling.nextElementSibling;
valuesContainer.scrollTop = 0;
this.zone.runOutsideAngular(() => setTimeout(() => {
const elementToFocus = valuesContainer.querySelector(`td[aria-describedby*="${e.target.getAttribute('id')}"]`);
pivotGrid.navigation.focusElement(elementToFocus, e.target);
}));
}
}
}, true);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PivotGridTableComponent, deps: [{ token: i0.ElementRef }, { token: i1.PivotGridDataService }, { token: i2.LocalizationService }, { token: i0.NgZone }, { token: i3.PivotGridScrollService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: PivotGridTableComponent, isStandalone: true, selector: "kendo-pivotgrid-table", inputs: { tableType: "tableType", colWidth: "colWidth", customCellTemplate: "customCellTemplate", valueCellTemplate: "valueCellTemplate", rowHeaderCellTemplate: "rowHeaderCellTemplate", columnHeaderCellTemplate: "columnHeaderCellTemplate", scrollableSettings: "scrollableSettings" }, ngImport: i0, template: `
<table
class="k-pivotgrid-table"
role="presentation"
[ngStyle]="{
float: tableType === 'values' ? this.rtl ? 'right' : 'left' : 'initial',
overflow: 'scroll',
'-ms-overflow-style': tableType !== 'values' ? 'none' : 'auto',
'scrollbar-width': tableType !== 'values' ? 'none' : 'auto'
}">
<colgroup>
<col *ngIf="tableType === 'values' && columnVirtualization && startColIndex > 0" [style.width]="startColIndex * (colWidth >= 0 ? colWidth : 200) + 'px'"/>
<col
*ngFor="let item of (tableType === 'values' && columnVirtualization ? headerItems?.slice(0, renderedCols) : headerItems);"
[style.width]="tableType !== 'rowHeader' ? colWidth >= 0 ? colWidth + 'px' : '200px' : undefined" />
</colgroup>
<tbody class="k-pivotgrid-tbody" [attr.role]="tableType === 'values' ? 'none' : 'rowgroup'">
<tr *ngFor="let row of (tableType === 'values' && rowVirtualization ? renderedRows : rows); index as rowIndex"
class="k-pivotgrid-row"
[attr.role]="tableType === 'values' ? 'none' : 'row'">
<td *ngIf="tableType === 'values' && columnVirtualization && startColIndex > 0" class="k-pivotgrid-cell"></td>
<ng-container *ngFor="let cell of (tableType === 'values' && columnVirtualization ? row?.cells.slice(startColIndex, (startColIndex + renderedCols)) : row?.cells); index as colIndex">
<th
*ngIf="cell && tableType !== 'values'"
[kendoPivotGridCell]="cell"
[customCellTemplate]="customCellTemplate"
[rowHeaderCellTemplate]="rowHeaderCellTemplate"
[columnHeaderCellTemplate]="columnHeaderCellTemplate"
[tableType]="tableType"
[colIndex]="colIndex + startColIndex"
[rowIndex]="rowIndex + startRowIndex"
[attr.aria-expanded]="cell.hasChildren && cell.children.length ? 'true' : 'false'"
[attr.role]="tableType === 'columnHeader' ? 'columnheader' : tableType === 'rowHeader' ? 'rowheader' : 'none'"
[attr.id]="pivotGridId + (tableType === 'columnHeader' ? 'ch-' : 'rh-') + (rowIndex + 1) + '-' + (colIndex + 1)"></th>
<td
*ngIf="cell && tableType === 'values'"
[customCellTemplate]="customCellTemplate"
[valueCellTemplate]="valueCellTemplate"
[kendoPivotGridCell]="cell"
tableType="values"
[colIndex]="colIndex + startColIndex"
[rowIndex]="rowIndex + startRowIndex"
role="gridcell"
[attr.id]="pivotGridId + 'cell-' + (rowIndex + startRowIndex + 1) + '-' + (colIndex + startColIndex + 1)"></td>
</ng-container>
</tr>
</tbody>
</table>
`, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: PivotGridCellDirective, selector: "[kendoPivotGridCell]", inputs: ["kendoPivotGridCell", "tableType", "rowIndex", "colIndex", "customCellTemplate", "valueCellTemplate", "rowHeaderCellTemplate", "columnHeaderCellTemplate"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PivotGridTableComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-pivotgrid-table',
template: `
<table
class="k-pivotgrid-table"
role="presentation"
[ngStyle]="{
float: tableType === 'values' ? this.rtl ? 'right' : 'left' : 'initial',
overflow: 'scroll',
'-ms-overflow-style': tableType !== 'values' ? 'none' : 'auto',
'scrollbar-width': tableType !== 'values' ? 'none' : 'auto'
}">
<colgroup>
<col *ngIf="tableType === 'values' && columnVirtualization && startColIndex > 0" [style.width]="startColIndex * (colWidth >= 0 ? colWidth : 200) + 'px'"/>
<col
*ngFor="let item of (tableType === 'values' && columnVirtualization ? headerItems?.slice(0, renderedCols) : headerItems);"
[style.width]="tableType !== 'rowHeader' ? colWidth >= 0 ? colWidth + 'px' : '200px' : undefined" />
</colgroup>
<tbody class="k-pivotgrid-tbody" [attr.role]="tableType === 'values' ? 'none' : 'rowgroup'">
<tr *ngFor="let row of (tableType === 'values' && rowVirtualization ? renderedRows : rows); index as rowIndex"
class="k-pivotgrid-row"
[attr.role]="tableType === 'values' ? 'none' : 'row'">
<td *ngIf="tableType === 'values' && columnVirtualization && startColIndex > 0" class="k-pivotgrid-cell"></td>
<ng-container *ngFor="let cell of (tableType === 'values' && columnVirtualization ? row?.cells.slice(startColIndex, (startColIndex + renderedCols)) : row?.cells); index as colIndex">
<th
*ngIf="cell && tableType !== 'values'"
[kendoPivotGridCell]="cell"
[customCellTemplate]="customCellTemplate"
[rowHeaderCellTemplate]="rowHeaderCellTemplate"
[columnHeaderCellTemplate]="columnHeaderCellTemplate"
[tableType]="tableType"
[colIndex]="colIndex + startColIndex"
[rowIndex]="rowIndex + startRowIndex"
[attr.aria-expanded]="cell.hasChildren && cell.children.length ? 'true' : 'false'"
[attr.role]="tableType === 'columnHeader' ? 'columnheader' : tableType === 'rowHeader' ? 'rowheader' : 'none'"
[attr.id]="pivotGridId + (tableType === 'columnHeader' ? 'ch-' : 'rh-') + (rowIndex + 1) + '-' + (colIndex + 1)"></th>
<td
*ngIf="cell && tableType === 'values'"
[customCellTemplate]="customCellTemplate"
[valueCellTemplate]="valueCellTemplate"
[kendoPivotGridCell]="cell"
tableType="values"
[colIndex]="colIndex + startColIndex"
[rowIndex]="rowIndex + startRowIndex"
role="gridcell"
[attr.id]="pivotGridId + 'cell-' + (rowIndex + startRowIndex + 1) + '-' + (colIndex + startColIndex + 1)"></td>
</ng-container>
</tr>
</tbody>
</table>
`,
standalone: true,
imports: [NgFor, NgIf, PivotGridCellDirective, NgStyle]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.PivotGridDataService }, { type: i2.LocalizationService }, { type: i0.NgZone }, { type: i3.PivotGridScrollService }]; }, propDecorators: { tableType: [{
type: Input
}], colWidth: [{
type: Input
}], customCellTemplate: [{
type: Input
}], valueCellTemplate: [{
type: Input
}], rowHeaderCellTemplate: [{
type: Input
}], columnHeaderCellTemplate: [{
type: Input
}], scrollableSettings: [{
type: Input
}] } });