igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,001 lines (995 loc) • 211 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, TemplateRef, Directive, Pipe, Input, ChangeDetectionStrategy, Component, forwardRef, HostBinding, ViewChildren, IterableDiffers, booleanAttribute, ContentChild, ViewChild, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { NgClass, NgStyle, NgTemplateOutlet, DecimalPipe, PercentPipe, CurrencyPipe, DatePipe } from '@angular/common';
import { GridBaseAPIService, IgxGridSelectionService, GridSelectionMode, IGX_GRID_BASE, GridSummaryPosition, IgxTreeGridRow, IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe, IgxRowDirective, IgxRowDragDirective, IgxGridCellComponent, IgxGridNotGroupedPipe, IgxGridCellStylesPipe, IgxGridCellStyleClassesPipe, IgxGridDataMapperPipe, IgxGridTransactionStatePipe, IgxGroupByAreaDirective, IgxGroupAreaDropDirective, IgxGroupByMetaPipe, IgxGridTransaction, IgxGridCell, IgxSummaryRow, IgxGridHeaderRowComponent, IgxGridBodyDirective, IgxGridDragSelectDirective, IgxColumnMovingDropDirective, IgxSummaryRowComponent, IgxRowEditTabStopDirective, IgxGridColumnResizerComponent, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxSummaryDataPipe, IgxGridCRUDService, IgxGridValidationService, IgxGridSummaryService, IgxGridNavigationService, IGX_GRID_SERVICE_BASE, IgxFilteringService, IgxColumnResizingService, IgxRowAddTextDirective, IgxRowEditActionsDirective, IgxRowEditTextDirective, IgxGridFooterComponent, IgxAdvancedFilteringDialogComponent, IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective, IgxHeaderExpandedIndicatorDirective, IgxHeaderCollapsedIndicatorDirective, IgxExcelStyleHeaderIconDirective, IgxSortAscendingHeaderIconDirective, IgxSortDescendingHeaderIconDirective, IgxSortHeaderIconDirective, IgxDragIndicatorIconDirective, IgxRowDragGhostDirective, IgxGridStateDirective, IgxGridPinningActionsComponent, IgxGridEditingActionsComponent, IgxGridActionsBaseDirective, IgxGridActionButtonComponent, IgxGridHeaderComponent, IgxGridHeaderGroupComponent, IgxFilterCellTemplateDirective, IgxSummaryTemplateDirective, IgxCellTemplateDirective, IgxCellValidationErrorDirective, IgxCellHeaderTemplateDirective, IgxCellFooterTemplateDirective, IgxCellEditorTemplateDirective, IgxCollapsibleIndicatorTemplateDirective, IgxColumnComponent, IgxColumnGroupComponent, IgxColumnLayoutComponent, IgxColumnActionsComponent, IgxColumnHidingDirective, IgxColumnPinningDirective, IgxRowSelectorDirective, IgxGroupByRowSelectorDirective, IgxHeadSelectorDirective, IgxCSVTextDirective, IgxExcelTextDirective, IgxGridToolbarActionsComponent, IgxGridToolbarAdvancedFilteringComponent, IgxGridToolbarComponent, IgxGridToolbarExporterComponent, IgxGridToolbarHidingComponent, IgxGridToolbarPinningComponent, IgxGridToolbarTitleComponent, IgxGridToolbarDirective, IgxGridExcelStyleFilteringComponent, IgxExcelStyleHeaderComponent, IgxExcelStyleSortingComponent, IgxExcelStylePinningComponent, IgxExcelStyleHidingComponent, IgxExcelStyleSelectingComponent, IgxExcelStyleClearFiltersComponent, IgxExcelStyleConditionalFilterComponent, IgxExcelStyleMovingComponent, IgxExcelStyleSearchComponent, IgxExcelStyleColumnOperationsTemplateDirective, IgxExcelStyleFilterOperationsTemplateDirective, IgxExcelStyleLoadingValuesTemplateDirective, IgxColumnRequiredValidatorDirective, IgxColumnMinValidatorDirective, IgxColumnMaxValidatorDirective, IgxColumnEmailValidatorDirective, IgxColumnMinLengthValidatorDirective, IgxColumnMaxLengthValidatorDirective, IgxColumnPatternValidatorDirective } from 'igniteui-angular/grids/core';
import { TransactionType, GridColumnDataType, DataUtil, cloneArray, IgxDataRecordSorting, TreeGridFilteringStrategy, FilterUtil, mergeObjects, GridSummaryCalculationMode, FilteringExpressionsTree, cloneHierarchicalArray, HammerGesturesManager, IgxHierarchicalTransactionFactory, DefaultTreeGridMergeStrategy, TransactionEventOrigin, IgxOverlayOutletDirective, IgxSorting, formatDate } from 'igniteui-angular/core';
import { IgxGridExpandableCellComponent, IgxGridBaseDirective, IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from 'igniteui-angular/grids/grid';
import { takeUntil, first } from 'rxjs/operators';
import * as i1 from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { IgxChipComponent, IgxChipsAreaComponent } from 'igniteui-angular/chips';
import { IgxTextHighlightDirective, IgxFocusDirective, IgxDateTimeEditorDirective, IgxTooltipTargetDirective, IgxTooltipDirective, IgxTextSelectionDirective, IgxGridForOfDirective, IgxDropDirective, IgxTemplateOutletDirective, IgxToggleDirective, IgxButtonDirective, IgxRippleDirective, IgxScrollInertiaDirective, IgxForOfSyncService, IgxForOfScrollSyncService } from 'igniteui-angular/directives';
import { IgxIconComponent } from 'igniteui-angular/icon';
import { IgxInputGroupComponent, IgxInputDirective, IgxPrefixDirective, IgxSuffixDirective } from 'igniteui-angular/input-group';
import { IgxCheckboxComponent } from 'igniteui-angular/checkbox';
import { IgxDatePickerComponent } from 'igniteui-angular/date-picker';
import { IgxTimePickerComponent } from 'igniteui-angular/time-picker';
import { IgxCircularProgressBarComponent } from 'igniteui-angular/progressbar';
import { IgxSnackbarComponent } from 'igniteui-angular/snackbar';
import { Subject } from 'rxjs';
import { IgxPaginatorComponent, IgxPageNavigationComponent, IgxPageSizeSelectorComponent, IgxPaginatorContentDirective, IgxPaginatorDirective } from 'igniteui-angular/paginator';
class IgxTreeGridAPIService extends GridBaseAPIService {
get_all_data(transactions) {
const grid = this.grid;
let data = grid && grid.flatData ? grid.flatData : [];
data = transactions ? grid.dataWithAddedInTransactionRows : data;
return data;
}
get_summary_data() {
const grid = this.grid;
const data = grid.processedRootRecords?.filter(row => row.isFilteredOutParent === undefined || row.isFilteredOutParent === false)
.map(rec => rec.data);
if (data && grid.transactions.enabled) {
const deletedRows = grid.transactions.getTransactionLog().filter(t => t.type === TransactionType.DELETE).map(t => t.id);
deletedRows.forEach(rowID => {
const tempData = grid.primaryKey ? data.map(rec => rec[grid.primaryKey]) : data;
const index = tempData.indexOf(rowID);
if (index !== -1) {
data.splice(index, 1);
}
});
}
return data;
}
allow_expansion_state_change(rowID, expanded) {
const grid = this.grid;
const row = grid.records.get(rowID);
if (row.expanded === expanded ||
((!row.children || !row.children.length) && (!grid.loadChildrenOnDemand ||
(grid.hasChildrenKey && !row.data[grid.hasChildrenKey])))) {
return false;
}
return true;
}
expand_path_to_record(record) {
const grid = this.grid;
const expandedStates = grid.expansionStates;
while (record.parent) {
record = record.parent;
const expanded = this.get_row_expansion_state(record);
if (!expanded) {
expandedStates.set(record.key, true);
}
}
grid.expansionStates = expandedStates;
if (grid.rowEditable) {
grid.gridAPI.crudService.endEdit(false);
}
}
get_row_expansion_state(record) {
const grid = this.grid;
const states = grid.expansionStates;
const expanded = states.get(record.key);
if (expanded !== undefined) {
return expanded;
}
else {
return record.children && record.children.length && record.level < grid.expansionDepth;
}
}
should_apply_number_style(column) {
return column.dataType === GridColumnDataType.Number && column.visibleIndex !== 0;
}
deleteRowById(rowID) {
const treeGrid = this.grid;
const flatDataWithCascadeOnDeleteAndTransactions = treeGrid.primaryKey &&
treeGrid.foreignKey &&
treeGrid.cascadeOnDelete &&
treeGrid.transactions.enabled;
if (flatDataWithCascadeOnDeleteAndTransactions) {
treeGrid.transactions.startPending();
}
const record = super.deleteRowById(rowID);
if (flatDataWithCascadeOnDeleteAndTransactions) {
treeGrid.transactions.endPending(true);
}
return record;
}
deleteRowFromData(rowID, index) {
const treeGrid = this.grid;
const record = treeGrid.records.get(rowID);
if (treeGrid.primaryKey && treeGrid.foreignKey) {
index = treeGrid.primaryKey ?
treeGrid.data.map(c => c[treeGrid.primaryKey]).indexOf(rowID) :
treeGrid.data.indexOf(rowID);
super.deleteRowFromData(rowID, index);
if (treeGrid.cascadeOnDelete) {
if (record && record.children) {
for (const child of record.children) {
super.deleteRowById(child.key);
}
}
}
}
else {
const collection = record.parent ? record.parent.data[treeGrid.childDataKey] : treeGrid.data;
index = treeGrid.primaryKey ?
collection.map(c => c[treeGrid.primaryKey]).indexOf(rowID) :
collection.indexOf(rowID);
const selectedChildren = [];
this.get_selected_children(record, selectedChildren);
if (selectedChildren.length > 0) {
treeGrid.deselectRows(selectedChildren);
}
if (treeGrid.transactions.enabled) {
const path = treeGrid.generateRowPath(rowID);
treeGrid.transactions.add({
id: rowID,
type: TransactionType.DELETE,
newValue: null,
path
}, collection[index]);
}
else {
collection.splice(index, 1);
}
this.grid.validation.clear(rowID);
}
}
get_selected_children(record, selectedRowIDs) {
const grid = this.grid;
if (!record.children || record.children.length === 0) {
return;
}
for (const child of record.children) {
if (grid.selectionService.isRowSelected(child.key)) {
selectedRowIDs.push(child.key);
}
this.get_selected_children(child, selectedRowIDs);
}
}
row_deleted_transaction(rowID) {
return this.row_deleted_parent(rowID) || super.row_deleted_transaction(rowID);
}
get_rec_by_id(rowID) {
return this.grid.records.get(rowID);
}
/**
* Returns the index of the record in the data view by pk or -1 if not found or primaryKey is not set.
*
* @param pk
* @param dataCollection
*/
get_rec_index_by_id(pk, dataCollection) {
dataCollection = dataCollection || this.grid.data;
return this.grid.primaryKey ? dataCollection.findIndex(rec => rec.data[this.grid.primaryKey] === pk) : -1;
}
addRowToData(data, parentRowID) {
if (parentRowID !== undefined && parentRowID !== null) {
const state = this.grid.transactions.getState(parentRowID);
// we should not allow adding of rows as child of deleted row
if (state && state.type === TransactionType.DELETE) {
throw Error(`Cannot add child row to deleted parent row`);
}
const parentRecord = this.grid.records.get(parentRowID);
if (!parentRecord) {
throw Error('Invalid parent row ID!');
}
this.grid.summaryService.clearSummaryCache({ rowID: parentRecord.key });
if (this.grid.primaryKey && this.grid.foreignKey) {
data[this.grid.foreignKey] = parentRowID;
super.addRowToData(data);
}
else {
const parentData = parentRecord.data;
const childKey = this.grid.childDataKey;
if (this.grid.transactions.enabled) {
const rowId = this.grid.primaryKey ? data[this.grid.primaryKey] : data;
const path = [];
path.push(...this.grid.generateRowPath(parentRowID));
path.push(parentRowID);
this.grid.transactions.add({
id: rowId,
path,
newValue: data,
type: TransactionType.ADD
}, null);
}
else {
if (!parentData[childKey]) {
parentData[childKey] = [];
}
parentData[childKey].push(data);
}
}
}
else {
super.addRowToData(data);
}
}
filterDataByExpressions(expressionsTree) {
const records = this.filterTreeDataByExpressions(expressionsTree);
const data = [];
this.getFlatDataFromFilteredRecords(records, data);
return data;
}
sortDataByExpressions(data, expressions) {
const records = DataUtil.sort(cloneArray(data), expressions, this.grid.sortStrategy ?? new IgxDataRecordSorting(), this.grid);
return records.map(r => r.data);
}
filterTreeDataByExpressions(expressionsTree) {
let records = this.grid.rootRecords;
if (expressionsTree.filteringOperands.length) {
const state = {
expressionsTree,
strategy: this.grid.filterStrategy ?? new TreeGridFilteringStrategy()
};
records = FilterUtil.filter(cloneArray(records), state, this.grid);
}
return records;
}
update_row_in_array(value, rowID, index) {
const grid = this.grid;
if (grid.primaryKey && grid.foreignKey) {
super.update_row_in_array(value, rowID, index);
}
else {
const record = grid.records.get(rowID);
const childData = record.parent ? record.parent.data[grid.childDataKey] : grid.data;
index = grid.primaryKey ? childData.map(c => c[grid.primaryKey]).indexOf(rowID) :
childData.indexOf(rowID);
childData[index] = value;
}
}
/**
* Updates related row of provided grid's data source with provided new row value
*
* @param grid Grid to update data for
* @param rowID ID of the row to update
* @param rowValueInDataSource Initial value of the row as it is in data source
* @param rowCurrentValue Current value of the row as it is with applied previous transactions
* @param rowNewValue New value of the row
*/
updateData(grid, rowID, rowValueInDataSource, rowCurrentValue, rowNewValue) {
if (grid.transactions.enabled) {
const path = grid.generateRowPath(rowID);
const transaction = {
id: rowID,
type: TransactionType.UPDATE,
newValue: rowNewValue,
path
};
grid.transactions.add(transaction, rowCurrentValue);
}
else {
mergeObjects(rowValueInDataSource, rowNewValue);
}
}
row_deleted_parent(rowID) {
const grid = this.grid;
if (!grid) {
return false;
}
if ((grid.cascadeOnDelete && grid.foreignKey) || grid.childDataKey) {
let node = grid.records.get(rowID);
while (node) {
const state = grid.transactions.getState(node.key);
if (state && state.type === TransactionType.DELETE) {
return true;
}
node = node.parent;
}
}
return false;
}
getFlatDataFromFilteredRecords(records, data) {
if (!records || records.length === 0) {
return;
}
for (const record of records) {
if (!record.isFilteredOutParent) {
data.push(record);
}
this.getFlatDataFromFilteredRecords(record.children, data);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridAPIService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridAPIService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridAPIService, decorators: [{
type: Injectable
}] });
/**
* @hidden
*/
class IgxRowLoadingIndicatorTemplateDirective {
constructor() {
this.template = inject(TemplateRef);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRowLoadingIndicatorTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxRowLoadingIndicatorTemplateDirective, isStandalone: true, selector: "[igxRowLoadingIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRowLoadingIndicatorTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxRowLoadingIndicator]',
standalone: true
}]
}] });
class IgxTreeGridSelectionService extends IgxGridSelectionService {
/** Select specified rows. No event is emitted. */
selectRowsWithNoEvent(rowIDs, clearPrevSelection) {
if (this.grid && this.grid.rowSelection === GridSelectionMode.multipleCascade) {
this.cascadeSelectRowsWithNoEvent(rowIDs, clearPrevSelection);
return;
}
super.selectRowsWithNoEvent(rowIDs, clearPrevSelection);
}
/** Deselect specified rows. No event is emitted. */
deselectRowsWithNoEvent(rowIDs) {
if (this.grid.rowSelection === GridSelectionMode.multipleCascade) {
this.cascadeDeselectRowsWithNoEvent(rowIDs);
return;
}
super.deselectRowsWithNoEvent(rowIDs);
}
emitRowSelectionEvent(newSelection, added, removed, event) {
if (this.grid.rowSelection === GridSelectionMode.multipleCascade) {
this.emitCascadeRowSelectionEvent(newSelection, added, removed, event);
return;
}
super.emitRowSelectionEvent(newSelection, added, removed, event);
}
updateCascadeSelectionOnFilterAndCRUD(parents, crudRowID, visibleRowIDs = null) {
if (visibleRowIDs === null) {
// if the tree grid has flat structure
// do not explicitly handle the selection state of the rows
if (!parents.size) {
return;
}
visibleRowIDs = new Set(this.getRowIDs(this.allData));
this.rowsToBeSelected = new Set(this.rowSelection);
this.rowsToBeIndeterminate = new Set(this.indeterminateRows);
if (crudRowID) {
this.rowSelection.delete(crudRowID);
}
}
if (!parents.size) {
this.rowSelection = new Set(this.rowsToBeSelected);
this.indeterminateRows = new Set(this.rowsToBeIndeterminate);
// TODO: emit selectionChangeD event, calculate its args through the handleAddedAndRemovedArgs method
this.clearHeaderCBState();
this.selectedRowsChange.next(this.getSelectedRows());
return;
}
const newParents = new Set();
parents.forEach(parent => {
this.handleRowSelectionState(parent, visibleRowIDs);
if (parent && parent.parent) {
newParents.add(parent.parent);
}
});
this.updateCascadeSelectionOnFilterAndCRUD(newParents, null, visibleRowIDs);
}
cascadeSelectRowsWithNoEvent(rowIDs, clearPrevSelection) {
if (clearPrevSelection) {
this.indeterminateRows.clear();
this.rowSelection.clear();
this.calculateRowsNewSelectionState({ added: rowIDs, removed: [] });
}
else {
const oldSelection = this.getSelectedRows();
const newSelection = [...oldSelection, ...rowIDs];
const args = { oldSelection, newSelection };
// retrieve only the rows without their parents/children which has to be added to the selection
this.handleAddedAndRemovedArgs(args);
this.calculateRowsNewSelectionState(args);
}
this.rowSelection = new Set(this.rowsToBeSelected);
this.indeterminateRows = new Set(this.rowsToBeIndeterminate);
this.clearHeaderCBState();
this.selectedRowsChange.next(this.getSelectedRows());
}
cascadeDeselectRowsWithNoEvent(rowIDs) {
const args = { added: [], removed: rowIDs };
this.calculateRowsNewSelectionState(args);
this.rowSelection = new Set(this.rowsToBeSelected);
this.indeterminateRows = new Set(this.rowsToBeIndeterminate);
this.clearHeaderCBState();
this.selectedRowsChange.next(this.getSelectedRows());
}
get selectionService() {
return this.grid.selectionService;
}
emitCascadeRowSelectionEvent(newSelection, added, removed, event) {
const currSelection = this.getSelectedRows();
if (this.areEqualCollections(currSelection, newSelection)) {
return;
}
const args = {
owner: this.grid, oldSelection: this.getSelectedRowsData(), newSelection,
added, removed, event, cancel: false
};
this.calculateRowsNewSelectionState(args, !!this.grid.primaryKey);
args.newSelection = Array.from(this.grid.gridAPI.get_all_data().filter(r => this.rowsToBeSelected.has(this.grid.primaryKey ? r[this.grid.primaryKey] : r)));
// retrieve rows/parents/children which has been added/removed from the selection
this.handleAddedAndRemovedArgs(args);
this.grid.rowSelectionChanging.emit(args);
if (args.cancel) {
return;
}
const newSelectionIDs = args.newSelection.map(r => this.grid.primaryKey ? r[this.grid.primaryKey] : r);
// if args.newSelection hasn't been modified
if (this.areEqualCollections(Array.from(this.rowsToBeSelected), newSelectionIDs)) {
this.rowSelection = new Set(this.rowsToBeSelected);
this.indeterminateRows = new Set(this.rowsToBeIndeterminate);
this.clearHeaderCBState();
this.selectedRowsChange.next(this.getSelectedRows());
}
else {
// select the rows within the modified args.newSelection with no event
this.cascadeSelectRowsWithNoEvent(newSelectionIDs, true);
}
}
/**
* retrieve the rows which should be added/removed to/from the old selection
*/
handleAddedAndRemovedArgs(args) {
const newSelectionSet = new Set(args.newSelection);
const oldSelectionSet = new Set(args.oldSelection);
args.removed = args.oldSelection.filter(x => !newSelectionSet.has(x));
args.added = args.newSelection.filter(x => !oldSelectionSet.has(x));
}
/**
* adds to rowsToBeProcessed set all visible children of the rows which was initially within the rowsToBeProcessed set
*
* @param rowsToBeProcessed set of the rows (without their parents/children) to be selected/deselected
* @param visibleRowIDs list of all visible rowIds
* @returns a new set with all direct parents of the rows within rowsToBeProcessed set
*/
collectRowsChildrenAndDirectParents(rowsToBeProcessed, visibleRowIDs, adding, shouldConvert) {
const processedRowsParents = new Set();
Array.from(rowsToBeProcessed).forEach((row) => {
const rowID = shouldConvert ? row[this.grid.primaryKey] : row;
this.selectDeselectRow(rowID, adding);
const rowTreeRecord = this.grid.gridAPI.get_rec_by_id(rowID);
const rowAndAllChildren = this.get_all_children(rowTreeRecord);
rowAndAllChildren.forEach(r => {
if (visibleRowIDs.has(r.key)) {
this.selectDeselectRow(r.key, adding);
}
});
if (rowTreeRecord && rowTreeRecord.parent) {
processedRowsParents.add(rowTreeRecord.parent);
}
});
return processedRowsParents;
}
/**
* populates the rowsToBeSelected and rowsToBeIndeterminate sets
* with the rows which will be eventually in selected/indeterminate state
*/
calculateRowsNewSelectionState(args, shouldConvert = false) {
this.rowsToBeSelected = new Set(args.oldSelection ? shouldConvert ? args.oldSelection.map(r => r[this.grid.primaryKey]) : args.oldSelection : this.getSelectedRows());
this.rowsToBeIndeterminate = new Set(this.getIndeterminateRows());
const visibleRowIDs = new Set(this.getRowIDs(this.allData));
const removed = new Set(args.removed);
const added = new Set(args.added);
if (removed && removed.size) {
let removedRowsParents = new Set();
removedRowsParents = this.collectRowsChildrenAndDirectParents(removed, visibleRowIDs, false, shouldConvert);
Array.from(removedRowsParents).forEach((parent) => {
this.handleParentSelectionState(parent, visibleRowIDs);
});
}
if (added && added.size) {
let addedRowsParents = new Set();
addedRowsParents = this.collectRowsChildrenAndDirectParents(added, visibleRowIDs, true, shouldConvert);
Array.from(addedRowsParents).forEach((parent) => {
this.handleParentSelectionState(parent, visibleRowIDs);
});
}
}
/**
* recursively handle the selection state of the direct and indirect parents
*/
handleParentSelectionState(treeRow, visibleRowIDs) {
if (!treeRow) {
return;
}
this.handleRowSelectionState(treeRow, visibleRowIDs);
if (treeRow.parent) {
this.handleParentSelectionState(treeRow.parent, visibleRowIDs);
}
}
/**
* Handle the selection state of a given row based the selection states of its direct children
*/
handleRowSelectionState(treeRow, visibleRowIDs) {
let visibleChildren = [];
if (treeRow && treeRow.children) {
visibleChildren = treeRow.children.filter(child => visibleRowIDs.has(child.key));
}
if (visibleChildren.length) {
if (visibleChildren.every(row => this.rowsToBeSelected.has(row.key))) {
this.selectDeselectRow(treeRow.key, true);
}
else if (visibleChildren.some(row => this.rowsToBeSelected.has(row.key) || this.rowsToBeIndeterminate.has(row.key))) {
this.rowsToBeIndeterminate.add(treeRow.key);
this.rowsToBeSelected.delete(treeRow.key);
}
else {
this.selectDeselectRow(treeRow.key, false);
}
}
else {
// if the children of the row has been deleted and the row was selected do not change its state
if (this.isRowSelected(treeRow.key)) {
this.selectDeselectRow(treeRow.key, true);
}
else {
this.selectDeselectRow(treeRow.key, false);
}
}
}
get_all_children(record) {
const children = [];
if (record && record.children && record.children.length) {
for (const child of record.children) {
children.push(...this.get_all_children(child));
children.push(child);
}
}
return children;
}
selectDeselectRow(rowID, select) {
if (select) {
this.rowsToBeSelected.add(rowID);
this.rowsToBeIndeterminate.delete(rowID);
}
else {
this.rowsToBeSelected.delete(rowID);
this.rowsToBeIndeterminate.delete(rowID);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSelectionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSelectionService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSelectionService, decorators: [{
type: Injectable
}] });
/** @hidden */
class IgxTreeGridSummaryPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(flatData, hasSummary, summaryCalculationMode, summaryPosition, showSummaryOnCollapse, _, __) {
if (!flatData || !hasSummary || summaryCalculationMode === GridSummaryCalculationMode.rootLevelOnly) {
return flatData;
}
return this.addSummaryRows(this.grid, flatData, summaryPosition, showSummaryOnCollapse);
}
addSummaryRows(grid, collection, summaryPosition, showSummaryOnCollapse) {
const recordsWithSummary = [];
const maxSummaryHeight = grid.summaryService.calcMaxSummaryHeight();
for (const record of collection) {
recordsWithSummary.push(record);
const isCollapsed = !record.expanded && record.children && record.children.length > 0 && showSummaryOnCollapse;
if (isCollapsed) {
let childData = record.children.filter(r => !r.isFilteredOutParent).map(r => r.data);
childData = this.removeDeletedRecord(grid, record.key, childData);
const summaries = grid.summaryService.calculateSummaries(record.key, childData);
const summaryRecord = {
summaries,
max: maxSummaryHeight,
cellIndentation: record.level + 1
};
recordsWithSummary.push(summaryRecord);
}
const isExpanded = record.children && record.children.length > 0 && record.expanded;
if (summaryPosition === GridSummaryPosition.bottom && !isExpanded) {
let childRecord = record;
let parent = record.parent;
while (parent) {
const children = parent.children;
if (children[children.length - 1] === childRecord) {
let childData = children.filter(r => !r.isFilteredOutParent).map(r => r.data);
childData = this.removeDeletedRecord(grid, parent.key, childData);
const summaries = grid.summaryService.calculateSummaries(parent.key, childData);
const summaryRecord = {
summaries,
max: maxSummaryHeight,
cellIndentation: parent.level + 1
};
recordsWithSummary.push(summaryRecord);
childRecord = parent;
parent = childRecord.parent;
}
else {
break;
}
}
}
else if (summaryPosition === GridSummaryPosition.top && isExpanded) {
let childData = record.children.filter(r => !r.isFilteredOutParent).map(r => r.data);
childData = this.removeDeletedRecord(grid, record.key, childData);
const summaries = grid.summaryService.calculateSummaries(record.key, childData);
const summaryRecord = {
summaries,
max: maxSummaryHeight,
cellIndentation: record.level + 1
};
recordsWithSummary.push(summaryRecord);
}
}
return recordsWithSummary;
}
removeDeletedRecord(grid, rowId, data) {
if (!grid.transactions.enabled || !grid.cascadeOnDelete) {
return data;
}
const deletedRows = grid.transactions.getTransactionLog().filter(t => t.type === 'delete').map(t => t.id);
let row = grid.records.get(rowId);
if (!row && deletedRows.lenght === 0) {
return [];
}
row = row.children ? row : row.parent;
while (row) {
rowId = row.key;
if (deletedRows.indexOf(rowId) !== -1) {
return [];
}
row = row.parent;
}
deletedRows.forEach(rowID => {
const tempData = grid.primaryKey ? data.map(rec => rec[grid.primaryKey]) : data;
const index = tempData.indexOf(rowID);
if (index !== -1) {
data.splice(index, 1);
}
});
return data;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSummaryPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSummaryPipe, isStandalone: true, name: "treeGridSummary" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSummaryPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridSummary',
standalone: true
}]
}] });
/** @hidden */
class IgxTreeGridFilteringPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(hierarchyData, expressionsTree, filterStrategy, advancedFilteringExpressionsTree, _, __, pinned) {
const state = {
expressionsTree,
advancedExpressionsTree: advancedFilteringExpressionsTree,
strategy: new TreeGridFilteringStrategy()
};
if (filterStrategy) {
state.strategy = filterStrategy;
}
if (FilteringExpressionsTree.empty(state.expressionsTree) && FilteringExpressionsTree.empty(state.advancedExpressionsTree)) {
this.grid.setFilteredData(null, pinned);
return hierarchyData;
}
const result = this.filter(hierarchyData, state, this.grid);
const filteredData = [];
this.expandAllRecursive(this.grid, result, this.grid.expansionStates, filteredData);
this.grid.setFilteredData(filteredData, pinned);
return result;
}
expandAllRecursive(grid, data, expandedStates, filteredData) {
for (const rec of data) {
filteredData.push(rec.data);
if (rec.children && rec.children.length > 0) {
expandedStates.set(rec.key, true);
this.expandAllRecursive(grid, rec.children, expandedStates, filteredData);
}
}
}
filter(data, state, grid) {
return state.strategy.filter(data, state.expressionsTree, state.advancedExpressionsTree, grid);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFilteringPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFilteringPipe, isStandalone: true, name: "treeGridFiltering" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFilteringPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridFiltering',
standalone: true
}]
}] });
/**
* @hidden
*/
class IgxTreeGridHierarchizingPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(collection, primaryKey, foreignKey, childDataKey, _) {
let hierarchicalRecords = [];
const treeGridRecordsMap = new Map();
const flatData = [];
if (!collection || !collection.length) {
this.grid.flatData = collection;
this.grid.records = treeGridRecordsMap;
this.grid.rootRecords = collection;
return collection;
}
if (childDataKey) {
hierarchicalRecords = this.hierarchizeRecursive(collection, primaryKey, childDataKey, undefined, flatData, 0, treeGridRecordsMap);
}
else if (primaryKey) {
hierarchicalRecords = this.hierarchizeFlatData(collection, primaryKey, foreignKey, treeGridRecordsMap, flatData);
}
this.grid.flatData = this.grid.transactions.enabled ?
flatData.filter(rec => {
const state = this.grid.transactions.getState(this.getRowID(primaryKey, rec));
return !state || state.type !== TransactionType.ADD;
}) : flatData;
this.grid.records = treeGridRecordsMap;
this.grid.rootRecords = hierarchicalRecords;
return hierarchicalRecords;
}
getRowID(primaryKey, rowData) {
return primaryKey ? rowData[primaryKey] : rowData;
}
/**
* Converts a flat array of data into a hierarchical (tree) structure,
* preserving the original order of the records among siblings.
*
* It uses a two-pass approach:
* 1. Creates all ITreeGridRecord objects and populates the Map for quick lookup.
* 2. Links the records by iterating again, ensuring children are added to
* their parent's children array in the order they appeared in the
* original collection.
*
* @param collection The flat array of data to be hierarchized. This is the array whose order should be preserved.
* @param primaryKey The name of the property in the data objects that serves as the unique identifier (e.g., 'id').
* @param foreignKey The name of the property in the data objects that links to the parent's primary key (e.g., 'parentId').
* @param map A pre-existing Map object (key: primaryKey value, value: ITreeGridRecord) used to store and quickly look up all created records.
* @param flatData The original flat data array. Used for passing to the setIndentationLevels method (not directly used for hierarchy building).
* @returns An array of ITreeGridRecord objects representing the root nodes of the hierarchy, ordered as they appeared in the original collection.
*/
hierarchizeFlatData(collection, primaryKey, foreignKey, map, flatData) {
collection.forEach(row => {
const record = {
key: this.getRowID(primaryKey, row),
data: row,
children: []
};
map.set(row[primaryKey], record);
});
const result = [];
collection.forEach(row => {
const record = map.get(row[primaryKey]);
const parent = map.get(row[foreignKey]);
if (parent) {
record.parent = parent;
parent.children.push(record);
}
else {
result.push(record);
}
});
this.setIndentationLevels(result, 0, flatData);
return result;
}
setIndentationLevels(collection, indentationLevel, flatData) {
for (const record of collection) {
record.level = indentationLevel;
record.expanded = this.grid.gridAPI.get_row_expansion_state(record);
flatData.push(record.data);
if (record.children && record.children.length > 0) {
this.setIndentationLevels(record.children, indentationLevel + 1, flatData);
}
}
}
hierarchizeRecursive(collection, primaryKey, childDataKey, parent, flatData, indentationLevel, map) {
const result = [];
for (const item of collection) {
const record = {
key: this.getRowID(primaryKey, item),
data: item,
parent,
level: indentationLevel
};
record.expanded = this.grid.gridAPI.get_row_expansion_state(record);
flatData.push(item);
map.set(record.key, record);
record.children = item[childDataKey] ?
this.hierarchizeRecursive(item[childDataKey], primaryKey, childDataKey, record, flatData, indentationLevel + 1, map) :
undefined;
result.push(record);
}
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridHierarchizingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridHierarchizingPipe, isStandalone: true, name: "treeGridHierarchizing" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridHierarchizingPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridHierarchizing',
standalone: true
}]
}] });
/**
* @hidden
*/
class IgxTreeGridFlatteningPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(collection, expandedLevels, expandedStates, _) {
const data = [];
this.grid.processedRootRecords = collection;
this.grid.processedRecords = new Map();
this.getFlatDataRecursive(collection, data, expandedLevels, expandedStates, true);
this.grid.processedExpandedFlatData = data.map(r => r.data);
return data;
}
getFlatDataRecursive(collection, data, expandedLevels, expandedStates, parentExpanded) {
if (!collection || !collection.length) {
return;
}
for (const hierarchicalRecord of collection) {
if (parentExpanded) {
data.push(hierarchicalRecord);
}
hierarchicalRecord.expanded = this.grid.gridAPI.get_row_expansion_state(hierarchicalRecord);
this.updateNonProcessedRecordExpansion(this.grid, hierarchicalRecord);
this.grid.processedRecords.set(hierarchicalRecord.key, hierarchicalRecord);
this.getFlatDataRecursive(hierarchicalRecord.children, data, expandedLevels, expandedStates, parentExpanded && hierarchicalRecord.expanded);
}
}
updateNonProcessedRecordExpansion(grid, record) {
const rec = grid.records.get(record.key);
rec.expanded = record.expanded;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFlatteningPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFlatteningPipe, isStandalone: true, name: "treeGridFlattening" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridFlatteningPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridFlattening',
standalone: true
}]
}] });
/** @hidden */
class IgxTreeGridSortingPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(hierarchicalData, sortExpressions, groupExpressions, sorting, _, pinned) {
const expressions = groupExpressions ? groupExpressions.concat(sortExpressions) : sortExpressions;
let result;
if (!expressions.length) {
result = hierarchicalData;
}
else {
result = DataUtil.treeGridSort(hierarchicalData, expressions, sorting, this.grid);
}
const filteredSortedData = [];
this.flattenTreeGridRecords(result, filteredSortedData);
this.grid.setFilteredSortedData(filteredSortedData, pinned);
return result;
}
flattenTreeGridRecords(records, flatData) {
if (records && records.length) {
for (const record of records) {
flatData.push(record.data);
this.flattenTreeGridRecords(record.children, flatData);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSortingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSortingPipe, isStandalone: true, name: "treeGridSorting" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridSortingPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridSorting',
standalone: true
}]
}] });
/** @hidden */
class IgxTreeGridPagingPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(collection, enabled, page = 0, perPage = 15, _) {
if (!enabled || this.grid.pagingMode !== 'local') {
return collection;
}
const len = this.grid._totalRecords >= 0 ? this.grid._totalRecords : collection.length;
const totalPages = Math.ceil(len / perPage);
const state = {
index: (totalPages > 0 && page >= totalPages) ? totalPages - 1 : page,
recordsPerPage: perPage
};
const result = DataUtil.page(cloneArray(collection), state, len);
this.grid.pagingState = state;
this.grid.page = state.index;
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridPagingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridPagingPipe, isStandalone: true, name: "treeGridPaging" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridPagingPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridPaging',
standalone: true
}]
}] });
/** @hidden */
class IgxTreeGridTransactionPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(collection, _) {
if (this.grid.transactions.enabled) {
const aggregatedChanges = this.grid.transactions.getAggregatedChanges(true);
if (aggregatedChanges.length > 0) {
const primaryKey = this.grid.primaryKey;
if (!primaryKey) {
return collection;
}
const childDataKey = this.grid.childDataKey;
if (childDataKey) {
const hierarchicalDataClone = cloneHierarchicalArray(collection, childDataKey);
return DataUtil.mergeHierarchicalTransactions(hierarchicalDataClone, aggregatedChanges, childDataKey, this.grid.primaryKey, this.grid.dataCloneStrategy);
}
else {
const flatDataClone = cloneArray(collection);
return DataUtil.mergeTransactions(flatDataClone, aggregatedChanges, this.grid.primaryKey, this.grid.dataCloneStrategy);
}
}
}
return collection;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridTransactionPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridTransactionPipe, isStandalone: true, name: "treeGridTransaction" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridTransactionPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGridTransaction',
standalone: true
}]
}] });
/**
* This pipe maps the original record to ITreeGridRecord format used in TreeGrid.
*/
class IgxTreeGridNormalizeRecordsPipe {
constructor() {
this.grid = inject(IGX_GRID_BASE);
}
transform(_, __) {
const primaryKey = this.grid.primaryKey;
// using flattened data because origin data may be hierarchical.
const flatData = this.grid.flatData;
const res = flatData ? flatData.map(rec => ({
rowID: this.grid.primaryKey ? rec[primaryKey] : rec,
data: rec,
level: 0,
children: []
})) : [];
return res;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridNormalizeRecordsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridNormalizeRecordsPipe, isStandalone: true, name: "treeGridNormalizeRecord" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxTreeGridNormalizeRecordsPipe, decorators: [{
type: Pipe,
args: [{
name: 'treeGri