@ng-matero/extensions
Version:
Angular Material Extensions
372 lines • 190 kB
JavaScript
import { animate, state, style, transition, trigger } from '@angular/animations';
import { SelectionModel } from '@angular/cdk/collections';
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, Inject, InjectionToken, Input, Optional, Output, ViewChild, ViewEncapsulation, booleanAttribute, } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatPaginator } from '@angular/material/paginator';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatSort, MatSortHeader } from '@angular/material/sort';
import { MatCell, MatCellDef, MatColumnDef, MatFooterCell, MatFooterCellDef, MatFooterRow, MatFooterRowDef, MatHeaderCell, MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatTable, MatTableDataSource, } from '@angular/material/table';
import { MtxIsTemplateRefPipe, MtxToObservablePipe } from '@ng-matero/extensions/core';
import { MtxGridCell } from './cell';
import { MtxGridColumnMenu } from './column-menu';
import { MatColumnResize, MatResizable } from './column-resize';
import { MtxGridExpansionToggle } from './expansion-toggle';
import { MtxGridColClassPipe, MtxGridRowClassPipe } from './grid-pipes';
import { MtxGridSelectableCell } from './selectable-cell';
import * as i0 from "@angular/core";
import * as i1 from "./grid-utils";
/** Injection token that can be used to specify default grid options. */
export const MTX_GRID_DEFAULT_OPTIONS = new InjectionToken('mtx-grid-default-options');
export class MtxGrid {
get _hasNoResult() {
return (!this.dataSource.data || this.dataSource.data.length === 0) && !this.loading;
}
// TODO: Summary display conditions
get _whetherShowSummary() {
return this.showSummary;
}
constructor(_utils, _changeDetectorRef, _defaultOptions) {
this._utils = _utils;
this._changeDetectorRef = _changeDetectorRef;
this._defaultOptions = _defaultOptions;
this.dataSource = new MatTableDataSource();
/** The grid's displayed columns. */
this.displayedColumns = [];
/** The grid's columns. */
this.columns = [];
/** The grid's data. */
this.data = [];
/** The total number of the data. */
this.length = 0;
/** Whether the grid is loading. */
this.loading = false;
/** Whether the column is resizable. */
this.columnResizable = this._defaultOptions?.columnResizable ?? false;
/** Placeholder for the empty value (`null`, `''`, `[]`). */
this.emptyValuePlaceholder = this._defaultOptions?.emptyValuePlaceholder ?? '--';
// ===== Page =====
/** Whether to paginate the data on front end. */
this.pageOnFront = this._defaultOptions?.pageOnFront ?? true;
/** Whether to show the paginator. */
this.showPaginator = this._defaultOptions?.showPaginator ?? true;
/** Whether the paginator is disabled. */
this.pageDisabled = this._defaultOptions?.pageDisabled ?? false;
/** Whether to show the first/last buttons UI to the user. */
this.showFirstLastButtons = this._defaultOptions?.showFirstLastButtons ?? true;
/** The zero-based page index of the displayed list of items. */
this.pageIndex = this._defaultOptions?.pageIndex ?? 0;
/** Number of items to display on a page. */
this.pageSize = this._defaultOptions?.pageSize ?? 10;
/** The set of provided page size options to display to the user. */
this.pageSizeOptions = this._defaultOptions?.pageSizeOptions ?? [10, 50, 100];
/** Whether to hide the page size selection UI from the user. */
this.hidePageSize = this._defaultOptions?.hidePageSize ?? false;
/** Event emitted when the paginator changes the page size or page index. */
this.page = new EventEmitter();
// ===== Sort =====
/** Whether to sort the data on front end. */
this.sortOnFront = this._defaultOptions?.sortOnFront ?? true;
/** The id of the most recently sorted MatSortable. */
this.sortActive = this._defaultOptions?.sortActive ?? '';
/** The sort direction of the currently active MatSortable. */
this.sortDirection = this._defaultOptions?.sortDirection ?? '';
/**
* Whether to disable the user from clearing the sort by finishing the sort direction cycle.
* May be overriden by the column's `disableClear` in `sortProp`.
*/
this.sortDisableClear = this._defaultOptions?.sortDisableClear ?? false;
/** Whether the sort is disabled. */
this.sortDisabled = this._defaultOptions?.sortDisabled ?? false;
/**
* The direction to set when an MatSortable is initially sorted.
* May be overriden by the column's `start` in `sortProp`.
*/
this.sortStart = this._defaultOptions?.sortStart ?? 'asc';
/** Event emitted when the user changes either the active sort or sort direction. */
this.sortChange = new EventEmitter();
// ===== Row =====
/** Whether to use the row hover style. */
this.rowHover = this._defaultOptions?.rowHover ?? false;
/** Whether to use the row striped style. */
this.rowStriped = this._defaultOptions?.rowStriped ?? false;
/** Event emitted when the user clicks the row. */
this.rowClick = new EventEmitter();
/** Event emitted when the user attempts to open a context menu. */
this.rowContextMenu = new EventEmitter();
// ===== Expandable Row =====
this.expansionRowStates = [];
/** Whether the row is expandable. */
this.expandable = false;
/** Event emitted when the user toggles the expandable row. */
this.expansionChange = new EventEmitter();
// ===== Row Selection =====
this.rowSelection = new SelectionModel(true, []);
/** Whether to support multiple row/cell selection. */
this.multiSelectable = this._defaultOptions?.multiSelectable ?? true;
/** Whether the user can select multiple rows with click. */
this.multiSelectionWithClick = this._defaultOptions?.multiSelectionWithClick ?? false;
/** Whether the row is selectable. */
this.rowSelectable = this._defaultOptions?.rowSelectable ?? false;
/** Whether to hide the row selection checkbox. */
this.hideRowSelectionCheckbox = this._defaultOptions?.hideRowSelectionCheckbox ?? false;
/** Whether disable rows to be selected when clicked. */
this.disableRowClickSelection = this._defaultOptions?.disableRowClickSelection ?? false;
/** The formatter to disable the row selection or hide the row's checkbox. */
this.rowSelectionFormatter = {};
/** The selected row items. */
this.rowSelected = [];
/** Event emitted when the row is selected. */
this.rowSelectedChange = new EventEmitter();
// ===== Cell Selection =====
this.cellSelection = [];
/** Whether the cell is selectable. */
this.cellSelectable = this._defaultOptions?.cellSelectable ?? true;
/** Event emitted when the cell is selected. */
this.cellSelectedChange = new EventEmitter();
// ===== Toolbar =====
/** Whether to show the toolbar. */
this.showToolbar = this._defaultOptions?.showToolbar ?? false;
/** The text of the toolbar's title. */
this.toolbarTitle = this._defaultOptions?.toolbarTitle ?? '';
// ===== Column Menu =====
/** Whether the column is hideable. */
this.columnHideable = this._defaultOptions?.columnHideable ?? true;
/** Hide or show when the column's checkbox is checked. */
this.columnHideableChecked = this._defaultOptions?.columnHideableChecked ?? 'show';
/** Whether the column is sortable. */
this.columnSortable = this._defaultOptions?.columnSortable ?? true;
/** Whether the column is pinnable. */
this.columnPinnable = this._defaultOptions?.columnPinnable ?? true;
/** Event emitted when the column is hided or is sorted. */
this.columnChange = new EventEmitter();
/** The options for the column pin list. */
this.columnPinOptions = this._defaultOptions?.columnPinOptions ?? [];
/** Whether to show the column menu button. */
this.showColumnMenuButton = this._defaultOptions?.showColumnMenuButton ?? true;
/** The text for the column menu button. */
this.columnMenuButtonText = this._defaultOptions?.columnMenuButtonText ?? '';
/** The type for the column menu button. */
this.columnMenuButtonType = this._defaultOptions?.columnMenuButtonType ?? 'stroked';
/** The color for the column menu button. */
this.columnMenuButtonColor = this._defaultOptions?.columnMenuButtonColor;
/** The class for the column menu button. */
this.columnMenuButtonClass = this._defaultOptions?.columnMenuButtonClass ?? '';
/** The icon for the column menu button. */
this.columnMenuButtonIcon = this._defaultOptions?.columnMenuButtonIcon ?? '';
/** Whether to show the column-menu's header. */
this.showColumnMenuHeader = this._defaultOptions?.showColumnMenuHeader ?? false;
/** The text for the column-menu's header. */
this.columnMenuHeaderText = this._defaultOptions?.columnMenuHeaderText ?? 'Columns Header';
/** Whether to show the the column-menu's footer. */
this.showColumnMenuFooter = this._defaultOptions?.showColumnMenuFooter ?? false;
/** The text for the column-menu's footer. */
this.columnMenuFooterText = this._defaultOptions?.columnMenuFooterText ?? 'Columns Footer';
// ===== No Result =====
/** The displayed text for the empty data. */
this.noResultText = this._defaultOptions?.noResultText ?? 'No records found';
// ===== Row Templates =====
/** Whether to use custom row template. If true, you should define a matRowDef. */
this.useContentRowTemplate = false;
// TODO: It can't use together with `useContentRowTemplate`
this.useContentHeaderRowTemplate = false;
// TODO: It's not working
this.useContentFooterRowTemplate = false;
// ===== Summary =====
/** Whether to show the summary. */
this.showSummary = false;
// ===== Side Bar =====
/** Whether to show the sidebar. */
this.showSidebar = false;
// ===== Status Bar =====
/** Whether to show the status bar. */
this.showStatusbar = false;
}
detectChanges() {
this._changeDetectorRef.detectChanges();
}
_getColData(data, colDef) {
return this._utils.getColData(data, colDef);
}
_isColumnHide(item) {
return item.hide !== undefined ? item.hide : item.show !== undefined ? !item.show : false;
}
// Waiting for async data
ngOnChanges(changes) {
this._countPinnedPosition();
this.displayedColumns = this.columns
.filter(item => !this._isColumnHide(item))
.map(item => item.field);
if (this.showColumnMenuButton) {
this.columns.forEach(item => {
item.hide = this._isColumnHide(item);
item.show = !item.hide;
});
}
if (this.rowSelectable && !this.hideRowSelectionCheckbox) {
this.displayedColumns.unshift('MtxGridCheckboxColumnDef');
}
// We should copy each item of data for expansion data
if (this.expandable) {
this.expansionRowStates = []; // reset
this.data?.forEach(_ => {
this.expansionRowStates.push({ expanded: false });
});
}
if (this.rowSelectable) {
this.rowSelection = new SelectionModel(this.multiSelectable, this.rowSelected);
}
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.paginator = this.pageOnFront ? this.paginator : null;
this.dataSource.sort = this.sortOnFront ? this.sort : null;
// Only scroll top with data change
if (changes.data) {
this.scrollTop(0);
}
}
ngAfterViewInit() {
if (this.pageOnFront) {
this.dataSource.paginator = this.paginator;
}
if (this.sortOnFront) {
this.dataSource.sort = this.sort;
}
if (this.rowDefs?.length > 0 && this.useContentRowTemplate) {
this.rowDefs.forEach(rowDef => this.table.addRowDef(rowDef));
}
if (this.headerRowDefs?.length > 0 && this.useContentHeaderRowTemplate) {
this.headerRowDefs.forEach(headerRowDef => this.table.addHeaderRowDef(headerRowDef));
}
if (this.footerRowDefs?.length > 0 && this.useContentFooterRowTemplate) {
this.footerRowDefs.forEach(footerRowDef => this.table.addFooterRowDef(footerRowDef));
}
}
ngOnDestroy() { }
_countPinnedPosition() {
const count = (acc, cur) => acc + parseFloat(cur.width || '80px');
const pinnedLeftCols = this.columns.filter(col => col.pinned && col.pinned === 'left');
pinnedLeftCols.forEach((item, idx) => {
item.left = pinnedLeftCols.slice(0, idx).reduce(count, 0) + 'px';
});
const pinnedRightCols = this.columns
.filter(col => col.pinned && col.pinned === 'right')
.reverse();
pinnedRightCols.forEach((item, idx) => {
item.right = pinnedRightCols.slice(0, idx).reduce(count, 0) + 'px';
});
}
_getIndex(index, dataIndex) {
return index === undefined ? dataIndex : index;
}
_onSortChange(sort) {
this.sortChange.emit(sort);
}
_onRowDataChange(record) {
this.rowChangeRecord = record;
this._changeDetectorRef.markForCheck();
}
/** Expansion change event */
_onExpansionChange(expansionRef, rowData, column, index) {
this.expansionChange.emit({ expanded: expansionRef.expanded, data: rowData, index, column });
}
/** Cell select event */
_selectCell(cellRef, rowData, colDef) {
// If not the same cell
if (this._selectedCell !== cellRef) {
const colValue = this._utils.getCellValue(rowData, colDef);
this.cellSelection = []; // reset
this.cellSelection.push({ cellData: colValue, rowData, colDef });
this.cellSelectedChange.emit(this.cellSelection);
if (this._selectedCell) {
this._selectedCell.deselect(); // the selectedCell will be undefined
}
}
this._selectedCell = cellRef.selected ? cellRef : undefined;
}
/** Row select event */
_selectRow(event, rowData, index) {
if (this.rowSelectable &&
!this.rowSelectionFormatter.disabled?.(rowData, index) &&
!this.rowSelectionFormatter.hideCheckbox?.(rowData, index) &&
!this.disableRowClickSelection) {
// metaKey -> command key
if (!this.multiSelectionWithClick && !event.ctrlKey && !event.metaKey) {
this.rowSelection.clear();
}
this._toggleNormalCheckbox(rowData);
}
this.rowClick.emit({ event, rowData, index });
}
/** Whether the number of selected elements matches the total number of rows. */
_isAllSelected() {
const numSelected = this.rowSelection.selected.length;
const numRows = this.dataSource.data.filter((row, index) => !this.rowSelectionFormatter.disabled?.(row, index)).length;
return numSelected === numRows;
}
/** Select all rows if they are not all selected; otherwise clear selection. */
_toggleMasterCheckbox() {
this._isAllSelected()
? this.rowSelection.clear()
: this.dataSource.data.forEach((row, index) => {
if (!this.rowSelectionFormatter.disabled?.(row, index)) {
this.rowSelection.select(row);
}
});
this.rowSelectedChange.emit(this.rowSelection.selected);
}
/** Select normal row */
_toggleNormalCheckbox(row) {
this.rowSelection.toggle(row);
this.rowSelectedChange.emit(this.rowSelection.selected);
}
/** Column change event */
_onColumnChange(columns) {
this.columnChange.emit(columns);
this.displayedColumns = Object.assign([], this.getDisplayedColumnFields(columns));
if (this.rowSelectable && !this.hideRowSelectionCheckbox) {
this.displayedColumns.unshift('MtxGridCheckboxColumnDef');
}
}
getDisplayedColumnFields(columns) {
const fields = columns
.filter(item => (this.columnHideableChecked === 'show' ? item.show : !item.hide))
.map(item => item.field);
return fields;
}
/** Customize expansion event */
toggleExpansion(index) {
if (!this.expandable) {
throw new Error('The `expandable` should be set true.');
}
this.expansionRowStates[index].expanded = !this.expansionRowStates[index].expanded;
return this.expansionRowStates[index].expanded;
}
/** Scroll to top when turn to the next page. */
_onPage(e) {
if (this.pageOnFront) {
this.scrollTop(0);
}
this.page.emit(e);
}
scrollTop(value) {
if (value == null) {
return this.tableContainer?.nativeElement.scrollTop;
}
if (this.tableContainer && !this.loading) {
this.tableContainer.nativeElement.scrollTop = value;
}
}
scrollLeft(value) {
if (value == null) {
return this.tableContainer?.nativeElement.scrollLeft;
}
if (this.tableContainer && !this.loading) {
this.tableContainer.nativeElement.scrollLeft = value;
}
}
_contextmenu(event, rowData, index) {
this.rowContextMenu.emit({ event, rowData, index });
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxGrid, deps: [{ token: i1.MtxGridUtils }, { token: i0.ChangeDetectorRef }, { token: MTX_GRID_DEFAULT_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.0", type: MtxGrid, isStandalone: true, selector: "mtx-grid", inputs: { displayedColumns: "displayedColumns", columns: "columns", data: "data", length: "length", loading: ["loading", "loading", booleanAttribute], trackBy: "trackBy", columnResizable: ["columnResizable", "columnResizable", booleanAttribute], emptyValuePlaceholder: "emptyValuePlaceholder", pageOnFront: ["pageOnFront", "pageOnFront", booleanAttribute], showPaginator: ["showPaginator", "showPaginator", booleanAttribute], pageDisabled: ["pageDisabled", "pageDisabled", booleanAttribute], showFirstLastButtons: ["showFirstLastButtons", "showFirstLastButtons", booleanAttribute], pageIndex: "pageIndex", pageSize: "pageSize", pageSizeOptions: "pageSizeOptions", hidePageSize: ["hidePageSize", "hidePageSize", booleanAttribute], paginationTemplate: "paginationTemplate", sortOnFront: ["sortOnFront", "sortOnFront", booleanAttribute], sortActive: "sortActive", sortDirection: "sortDirection", sortDisableClear: ["sortDisableClear", "sortDisableClear", booleanAttribute], sortDisabled: ["sortDisabled", "sortDisabled", booleanAttribute], sortStart: "sortStart", rowHover: ["rowHover", "rowHover", booleanAttribute], rowStriped: ["rowStriped", "rowStriped", booleanAttribute], expandable: ["expandable", "expandable", booleanAttribute], expansionTemplate: "expansionTemplate", multiSelectable: ["multiSelectable", "multiSelectable", booleanAttribute], multiSelectionWithClick: ["multiSelectionWithClick", "multiSelectionWithClick", booleanAttribute], rowSelectable: ["rowSelectable", "rowSelectable", booleanAttribute], hideRowSelectionCheckbox: ["hideRowSelectionCheckbox", "hideRowSelectionCheckbox", booleanAttribute], disableRowClickSelection: ["disableRowClickSelection", "disableRowClickSelection", booleanAttribute], rowSelectionFormatter: "rowSelectionFormatter", rowClassFormatter: "rowClassFormatter", rowSelected: "rowSelected", cellSelectable: ["cellSelectable", "cellSelectable", booleanAttribute], showToolbar: ["showToolbar", "showToolbar", booleanAttribute], toolbarTitle: "toolbarTitle", toolbarTemplate: "toolbarTemplate", columnHideable: ["columnHideable", "columnHideable", booleanAttribute], columnHideableChecked: "columnHideableChecked", columnSortable: ["columnSortable", "columnSortable", booleanAttribute], columnPinnable: ["columnPinnable", "columnPinnable", booleanAttribute], columnPinOptions: "columnPinOptions", showColumnMenuButton: ["showColumnMenuButton", "showColumnMenuButton", booleanAttribute], columnMenuButtonText: "columnMenuButtonText", columnMenuButtonType: "columnMenuButtonType", columnMenuButtonColor: "columnMenuButtonColor", columnMenuButtonClass: "columnMenuButtonClass", columnMenuButtonIcon: "columnMenuButtonIcon", showColumnMenuHeader: ["showColumnMenuHeader", "showColumnMenuHeader", booleanAttribute], columnMenuHeaderText: "columnMenuHeaderText", columnMenuHeaderTemplate: "columnMenuHeaderTemplate", showColumnMenuFooter: ["showColumnMenuFooter", "showColumnMenuFooter", booleanAttribute], columnMenuFooterText: "columnMenuFooterText", columnMenuFooterTemplate: "columnMenuFooterTemplate", noResultText: "noResultText", noResultTemplate: "noResultTemplate", headerTemplate: "headerTemplate", headerExtraTemplate: "headerExtraTemplate", cellTemplate: "cellTemplate", useContentRowTemplate: ["useContentRowTemplate", "useContentRowTemplate", booleanAttribute], useContentHeaderRowTemplate: ["useContentHeaderRowTemplate", "useContentHeaderRowTemplate", booleanAttribute], useContentFooterRowTemplate: ["useContentFooterRowTemplate", "useContentFooterRowTemplate", booleanAttribute], showSummary: ["showSummary", "showSummary", booleanAttribute], summaryTemplate: "summaryTemplate", showSidebar: ["showSidebar", "showSidebar", booleanAttribute], sidebarTemplate: "sidebarTemplate", showStatusbar: ["showStatusbar", "showStatusbar", booleanAttribute], statusbarTemplate: "statusbarTemplate" }, outputs: { page: "page", sortChange: "sortChange", rowClick: "rowClick", rowContextMenu: "rowContextMenu", expansionChange: "expansionChange", rowSelectedChange: "rowSelectedChange", cellSelectedChange: "cellSelectedChange", columnChange: "columnChange" }, host: { classAttribute: "mtx-grid" }, queries: [{ propertyName: "rowDefs", predicate: MatRowDef }, { propertyName: "headerRowDefs", predicate: MatHeaderRowDef }, { propertyName: "footerRowDefs", predicate: MatFooterRow }], viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "columnMenu", first: true, predicate: ["columnMenu"], descendants: true }, { propertyName: "tableContainer", first: true, predicate: ["tableContainer"], descendants: true }], exportAs: ["mtxGrid"], usesOnChanges: true, ngImport: i0, template: "<!-- Progress bar-->\n@if (loading) {\n <div class=\"mtx-grid-progress\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n}\n\n<!-- Toolbar -->\n@if (showToolbar) {\n <div class=\"mtx-grid-toolbar\">\n <div class=\"mtx-grid-toolbar-content\">\n @if (toolbarTemplate) {\n <ng-template [ngTemplateOutlet]=\"toolbarTemplate\"></ng-template>\n } @else {\n @if (toolbarTitle) {\n <div class=\"mtx-grid-toolbar-title\">{{toolbarTitle}}</div>\n }\n }\n </div>\n <div class=\"mtx-grid-toolbar-actions\">\n @if (showColumnMenuButton) {\n <mtx-grid-column-menu #columnMenu\n [columns]=\"columns\"\n [buttonText]=\"columnMenuButtonText\"\n [buttonType]=\"columnMenuButtonType\"\n [buttonColor]=\"columnMenuButtonColor\"\n [buttonClass]=\"columnMenuButtonClass\"\n [buttonIcon]=\"columnMenuButtonIcon\"\n [selectable]=\"columnHideable\"\n [selectableChecked]=\"columnHideableChecked\"\n [sortable]=\"columnSortable\"\n [pinnable]=\"columnPinnable\"\n [showHeader]=\"showColumnMenuHeader\"\n [headerText]=\"columnMenuHeaderText\"\n [headerTemplate]=\"columnMenuHeaderTemplate\"\n [showFooter]=\"showColumnMenuFooter\"\n [footerText]=\"columnMenuFooterText\"\n [footerTemplate]=\"columnMenuFooterTemplate\"\n [pinOptions]=\"columnPinOptions\"\n (columnChange)=\"_onColumnChange($event)\">\n </mtx-grid-column-menu>\n }\n </div>\n </div>\n}\n\n<div class=\"mtx-grid-main mtx-grid-layout\">\n <!-- Table content -->\n <div class=\"mtx-grid-content mtx-grid-layout\">\n <div #tableContainer class=\"mat-table-container\" [class.mat-table-with-data]=\"!_hasNoResult\">\n @if (!columnResizable) {\n <table mat-table\n [class]=\"{'mat-table-hover': rowHover, 'mat-table-striped': rowStriped, 'mat-table-expandable': expandable}\"\n [dataSource]=\"dataSource\" [multiTemplateDataRows]=\"expandable\"\n matSort\n [matSortActive]=\"sortActive\"\n [matSortDirection]=\"sortDirection\"\n [matSortDisableClear]=\"sortDisableClear\"\n [matSortDisabled]=\"sortDisabled\"\n [matSortStart]=\"sortStart\"\n (matSortChange)=\"_onSortChange($event)\"\n [trackBy]=\"trackBy\">\n @if (rowSelectable && !hideRowSelectionCheckbox) {\n <ng-container matColumnDef=\"MtxGridCheckboxColumnDef\">\n <th mat-header-cell *matHeaderCellDef class=\"mtx-grid-checkbox-cell\">\n @if (multiSelectable) {\n <mat-checkbox\n [checked]=\"rowSelection.hasValue() && _isAllSelected()\"\n [indeterminate]=\"rowSelection.hasValue() && !_isAllSelected()\"\n (change)=\"$event ? _toggleMasterCheckbox() : null\">\n </mat-checkbox>\n }\n </th>\n <td mat-cell *matCellDef=\"let row; let index = index; let dataIndex = dataIndex;\"\n class=\"mtx-grid-checkbox-cell\">\n @if (!(rowSelectionFormatter.hideCheckbox && rowSelectionFormatter.hideCheckbox(row, _getIndex(index, dataIndex)))) {\n <mat-checkbox\n [disabled]=\"rowSelectionFormatter.disabled && rowSelectionFormatter.disabled(row, _getIndex(index, dataIndex))\"\n [checked]=\"rowSelection.isSelected(row)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? _toggleNormalCheckbox(row) : null\">\n </mat-checkbox>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef class=\"mtx-grid-checkbox-cell\"></td>\n </ng-container>\n }\n @for (col of columns; track col) {\n <ng-container [matColumnDef]=\"col.field\"\n [sticky]=\"col.pinned==='left'\" [stickyEnd]=\"col.pinned==='right'\">\n <th mat-header-cell *matHeaderCellDef\n [class]=\"col | colClass\"\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'min-width': col.width, 'left': col.left, 'right': col.right}\">\n <div class=\"mat-header-cell-inner\">\n @if (headerTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(headerTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n } @else {\n @if ($any(headerTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(headerTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n } @else {\n <div [mat-sort-header]=\"col.sortProp?.id || col.field\"\n [disabled]=\"!col.sortable\"\n [disableClear]=\"col.sortProp?.disableClear ?? sortDisableClear\"\n [arrowPosition]=\"col.sortProp?.arrowPosition!\"\n [start]=\"col.sortProp?.start!\">\n @if (col.showExpand) {\n <span class=\"mtx-grid-expansion-placeholder\"></span>\n }\n <span>{{col.header | toObservable | async}}</span>\n @if (col.sortable) {\n <svg class=\"mtx-grid-icon mat-sort-header-icon\" viewBox=\"0 0 24 24\"\n width=\"24px\" height=\"24px\" fill=\"currentColor\" focusable=\"false\">\n <path d=\"M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z\" />\n </svg>\n }\n </div>\n <ng-template [ngTemplateOutlet]=\"headerExtraTplBase\"\n [ngTemplateOutletContext]=\"{ $implicit: headerExtraTemplate, colDef: col }\">\n </ng-template>\n }\n }\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row; let index = index; let dataIndex = dataIndex;\"\n [class]=\"col | colClass: row: rowChangeRecord: rowChangeRecord?.currentValue\"\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'min-width': col.width, 'left': col.left, 'right': col.right}\"\n mtx-grid-selectable-cell [cellSelectable]=\"cellSelectable\"\n (cellSelectedChange)=\"_selectCell($event, row, col)\">\n @if (cellTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(cellTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if ($any(cellTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(cellTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if (col.cellTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"col.cellTemplate!\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if (col.showExpand) {\n <button class=\"mtx-grid-row-expand-button\"\n mat-icon-button mtx-grid-expansion-toggle type=\"button\"\n [(opened)]=\"expansionRowStates[dataIndex].expanded\"\n (toggleChange)=\"_onExpansionChange($event, row, col, dataIndex);\">\n <svg class=\"mtx-grid-icon mtx-grid-row-expand-icon\" viewBox=\"0 0 24 24\"\n width=\"24px\" height=\"24px\" fill=\"currentColor\" focusable=\"false\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n }\n <mtx-grid-cell [rowData]=\"row\" [colDef]=\"col\" [placeholder]=\"emptyValuePlaceholder\"\n (rowDataChange)=\"_onRowDataChange($event)\"></mtx-grid-cell>\n }\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'min-width': col.width, 'left': col.left, 'right': col.right}\">\n @if (col.showExpand) {\n <span class=\"mtx-grid-expansion-placeholder\"></span>\n }\n @if (summaryTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(summaryTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col, data: data }\">\n </ng-template>\n } @else {\n @if ($any(summaryTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(summaryTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: _getColData(data, col), colData: _getColData(data, col), colDef: col }\">\n </ng-template>\n } @else {\n <mtx-grid-cell [summary]=\"true\" [data]=\"data\" [colDef]=\"col\"\n [placeholder]=\"emptyValuePlaceholder\"></mtx-grid-cell>\n }\n }\n </td>\n </ng-container>\n }\n @if (!useContentHeaderRowTemplate) {\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n }\n @if (!useContentRowTemplate) {\n <tr mat-row\n *matRowDef=\"let row; let index = index; let dataIndex = dataIndex; columns: displayedColumns;\"\n [class]=\"row | rowClass: index: dataIndex: rowClassFormatter\"\n [class.selected]=\"rowSelection.isSelected(row)\"\n (click)=\"_selectRow($event, row, _getIndex(index, dataIndex))\"\n (contextmenu)=\"_contextmenu($event, row, _getIndex(index, dataIndex))\">\n </tr>\n }\n @if (_whetherShowSummary) {\n <tr mat-footer-row *matFooterRowDef=\"displayedColumns; sticky: true\"></tr>\n }\n @if (expandable) {\n <!-- Expanded Content Column - The expandable row is made up of this one column that spans across all columns -->\n <ng-container matColumnDef=\"MtxGridExpansionColumnDef\">\n <td mat-cell *matCellDef=\"let row; let dataIndex = dataIndex\"\n [attr.colspan]=\"displayedColumns.length\">\n <div class=\"mtx-grid-expansion-detail\"\n [@expansion]=\"expansionRowStates[dataIndex].expanded ? 'expanded' : 'collapsed'\">\n <ng-template [ngTemplateOutlet]=\"expansionTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: dataIndex, expanded: expansionRowStates[dataIndex].expanded }\">\n </ng-template>\n </div>\n </td>\n </ng-container>\n <tr mat-row\n *matRowDef=\"let row; columns: ['MtxGridExpansionColumnDef']; let dataIndex = dataIndex\"\n class=\"mtx-grid-expansion\"\n [class]=\"expansionRowStates[dataIndex].expanded ? 'expanded' : 'collapsed'\">\n </tr>\n }\n </table>\n } @else {\n <!-- TODO: Use flexbox-based mat-table -->\n <table mat-table\n columnResize\n [class]=\"{'mat-table-hover': rowHover, 'mat-table-striped': rowStriped, 'mat-table-expandable': expandable}\"\n [dataSource]=\"dataSource\" [multiTemplateDataRows]=\"expandable\"\n matSort\n [matSortActive]=\"sortActive\"\n [matSortDirection]=\"sortDirection\"\n [matSortDisableClear]=\"sortDisableClear\"\n [matSortDisabled]=\"sortDisabled\"\n [matSortStart]=\"sortStart\"\n (matSortChange)=\"_onSortChange($event)\"\n [trackBy]=\"trackBy\">\n @if (rowSelectable && !hideRowSelectionCheckbox) {\n <ng-container matColumnDef=\"MtxGridCheckboxColumnDef\">\n <th mat-header-cell *matHeaderCellDef class=\"mtx-grid-checkbox-cell\">\n @if (multiSelectable) {\n <mat-checkbox\n [checked]=\"rowSelection.hasValue() && _isAllSelected()\"\n [indeterminate]=\"rowSelection.hasValue() && !_isAllSelected()\"\n (change)=\"$event ? _toggleMasterCheckbox() : null\">\n </mat-checkbox>\n }\n </th>\n <td mat-cell *matCellDef=\"let row; let index = index; let dataIndex = dataIndex;\"\n class=\"mtx-grid-checkbox-cell\">\n @if (!(rowSelectionFormatter.hideCheckbox && rowSelectionFormatter.hideCheckbox(row, _getIndex(index, dataIndex)))) {\n <mat-checkbox\n [disabled]=\"rowSelectionFormatter.disabled && rowSelectionFormatter.disabled(row, _getIndex(index, dataIndex))\"\n [checked]=\"rowSelection.isSelected(row)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? _toggleNormalCheckbox(row) : null\">\n </mat-checkbox>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef class=\"mtx-grid-checkbox-cell\"></td>\n </ng-container>\n }\n @for (col of columns; track col) {\n <ng-container [matColumnDef]=\"col.field\"\n [sticky]=\"col.pinned==='left'\" [stickyEnd]=\"col.pinned==='right'\">\n <th mat-header-cell *matHeaderCellDef\n [class]=\"col | colClass\"\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'left': col.left, 'right': col.right}\"\n [resizable]=\"col.resizable\"\n [matResizableMinWidthPx]=\"col.minWidth\" [matResizableMaxWidthPx]=\"col.maxWidth\">\n <div class=\"mat-header-cell-inner\">\n @if (headerTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(headerTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n } @else {\n @if ($any(headerTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(headerTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n } @else {\n <div [mat-sort-header]=\"col.sortProp?.id || col.field\"\n [disabled]=\"!col.sortable\"\n [disableClear]=\"col.sortProp?.disableClear ?? sortDisableClear\"\n [arrowPosition]=\"col.sortProp?.arrowPosition!\"\n [start]=\"col.sortProp?.start!\">\n @if (col.showExpand) {\n <span class=\"mtx-grid-expansion-placeholder\"></span>\n }\n <span>{{col.header | toObservable | async}}</span>\n @if (col.sortable) {\n <svg class=\"mtx-grid-icon mat-sort-header-icon\" viewBox=\"0 0 24 24\"\n width=\"24px\" height=\"24px\" fill=\"currentColor\" focusable=\"false\">\n <path d=\"M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z\" />\n </svg>\n }\n </div>\n <ng-template [ngTemplateOutlet]=\"headerExtraTplBase\"\n [ngTemplateOutletContext]=\"{ $implicit: headerExtraTemplate, colDef: col }\">\n </ng-template>\n }\n }\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row; let index = index; let dataIndex = dataIndex;\"\n [class]=\"col | colClass: row: rowChangeRecord :rowChangeRecord?.currentValue\"\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'left': col.left, 'right': col.right}\"\n mtx-grid-selectable-cell [cellSelectable]=\"cellSelectable\"\n (cellSelectedChange)=\"_selectCell($event, row, col)\">\n @if (cellTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(cellTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if ($any(cellTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(cellTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if (col.cellTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"col.cellTemplate!\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: _getIndex(index, dataIndex), colDef: col }\">\n </ng-template>\n } @else {\n @if (col.showExpand) {\n <button class=\"mtx-grid-row-expand-button\"\n mat-icon-button mtx-grid-expansion-toggle type=\"button\"\n [(opened)]=\"expansionRowStates[dataIndex].expanded\"\n (toggleChange)=\"_onExpansionChange($event, row, col, dataIndex);\">\n <svg class=\"mtx-grid-icon mtx-grid-row-expand-icon\" viewBox=\"0 0 24 24\"\n width=\"24px\" height=\"24px\" fill=\"currentColor\" focusable=\"false\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n </button>\n }\n <mtx-grid-cell [rowData]=\"row\" [colDef]=\"col\" [placeholder]=\"emptyValuePlaceholder\"\n (rowDataChange)=\"_onRowDataChange($event)\"></mtx-grid-cell>\n }\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef\n [class.mat-table-sticky-left]=\"col.pinned === 'left'\"\n [class.mat-table-sticky-right]=\"col.pinned === 'right'\"\n [style]=\"{'width': col.width, 'left': col.left, 'right': col.right}\">\n @if (col.showExpand) {\n <span class=\"mtx-grid-expansion-placeholder\"></span>\n }\n @if (summaryTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(summaryTemplate)\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col, data: data }\">\n </ng-template>\n } @else {\n @if ($any(summaryTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"$any(summaryTemplate)[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: _getColData(data, col), colData: _getColData(data, col), colDef: col }\">\n </ng-template>\n } @else {\n <mtx-grid-cell [summary]=\"true\" [data]=\"data\" [colDef]=\"col\"\n [placeholder]=\"emptyValuePlaceholder\"></mtx-grid-cell>\n }\n }\n </td>\n </ng-container>\n }\n @if (!useContentHeaderRowTemplate) {\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n }\n @if (!useContentRowTemplate) {\n <tr mat-row\n *matRowDef=\"let row; let index = index; let dataIndex = dataIndex; columns: displayedColumns;\"\n [class]=\"row | rowClass: index: dataIndex: rowClassFormatter\"\n [class.selected]=\"rowSelection.isSelected(row)\"\n (click)=\"_selectRow($event, row, _getIndex(index, dataIndex))\"\n (contextmenu)=\"_contextmenu($event, row, _getIndex(index, dataIndex))\">\n </tr>\n }\n @if (_whetherShowSummary) {\n <tr mat-footer-row *matFooterRowDef=\"displayedColumns; sticky: true\"></tr>\n }\n @if (expandable) {\n <!-- Expanded Content Column - The expandable row is made up of this one column that spans across all columns -->\n <ng-container matColumnDef=\"MtxGridExpansionColumnDef\">\n <td mat-cell *matCellDef=\"let row; let dataIndex = dataIndex\"\n [attr.colspan]=\"displayedColumns.length\">\n <div class=\"mtx-grid-expansion-detail\"\n [@expansion]=\"expansionRowStates[dataIndex].expanded ? 'expanded' : 'collapsed'\">\n <ng-template [ngTemplateOutlet]=\"expansionTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row, rowData: row, index: dataIndex, expanded: expansionRowStates[dataIndex].expanded }\">\n </ng-template>\n </div>\n </td>\n </ng-container>\n <tr mat-row\n *matRowDef=\"let row; columns: ['MtxGridExpansionColumnDef']; let dataIndex = dataIndex\"\n class=\"mtx-grid-expansion\"\n [class]=\"expansionRowStates[dataIndex].expanded ? 'expanded' : 'collapsed'\">\n </tr>\n }\n </table>\n }\n </div>\n\n <!-- No result -->\n @if (_hasNoResult) {\n <div class=\"mtx-grid-no-result\">\n @if (noResultTemplate) {\n <ng-template [ngTemplateOutlet]=\"noResultTemplate\"></ng-template>\n } @else {\n {{noResultText}}\n }\n </div>\n }\n </div>\n\n <!-- Tool sidebar -->\n @if (showSidebar) {\n <div class=\"mtx-grid-sidebar\">\n @if (sidebarTemplate) {\n <ng-template [ngTemplateOutlet]=\"sidebarTemplate\"></ng-template>\n }\n </div>\n }\n</div>\n\n<div class=\"mtx-grid-footer\">\n <!-- Status Bar -->\n @if (showStatusbar) {\n <div class=\"mtx-grid-statusbar\">\n @if (statusbarTemplate) {\n <ng-template [ngTemplateOutlet]=\"statusbarTemplate\"></ng-template>\n }\n </div>\n }\n\n <!-- Pagination -->\n <div class=\"mtx-grid-pagination\">\n @if (paginationTemplate) {\n <ng-template [ngTemplateOutlet]=\"paginationTemplate\"></ng-template>\n } @else {\n <mat-paginator [class.mat-paginator-hidden]=\"!showPaginator\"\n [showFirstLastButtons]=\"showFirstLastButtons\"\n [length]=\"length\"\n [pageIndex]=\"pageIndex\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [hidePageSize]=\"hidePageSize\"\n (page)=\"_onPage($event)\"\n [disabled]=\"pageDisabled\">\n </mat-paginator>\n }\n </div>\n</div>\n\n<!-- Header template for extra content -->\n<ng-template #headerExtraTplBase let-headerExtraTemplate let-col=\"colDef\">\n @if (headerExtraTemplate | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"headerExtraTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n } @else {\n @if ($any(headerExtraTemplate)?.[col.field] | isTemplateRef) {\n <ng-template [ngTemplateOutlet]=\"headerExtraTemplate[col.field]\"\n [ngTemplateOutletContext]=\"{ $implicit: col, colDef: col }\">\n </ng-template>\n }\n }\n</ng-template>\n", styles: [".mtx-grid{position:relative;display:flex;flex-direction:column;width:100%;overflow:hidden;border:1px solid var(--mtx-grid-outline-color, var(--mat-app-outline-variant));border-radius:var(--mtx-grid-container-shape, var(--mat-app-corner-medium))}.mtx-grid .mat-mdc-table{--mat-table-row-item-outline-color: var(--mtx-grid-outline-color, var(--mat-app-outline-variant))}.mtx-grid .mat-mdc-table.mat-table-striped .mat-row-odd{background-color:var(--mtx-grid-table-row-striped-background-color, var(--mat-app-surface-container))}.mtx-grid .mat-mdc-table.mat-table-hover .mat-mdc-row:hover{background-color:var(--mtx-grid-table-row-hover-background-color, var(--mat-app-secondary-container))}.mtx-grid .mat-mdc-table.mat-table-hover .mat-mdc-row.selected:hover{background-color:var(--mtx-grid-table-row-selected-hover-background-color, var(--mat-app-primary-container))}.mtx-grid .mat-mdc-table .mat-mdc-row.selected{background-color:var(--mtx-grid-table-row-selected-background-color, var(--mat-app-secondary-container))}.mtx-grid .mat-mdc-table