UNPKG

igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1 lines • 238 kB
{"version":3,"file":"igniteui-angular-grids-tree-grid.mjs","sources":["../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid-api.service.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.directives.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid-selection.service.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.summary.pipe.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.filtering.pipe.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.pipes.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.html","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid-row.component.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid-row.component.html","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid-group-by-area.component.ts","../../../projects/igniteui-angular/grids/core/src/grouping/group-by-area.component.html","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.html","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.grouping.pipe.ts","../../../projects/igniteui-angular/grids/tree-grid/src/tree-grid.module.ts","../../../projects/igniteui-angular/grids/tree-grid/src/igniteui-angular-grids-tree-grid.ts"],"sourcesContent":["import { GridBaseAPIService } from 'igniteui-angular/grids/core';\nimport {\n HierarchicalTransaction,\n TransactionType,\n State,\n IgxDataRecordSorting,\n TreeGridFilteringStrategy,\n cloneArray,\n DataUtil,\n FilterUtil,\n GridColumnDataType,\n IFilteringExpressionsTree,\n ISortingExpression,\n mergeObjects,\n ColumnType,\n ITreeGridRecord\n} from 'igniteui-angular/core';\nimport { Injectable } from '@angular/core';\nimport { GridType } from 'igniteui-angular/grids/core';\n\n@Injectable()\nexport class IgxTreeGridAPIService extends GridBaseAPIService<GridType> {\n\n public override get_all_data(transactions?: boolean): any[] {\n const grid = this.grid;\n let data = grid && grid.flatData ? grid.flatData : [];\n data = transactions ? grid.dataWithAddedInTransactionRows : data;\n return data;\n }\n\n public override get_summary_data(): any[] | null {\n const grid = this.grid;\n const data = grid.processedRootRecords?.filter(row => row.isFilteredOutParent === undefined || row.isFilteredOutParent === false)\n .map(rec => rec.data);\n if (data && grid.transactions.enabled) {\n const deletedRows = grid.transactions.getTransactionLog().filter(t => t.type === TransactionType.DELETE).map(t => t.id);\n deletedRows.forEach(rowID => {\n const tempData = grid.primaryKey ? data.map(rec => rec[grid.primaryKey]) : data;\n const index = tempData.indexOf(rowID);\n if (index !== -1) {\n data.splice(index, 1);\n }\n });\n }\n return data;\n }\n\n public override allow_expansion_state_change(rowID, expanded): boolean {\n const grid = this.grid;\n const row = grid.records.get(rowID);\n if (row.expanded === expanded ||\n ((!row.children || !row.children.length) && (!grid.loadChildrenOnDemand ||\n (grid.hasChildrenKey && !row.data[grid.hasChildrenKey])))) {\n return false;\n }\n return true;\n }\n\n public expand_path_to_record(record: ITreeGridRecord) {\n const grid = this.grid;\n const expandedStates = grid.expansionStates;\n\n while (record.parent) {\n record = record.parent;\n const expanded = this.get_row_expansion_state(record);\n\n if (!expanded) {\n expandedStates.set(record.key, true);\n }\n }\n grid.expansionStates = expandedStates;\n\n if (grid.rowEditable) {\n grid.gridAPI.crudService.endEdit(false);\n }\n }\n\n public override get_row_expansion_state(record: ITreeGridRecord): boolean {\n const grid = this.grid;\n const states = grid.expansionStates;\n const expanded = states.get(record.key);\n\n if (expanded !== undefined) {\n return expanded;\n } else {\n return record.children && record.children.length && record.level < grid.expansionDepth;\n }\n }\n\n public override should_apply_number_style(column: ColumnType): boolean {\n return column.dataType === GridColumnDataType.Number && column.visibleIndex !== 0;\n }\n\n public override deleteRowById(rowID: any): any {\n const treeGrid = this.grid;\n const flatDataWithCascadeOnDeleteAndTransactions =\n treeGrid.primaryKey &&\n treeGrid.foreignKey &&\n treeGrid.cascadeOnDelete &&\n treeGrid.transactions.enabled;\n\n if (flatDataWithCascadeOnDeleteAndTransactions) {\n treeGrid.transactions.startPending();\n }\n\n const record = super.deleteRowById(rowID);\n\n if (flatDataWithCascadeOnDeleteAndTransactions) {\n treeGrid.transactions.endPending(true);\n }\n\n return record;\n }\n\n public override deleteRowFromData(rowID: any, index: number) {\n const treeGrid = this.grid;\n const record = treeGrid.records.get(rowID);\n\n if (treeGrid.primaryKey && treeGrid.foreignKey) {\n index = treeGrid.primaryKey ?\n treeGrid.data.map(c => c[treeGrid.primaryKey]).indexOf(rowID) :\n treeGrid.data.indexOf(rowID);\n super.deleteRowFromData(rowID, index);\n\n if (treeGrid.cascadeOnDelete) {\n if (record && record.children) {\n for (const child of record.children) {\n super.deleteRowById(child.key);\n }\n }\n }\n } else {\n const collection = record.parent ? record.parent.data[treeGrid.childDataKey] : treeGrid.data;\n index = treeGrid.primaryKey ?\n collection.map(c => c[treeGrid.primaryKey]).indexOf(rowID) :\n collection.indexOf(rowID);\n\n const selectedChildren = [];\n this.get_selected_children(record, selectedChildren);\n if (selectedChildren.length > 0) {\n treeGrid.deselectRows(selectedChildren);\n }\n\n if (treeGrid.transactions.enabled) {\n const path = treeGrid.generateRowPath(rowID);\n treeGrid.transactions.add({\n id: rowID,\n type: TransactionType.DELETE,\n newValue: null,\n path\n } as HierarchicalTransaction,\n collection[index]\n );\n } else {\n collection.splice(index, 1);\n }\n this.grid.validation.clear(rowID);\n }\n }\n\n public get_selected_children(record: ITreeGridRecord, selectedRowIDs: any[]) {\n const grid = this.grid;\n if (!record.children || record.children.length === 0) {\n return;\n }\n for (const child of record.children) {\n if (grid.selectionService.isRowSelected(child.key)) {\n selectedRowIDs.push(child.key);\n }\n this.get_selected_children(child, selectedRowIDs);\n }\n }\n\n public override row_deleted_transaction(rowID: any): boolean {\n return this.row_deleted_parent(rowID) || super.row_deleted_transaction(rowID);\n }\n\n public override get_rec_by_id(rowID) {\n return this.grid.records.get(rowID);\n }\n\n /**\n * Returns the index of the record in the data view by pk or -1 if not found or primaryKey is not set.\n *\n * @param pk\n * @param dataCollection\n */\n public override get_rec_index_by_id(pk: string | number, dataCollection?: any[]): number {\n dataCollection = dataCollection || this.grid.data;\n return this.grid.primaryKey ? dataCollection.findIndex(rec => rec.data[this.grid.primaryKey] === pk) : -1;\n }\n\n public override addRowToData(data: any, parentRowID?: any) {\n if (parentRowID !== undefined && parentRowID !== null) {\n\n const state = this.grid.transactions.getState(parentRowID);\n // we should not allow adding of rows as child of deleted row\n if (state && state.type === TransactionType.DELETE) {\n throw Error(`Cannot add child row to deleted parent row`);\n }\n\n const parentRecord = this.grid.records.get(parentRowID);\n\n if (!parentRecord) {\n throw Error('Invalid parent row ID!');\n }\n this.grid.summaryService.clearSummaryCache({ rowID: parentRecord.key });\n if (this.grid.primaryKey && this.grid.foreignKey) {\n data[this.grid.foreignKey] = parentRowID;\n super.addRowToData(data);\n } else {\n const parentData = parentRecord.data;\n const childKey = this.grid.childDataKey;\n if (this.grid.transactions.enabled) {\n const rowId = this.grid.primaryKey ? data[this.grid.primaryKey] : data;\n const path: any[] = [];\n path.push(...this.grid.generateRowPath(parentRowID));\n path.push(parentRowID);\n this.grid.transactions.add({\n id: rowId,\n path,\n newValue: data,\n type: TransactionType.ADD\n } as HierarchicalTransaction,\n null);\n } else {\n if (!parentData[childKey]) {\n parentData[childKey] = [];\n }\n parentData[childKey].push(data);\n }\n }\n } else {\n super.addRowToData(data);\n }\n }\n\n public override filterDataByExpressions(expressionsTree: IFilteringExpressionsTree): any[] {\n const records = this.filterTreeDataByExpressions(expressionsTree);\n const data = [];\n\n this.getFlatDataFromFilteredRecords(records, data);\n\n return data;\n }\n\n public override sortDataByExpressions(data: ITreeGridRecord[], expressions: ISortingExpression[]) {\n const records: ITreeGridRecord[] = DataUtil.sort(\n cloneArray(data),\n expressions,\n this.grid.sortStrategy ?? new IgxDataRecordSorting(),\n this.grid);\n return records.map(r => r.data);\n }\n\n public filterTreeDataByExpressions(expressionsTree: IFilteringExpressionsTree): ITreeGridRecord[] {\n let records = this.grid.rootRecords;\n\n if (expressionsTree.filteringOperands.length) {\n const state = {\n expressionsTree,\n strategy: this.grid.filterStrategy ?? new TreeGridFilteringStrategy()\n };\n records = FilterUtil.filter(cloneArray(records), state, this.grid);\n }\n\n return records;\n }\n\n protected override update_row_in_array(value: any, rowID: any, index: number) {\n const grid = this.grid;\n if (grid.primaryKey && grid.foreignKey) {\n super.update_row_in_array(value, rowID, index);\n } else {\n const record = grid.records.get(rowID);\n const childData = record.parent ? record.parent.data[grid.childDataKey] : grid.data;\n index = grid.primaryKey ? childData.map(c => c[grid.primaryKey]).indexOf(rowID) :\n childData.indexOf(rowID);\n childData[index] = value;\n }\n }\n\n /**\n * Updates related row of provided grid's data source with provided new row value\n *\n * @param grid Grid to update data for\n * @param rowID ID of the row to update\n * @param rowValueInDataSource Initial value of the row as it is in data source\n * @param rowCurrentValue Current value of the row as it is with applied previous transactions\n * @param rowNewValue New value of the row\n */\n protected override updateData(\n grid: GridType,\n rowID: any,\n rowValueInDataSource: any,\n rowCurrentValue: any,\n rowNewValue: { [x: string]: any }) {\n if (grid.transactions.enabled) {\n const path = grid.generateRowPath(rowID);\n const transaction: HierarchicalTransaction = {\n id: rowID,\n type: TransactionType.UPDATE,\n newValue: rowNewValue,\n path\n };\n grid.transactions.add(transaction, rowCurrentValue);\n } else {\n mergeObjects(rowValueInDataSource, rowNewValue);\n }\n }\n\n private row_deleted_parent(rowID: any): boolean {\n const grid = this.grid;\n if (!grid) {\n return false;\n }\n if ((grid.cascadeOnDelete && grid.foreignKey) || grid.childDataKey) {\n let node = grid.records.get(rowID);\n while (node) {\n const state: State = grid.transactions.getState(node.key);\n if (state && state.type === TransactionType.DELETE) {\n return true;\n }\n node = node.parent;\n }\n }\n return false;\n }\n\n private getFlatDataFromFilteredRecords(records: ITreeGridRecord[], data: any[]) {\n if (!records || records.length === 0) {\n return;\n }\n\n for (const record of records) {\n if (!record.isFilteredOutParent) {\n data.push(record);\n }\n this.getFlatDataFromFilteredRecords(record.children, data);\n }\n }\n}\n","import { Directive, TemplateRef, inject } from '@angular/core';\n\n/**\n * @hidden\n */\n@Directive({\n selector: '[igxRowLoadingIndicator]',\n standalone: true\n})\nexport class IgxRowLoadingIndicatorTemplateDirective {\n public template = inject<TemplateRef<any>>(TemplateRef);\n}\n","import { Injectable } from '@angular/core';\nimport { GridSelectionMode } from 'igniteui-angular/grids/core';\nimport { IgxGridSelectionService } from 'igniteui-angular/grids/core';\nimport { ITreeGridRecord } from 'igniteui-angular/core';\n\n@Injectable()\nexport class IgxTreeGridSelectionService extends IgxGridSelectionService {\n private rowsToBeSelected: Set<any>;\n private rowsToBeIndeterminate: Set<any>;\n\n /** Select specified rows. No event is emitted. */\n public override selectRowsWithNoEvent(rowIDs: any[], clearPrevSelection?): void {\n if (this.grid && this.grid.rowSelection === GridSelectionMode.multipleCascade) {\n this.cascadeSelectRowsWithNoEvent(rowIDs, clearPrevSelection);\n return;\n }\n super.selectRowsWithNoEvent(rowIDs, clearPrevSelection);\n }\n\n /** Deselect specified rows. No event is emitted. */\n public override deselectRowsWithNoEvent(rowIDs: any[]): void {\n if (this.grid.rowSelection === GridSelectionMode.multipleCascade) {\n this.cascadeDeselectRowsWithNoEvent(rowIDs);\n return;\n }\n super.deselectRowsWithNoEvent(rowIDs);\n }\n\n public override emitRowSelectionEvent(newSelection, added, removed, event?): boolean {\n if (this.grid.rowSelection === GridSelectionMode.multipleCascade) {\n this.emitCascadeRowSelectionEvent(newSelection, added, removed, event);\n return;\n }\n\n super.emitRowSelectionEvent(newSelection, added, removed, event);\n }\n\n public updateCascadeSelectionOnFilterAndCRUD(\n parents: Set<any>,\n crudRowID?: any,\n visibleRowIDs: Set<any> = null) {\n if (visibleRowIDs === null) {\n // if the tree grid has flat structure\n // do not explicitly handle the selection state of the rows\n if (!parents.size) {\n return;\n }\n visibleRowIDs = new Set(this.getRowIDs(this.allData));\n this.rowsToBeSelected = new Set(this.rowSelection);\n this.rowsToBeIndeterminate = new Set(this.indeterminateRows);\n if (crudRowID) {\n this.rowSelection.delete(crudRowID);\n }\n }\n if (!parents.size) {\n this.rowSelection = new Set(this.rowsToBeSelected);\n this.indeterminateRows = new Set(this.rowsToBeIndeterminate);\n // TODO: emit selectionChangeD event, calculate its args through the handleAddedAndRemovedArgs method\n this.clearHeaderCBState();\n this.selectedRowsChange.next(this.getSelectedRows());\n return;\n }\n const newParents = new Set<any>();\n parents.forEach(parent => {\n this.handleRowSelectionState(parent, visibleRowIDs);\n if (parent && parent.parent) {\n newParents.add(parent.parent);\n }\n });\n this.updateCascadeSelectionOnFilterAndCRUD(newParents, null, visibleRowIDs);\n }\n\n private cascadeSelectRowsWithNoEvent(rowIDs: any[], clearPrevSelection?: boolean): void {\n if (clearPrevSelection) {\n this.indeterminateRows.clear();\n this.rowSelection.clear();\n this.calculateRowsNewSelectionState({ added: rowIDs, removed: [] });\n } else {\n const oldSelection = this.getSelectedRows();\n const newSelection = [...oldSelection, ...rowIDs];\n const args = { oldSelection, newSelection };\n\n // retrieve only the rows without their parents/children which has to be added to the selection\n this.handleAddedAndRemovedArgs(args);\n\n this.calculateRowsNewSelectionState(args);\n }\n this.rowSelection = new Set(this.rowsToBeSelected);\n this.indeterminateRows = new Set(this.rowsToBeIndeterminate);\n this.clearHeaderCBState();\n this.selectedRowsChange.next(this.getSelectedRows());\n }\n\n private cascadeDeselectRowsWithNoEvent(rowIDs: any[]): void {\n const args = { added: [], removed: rowIDs };\n this.calculateRowsNewSelectionState(args);\n\n this.rowSelection = new Set(this.rowsToBeSelected);\n this.indeterminateRows = new Set(this.rowsToBeIndeterminate);\n this.clearHeaderCBState();\n this.selectedRowsChange.next(this.getSelectedRows());\n }\n\n public get selectionService(): IgxGridSelectionService {\n return this.grid.selectionService;\n }\n\n private emitCascadeRowSelectionEvent(newSelection, added, removed, event?): boolean {\n const currSelection = this.getSelectedRows();\n if (this.areEqualCollections(currSelection, newSelection)) {\n return;\n }\n\n const args = {\n owner: this.grid, oldSelection: this.getSelectedRowsData(), newSelection,\n added, removed, event, cancel: false\n };\n\n this.calculateRowsNewSelectionState(args, !!this.grid.primaryKey);\n args.newSelection = Array.from(this.grid.gridAPI.get_all_data().filter(r => this.rowsToBeSelected.has(this.grid.primaryKey ? r[this.grid.primaryKey] : r)));\n\n // retrieve rows/parents/children which has been added/removed from the selection\n this.handleAddedAndRemovedArgs(args);\n\n this.grid.rowSelectionChanging.emit(args);\n\n if (args.cancel) {\n return;\n }\n const newSelectionIDs = args.newSelection.map(r => this.grid.primaryKey? r[this.grid.primaryKey] : r)\n // if args.newSelection hasn't been modified\n if (this.areEqualCollections(Array.from(this.rowsToBeSelected), newSelectionIDs)) {\n this.rowSelection = new Set(this.rowsToBeSelected);\n this.indeterminateRows = new Set(this.rowsToBeIndeterminate);\n this.clearHeaderCBState();\n this.selectedRowsChange.next(this.getSelectedRows());\n } else {\n // select the rows within the modified args.newSelection with no event\n this.cascadeSelectRowsWithNoEvent(newSelectionIDs, true);\n }\n }\n\n\n /**\n * retrieve the rows which should be added/removed to/from the old selection\n */\n private handleAddedAndRemovedArgs(args: any) {\n const newSelectionSet = new Set(args.newSelection);\n const oldSelectionSet = new Set(args.oldSelection);\n args.removed = args.oldSelection.filter(x => !newSelectionSet.has(x));\n args.added = args.newSelection.filter(x => !oldSelectionSet.has(x));\n }\n\n /**\n * adds to rowsToBeProcessed set all visible children of the rows which was initially within the rowsToBeProcessed set\n *\n * @param rowsToBeProcessed set of the rows (without their parents/children) to be selected/deselected\n * @param visibleRowIDs list of all visible rowIds\n * @returns a new set with all direct parents of the rows within rowsToBeProcessed set\n */\n private collectRowsChildrenAndDirectParents(rowsToBeProcessed: Set<any>, visibleRowIDs: Set<any>, adding: boolean, shouldConvert: boolean): Set<any> {\n const processedRowsParents = new Set<any>();\n Array.from(rowsToBeProcessed).forEach((row) => {\n const rowID = shouldConvert ? row[this.grid.primaryKey] : row;\n this.selectDeselectRow(rowID, adding);\n const rowTreeRecord = this.grid.gridAPI.get_rec_by_id(rowID);\n const rowAndAllChildren = this.get_all_children(rowTreeRecord);\n rowAndAllChildren.forEach(r => {\n if (visibleRowIDs.has(r.key)) {\n this.selectDeselectRow(r.key, adding);\n }\n });\n if (rowTreeRecord && rowTreeRecord.parent) {\n processedRowsParents.add(rowTreeRecord.parent);\n }\n });\n return processedRowsParents;\n }\n\n\n /**\n * populates the rowsToBeSelected and rowsToBeIndeterminate sets\n * with the rows which will be eventually in selected/indeterminate state\n */\n private calculateRowsNewSelectionState(args: any, shouldConvert = false) {\n this.rowsToBeSelected = new Set<any>(args.oldSelection ? shouldConvert ? args.oldSelection.map(r => r[this.grid.primaryKey]) : args.oldSelection : this.getSelectedRows());\n this.rowsToBeIndeterminate = new Set<any>(this.getIndeterminateRows());\n\n const visibleRowIDs = new Set(this.getRowIDs(this.allData));\n\n const removed = new Set(args.removed);\n const added = new Set(args.added);\n\n if (removed && removed.size) {\n let removedRowsParents = new Set<any>();\n\n removedRowsParents = this.collectRowsChildrenAndDirectParents(removed, visibleRowIDs, false, shouldConvert);\n\n Array.from(removedRowsParents).forEach((parent) => {\n this.handleParentSelectionState(parent, visibleRowIDs);\n });\n }\n\n if (added && added.size) {\n let addedRowsParents = new Set<any>();\n\n addedRowsParents = this.collectRowsChildrenAndDirectParents(added, visibleRowIDs, true, shouldConvert);\n\n Array.from(addedRowsParents).forEach((parent) => {\n this.handleParentSelectionState(parent, visibleRowIDs);\n });\n }\n }\n\n /**\n * recursively handle the selection state of the direct and indirect parents\n */\n private handleParentSelectionState(treeRow: ITreeGridRecord, visibleRowIDs: Set<any>) {\n if (!treeRow) {\n return;\n }\n this.handleRowSelectionState(treeRow, visibleRowIDs);\n if (treeRow.parent) {\n this.handleParentSelectionState(treeRow.parent, visibleRowIDs);\n }\n }\n\n /**\n * Handle the selection state of a given row based the selection states of its direct children\n */\n private handleRowSelectionState(treeRow: ITreeGridRecord, visibleRowIDs: Set<any>) {\n let visibleChildren = [];\n if (treeRow && treeRow.children) {\n visibleChildren = treeRow.children.filter(child => visibleRowIDs.has(child.key));\n }\n if (visibleChildren.length) {\n if (visibleChildren.every(row => this.rowsToBeSelected.has(row.key))) {\n this.selectDeselectRow(treeRow.key, true);\n } else if (visibleChildren.some(row => this.rowsToBeSelected.has(row.key) || this.rowsToBeIndeterminate.has(row.key))) {\n this.rowsToBeIndeterminate.add(treeRow.key);\n this.rowsToBeSelected.delete(treeRow.key);\n } else {\n this.selectDeselectRow(treeRow.key, false);\n }\n } else {\n // if the children of the row has been deleted and the row was selected do not change its state\n if (this.isRowSelected(treeRow.key)) {\n this.selectDeselectRow(treeRow.key, true);\n } else {\n this.selectDeselectRow(treeRow.key, false);\n }\n }\n }\n\n private get_all_children(record: ITreeGridRecord): any[] {\n const children = [];\n if (record && record.children && record.children.length) {\n for (const child of record.children) {\n children.push(...this.get_all_children(child));\n children.push(child);\n }\n }\n return children;\n\n }\n\n private selectDeselectRow(rowID: any, select: boolean) {\n if (select) {\n this.rowsToBeSelected.add(rowID);\n this.rowsToBeIndeterminate.delete(rowID);\n } else {\n this.rowsToBeSelected.delete(rowID);\n this.rowsToBeIndeterminate.delete(rowID);\n }\n }\n\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { GridType, IGX_GRID_BASE, GridSummaryPosition } from 'igniteui-angular/grids/core';\nimport { GridSummaryCalculationMode, ISummaryRecord, ITreeGridRecord } from 'igniteui-angular/core';\n\n/** @hidden */\n@Pipe({\n name: 'treeGridSummary',\n standalone: true\n})\nexport class IgxTreeGridSummaryPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(flatData: ITreeGridRecord[],\n hasSummary: boolean,\n summaryCalculationMode: GridSummaryCalculationMode,\n summaryPosition: GridSummaryPosition, showSummaryOnCollapse: boolean, _: number, __: number): any[] {\n\n if (!flatData || !hasSummary || summaryCalculationMode === GridSummaryCalculationMode.rootLevelOnly) {\n return flatData;\n }\n\n return this.addSummaryRows(this.grid, flatData, summaryPosition, showSummaryOnCollapse);\n }\n\n private addSummaryRows(grid: GridType, collection: ITreeGridRecord[],\n summaryPosition: GridSummaryPosition, showSummaryOnCollapse: boolean): any[] {\n const recordsWithSummary = [];\n const maxSummaryHeight = grid.summaryService.calcMaxSummaryHeight();\n\n for (const record of collection) {\n recordsWithSummary.push(record);\n\n const isCollapsed = !record.expanded && record.children && record.children.length > 0 && showSummaryOnCollapse;\n if (isCollapsed) {\n let childData = record.children.filter(r => !r.isFilteredOutParent).map(r => r.data);\n childData = this.removeDeletedRecord(grid, record.key, childData);\n const summaries = grid.summaryService.calculateSummaries(record.key, childData);\n const summaryRecord: ISummaryRecord = {\n summaries,\n max: maxSummaryHeight,\n cellIndentation: record.level + 1\n };\n recordsWithSummary.push(summaryRecord);\n }\n const isExpanded = record.children && record.children.length > 0 && record.expanded;\n if (summaryPosition === GridSummaryPosition.bottom && !isExpanded) {\n let childRecord = record;\n let parent = record.parent;\n\n while (parent) {\n const children = parent.children;\n\n if (children[children.length - 1] === childRecord ) {\n let childData = children.filter(r => !r.isFilteredOutParent).map(r => r.data);\n childData = this.removeDeletedRecord(grid, parent.key, childData);\n const summaries = grid.summaryService.calculateSummaries(parent.key, childData);\n const summaryRecord: ISummaryRecord = {\n summaries,\n max: maxSummaryHeight,\n cellIndentation: parent.level + 1\n };\n recordsWithSummary.push(summaryRecord);\n\n childRecord = parent;\n parent = childRecord.parent;\n } else {\n break;\n }\n }\n } else if (summaryPosition === GridSummaryPosition.top && isExpanded) {\n let childData = record.children.filter(r => !r.isFilteredOutParent).map(r => r.data);\n childData = this.removeDeletedRecord(grid, record.key, childData);\n const summaries = grid.summaryService.calculateSummaries(record.key, childData);\n const summaryRecord: ISummaryRecord = {\n summaries,\n max: maxSummaryHeight,\n cellIndentation: record.level + 1\n };\n recordsWithSummary.push(summaryRecord);\n }\n }\n return recordsWithSummary;\n }\n\n private removeDeletedRecord(grid, rowId, data) {\n if (!grid.transactions.enabled || !grid.cascadeOnDelete) {\n return data;\n }\n const deletedRows = grid.transactions.getTransactionLog().filter(t => t.type === 'delete').map(t => t.id);\n let row = grid.records.get(rowId);\n if (!row && deletedRows.lenght === 0) {\n return [];\n }\n row = row.children ? row : row.parent;\n while (row) {\n rowId = row.key;\n if (deletedRows.indexOf(rowId) !== -1) {\n return [];\n }\n row = row.parent;\n }\n deletedRows.forEach(rowID => {\n const tempData = grid.primaryKey ? data.map(rec => rec[grid.primaryKey]) : data;\n const index = tempData.indexOf(rowID);\n if (index !== -1) {\n data.splice(index, 1);\n }\n });\n return data;\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { GridType, IGX_GRID_BASE } from 'igniteui-angular/grids/core';\nimport { FilteringExpressionsTree, IFilteringExpressionsTree, IFilteringState, IFilteringStrategy, ITreeGridRecord, TreeGridFilteringStrategy } from 'igniteui-angular/core';\n\n/** @hidden */\n@Pipe({\n name: 'treeGridFiltering',\n standalone: true\n})\nexport class IgxTreeGridFilteringPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(hierarchyData: ITreeGridRecord[], expressionsTree: IFilteringExpressionsTree,\n filterStrategy: IFilteringStrategy,\n advancedFilteringExpressionsTree: IFilteringExpressionsTree,\n _: number, __: number, pinned?): ITreeGridRecord[] {\n const state: IFilteringState = {\n expressionsTree,\n advancedExpressionsTree: advancedFilteringExpressionsTree,\n strategy: new TreeGridFilteringStrategy()\n };\n\n if (filterStrategy) {\n state.strategy = filterStrategy;\n }\n\n if (FilteringExpressionsTree.empty(state.expressionsTree) && FilteringExpressionsTree.empty(state.advancedExpressionsTree)) {\n this.grid.setFilteredData(null, pinned);\n return hierarchyData;\n }\n\n const result = this.filter(hierarchyData, state, this.grid);\n const filteredData: any[] = [];\n this.expandAllRecursive(this.grid, result, this.grid.expansionStates, filteredData);\n this.grid.setFilteredData(filteredData, pinned);\n\n return result;\n }\n\n private expandAllRecursive(grid: GridType, data: ITreeGridRecord[],\n expandedStates: Map<any, boolean>, filteredData: any[]) {\n for (const rec of data) {\n filteredData.push(rec.data);\n\n if (rec.children && rec.children.length > 0) {\n expandedStates.set(rec.key, true);\n this.expandAllRecursive(grid, rec.children, expandedStates, filteredData);\n }\n }\n }\n\n private filter(data: ITreeGridRecord[], state: IFilteringState, grid?: GridType): ITreeGridRecord[] {\n return state.strategy.filter(data, state.expressionsTree, state.advancedExpressionsTree, grid);\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { GridType, IGX_GRID_BASE } from 'igniteui-angular/grids/core';\nimport { cloneArray, cloneHierarchicalArray, DataUtil, IGroupingExpression, ISortingExpression, TransactionType, IGridSortingStrategy, ITreeGridRecord } from 'igniteui-angular/core';\nimport { IgxAddRow } from 'igniteui-angular/grids/core';\n\n/**\n * @hidden\n */\n@Pipe({\n name: 'treeGridHierarchizing',\n standalone: true\n})\nexport class IgxTreeGridHierarchizingPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(collection: any[], primaryKey: string, foreignKey: string, childDataKey: string, _: number): ITreeGridRecord[] {\n let hierarchicalRecords: ITreeGridRecord[] = [];\n const treeGridRecordsMap = new Map<any, ITreeGridRecord>();\n const flatData: any[] = [];\n\n if (!collection || !collection.length) {\n this.grid.flatData = collection;\n this.grid.records = treeGridRecordsMap;\n this.grid.rootRecords = collection;\n return collection;\n }\n\n if (childDataKey) {\n hierarchicalRecords = this.hierarchizeRecursive(collection, primaryKey, childDataKey, undefined,\n flatData, 0, treeGridRecordsMap);\n } else if (primaryKey) {\n hierarchicalRecords = this.hierarchizeFlatData(collection, primaryKey, foreignKey, treeGridRecordsMap, flatData);\n }\n\n this.grid.flatData = this.grid.transactions.enabled ?\n flatData.filter(rec => {\n const state = this.grid.transactions.getState(this.getRowID(primaryKey, rec));\n return !state || state.type !== TransactionType.ADD;\n }) : flatData;\n this.grid.records = treeGridRecordsMap;\n this.grid.rootRecords = hierarchicalRecords;\n return hierarchicalRecords;\n }\n\n private getRowID(primaryKey: any, rowData: any) {\n return primaryKey ? rowData[primaryKey] : rowData;\n }\n\n /**\n * Converts a flat array of data into a hierarchical (tree) structure,\n * preserving the original order of the records among siblings.\n *\n * It uses a two-pass approach:\n * 1. Creates all ITreeGridRecord objects and populates the Map for quick lookup.\n * 2. Links the records by iterating again, ensuring children are added to\n * their parent's children array in the order they appeared in the\n * original collection.\n *\n * @param collection The flat array of data to be hierarchized. This is the array whose order should be preserved.\n * @param primaryKey The name of the property in the data objects that serves as the unique identifier (e.g., 'id').\n * @param foreignKey The name of the property in the data objects that links to the parent's primary key (e.g., 'parentId').\n * @param map A pre-existing Map object (key: primaryKey value, value: ITreeGridRecord) used to store and quickly look up all created records.\n * @param flatData The original flat data array. Used for passing to the setIndentationLevels method (not directly used for hierarchy building).\n * @returns An array of ITreeGridRecord objects representing the root nodes of the hierarchy, ordered as they appeared in the original collection.\n */\n private hierarchizeFlatData(\n collection: any[],\n primaryKey: string,\n foreignKey: string,\n map: Map<any, ITreeGridRecord>,\n flatData: any[]\n ): ITreeGridRecord[] {\n collection.forEach(row => {\n const record: ITreeGridRecord = {\n key: this.getRowID(primaryKey, row),\n data: row,\n children: []\n };\n map.set(row[primaryKey], record);\n });\n\n const result: ITreeGridRecord[] = [];\n collection.forEach(row => {\n const record: ITreeGridRecord = map.get(row[primaryKey])!;\n const parent = map.get(row[foreignKey]);\n\n if (parent) {\n record.parent = parent;\n parent.children.push(record);\n } else {\n result.push(record);\n }\n });\n\n this.setIndentationLevels(result, 0, flatData);\n\n return result;\n }\n\n private setIndentationLevels(collection: ITreeGridRecord[], indentationLevel: number, flatData: any[]) {\n for (const record of collection) {\n record.level = indentationLevel;\n record.expanded = this.grid.gridAPI.get_row_expansion_state(record);\n flatData.push(record.data);\n\n if (record.children && record.children.length > 0) {\n this.setIndentationLevels(record.children, indentationLevel + 1, flatData);\n }\n }\n }\n\n private hierarchizeRecursive(collection: any[], primaryKey: string, childDataKey: string,\n parent: ITreeGridRecord, flatData: any[], indentationLevel: number, map: Map<any, ITreeGridRecord>): ITreeGridRecord[] {\n const result: ITreeGridRecord[] = [];\n\n for (const item of collection) {\n const record: ITreeGridRecord = {\n key: this.getRowID(primaryKey, item),\n data: item,\n parent,\n level: indentationLevel\n };\n record.expanded = this.grid.gridAPI.get_row_expansion_state(record);\n flatData.push(item);\n map.set(record.key, record);\n record.children = item[childDataKey] ?\n this.hierarchizeRecursive(item[childDataKey], primaryKey, childDataKey, record, flatData, indentationLevel + 1, map) :\n undefined;\n result.push(record);\n }\n\n return result;\n }\n}\n\n/**\n * @hidden\n */\n@Pipe({\n name: 'treeGridFlattening',\n standalone: true\n})\nexport class IgxTreeGridFlatteningPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(collection: ITreeGridRecord[],\n expandedLevels: number, expandedStates: Map<any, boolean>, _: number): any[] {\n\n const data: ITreeGridRecord[] = [];\n\n this.grid.processedRootRecords = collection;\n this.grid.processedRecords = new Map<any, ITreeGridRecord>();\n\n this.getFlatDataRecursive(collection, data, expandedLevels, expandedStates, true);\n\n this.grid.processedExpandedFlatData = data.map(r => r.data);\n\n return data;\n }\n\n private getFlatDataRecursive(collection: ITreeGridRecord[], data: ITreeGridRecord[],\n expandedLevels: number, expandedStates: Map<any, boolean>, parentExpanded: boolean) {\n if (!collection || !collection.length) {\n return;\n }\n\n for (const hierarchicalRecord of collection) {\n if (parentExpanded) {\n data.push(hierarchicalRecord);\n }\n\n hierarchicalRecord.expanded = this.grid.gridAPI.get_row_expansion_state(hierarchicalRecord);\n\n this.updateNonProcessedRecordExpansion(this.grid, hierarchicalRecord);\n\n this.grid.processedRecords.set(hierarchicalRecord.key, hierarchicalRecord);\n\n this.getFlatDataRecursive(hierarchicalRecord.children, data, expandedLevels,\n expandedStates, parentExpanded && hierarchicalRecord.expanded);\n }\n }\n\n private updateNonProcessedRecordExpansion(grid: GridType, record: ITreeGridRecord) {\n const rec = grid.records.get(record.key);\n rec.expanded = record.expanded;\n }\n}\n\n/** @hidden */\n@Pipe({\n name: 'treeGridSorting',\n standalone: true\n})\nexport class IgxTreeGridSortingPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(\n hierarchicalData: ITreeGridRecord[],\n sortExpressions: ISortingExpression[],\n groupExpressions: IGroupingExpression[],\n sorting: IGridSortingStrategy,\n _: number,\n pinned?: boolean): ITreeGridRecord[] {\n\n const expressions = groupExpressions ? groupExpressions.concat(sortExpressions) : sortExpressions;\n let result: ITreeGridRecord[];\n if (!expressions.length) {\n result = hierarchicalData;\n } else {\n result = DataUtil.treeGridSort(hierarchicalData, expressions, sorting, this.grid);\n }\n\n const filteredSortedData = [];\n this.flattenTreeGridRecords(result, filteredSortedData);\n this.grid.setFilteredSortedData(filteredSortedData, pinned);\n\n return result;\n }\n\n private flattenTreeGridRecords(records: ITreeGridRecord[], flatData: any[]) {\n if (records && records.length) {\n for (const record of records) {\n flatData.push(record.data);\n this.flattenTreeGridRecords(record.children, flatData);\n }\n }\n }\n}\n\n/** @hidden */\n@Pipe({\n name: 'treeGridPaging',\n standalone: true\n})\nexport class IgxTreeGridPagingPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(collection: ITreeGridRecord[], enabled: boolean, page = 0, perPage = 15, _: number): ITreeGridRecord[] {\n if (!enabled || this.grid.pagingMode !== 'local') {\n return collection;\n }\n\n const len = this.grid._totalRecords >= 0 ? this.grid._totalRecords : collection.length;\n const totalPages = Math.ceil(len / perPage);\n\n const state = {\n index: (totalPages > 0 && page >= totalPages) ? totalPages - 1 : page,\n recordsPerPage: perPage\n };\n\n const result: ITreeGridRecord[] = DataUtil.page(cloneArray(collection), state, len);\n this.grid.pagingState = state;\n this.grid.page = state.index;\n\n return result;\n }\n}\n/** @hidden */\n@Pipe({\n name: 'treeGridTransaction',\n standalone: true\n})\nexport class IgxTreeGridTransactionPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(collection: any[], _: number): any[] {\n\n if (this.grid.transactions.enabled) {\n const aggregatedChanges = this.grid.transactions.getAggregatedChanges(true);\n if (aggregatedChanges.length > 0) {\n const primaryKey = this.grid.primaryKey;\n if (!primaryKey) {\n return collection;\n }\n\n const childDataKey = this.grid.childDataKey;\n\n if (childDataKey) {\n const hierarchicalDataClone = cloneHierarchicalArray(collection, childDataKey);\n return DataUtil.mergeHierarchicalTransactions(\n hierarchicalDataClone,\n aggregatedChanges,\n childDataKey,\n this.grid.primaryKey,\n this.grid.dataCloneStrategy\n );\n } else {\n const flatDataClone = cloneArray(collection);\n return DataUtil.mergeTransactions(\n flatDataClone,\n aggregatedChanges,\n this.grid.primaryKey,\n this.grid.dataCloneStrategy);\n }\n }\n }\n return collection;\n }\n}\n\n/**\n * This pipe maps the original record to ITreeGridRecord format used in TreeGrid.\n */\n@Pipe({\n name: 'treeGridNormalizeRecord',\n standalone: true\n})\nexport class IgxTreeGridNormalizeRecordsPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(_: any[], __: number): any[] {\n const primaryKey = this.grid.primaryKey;\n // using flattened data because origin data may be hierarchical.\n const flatData = this.grid.flatData;\n const res = flatData ? flatData.map(rec =>\n ({\n rowID: this.grid.primaryKey ? rec[primaryKey] : rec,\n data: rec,\n level: 0,\n children: []\n })) : [];\n return res;\n }\n}\n\n@Pipe({\n name: 'treeGridAddRow',\n standalone: true\n})\nexport class IgxTreeGridAddRowPipe implements PipeTransform {\n private grid = inject<GridType>(IGX_GRID_BASE);\n\n\n public transform(collection: any, isPinned = false, _pipeTrigger: number) {\n if (!this.grid.rowEditable || !this.grid.crudService.row || !this.grid.crudService.row.isAddRow ||\n !this.grid.gridAPI.crudService.addRowParent || isPinned !== this.grid.gridAPI.crudService.addRowParent.isPinned) {\n return collection;\n }\n const copy = collection.slice(0);\n const rec = (this.grid.crudService.row as IgxAddRow).recordRef;\n if (this.grid.crudService.addRowParent.isPinned) {\n const parentRowIndex = copy.findIndex(record => record.rowID === this.grid.crudService.addRowParent.rowID);\n copy.splice(parentRowIndex + 1, 0, rec);\n } else {\n copy.splice(this.grid.crudService.row.index, 0, rec);\n }\n this.grid.records.set(rec.key, rec);\n return copy;\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n Input\n} from '@angular/core';\nimport { NgClass, NgStyle, NgTemplateOutlet, DecimalPipe, PercentPipe, CurrencyPipe, DatePipe } from '@angular/common';\n\nimport { IgxTreeGridRow } from 'igniteui-angular/grids/core';\nimport { RowType } from 'igniteui-angular/grids/core';\nimport { IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe } from 'igniteui-angular/grids/core';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { HammerGesturesManager } from 'igniteui-angular/core';\nimport { IgxChipComponent } from 'igniteui-angular/chips';\nimport { IgxDateTimeEditorDirective, IgxFocusDirective, IgxTextHighlightDirective, IgxTextSelectionDirective, IgxTooltipDirective, IgxTooltipTargetDirective } from 'igniteui-angular/directives';\nimport { IgxIconComponent } from 'igniteui-angular/icon';\nimport { IgxInputDirective, IgxInputGroupComponent, IgxPrefixDirective, IgxSuffixDirective } from 'igniteui-angular/input-group';\nimport { IgxCheckboxComponent } from 'igniteui-angular/checkbox';\nimport { IgxDatePickerComponent } from 'igniteui-angular/date-picker';\nimport { IgxTimePickerComponent } from 'igniteui-angular/time-picker';\nimport { IgxCircularProgressBarComponent } from 'igniteui-angular/progressbar';\nimport { IgxGridExpandableCellComponent } from 'igniteui-angular/grids/grid';\n\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'igx-tree-grid-cell',\n templateUrl: 'tree-cell.component.html',\n providers: [HammerGesturesManager],\n imports: [\n NgClass,\n NgStyle,\n NgTemplateOutlet,\n DecimalPipe,\n PercentPipe,\n CurrencyPipe,\n DatePipe,\n IgxChipComponent,\n IgxTextHighlightDirective,\n IgxIconComponent,\n ReactiveFormsModule,\n IgxInputGroupComponent,\n IgxInputDirective,\n IgxFocusDirective,\n IgxCheckboxComponent,\n IgxDatePickerComponent,\n IgxTimePickerComponent,\n IgxDateTimeEditorDirective,\n IgxPrefixDirective,\n IgxSuffixDirective,\n IgxCircularProgressBarComponent,\n IgxTooltipTargetDirective,\n IgxTooltipDirective,\n IgxGridCellImageAltPipe,\n IgxStringReplacePipe,\n IgxColumnFormatterPipe,\n IgxTextSelectionDirective\n ]\n})\nexport class IgxTreeGridCellComponent extends IgxGridExpandableCellComponent {\n\n /**\n * @hidden\n */\n @Input()\n public level = 0;\n\n /**\n * @hidden\n */\n @Input()\n public showIndicator = false;\n\n /**\n * @hidden\n */\n @Input()\n public isLoading: boolean;\n\n /**\n * Gets the row of the cell.\n * ```typescript\n * let cellRow = this.cell.row;\n * ```\n *\n * @mem