UNPKG

igniteui-angular

Version:

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

1 lines • 241 kB
{"version":3,"file":"igniteui-angular-grids-hierarchical-grid.mjs","sources":["../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-api.service.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-navigation.service.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-base.directive.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/row-island-api.service.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/row-island.component.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.pipes.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-cell.component.ts","../../../projects/igniteui-angular/grids/core/src/cell.component.html","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-row.component.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-row.component.html","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/child-grid-row.component.html","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html","../../../projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.module.ts","../../../projects/igniteui-angular/grids/hierarchical-grid/src/igniteui-angular-grids-hierarchical-grid.ts"],"sourcesContent":["import { IgxRowIslandComponent } from './row-island.component';\nimport { Subject } from 'rxjs';\nimport { Injectable } from '@angular/core';\nimport { GridBaseAPIService, GridType } from 'igniteui-angular/grids/core';\nimport { IPathSegment } from 'igniteui-angular/core';\n\n@Injectable()\nexport class IgxHierarchicalGridAPIService extends GridBaseAPIService<GridType> {\n protected childRowIslands: Map<string, IgxRowIslandComponent> = new Map<string, IgxRowIslandComponent>();\n protected childGrids: Map<string, Map<any, GridType>> =\n new Map<string, Map<any, GridType>>();\n\n public registerChildRowIsland(rowIsland: IgxRowIslandComponent) {\n this.childRowIslands.set(rowIsland.key, rowIsland);\n this.destroyMap.set(rowIsland.key, new Subject<boolean>());\n }\n\n public unsetChildRowIsland(rowIsland: IgxRowIslandComponent) {\n this.childGrids.delete(rowIsland.key);\n this.childRowIslands.delete(rowIsland.key);\n this.destroyMap.delete(rowIsland.key);\n }\n\n public getChildRowIsland(key: string) {\n return this.childRowIslands.get(key);\n }\n\n public getChildGrid(path: Array<IPathSegment>): GridType | undefined {\n const currPath = path;\n let grid;\n const pathElem = currPath.shift();\n const childrenForLayout = this.childGrids.get(pathElem.rowIslandKey);\n if (childrenForLayout) {\n const childGrid = childrenForLayout.get(pathElem.rowKey);\n if (currPath.length === 0) {\n grid = childGrid;\n } else {\n grid = childGrid.gridAPI.getChildGrid(currPath);\n }\n }\n return grid;\n }\n\n public getChildGrids(inDepth?: boolean) {\n let allChildren: GridType [] = [];\n this.childGrids.forEach((layoutMap) => {\n layoutMap.forEach((grid) => {\n allChildren.push(grid);\n if (inDepth) {\n const children = grid.gridAPI.getChildGrids(inDepth);\n allChildren = allChildren.concat(children);\n }\n });\n });\n\n return allChildren;\n }\n\n public getParentRowId(childGrid: GridType) {\n let rowID;\n this.childGrids.forEach((layoutMap) => {\n layoutMap.forEach((grid, key) => {\n if (grid === childGrid) {\n rowID = key;\n return;\n }\n });\n });\n return rowID;\n }\n\n public registerChildGrid(parentRowID: any, rowIslandKey: string, grid: GridType) {\n let childrenForLayout = this.childGrids.get(rowIslandKey);\n if (!childrenForLayout) {\n this.childGrids.set(rowIslandKey, new Map<any, GridType>());\n childrenForLayout = this.childGrids.get(rowIslandKey);\n }\n childrenForLayout.set(parentRowID, grid);\n }\n\n public getChildGridsForRowIsland(rowIslandKey: string): GridType[] {\n const childrenForLayout = this.childGrids.get(rowIslandKey);\n const children = [];\n if (childrenForLayout) {\n childrenForLayout.forEach((child) => {\n children.push(child);\n });\n }\n return children;\n }\n\n public getChildGridByID(rowIslandKey, rowID) {\n const childrenForLayout = this.childGrids.get(rowIslandKey);\n return childrenForLayout.get(rowID);\n }\n\n public override get_row_expansion_state(record: any): boolean {\n let inState;\n if (record.childGridsData !== undefined) {\n const ri = record.key;\n const states = this.grid.expansionStates;\n const expanded = states.get(ri);\n if (expanded !== undefined) {\n return expanded;\n } else {\n return this.grid.getDefaultExpandState(record);\n }\n } else {\n inState = !!super.get_row_expansion_state(record);\n }\n return inState && (this.grid as any).childLayoutList.length !== 0;\n }\n\n public override allow_expansion_state_change(rowID, expanded): boolean {\n const rec = this.get_rec_by_id(rowID);\n const grid = (this.grid as any);\n if (grid.hasChildrenKey && !rec[grid.hasChildrenKey]) {\n return false;\n }\n return !!rec && this.grid.expansionStates.get(rowID) !== expanded;\n }\n\n public override get_rec_by_id(rowID): any {\n const data = this.get_all_data(false);\n const index = this.get_row_index_in_data(rowID, data);\n return data[index];\n }\n}\n","import { Injectable } from '@angular/core';\nimport { first } from 'rxjs/operators';\nimport { GridType, RowType } from 'igniteui-angular/grids/core';\nimport { IActiveNode, IgxGridNavigationService } from 'igniteui-angular/grids/core';\nimport { IPathSegment, NAVIGATION_KEYS, SUPPORTED_KEYS } from 'igniteui-angular/core';\n\n@Injectable()\nexport class IgxHierarchicalGridNavigationService extends IgxGridNavigationService {\n protected _pendingNavigation = false;\n\n\n public override dispatchEvent(event: KeyboardEvent) {\n const key = event.key.toLowerCase();\n const cellOrRowInEdit = this.grid.crudService.cell || this.grid.crudService.row;\n if (!this.activeNode || !(SUPPORTED_KEYS.has(key) || (key === 'tab' && cellOrRowInEdit))) {\n return;\n }\n\n const targetGrid = this.getClosestElemByTag(event.target, 'igx-hierarchical-grid')\n || this.getClosestElemByTag(event.target, 'igc-hierarchical-grid');\n if (targetGrid !== this.grid.nativeElement) {\n return;\n }\n\n if (this._pendingNavigation && NAVIGATION_KEYS.has(key)) {\n // In case focus needs to be moved from one grid to another, however there is a pending scroll operation\n // which is an async operation, any additional navigation keys should be ignored\n // untill operation complete.\n event.preventDefault();\n return;\n }\n super.dispatchEvent(event);\n }\n\n public override navigateInBody(rowIndex, visibleColIndex, cb: (arg: any) => void = null): void {\n const rec = this.grid.dataView[rowIndex];\n if (rec && this.grid.isChildGridRecord(rec)) {\n // target is child grid\n const virtState = this.grid.verticalScrollContainer.state;\n const inView = rowIndex >= virtState.startIndex && rowIndex <= virtState.startIndex + virtState.chunkSize;\n const isNext = this.activeNode.row < rowIndex;\n const targetLayoutIndex = isNext ? null : this.grid.childLayoutKeys.length - 1;\n if (inView) {\n this._moveToChild(rowIndex, visibleColIndex, isNext, targetLayoutIndex, cb);\n } else {\n let scrollAmount = this.grid.verticalScrollContainer.getScrollForIndex(rowIndex, !isNext);\n scrollAmount += isNext ? 1 : -1;\n this.grid.verticalScrollContainer.getScroll().scrollTop = scrollAmount;\n this._pendingNavigation = true;\n this.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {\n this._moveToChild(rowIndex, visibleColIndex, isNext, targetLayoutIndex, cb);\n this._pendingNavigation = false;\n });\n }\n return;\n }\n\n const isLast = rowIndex === this.grid.dataView.length;\n if ((rowIndex === -1 || isLast) &&\n this.grid.parent !== null) {\n // reached end of child grid\n const nextSiblingIndex = this.nextSiblingIndex(isLast);\n if (nextSiblingIndex !== null) {\n this.grid.parent.navigation._moveToChild(this.grid.childRow.index, visibleColIndex, isLast, nextSiblingIndex, cb);\n } else {\n this._moveToParent(isLast, visibleColIndex, cb);\n }\n return;\n }\n\n if (this.grid.parent) {\n const isNext = this.activeNode && typeof this.activeNode.row === 'number' ? rowIndex > this.activeNode.row : false;\n const cbHandler = (args) => {\n this._handleScrollInChild(rowIndex, isNext);\n cb(args);\n };\n if (!this.activeNode) {\n this.activeNode = { row: null, column: null };\n }\n super.navigateInBody(rowIndex, visibleColIndex, cbHandler);\n return;\n }\n\n if (!this.activeNode) {\n this.activeNode = { row: null, column: null };\n }\n super.navigateInBody(rowIndex, visibleColIndex, cb);\n }\n\n public override shouldPerformVerticalScroll(index, visibleColumnIndex = -1, isNext?) {\n const targetRec = this.grid.dataView[index];\n if (this.grid.isChildGridRecord(targetRec)) {\n const scrollAmount = this.grid.verticalScrollContainer.getScrollForIndex(index, !isNext);\n const currScroll = this.grid.verticalScrollContainer.getScroll().scrollTop;\n const shouldScroll = !isNext ? scrollAmount > currScroll : currScroll < scrollAmount;\n return shouldScroll;\n } else {\n return super.shouldPerformVerticalScroll(index, visibleColumnIndex);\n }\n }\n\n public override focusTbody(event) {\n if (!this.activeNode || this.activeNode.row === null) {\n this.activeNode = {\n row: 0,\n column: 0\n };\n\n this.grid.navigateTo(0, 0, (obj) => {\n this.grid.clearCellSelection();\n obj.target.activate(event);\n });\n\n } else {\n super.focusTbody(event);\n }\n }\n\n protected nextSiblingIndex(isNext) {\n const layoutKey = this.grid.childRow.layout.key;\n const layoutIndex = this.grid.parent.childLayoutKeys.indexOf(layoutKey);\n const nextIndex = isNext ? layoutIndex + 1 : layoutIndex - 1;\n if (nextIndex <= this.grid.parent.childLayoutKeys.length - 1 && nextIndex > -1) {\n return nextIndex;\n } else {\n return null;\n }\n }\n\n /**\n * Handles scrolling in child grid and ensures target child row is in main grid view port.\n *\n * @param rowIndex The row index which should be in view.\n * @param isNext Optional. Whether we are navigating to next. Used to determine scroll direction.\n * @param cb Optional.Callback function called when operation is complete.\n */\n protected _handleScrollInChild(rowIndex: number, isNext?: boolean, cb?: () => void) {\n const shouldScroll = this.shouldPerformVerticalScroll(rowIndex, -1, isNext);\n if (shouldScroll) {\n this.grid.navigation.performVerticalScrollToCell(rowIndex, -1, () => {\n this.positionInParent(rowIndex, isNext, cb);\n });\n } else {\n this.positionInParent(rowIndex, isNext, cb);\n }\n }\n\n /**\n *\n * @param rowIndex Row index that should come in view.\n * @param isNext Whether we are navigating to next. Used to determine scroll direction.\n * @param cb Optional.Callback function called when operation is complete.\n */\n protected positionInParent(rowIndex, isNext, cb?: () => void) {\n const row = this.grid.gridAPI.get_row_by_index(rowIndex);\n if (!row) {\n if (cb) {\n cb();\n }\n return;\n }\n const positionInfo = this.getPositionInfo(row, isNext);\n if (!positionInfo.inView) {\n // stop event from triggering multiple times before scrolling is complete.\n this._pendingNavigation = true;\n const scrollableGrid = isNext ? this.getNextScrollableDown(this.grid) : this.getNextScrollableUp(this.grid);\n scrollableGrid.grid.verticalScrollContainer.recalcUpdateSizes();\n scrollableGrid.grid.verticalScrollContainer.addScrollTop(positionInfo.offset);\n scrollableGrid.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {\n this._pendingNavigation = false;\n if (cb) {\n cb();\n }\n });\n } else {\n if (cb) {\n cb();\n }\n }\n }\n\n /**\n * Navigates to the specific child grid based on the array of paths leading to it\n *\n * @param pathToChildGrid Array of IPathSegments that describe the path to the child grid\n * each segment is described by the rowKey of the parent row and the rowIslandKey.\n */\n public navigateToChildGrid(pathToChildGrid: IPathSegment[], cb?: () => void) {\n if (pathToChildGrid.length == 0) {\n if (cb) {\n cb();\n }\n return;\n }\n const pathElem = pathToChildGrid.shift();\n const rowKey = pathElem.rowKey;\n const rowIndex = this.grid.gridAPI.get_row_index_in_data(rowKey);\n if (rowIndex === -1) {\n if (cb) {\n cb();\n }\n return;\n }\n // scroll to row, since it can be out of view\n this.performVerticalScrollToCell(rowIndex, -1, () => {\n this.grid.cdr.detectChanges();\n // next, expand row, if it is collapsed\n const row = this.grid.getRowByIndex(rowIndex);\n if (!row.expanded) {\n row.expanded = true;\n // update sizes after expand\n this.grid.verticalScrollContainer.recalcUpdateSizes();\n this.grid.cdr.detectChanges();\n }\n\n const childGrid = this.grid.gridAPI.getChildGrid([pathElem]);\n if (!childGrid) {\n if (cb) {\n cb();\n }\n return;\n }\n const positionInfo = this.getElementPosition(childGrid.nativeElement, false);\n this.grid.verticalScrollContainer.addScrollTop(positionInfo.offset);\n this.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {\n childGrid.navigation.navigateToChildGrid(pathToChildGrid, cb);\n });\n });\n }\n\n /**\n * Moves navigation to child grid.\n *\n * @param parentRowIndex The parent row index, at which the child grid is rendered.\n * @param childLayoutIndex Optional. The index of the child row island to which the child grid belongs to. Uses first if not set.\n */\n protected _moveToChild(parentRowIndex: number, visibleColIndex: number, isNext: boolean, childLayoutIndex?: number,\n cb?: (arg: any) => void) {\n const ri = typeof childLayoutIndex !== 'number' ?\n this.grid.childLayoutList.first : this.grid.childLayoutList.toArray()[childLayoutIndex];\n const rowId = this.grid.dataView[parentRowIndex].rowID;\n const pathSegment: IPathSegment = {\n rowID: rowId,\n rowKey: rowId,\n rowIslandKey: ri.key\n };\n const childGrid = this.grid.gridAPI.getChildGrid([pathSegment]);\n const targetIndex = isNext ? 0 : childGrid.dataView.length - 1;\n const targetRec = childGrid.dataView[targetIndex];\n if (!targetRec) {\n // if no target rec, then move on in next sibling or parent\n childGrid.navigation.navigateInBody(targetIndex, visibleColIndex, cb);\n return;\n }\n if (childGrid.isChildGridRecord(targetRec)) {\n // if target is a child grid record should move into it.\n this.grid.navigation.activeNode.row = null;\n childGrid.navigation.activeNode = { row: targetIndex, column: this.activeNode.column};\n childGrid.navigation._handleScrollInChild(targetIndex, isNext, () => {\n const targetLayoutIndex = isNext ? 0 : childGrid.childLayoutList.toArray().length - 1;\n childGrid.navigation._moveToChild(targetIndex, visibleColIndex, isNext, targetLayoutIndex, cb);\n });\n return;\n }\n\n const childGridNav = childGrid.navigation;\n this.clearActivation();\n const lastVisibleIndex = childGridNav.lastColumnIndex;\n const columnIndex = visibleColIndex <= lastVisibleIndex ? visibleColIndex : lastVisibleIndex;\n childGridNav.activeNode = { row: targetIndex, column: columnIndex};\n childGrid.tbody.nativeElement.focus({preventScroll: true});\n this._pendingNavigation = false;\n childGrid.navigation._handleScrollInChild(targetIndex, isNext, () => {\n childGrid.navigateTo(targetIndex, columnIndex, cb);\n });\n }\n\n /**\n * Moves navigation back to parent grid.\n *\n * @param rowIndex\n */\n protected _moveToParent(isNext: boolean, columnIndex, cb?) {\n const indexInParent = this.grid.childRow.index;\n const hasNextTarget = this.hasNextTarget(this.grid.parent, indexInParent, isNext);\n if (!hasNextTarget) {\n return;\n }\n this.clearActivation();\n const targetRowIndex = isNext ? indexInParent + 1 : indexInParent - 1;\n const lastVisibleIndex = this.grid.parent.navigation.lastColumnIndex;\n const nextColumnIndex = columnIndex <= lastVisibleIndex ? columnIndex : lastVisibleIndex;\n this._pendingNavigation = true;\n const cbFunc = (args) => {\n this._pendingNavigation = false;\n cb(args);\n args.target.grid.tbody.nativeElement.focus();\n };\n this.grid.parent.navigation.navigateInBody(targetRowIndex, nextColumnIndex, cbFunc);\n }\n\n /**\n * Gets information on the row position relative to the root grid view port.\n * Returns whether the row is in view and its offset.\n *\n * @param rowObj\n * @param isNext\n */\n protected getPositionInfo(row: RowType, isNext: boolean) {\n // XXX: Fix type\n let rowElem = row.nativeElement;\n if ((row as any).layout) {\n const childLayoutKeys = this.grid.childLayoutKeys;\n const riKey = isNext ? childLayoutKeys[0] : childLayoutKeys[childLayoutKeys.length - 1];\n const pathSegment: IPathSegment = {\n rowID: row.data.rowID, rowKey: row.data.rowID,\n rowIslandKey: riKey\n };\n const childGrid = this.grid.gridAPI.getChildGrid([pathSegment]);\n rowElem = childGrid.tfoot.nativeElement;\n }\n\n return this.getElementPosition(rowElem, isNext);\n }\n\n protected getElementPosition(element: HTMLElement, isNext: boolean) {\n // Special handling for scenarios where there is css transformations applied that affects scale.\n // getBoundingClientRect().height returns size after transformations\n // element.offsetHeight returns size without any transformations\n // get the ratio to figure out if anything has applied transformations\n const scaling = element.getBoundingClientRect().height / element.offsetHeight;\n\n const gridBottom = this._getMinBottom(this.grid);\n const diffBottom =\n element.getBoundingClientRect().bottom - gridBottom;\n const gridTop = this._getMaxTop(this.grid);\n const diffTop = element.getBoundingClientRect().bottom -\n element.getBoundingClientRect().height - gridTop;\n // Adding Math.Round because Chrome has some inconsistencies when the page is zoomed\n const isInView = isNext ? Math.round(diffBottom) <= 0 : Math.round(diffTop) >= 0;\n const calcOffset = isNext ? diffBottom : diffTop;\n\n return { inView: isInView, offset: calcOffset / scaling};\n }\n\n /**\n * Gets closest element by its tag name.\n *\n * @param sourceElem The element from which to start the search.\n * @param targetTag The target element tag name, for which to search.\n */\n protected getClosestElemByTag(sourceElem, targetTag) {\n let result = sourceElem;\n while (result !== null && result.nodeType === 1) {\n if (result.tagName.toLowerCase() === targetTag.toLowerCase()) {\n return result;\n }\n result = result.parentNode;\n }\n return null;\n }\n\n private clearActivation() {\n // clear if previous activation exists.\n if (this.activeNode && Object.keys(this.activeNode).length) {\n this.activeNode = Object.assign({} as IActiveNode);\n }\n }\n\n private hasNextTarget(grid: GridType, index: number, isNext: boolean) {\n const targetRowIndex = isNext ? index + 1 : index - 1;\n const hasTargetRecord = !!grid.dataView[targetRowIndex];\n if (hasTargetRecord) {\n return true;\n } else {\n let hasTargetRecordInParent = false;\n if (grid.parent) {\n const indexInParent = grid.childRow.index;\n hasTargetRecordInParent = this.hasNextTarget(grid.parent, indexInParent, isNext);\n }\n return hasTargetRecordInParent;\n }\n }\n\n /**\n * Gets the max top view in the current grid hierarchy.\n *\n * @param grid\n */\n private _getMaxTop(grid) {\n let currGrid = grid;\n let top = currGrid.tbody.nativeElement.getBoundingClientRect().top;\n while (currGrid.parent) {\n currGrid = currGrid.parent;\n const pinnedRowsHeight = currGrid.hasPinnedRecords && currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;\n top = Math.max(top, currGrid.tbody.nativeElement.getBoundingClientRect().top + pinnedRowsHeight);\n }\n return top;\n }\n\n /**\n * Gets the min bottom view in the current grid hierarchy.\n *\n * @param grid\n */\n private _getMinBottom(grid) {\n let currGrid = grid;\n let bottom = currGrid.tbody.nativeElement.getBoundingClientRect().bottom;\n while (currGrid.parent) {\n currGrid = currGrid.parent;\n const pinnedRowsHeight = currGrid.hasPinnedRecords && !currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;\n bottom = Math.min(bottom, currGrid.tbody.nativeElement.getBoundingClientRect().bottom - pinnedRowsHeight);\n }\n return bottom;\n }\n\n /**\n * Finds the next grid that allows scrolling down.\n *\n * @param grid The grid from which to begin the search.\n */\n private getNextScrollableDown(grid) {\n let currGrid = grid.parent;\n if (!currGrid) {\n return { grid, prev: null };\n }\n let scrollTop = currGrid.verticalScrollContainer.scrollPosition;\n let scrollHeight = currGrid.verticalScrollContainer.getScroll().scrollHeight;\n let nonScrollable = scrollHeight === 0 ||\n Math.round(scrollTop + currGrid.verticalScrollContainer.igxForContainerSize) === scrollHeight;\n let prev = grid;\n while (nonScrollable && currGrid.parent !== null) {\n prev = currGrid;\n currGrid = currGrid.parent;\n scrollTop = currGrid.verticalScrollContainer.scrollPosition;\n scrollHeight = currGrid.verticalScrollContainer.getScroll().scrollHeight;\n nonScrollable = scrollHeight === 0 ||\n Math.round(scrollTop + currGrid.verticalScrollContainer.igxForContainerSize) === scrollHeight;\n }\n return { grid: currGrid, prev };\n }\n\n /**\n * Finds the next grid that allows scrolling up.\n *\n * @param grid The grid from which to begin the search.\n */\n private getNextScrollableUp(grid) {\n let currGrid = grid.parent;\n if (!currGrid) {\n return { grid, prev: null };\n }\n let nonScrollable = currGrid.verticalScrollContainer.scrollPosition === 0;\n let prev = grid;\n while (nonScrollable && currGrid.parent !== null) {\n prev = currGrid;\n currGrid = currGrid.parent;\n nonScrollable = currGrid.verticalScrollContainer.scrollPosition === 0;\n }\n return { grid: currGrid, prev };\n }\n}\n","import {\n booleanAttribute,\n createComponent,\n Directive,\n EventEmitter,\n Input,\n Output,\n reflectComponentType,\n inject\n} from '@angular/core';\nimport { IgxHierarchicalGridAPIService } from './hierarchical-grid-api.service';\nimport { IgxRowIslandComponent } from './row-island.component';\nimport { IgxSummaryOperand } from 'igniteui-angular/grids/core';\nimport { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service';\nimport { GridType, IGX_GRID_SERVICE_BASE } from 'igniteui-angular/grids/core';\nimport { IgxColumnGroupComponent } from 'igniteui-angular/grids/core';\nimport { IgxColumnComponent } from 'igniteui-angular/grids/core';\nimport { takeUntil } from 'rxjs/operators';\nimport { IgxGridTransaction } from 'igniteui-angular/grids/core';\nimport { IgxTransactionService, IPathSegment } from 'igniteui-angular/core';\nimport { IForOfState } from 'igniteui-angular/directives';\nimport { IgxGridBaseDirective } from 'igniteui-angular/grids/grid';\n\nexport const hierarchicalTransactionServiceFactory = () => new IgxTransactionService();\n\nexport const IgxHierarchicalTransactionServiceFactory = {\n provide: IgxGridTransaction,\n useFactory: hierarchicalTransactionServiceFactory\n};\n\n/* blazorIndirectRender\n blazorComponent\n omitModule\n wcSkipComponentSuffix */\n@Directive()\nexport abstract class IgxHierarchicalGridBaseDirective extends IgxGridBaseDirective implements GridType {\n public override gridAPI = inject<IgxHierarchicalGridAPIService>(IGX_GRID_SERVICE_BASE);\n public override navigation = inject(IgxHierarchicalGridNavigationService);\n /**\n * Gets/Sets the key indicating whether a row has children. If row has no children it does not render an expand indicator.\n *\n * @example\n * ```html\n * <igx-hierarchical-grid #grid [data]=\"localData\" [hasChildrenKey]=\"'hasEmployees'\">\n * </igx-hierarchical-grid>\n * ```\n */\n @Input()\n public hasChildrenKey: string;\n\n /**\n * Gets/Sets whether the expand/collapse all button in the header should be rendered.\n *\n * @remarks\n * The default value is false.\n * @example\n * ```html\n * <igx-hierarchical-grid #grid [data]=\"localData\" [showExpandAll]=\"true\">\n * </igx-hierarchical-grid>\n * ```\n */\n @Input({ transform: booleanAttribute })\n public showExpandAll = false;\n\n /**\n * Emitted when a new chunk of data is loaded from virtualization.\n *\n * @example\n * ```typescript\n * <igx-hierarchical-grid [id]=\"'igx-grid-1'\" [data]=\"Data\" [autoGenerate]=\"true\" (dataPreLoad)=\"handleEvent()\">\n * </igx-hierarchical-grid>\n * ```\n */\n @Output()\n public dataPreLoad = new EventEmitter<IForOfState>();\n\n /** @hidden @internal */\n public override get type(): GridType[\"type\"] {\n return 'hierarchical';\n }\n\n /* blazorSuppress */\n /**\n * Gets the outlet used to attach the grid's overlays to.\n *\n * @remarks\n * If set, returns the outlet defined outside the grid. Otherwise returns the grid's internal outlet directive.\n */\n public override get outlet() {\n return this.rootGrid ? this.rootGrid.resolveOutlet() : this.resolveOutlet();\n }\n\n /* blazorSuppress */\n /**\n * Sets the outlet used to attach the grid's overlays to.\n */\n public override set outlet(val: any) {\n this._userOutletDirective = val;\n }\n\n /** @hidden @internal */\n public batchEditingChange: EventEmitter<boolean> = new EventEmitter<boolean>();\n\n public override get batchEditing(): boolean {\n return this._batchEditing;\n }\n\n public override set batchEditing(val: boolean) {\n if (val !== this._batchEditing) {\n delete this._transactions;\n this.switchTransactionService(val);\n this.subscribeToTransactions();\n this.batchEditingChange.emit(val);\n this._batchEditing = val;\n }\n }\n\n /**\n * @hidden\n */\n public parentIsland: IgxRowIslandComponent;\n public abstract rootGrid: GridType;\n\n /* blazorSuppress */\n public abstract expandChildren: boolean;\n\n /**\n * @hidden\n */\n public createColumnsList(cols: Array<any>) {\n const columns = [];\n const topLevelCols = cols.filter(c => c.level === 0);\n topLevelCols.forEach((col) => {\n col.grid = this;\n const ref = this._createColumn(col);\n ref.changeDetectorRef.detectChanges();\n columns.push(ref.instance);\n });\n const result = flatten(columns);\n this.updateColumns(result);\n this.initPinning();\n\n result.forEach(col => {\n this.columnInit.emit(col);\n });\n\n const mirror = reflectComponentType(IgxColumnComponent);\n const outputs = mirror.outputs.filter(o => o.propName !== 'columnChange');\n outputs.forEach(output => {\n this.columns.forEach(column => {\n if (column[output.propName]) {\n column[output.propName].pipe(takeUntil(column.destroy$)).subscribe((args) => {\n const rowIslandColumn = this.parentIsland.columnList.find((col) => col.field\n ? col.field === column.field\n : col.header === column.header);\n rowIslandColumn[output.propName].emit({ args, owner: this });\n });\n }\n });\n });\n }\n\n protected _createColumn(col) {\n let ref;\n if (col instanceof IgxColumnGroupComponent) {\n ref = this._createColGroupComponent(col);\n } else {\n ref = this._createColComponent(col);\n }\n return ref;\n }\n\n protected _createColGroupComponent(col: IgxColumnGroupComponent) {\n const ref = createComponent(IgxColumnGroupComponent, { environmentInjector: this.envInjector, elementInjector: this.injector });\n ref.changeDetectorRef.detectChanges();\n const mirror = reflectComponentType(IgxColumnGroupComponent);\n mirror.inputs.forEach((input) => {\n const propName = input.propName;\n ref.instance[propName] = col[propName];\n });\n if (col.children.length > 0) {\n const newChildren = [];\n col.children.forEach(child => {\n const newCol = this._createColumn(child).instance;\n newCol.parent = ref.instance;\n newChildren.push(newCol);\n });\n ref.instance.children.reset(newChildren);\n ref.instance.children.notifyOnChanges();\n }\n return ref;\n }\n\n protected _createColComponent(col) {\n const ref = createComponent(IgxColumnComponent, { environmentInjector: this.envInjector, elementInjector: this.injector });\n const mirror = reflectComponentType(IgxColumnComponent);\n mirror.inputs.forEach((input) => {\n const propName = input.propName;\n if (!(col[propName] instanceof IgxSummaryOperand)) {\n ref.instance[propName] = col[propName];\n } else {\n ref.instance[propName] = col[propName].constructor;\n }\n });\n ref.instance.validators = col.validators;\n return ref;\n }\n\n protected getGridsForIsland(rowIslandID: string) {\n return this.gridAPI.getChildGridsForRowIsland(rowIslandID);\n }\n\n protected getChildGrid(path: Array<IPathSegment>) {\n if (!path) {\n return;\n }\n return this.gridAPI.getChildGrid(path);\n }\n}\n\nconst flatten = (arr: any[]) => {\n let result = [];\n\n arr.forEach(el => {\n result.push(el);\n if (el.children) {\n result = result.concat(flatten(el.children.toArray()));\n }\n });\n return result;\n};\n","import { IgxHierarchicalGridComponent } from './hierarchical-grid.component';\nimport { IgxRowIslandComponent } from './row-island.component';\nimport { Subject } from 'rxjs';\nimport { Injectable } from '@angular/core';\n\n@Injectable()\nexport class IgxRowIslandAPIService {\n public rowIsland: IgxRowIslandComponent;\n public change: Subject<any> = new Subject<any>();\n protected state: Map<string, IgxRowIslandComponent> = new Map<string, IgxRowIslandComponent>();\n protected destroyMap: Map<string, Subject<boolean>> = new Map<string, Subject<boolean>>();\n\n protected childRowIslands: Map<string, IgxRowIslandComponent> = new Map<string, IgxRowIslandComponent>();\n protected childGrids: Map<any, IgxHierarchicalGridComponent> = new Map<any, IgxHierarchicalGridComponent>();\n\n public register(rowIsland: IgxRowIslandComponent) {\n this.state.set(rowIsland.id, rowIsland);\n this.destroyMap.set(rowIsland.id, new Subject<boolean>());\n }\n\n public unsubscribe(rowIsland: IgxRowIslandComponent) {\n this.state.delete(rowIsland.id);\n }\n\n public get(id: string): IgxRowIslandComponent {\n return this.state.get(id);\n }\n\n public unset(id: string) {\n this.state.delete(id);\n this.destroyMap.delete(id);\n }\n\n public reset(oldId: string, newId: string) {\n const destroy = this.destroyMap.get(oldId);\n const rowIsland = this.get(oldId);\n\n this.unset(oldId);\n\n if (rowIsland) {\n this.state.set(newId, rowIsland);\n }\n\n if (destroy) {\n this.destroyMap.set(newId, destroy);\n }\n }\n\n public registerChildRowIsland(rowIsland: IgxRowIslandComponent) {\n this.childRowIslands.set(rowIsland.key, rowIsland);\n this.destroyMap.set(rowIsland.key, new Subject<boolean>());\n }\n\n public unsetChildRowIsland(rowIsland: IgxRowIslandComponent) {\n this.childRowIslands.delete(rowIsland.key);\n this.destroyMap.delete(rowIsland.key);\n }\n\n public getChildRowIsland(rowIslandKey: string) {\n return this.childRowIslands.get(rowIslandKey);\n }\n\n public registerChildGrid(parentRowID: any, grid: IgxHierarchicalGridComponent) {\n this.childGrids.set(parentRowID, grid);\n }\n\n public getChildGrids(inDepth?: boolean) {\n let allChildren = [];\n this.childGrids.forEach((grid) => {\n allChildren.push(grid);\n });\n if (inDepth) {\n this.childRowIslands.forEach((layout) => {\n allChildren = allChildren.concat(layout.rowIslandAPI.getChildGrids(inDepth));\n });\n }\n\n return allChildren;\n }\n\n public getChildGridByID(rowID) {\n return this.childGrids.get(rowID);\n }\n}\n","import {\n AfterContentInit,\n AfterViewInit,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n ContentChildren,\n EventEmitter,\n forwardRef,\n Input,\n IterableChangeRecord,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n TemplateRef,\n inject\n} from '@angular/core';\nimport {\n GridType,\n IgxColumnComponent,\n IgxFilteringService,\n IgxGridPaginatorTemplateContext,\n IgxGridSelectionService,\n IgxGridToolbarDirective,\n IgxGridToolbarTemplateContext,\n ISearchInfo\n} from 'igniteui-angular/grids/core';\nimport { IgxHierarchicalGridBaseDirective } from './hierarchical-grid-base.directive';\nimport { IgxActionStripToken } from 'igniteui-angular/core';\nimport { first, filter, takeUntil, pluck } from 'rxjs/operators';\nimport { IgxRowIslandAPIService } from './row-island-api.service';\nimport { IGridCreatedEventArgs } from './events';\nimport { IgxPaginatorComponent, IgxPaginatorDirective } from 'igniteui-angular/paginator';\nimport { IForOfState } from 'igniteui-angular/directives';\n\n/* blazorCopyInheritedMembers */\n/* blazorElement */\n/* wcElementTag: igc-row-island */\n/* blazorIndirectRender */\n/* jsonAPIManageCollectionInMarkup */\n/* jsonAPIManageItemInMarkup */\n/* mustUseNGParentAnchor */\n/* additionalIdentifier: ChildDataKey */\n/* contentParent: RowIsland */\n/* contentParent: HierarchicalGrid */\n/**\n * Row island\n *\n * @igxModule IgxHierarchicalGridModule\n * @igxParent IgxHierarchicalGridComponent, IgxRowIslandComponent\n *\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'igx-row-island',\n template: `@if (platform.isElements) {\n <ng-content select=\"igx-column,igc-column,igx-column-group,igc-column-group,igx-action-strip,igc-action-strip\"></ng-content>\n <ng-content select=\"igx-row-island,igc-row-island\"></ng-content>\n }`,\n providers: [\n IgxRowIslandAPIService,\n IgxFilteringService,\n IgxGridSelectionService\n ],\n standalone: true\n})\nexport class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective\n implements AfterContentInit, AfterViewInit, OnChanges, OnInit, OnDestroy {\n public rowIslandAPI = inject(IgxRowIslandAPIService);\n\n\n /* blazorSuppress */\n /**\n * Sets the key of the row island by which child data would be taken from the row data if such is provided.\n * ```html\n * <igx-hierarchical-grid [data]=\"Data\" [autoGenerate]=\"true\">\n * <igx-row-island [key]=\"'childData'\">\n * <!-- ... -->\n * </igx-row-island>\n * </igx-hierarchical-grid>\n * ```\n *\n * @memberof IgxRowIslandComponent\n */\n @Input()\n public key: string;\n\n /* blazorInclude,wcInclude TODO: Move to Elements-only component */\n /**\n * Sets the key of the row island by which child data would be taken from the row data if such is provided.\n * @hidden @internal\n */\n @Input()\n public get childDataKey() {\n return this.key;\n }\n /* blazorInclude,wcInclude */\n public set childDataKey(value: string) {\n this.key = value;\n }\n\n /**\n * @hidden\n */\n @ContentChildren(forwardRef(() => IgxRowIslandComponent), { read: IgxRowIslandComponent, descendants: false })\n public children = new QueryList<IgxRowIslandComponent>();\n\n /* contentChildren */\n /* blazorInclude */\n /* blazorTreatAsCollection */\n /* blazorCollectionName: RowIslandCollection */\n /**\n * @hidden @internal\n */\n @ContentChildren(forwardRef(() => IgxRowIslandComponent), { read: IgxRowIslandComponent, descendants: false })\n public childLayoutList = new QueryList<IgxRowIslandComponent>();\n\n /**\n * @hidden\n */\n @ContentChildren(IgxColumnComponent, { read: IgxColumnComponent, descendants: false })\n public childColumns = new QueryList<IgxColumnComponent>();\n\n @ContentChild(IgxGridToolbarDirective, { read: TemplateRef, descendants: false })\n protected toolbarDirectiveTemplate: TemplateRef<IgxGridToolbarTemplateContext>;\n\n @ContentChild(IgxPaginatorDirective, { read: TemplateRef, descendants: false })\n protected paginatorDirectiveTemplate: TemplateRef<any>;\n\n /* csSuppress */\n /**\n * Sets/Gets the toolbar template for each child grid created from this row island.\n */\n @Input()\n public get toolbarTemplate(): TemplateRef<IgxGridToolbarTemplateContext> {\n return this._toolbarTemplate || this.toolbarDirectiveTemplate;\n }\n\n public set toolbarTemplate(template: TemplateRef<IgxGridToolbarTemplateContext>) {\n this._toolbarTemplate = template;\n }\n\n\n /* csSuppress */\n /**\n * Sets/Gets the paginator template for each child grid created from this row island.\n */\n @Input()\n public get paginatorTemplate(): TemplateRef<IgxGridPaginatorTemplateContext> {\n return this._paginatorTemplate || this.paginatorDirectiveTemplate;\n }\n\n public set paginatorTemplate(template: TemplateRef<IgxGridPaginatorTemplateContext>) {\n this._paginatorTemplate = template;\n }\n\n // TODO(api-analyzer): Shouldn't need all tags to copy from base or hidden/internal due to include tag\n /* contentChildren */\n /* blazorInclude */\n /* blazorTreatAsCollection */\n /* blazorCollectionName: ActionStripCollection */\n /* blazorCollectionItemName: ActionStrip */\n /* ngQueryListName: actionStripComponents */\n /** @hidden @internal */\n @ContentChildren(IgxActionStripToken, { read: IgxActionStripToken, descendants: false })\n protected override actionStripComponents: QueryList<IgxActionStripToken>;\n\n /**\n * @hidden\n */\n @Output()\n public layoutChange = new EventEmitter<any>();\n\n /**\n * Event emitted when a grid is being created based on this row island.\n * ```html\n * <igx-hierarchical-grid [data]=\"Data\" [autoGenerate]=\"true\">\n * <igx-row-island [key]=\"'childData'\" (gridCreated)=\"gridCreated($event)\" #rowIsland>\n * <!-- ... -->\n * </igx-row-island>\n * </igx-hierarchical-grid>\n * ```\n *\n * @memberof IgxRowIslandComponent\n */\n @Output()\n public gridCreated = new EventEmitter<IGridCreatedEventArgs>();\n\n /**\n * Emitted after a grid is being initialized for this row island.\n * The emitting is done in `ngAfterViewInit`.\n * ```html\n * <igx-hierarchical-grid [data]=\"Data\" [autoGenerate]=\"true\">\n * <igx-row-island [key]=\"'childData'\" (gridInitialized)=\"gridInitialized($event)\" #rowIsland>\n * <!-- ... -->\n * </igx-row-island>\n * </igx-hierarchical-grid>\n * ```\n *\n * @memberof IgxRowIslandComponent\n */\n @Output()\n public gridInitialized = new EventEmitter<IGridCreatedEventArgs>();\n\n /**\n * @hidden\n */\n public initialChanges = [];\n\n /**\n * @hidden\n */\n public rootGrid: GridType = null;\n\n /** @hidden */\n public readonly data: any[] | null;\n\n /** @hidden */\n public override get hiddenColumnsCount(): number {\n return 0;\n }\n\n /** @hidden */\n public override get pinnedColumnsCount(): number {\n return 0;\n }\n\n /** @hidden */\n public override get lastSearchInfo(): ISearchInfo {\n return null;\n }\n\n /** @hidden */\n public override get filteredData(): any {\n return [];\n }\n\n /** @hidden */\n public override get filteredSortedData(): any[] {\n return [];\n }\n\n /** @hidden */\n public override get virtualizationState(): IForOfState {\n return null;\n }\n\n /** @hidden */\n public override get pinnedColumns(): IgxColumnComponent[] {\n return [];\n }\n\n /** @hidden */\n public override get unpinnedColumns(): IgxColumnComponent[] {\n return [];\n }\n\n /** @hidden */\n public override get visibleColumns(): IgxColumnComponent[] {\n return [];\n }\n\n /** @hidden */\n public override get dataView(): any[] {\n return [];\n }\n\n //#region inert, not-a-grid component\n /** @hidden @internal */\n public override tabindex = -1;\n\n /** @hidden @internal */\n public override hostRole = null;\n\n protected override baseClass = null;\n\n /** @hidden @internal */\n public override get hostWidth(): any {\n return null;\n }\n\n protected override displayStyle = 'none';\n protected override templateRows = null;\n //#endregion\n\n private ri_columnListDiffer;\n private layout_id = `igx-row-island-`;\n private isInit = false;\n private _toolbarTemplate: TemplateRef<IgxGridToolbarTemplateContext>;\n private _paginatorTemplate: TemplateRef<IgxGridPaginatorTemplateContext>;\n\n /**\n * Sets if all immediate children of the grids for this `IgxRowIslandComponent` should be expanded/collapsed.\n * ```html\n * <igx-hierarchical-grid [data]=\"Data\" [autoGenerate]=\"true\">\n * <igx-row-island [key]=\"'childData'\" [expandChildren]=\"true\" #rowIsland>\n * <!-- ... -->\n * </igx-row-island>\n * </igx-hierarchical-grid>\n * ```\n *\n * @memberof IgxRowIslandComponent\n */\n @Input({ transform: booleanAttribute })\n public set expandChildren(value: boolean) {\n this._defaultExpandState = value;\n this.rowIslandAPI.getChildGrids().forEach((grid) => {\n if (this.document.body.contains(grid.nativeElement)) {\n // Detect changes right away if the grid is visible\n grid.expandChildren = value;\n grid.cdr.detectChanges();\n } else {\n // Else defer the detection on changes when the grid gets into view for performance.\n grid.updateOnRender = true;\n }\n });\n }\n\n /**\n * Gets if all immediate children of the grids for this `IgxRowIslandComponent` have been set to be expanded/collapsed.\n * ```typescript\n * const expanded = this.rowIsland.expandChildren;\n * ```\n *\n * @memberof IgxRowIslandComponent\n */\n public get expandChildren(): boolean {\n return this._defaultExpandState;\n }\n\n /**\n * @hidden\n */\n public get id() {\n const pId = this.parentId ? this.parentId.substring(this.parentId.indexOf(this.layout_id) + this.layout_id.length) + '-' : '';\n return this.layout_id + pId + this.key;\n }\n\n /**\n * @hidden\n */\n public get parentId() {\n return this.parentIsland ? this.parentIsland.id : null;\n }\n\n /**\n * @hidden\n */\n public get level() {\n let ptr = this.parentIsland;\n let lvl = 0;\n while (ptr) {\n lvl++;\n ptr = ptr.parentIsland;\n }\n return lvl + 1;\n }\n\n /**\n * @hidden\n */\n public override ngOnInit() {\n this.filteringService.grid = this as GridType;\n this.rootGrid = this.gridAPI.grid;\n this.rowIslandAPI.rowIsland = this;\n this.ri_columnListDiffer = this.differs.find([]).create(null);\n }\n\n /**\n * @hidden\n */\n public override ngAfterContentInit() {\n this.updateChildren();\n this.children.notifyOnChanges();\n this.children.changes.pipe(takeUntil(this.destroy$))\n .subscribe(() => {\n this.updateChildren();\n // update existing grids since their child ri have been changed.\n this.rowIslandAPI.getChildGrids(false).forEach(grid => {\n (grid as any).onRowIslandChange(this.children);\n });\n });\n const nestedColumns = this.children.map((layout) => layout.columnList.toArray());\n const colsArray = [].concat.apply([], nestedColumns);\n const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);\n this._childColumns = topCols;\n this.updateColumns(this._childColumns);\n this.columnList.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {\n Promise.resolve().then(() => {\n this.updateColumnList();\n });\n });\n\n // handle column changes so that they are passed to child grid instances when columnChange is emitted.\n this.ri_columnListDiffer.diff(this.childColumns);\n this._childColumns.forEach(x => x.columnChange.pipe(takeUntil(x.destroy$)).subscribe(() => this.updateColumnList()));\n this.childColumns.changes.pipe(takeUntil(this.destroy$)).subscribe((change: QueryList<IgxColumnComponent>) => {\n const diff = this.ri_columnListDiffer.diff(change);\n