UNPKG

@ng-matero/extensions

Version:
372 lines 190 kB
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