UNPKG

@progress/kendo-angular-pivotgrid

Version:
325 lines (324 loc) 20 kB
/**----------------------------------------------------------------------------------------- * 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 }] } });