igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,183 lines (1,178 loc) • 208 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, EventEmitter, reflectComponentType, createComponent, booleanAttribute, Output, Input, Directive, QueryList, TemplateRef, ContentChildren, ContentChild, forwardRef, ChangeDetectionStrategy, Component, Pipe, ElementRef, HostBinding, ViewChild, ViewChildren, ChangeDetectorRef, ViewContainerRef, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { NgClass, NgTemplateOutlet, DecimalPipe, PercentPipe, CurrencyPipe, DatePipe, NgStyle } from '@angular/common';
import { Subject } from 'rxjs';
import { GridBaseAPIService, IgxGridNavigationService, IgxGridTransaction, IGX_GRID_SERVICE_BASE, IgxColumnComponent, IgxColumnGroupComponent, IgxSummaryOperand, IgxGridToolbarDirective, IgxFilteringService, IgxGridSelectionService, IGX_GRID_BASE, IgxGridCellComponent, IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe, IgxRowDirective, IgxRowDragDirective, IgxGridNotGroupedPipe, IgxGridCellStylesPipe, IgxGridCellStyleClassesPipe, IgxGridDataMapperPipe, IgxGridTransactionStatePipe, IgxHierarchicalGridRow, IgxGridCell, IgxGridHeaderRowComponent, IgxGridBodyDirective, IgxGridDragSelectDirective, IgxColumnMovingDropDirective, IgxSummaryRowComponent, IgxRowEditTabStopDirective, IgxGridColumnResizerComponent, IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxSummaryDataPipe, IgxGridCRUDService, IgxGridValidationService, IgxGridSummaryService, 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, IgxColumnLayoutComponent, IgxColumnActionsComponent, IgxColumnHidingDirective, IgxColumnPinningDirective, IgxRowSelectorDirective, IgxGroupByRowSelectorDirective, IgxHeadSelectorDirective, IgxCSVTextDirective, IgxExcelTextDirective, IgxGridToolbarActionsComponent, IgxGridToolbarAdvancedFilteringComponent, IgxGridToolbarComponent, IgxGridToolbarExporterComponent, IgxGridToolbarHidingComponent, IgxGridToolbarPinningComponent, IgxGridToolbarTitleComponent, 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 { first, takeUntil, pluck, filter } from 'rxjs/operators';
import { SUPPORTED_KEYS, NAVIGATION_KEYS, IgxTransactionService, IgxActionStripToken, cloneArray, resolveNestedPath, columnFieldPath, DataUtil, HammerGesturesManager, flatten as flatten$1, IgxOverlayOutletDirective } from 'igniteui-angular/core';
import { IgxGridBaseDirective, IgxGridComponent, IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from 'igniteui-angular/grids/grid';
import { IgxPaginatorDirective, IgxPaginatorToken, IgxPaginatorComponent, IgxPageNavigationComponent, IgxPageSizeSelectorComponent, IgxPaginatorContentDirective } from 'igniteui-angular/paginator';
import * as i1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IgxChipComponent } from 'igniteui-angular/chips';
import { IgxTextHighlightDirective, IgxFocusDirective, IgxTextSelectionDirective, IgxDateTimeEditorDirective, IgxTooltipTargetDirective, IgxTooltipDirective, IgxGridForOfDirective, 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';
class IgxHierarchicalGridAPIService extends GridBaseAPIService {
constructor() {
super(...arguments);
this.childRowIslands = new Map();
this.childGrids = new Map();
}
registerChildRowIsland(rowIsland) {
this.childRowIslands.set(rowIsland.key, rowIsland);
this.destroyMap.set(rowIsland.key, new Subject());
}
unsetChildRowIsland(rowIsland) {
this.childGrids.delete(rowIsland.key);
this.childRowIslands.delete(rowIsland.key);
this.destroyMap.delete(rowIsland.key);
}
getChildRowIsland(key) {
return this.childRowIslands.get(key);
}
getChildGrid(path) {
const currPath = path;
let grid;
const pathElem = currPath.shift();
const childrenForLayout = this.childGrids.get(pathElem.rowIslandKey);
if (childrenForLayout) {
const childGrid = childrenForLayout.get(pathElem.rowKey);
if (currPath.length === 0) {
grid = childGrid;
}
else {
grid = childGrid.gridAPI.getChildGrid(currPath);
}
}
return grid;
}
getChildGrids(inDepth) {
let allChildren = [];
this.childGrids.forEach((layoutMap) => {
layoutMap.forEach((grid) => {
allChildren.push(grid);
if (inDepth) {
const children = grid.gridAPI.getChildGrids(inDepth);
allChildren = allChildren.concat(children);
}
});
});
return allChildren;
}
getParentRowId(childGrid) {
let rowID;
this.childGrids.forEach((layoutMap) => {
layoutMap.forEach((grid, key) => {
if (grid === childGrid) {
rowID = key;
return;
}
});
});
return rowID;
}
registerChildGrid(parentRowID, rowIslandKey, grid) {
let childrenForLayout = this.childGrids.get(rowIslandKey);
if (!childrenForLayout) {
this.childGrids.set(rowIslandKey, new Map());
childrenForLayout = this.childGrids.get(rowIslandKey);
}
childrenForLayout.set(parentRowID, grid);
}
getChildGridsForRowIsland(rowIslandKey) {
const childrenForLayout = this.childGrids.get(rowIslandKey);
const children = [];
if (childrenForLayout) {
childrenForLayout.forEach((child) => {
children.push(child);
});
}
return children;
}
getChildGridByID(rowIslandKey, rowID) {
const childrenForLayout = this.childGrids.get(rowIslandKey);
return childrenForLayout.get(rowID);
}
get_row_expansion_state(record) {
let inState;
if (record.childGridsData !== undefined) {
const ri = record.key;
const states = this.grid.expansionStates;
const expanded = states.get(ri);
if (expanded !== undefined) {
return expanded;
}
else {
return this.grid.getDefaultExpandState(record);
}
}
else {
inState = !!super.get_row_expansion_state(record);
}
return inState && this.grid.childLayoutList.length !== 0;
}
allow_expansion_state_change(rowID, expanded) {
const rec = this.get_rec_by_id(rowID);
const grid = this.grid;
if (grid.hasChildrenKey && !rec[grid.hasChildrenKey]) {
return false;
}
return !!rec && this.grid.expansionStates.get(rowID) !== expanded;
}
get_rec_by_id(rowID) {
const data = this.get_all_data(false);
const index = this.get_row_index_in_data(rowID, data);
return data[index];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridAPIService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridAPIService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridAPIService, decorators: [{
type: Injectable
}] });
class IgxHierarchicalGridNavigationService extends IgxGridNavigationService {
constructor() {
super(...arguments);
this._pendingNavigation = false;
}
dispatchEvent(event) {
const key = event.key.toLowerCase();
const cellOrRowInEdit = this.grid.crudService.cell || this.grid.crudService.row;
if (!this.activeNode || !(SUPPORTED_KEYS.has(key) || (key === 'tab' && cellOrRowInEdit))) {
return;
}
const targetGrid = this.getClosestElemByTag(event.target, 'igx-hierarchical-grid')
|| this.getClosestElemByTag(event.target, 'igc-hierarchical-grid');
if (targetGrid !== this.grid.nativeElement) {
return;
}
if (this._pendingNavigation && NAVIGATION_KEYS.has(key)) {
// In case focus needs to be moved from one grid to another, however there is a pending scroll operation
// which is an async operation, any additional navigation keys should be ignored
// untill operation complete.
event.preventDefault();
return;
}
super.dispatchEvent(event);
}
navigateInBody(rowIndex, visibleColIndex, cb = null) {
const rec = this.grid.dataView[rowIndex];
if (rec && this.grid.isChildGridRecord(rec)) {
// target is child grid
const virtState = this.grid.verticalScrollContainer.state;
const inView = rowIndex >= virtState.startIndex && rowIndex <= virtState.startIndex + virtState.chunkSize;
const isNext = this.activeNode.row < rowIndex;
const targetLayoutIndex = isNext ? null : this.grid.childLayoutKeys.length - 1;
if (inView) {
this._moveToChild(rowIndex, visibleColIndex, isNext, targetLayoutIndex, cb);
}
else {
let scrollAmount = this.grid.verticalScrollContainer.getScrollForIndex(rowIndex, !isNext);
scrollAmount += isNext ? 1 : -1;
this.grid.verticalScrollContainer.getScroll().scrollTop = scrollAmount;
this._pendingNavigation = true;
this.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {
this._moveToChild(rowIndex, visibleColIndex, isNext, targetLayoutIndex, cb);
this._pendingNavigation = false;
});
}
return;
}
const isLast = rowIndex === this.grid.dataView.length;
if ((rowIndex === -1 || isLast) &&
this.grid.parent !== null) {
// reached end of child grid
const nextSiblingIndex = this.nextSiblingIndex(isLast);
if (nextSiblingIndex !== null) {
this.grid.parent.navigation._moveToChild(this.grid.childRow.index, visibleColIndex, isLast, nextSiblingIndex, cb);
}
else {
this._moveToParent(isLast, visibleColIndex, cb);
}
return;
}
if (this.grid.parent) {
const isNext = this.activeNode && typeof this.activeNode.row === 'number' ? rowIndex > this.activeNode.row : false;
const cbHandler = (args) => {
this._handleScrollInChild(rowIndex, isNext);
cb(args);
};
if (!this.activeNode) {
this.activeNode = { row: null, column: null };
}
super.navigateInBody(rowIndex, visibleColIndex, cbHandler);
return;
}
if (!this.activeNode) {
this.activeNode = { row: null, column: null };
}
super.navigateInBody(rowIndex, visibleColIndex, cb);
}
shouldPerformVerticalScroll(index, visibleColumnIndex = -1, isNext) {
const targetRec = this.grid.dataView[index];
if (this.grid.isChildGridRecord(targetRec)) {
const scrollAmount = this.grid.verticalScrollContainer.getScrollForIndex(index, !isNext);
const currScroll = this.grid.verticalScrollContainer.getScroll().scrollTop;
const shouldScroll = !isNext ? scrollAmount > currScroll : currScroll < scrollAmount;
return shouldScroll;
}
else {
return super.shouldPerformVerticalScroll(index, visibleColumnIndex);
}
}
focusTbody(event) {
if (!this.activeNode || this.activeNode.row === null) {
this.activeNode = {
row: 0,
column: 0
};
this.grid.navigateTo(0, 0, (obj) => {
this.grid.clearCellSelection();
obj.target.activate(event);
});
}
else {
super.focusTbody(event);
}
}
nextSiblingIndex(isNext) {
const layoutKey = this.grid.childRow.layout.key;
const layoutIndex = this.grid.parent.childLayoutKeys.indexOf(layoutKey);
const nextIndex = isNext ? layoutIndex + 1 : layoutIndex - 1;
if (nextIndex <= this.grid.parent.childLayoutKeys.length - 1 && nextIndex > -1) {
return nextIndex;
}
else {
return null;
}
}
/**
* Handles scrolling in child grid and ensures target child row is in main grid view port.
*
* @param rowIndex The row index which should be in view.
* @param isNext Optional. Whether we are navigating to next. Used to determine scroll direction.
* @param cb Optional.Callback function called when operation is complete.
*/
_handleScrollInChild(rowIndex, isNext, cb) {
const shouldScroll = this.shouldPerformVerticalScroll(rowIndex, -1, isNext);
if (shouldScroll) {
this.grid.navigation.performVerticalScrollToCell(rowIndex, -1, () => {
this.positionInParent(rowIndex, isNext, cb);
});
}
else {
this.positionInParent(rowIndex, isNext, cb);
}
}
/**
*
* @param rowIndex Row index that should come in view.
* @param isNext Whether we are navigating to next. Used to determine scroll direction.
* @param cb Optional.Callback function called when operation is complete.
*/
positionInParent(rowIndex, isNext, cb) {
const row = this.grid.gridAPI.get_row_by_index(rowIndex);
if (!row) {
if (cb) {
cb();
}
return;
}
const positionInfo = this.getPositionInfo(row, isNext);
if (!positionInfo.inView) {
// stop event from triggering multiple times before scrolling is complete.
this._pendingNavigation = true;
const scrollableGrid = isNext ? this.getNextScrollableDown(this.grid) : this.getNextScrollableUp(this.grid);
scrollableGrid.grid.verticalScrollContainer.recalcUpdateSizes();
scrollableGrid.grid.verticalScrollContainer.addScrollTop(positionInfo.offset);
scrollableGrid.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {
this._pendingNavigation = false;
if (cb) {
cb();
}
});
}
else {
if (cb) {
cb();
}
}
}
/**
* Navigates to the specific child grid based on the array of paths leading to it
*
* @param pathToChildGrid Array of IPathSegments that describe the path to the child grid
* each segment is described by the rowKey of the parent row and the rowIslandKey.
*/
navigateToChildGrid(pathToChildGrid, cb) {
if (pathToChildGrid.length == 0) {
if (cb) {
cb();
}
return;
}
const pathElem = pathToChildGrid.shift();
const rowKey = pathElem.rowKey;
const rowIndex = this.grid.gridAPI.get_row_index_in_data(rowKey);
if (rowIndex === -1) {
if (cb) {
cb();
}
return;
}
// scroll to row, since it can be out of view
this.performVerticalScrollToCell(rowIndex, -1, () => {
this.grid.cdr.detectChanges();
// next, expand row, if it is collapsed
const row = this.grid.getRowByIndex(rowIndex);
if (!row.expanded) {
row.expanded = true;
// update sizes after expand
this.grid.verticalScrollContainer.recalcUpdateSizes();
this.grid.cdr.detectChanges();
}
const childGrid = this.grid.gridAPI.getChildGrid([pathElem]);
if (!childGrid) {
if (cb) {
cb();
}
return;
}
const positionInfo = this.getElementPosition(childGrid.nativeElement, false);
this.grid.verticalScrollContainer.addScrollTop(positionInfo.offset);
this.grid.verticalScrollContainer.chunkLoad.pipe(first()).subscribe(() => {
childGrid.navigation.navigateToChildGrid(pathToChildGrid, cb);
});
});
}
/**
* Moves navigation to child grid.
*
* @param parentRowIndex The parent row index, at which the child grid is rendered.
* @param childLayoutIndex Optional. The index of the child row island to which the child grid belongs to. Uses first if not set.
*/
_moveToChild(parentRowIndex, visibleColIndex, isNext, childLayoutIndex, cb) {
const ri = typeof childLayoutIndex !== 'number' ?
this.grid.childLayoutList.first : this.grid.childLayoutList.toArray()[childLayoutIndex];
const rowId = this.grid.dataView[parentRowIndex].rowID;
const pathSegment = {
rowID: rowId,
rowKey: rowId,
rowIslandKey: ri.key
};
const childGrid = this.grid.gridAPI.getChildGrid([pathSegment]);
const targetIndex = isNext ? 0 : childGrid.dataView.length - 1;
const targetRec = childGrid.dataView[targetIndex];
if (!targetRec) {
// if no target rec, then move on in next sibling or parent
childGrid.navigation.navigateInBody(targetIndex, visibleColIndex, cb);
return;
}
if (childGrid.isChildGridRecord(targetRec)) {
// if target is a child grid record should move into it.
this.grid.navigation.activeNode.row = null;
childGrid.navigation.activeNode = { row: targetIndex, column: this.activeNode.column };
childGrid.navigation._handleScrollInChild(targetIndex, isNext, () => {
const targetLayoutIndex = isNext ? 0 : childGrid.childLayoutList.toArray().length - 1;
childGrid.navigation._moveToChild(targetIndex, visibleColIndex, isNext, targetLayoutIndex, cb);
});
return;
}
const childGridNav = childGrid.navigation;
this.clearActivation();
const lastVisibleIndex = childGridNav.lastColumnIndex;
const columnIndex = visibleColIndex <= lastVisibleIndex ? visibleColIndex : lastVisibleIndex;
childGridNav.activeNode = { row: targetIndex, column: columnIndex };
childGrid.tbody.nativeElement.focus({ preventScroll: true });
this._pendingNavigation = false;
childGrid.navigation._handleScrollInChild(targetIndex, isNext, () => {
childGrid.navigateTo(targetIndex, columnIndex, cb);
});
}
/**
* Moves navigation back to parent grid.
*
* @param rowIndex
*/
_moveToParent(isNext, columnIndex, cb) {
const indexInParent = this.grid.childRow.index;
const hasNextTarget = this.hasNextTarget(this.grid.parent, indexInParent, isNext);
if (!hasNextTarget) {
return;
}
this.clearActivation();
const targetRowIndex = isNext ? indexInParent + 1 : indexInParent - 1;
const lastVisibleIndex = this.grid.parent.navigation.lastColumnIndex;
const nextColumnIndex = columnIndex <= lastVisibleIndex ? columnIndex : lastVisibleIndex;
this._pendingNavigation = true;
const cbFunc = (args) => {
this._pendingNavigation = false;
cb(args);
args.target.grid.tbody.nativeElement.focus();
};
this.grid.parent.navigation.navigateInBody(targetRowIndex, nextColumnIndex, cbFunc);
}
/**
* Gets information on the row position relative to the root grid view port.
* Returns whether the row is in view and its offset.
*
* @param rowObj
* @param isNext
*/
getPositionInfo(row, isNext) {
// XXX: Fix type
let rowElem = row.nativeElement;
if (row.layout) {
const childLayoutKeys = this.grid.childLayoutKeys;
const riKey = isNext ? childLayoutKeys[0] : childLayoutKeys[childLayoutKeys.length - 1];
const pathSegment = {
rowID: row.data.rowID, rowKey: row.data.rowID,
rowIslandKey: riKey
};
const childGrid = this.grid.gridAPI.getChildGrid([pathSegment]);
rowElem = childGrid.tfoot.nativeElement;
}
return this.getElementPosition(rowElem, isNext);
}
getElementPosition(element, isNext) {
// Special handling for scenarios where there is css transformations applied that affects scale.
// getBoundingClientRect().height returns size after transformations
// element.offsetHeight returns size without any transformations
// get the ratio to figure out if anything has applied transformations
const scaling = element.getBoundingClientRect().height / element.offsetHeight;
const gridBottom = this._getMinBottom(this.grid);
const diffBottom = element.getBoundingClientRect().bottom - gridBottom;
const gridTop = this._getMaxTop(this.grid);
const diffTop = element.getBoundingClientRect().bottom -
element.getBoundingClientRect().height - gridTop;
// Adding Math.Round because Chrome has some inconsistencies when the page is zoomed
const isInView = isNext ? Math.round(diffBottom) <= 0 : Math.round(diffTop) >= 0;
const calcOffset = isNext ? diffBottom : diffTop;
return { inView: isInView, offset: calcOffset / scaling };
}
/**
* Gets closest element by its tag name.
*
* @param sourceElem The element from which to start the search.
* @param targetTag The target element tag name, for which to search.
*/
getClosestElemByTag(sourceElem, targetTag) {
let result = sourceElem;
while (result !== null && result.nodeType === 1) {
if (result.tagName.toLowerCase() === targetTag.toLowerCase()) {
return result;
}
result = result.parentNode;
}
return null;
}
clearActivation() {
// clear if previous activation exists.
if (this.activeNode && Object.keys(this.activeNode).length) {
this.activeNode = Object.assign({});
}
}
hasNextTarget(grid, index, isNext) {
const targetRowIndex = isNext ? index + 1 : index - 1;
const hasTargetRecord = !!grid.dataView[targetRowIndex];
if (hasTargetRecord) {
return true;
}
else {
let hasTargetRecordInParent = false;
if (grid.parent) {
const indexInParent = grid.childRow.index;
hasTargetRecordInParent = this.hasNextTarget(grid.parent, indexInParent, isNext);
}
return hasTargetRecordInParent;
}
}
/**
* Gets the max top view in the current grid hierarchy.
*
* @param grid
*/
_getMaxTop(grid) {
let currGrid = grid;
let top = currGrid.tbody.nativeElement.getBoundingClientRect().top;
while (currGrid.parent) {
currGrid = currGrid.parent;
const pinnedRowsHeight = currGrid.hasPinnedRecords && currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;
top = Math.max(top, currGrid.tbody.nativeElement.getBoundingClientRect().top + pinnedRowsHeight);
}
return top;
}
/**
* Gets the min bottom view in the current grid hierarchy.
*
* @param grid
*/
_getMinBottom(grid) {
let currGrid = grid;
let bottom = currGrid.tbody.nativeElement.getBoundingClientRect().bottom;
while (currGrid.parent) {
currGrid = currGrid.parent;
const pinnedRowsHeight = currGrid.hasPinnedRecords && !currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;
bottom = Math.min(bottom, currGrid.tbody.nativeElement.getBoundingClientRect().bottom - pinnedRowsHeight);
}
return bottom;
}
/**
* Finds the next grid that allows scrolling down.
*
* @param grid The grid from which to begin the search.
*/
getNextScrollableDown(grid) {
let currGrid = grid.parent;
if (!currGrid) {
return { grid, prev: null };
}
let scrollTop = currGrid.verticalScrollContainer.scrollPosition;
let scrollHeight = currGrid.verticalScrollContainer.getScroll().scrollHeight;
let nonScrollable = scrollHeight === 0 ||
Math.round(scrollTop + currGrid.verticalScrollContainer.igxForContainerSize) === scrollHeight;
let prev = grid;
while (nonScrollable && currGrid.parent !== null) {
prev = currGrid;
currGrid = currGrid.parent;
scrollTop = currGrid.verticalScrollContainer.scrollPosition;
scrollHeight = currGrid.verticalScrollContainer.getScroll().scrollHeight;
nonScrollable = scrollHeight === 0 ||
Math.round(scrollTop + currGrid.verticalScrollContainer.igxForContainerSize) === scrollHeight;
}
return { grid: currGrid, prev };
}
/**
* Finds the next grid that allows scrolling up.
*
* @param grid The grid from which to begin the search.
*/
getNextScrollableUp(grid) {
let currGrid = grid.parent;
if (!currGrid) {
return { grid, prev: null };
}
let nonScrollable = currGrid.verticalScrollContainer.scrollPosition === 0;
let prev = grid;
while (nonScrollable && currGrid.parent !== null) {
prev = currGrid;
currGrid = currGrid.parent;
nonScrollable = currGrid.verticalScrollContainer.scrollPosition === 0;
}
return { grid: currGrid, prev };
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridNavigationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridNavigationService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridNavigationService, decorators: [{
type: Injectable
}] });
const hierarchicalTransactionServiceFactory = () => new IgxTransactionService();
const IgxHierarchicalTransactionServiceFactory = {
provide: IgxGridTransaction,
useFactory: hierarchicalTransactionServiceFactory
};
/* blazorIndirectRender
blazorComponent
omitModule
wcSkipComponentSuffix */
class IgxHierarchicalGridBaseDirective extends IgxGridBaseDirective {
constructor() {
super(...arguments);
this.gridAPI = inject(IGX_GRID_SERVICE_BASE);
this.navigation = inject(IgxHierarchicalGridNavigationService);
/**
* Gets/Sets whether the expand/collapse all button in the header should be rendered.
*
* @remarks
* The default value is false.
* @example
* ```html
* <igx-hierarchical-grid #grid [data]="localData" [showExpandAll]="true">
* </igx-hierarchical-grid>
* ```
*/
this.showExpandAll = false;
/**
* Emitted when a new chunk of data is loaded from virtualization.
*
* @example
* ```typescript
* <igx-hierarchical-grid [id]="'igx-grid-1'" [data]="Data" [autoGenerate]="true" (dataPreLoad)="handleEvent()">
* </igx-hierarchical-grid>
* ```
*/
this.dataPreLoad = new EventEmitter();
/** @hidden @internal */
this.batchEditingChange = new EventEmitter();
}
/** @hidden @internal */
get type() {
return 'hierarchical';
}
/* blazorSuppress */
/**
* Gets the outlet used to attach the grid's overlays to.
*
* @remarks
* If set, returns the outlet defined outside the grid. Otherwise returns the grid's internal outlet directive.
*/
get outlet() {
return this.rootGrid ? this.rootGrid.resolveOutlet() : this.resolveOutlet();
}
/* blazorSuppress */
/**
* Sets the outlet used to attach the grid's overlays to.
*/
set outlet(val) {
this._userOutletDirective = val;
}
get batchEditing() {
return this._batchEditing;
}
set batchEditing(val) {
if (val !== this._batchEditing) {
delete this._transactions;
this.switchTransactionService(val);
this.subscribeToTransactions();
this.batchEditingChange.emit(val);
this._batchEditing = val;
}
}
/**
* @hidden
*/
createColumnsList(cols) {
const columns = [];
const topLevelCols = cols.filter(c => c.level === 0);
topLevelCols.forEach((col) => {
col.grid = this;
const ref = this._createColumn(col);
ref.changeDetectorRef.detectChanges();
columns.push(ref.instance);
});
const result = flatten(columns);
this.updateColumns(result);
this.initPinning();
result.forEach(col => {
this.columnInit.emit(col);
});
const mirror = reflectComponentType(IgxColumnComponent);
const outputs = mirror.outputs.filter(o => o.propName !== 'columnChange');
outputs.forEach(output => {
this.columns.forEach(column => {
if (column[output.propName]) {
column[output.propName].pipe(takeUntil(column.destroy$)).subscribe((args) => {
const rowIslandColumn = this.parentIsland.columnList.find((col) => col.field
? col.field === column.field
: col.header === column.header);
rowIslandColumn[output.propName].emit({ args, owner: this });
});
}
});
});
}
_createColumn(col) {
let ref;
if (col instanceof IgxColumnGroupComponent) {
ref = this._createColGroupComponent(col);
}
else {
ref = this._createColComponent(col);
}
return ref;
}
_createColGroupComponent(col) {
const ref = createComponent(IgxColumnGroupComponent, { environmentInjector: this.envInjector, elementInjector: this.injector });
ref.changeDetectorRef.detectChanges();
const mirror = reflectComponentType(IgxColumnGroupComponent);
mirror.inputs.forEach((input) => {
const propName = input.propName;
ref.instance[propName] = col[propName];
});
if (col.children.length > 0) {
const newChildren = [];
col.children.forEach(child => {
const newCol = this._createColumn(child).instance;
newCol.parent = ref.instance;
newChildren.push(newCol);
});
ref.instance.children.reset(newChildren);
ref.instance.children.notifyOnChanges();
}
return ref;
}
_createColComponent(col) {
const ref = createComponent(IgxColumnComponent, { environmentInjector: this.envInjector, elementInjector: this.injector });
const mirror = reflectComponentType(IgxColumnComponent);
mirror.inputs.forEach((input) => {
const propName = input.propName;
if (!(col[propName] instanceof IgxSummaryOperand)) {
ref.instance[propName] = col[propName];
}
else {
ref.instance[propName] = col[propName].constructor;
}
});
ref.instance.validators = col.validators;
return ref;
}
getGridsForIsland(rowIslandID) {
return this.gridAPI.getChildGridsForRowIsland(rowIslandID);
}
getChildGrid(path) {
if (!path) {
return;
}
return this.gridAPI.getChildGrid(path);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridBaseDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxHierarchicalGridBaseDirective, isStandalone: true, inputs: { hasChildrenKey: "hasChildrenKey", showExpandAll: ["showExpandAll", "showExpandAll", booleanAttribute] }, outputs: { dataPreLoad: "dataPreLoad" }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxHierarchicalGridBaseDirective, decorators: [{
type: Directive
}], propDecorators: { hasChildrenKey: [{
type: Input
}], showExpandAll: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], dataPreLoad: [{
type: Output
}] } });
const flatten = (arr) => {
let result = [];
arr.forEach(el => {
result.push(el);
if (el.children) {
result = result.concat(flatten(el.children.toArray()));
}
});
return result;
};
class IgxRowIslandAPIService {
constructor() {
this.change = new Subject();
this.state = new Map();
this.destroyMap = new Map();
this.childRowIslands = new Map();
this.childGrids = new Map();
}
register(rowIsland) {
this.state.set(rowIsland.id, rowIsland);
this.destroyMap.set(rowIsland.id, new Subject());
}
unsubscribe(rowIsland) {
this.state.delete(rowIsland.id);
}
get(id) {
return this.state.get(id);
}
unset(id) {
this.state.delete(id);
this.destroyMap.delete(id);
}
reset(oldId, newId) {
const destroy = this.destroyMap.get(oldId);
const rowIsland = this.get(oldId);
this.unset(oldId);
if (rowIsland) {
this.state.set(newId, rowIsland);
}
if (destroy) {
this.destroyMap.set(newId, destroy);
}
}
registerChildRowIsland(rowIsland) {
this.childRowIslands.set(rowIsland.key, rowIsland);
this.destroyMap.set(rowIsland.key, new Subject());
}
unsetChildRowIsland(rowIsland) {
this.childRowIslands.delete(rowIsland.key);
this.destroyMap.delete(rowIsland.key);
}
getChildRowIsland(rowIslandKey) {
return this.childRowIslands.get(rowIslandKey);
}
registerChildGrid(parentRowID, grid) {
this.childGrids.set(parentRowID, grid);
}
getChildGrids(inDepth) {
let allChildren = [];
this.childGrids.forEach((grid) => {
allChildren.push(grid);
});
if (inDepth) {
this.childRowIslands.forEach((layout) => {
allChildren = allChildren.concat(layout.rowIslandAPI.getChildGrids(inDepth));
});
}
return allChildren;
}
getChildGridByID(rowID) {
return this.childGrids.get(rowID);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRowIslandAPIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRowIslandAPIService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRowIslandAPIService, decorators: [{
type: Injectable
}] });
/* blazorCopyInheritedMembers */
/* blazorElement */
/* wcElementTag: igc-row-island */
/* blazorIndirectRender */
/* jsonAPIManageCollectionInMarkup */
/* jsonAPIManageItemInMarkup */
/* mustUseNGParentAnchor */
/* additionalIdentifier: ChildDataKey */
/* contentParent: RowIsland */
/* contentParent: HierarchicalGrid */
/**
* Row island
*
* @igxModule IgxHierarchicalGridModule
* @igxParent IgxHierarchicalGridComponent, IgxRowIslandComponent
*
*/
class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective {
constructor() {
super(...arguments);
this.rowIslandAPI = inject(IgxRowIslandAPIService);
/**
* @hidden
*/
this.children = new QueryList();
/* contentChildren */
/* blazorInclude */
/* blazorTreatAsCollection */
/* blazorCollectionName: RowIslandCollection */
/**
* @hidden @internal
*/
this.childLayoutList = new QueryList();
/**
* @hidden
*/
this.childColumns = new QueryList();
/**
* @hidden
*/
this.layoutChange = new EventEmitter();
/**
* Event emitted when a grid is being created based on this row island.
* ```html
* <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
* <igx-row-island [key]="'childData'" (gridCreated)="gridCreated($event)" #rowIsland>
* <!-- ... -->
* </igx-row-island>
* </igx-hierarchical-grid>
* ```
*
* @memberof IgxRowIslandComponent
*/
this.gridCreated = new EventEmitter();
/**
* Emitted after a grid is being initialized for this row island.
* The emitting is done in `ngAfterViewInit`.
* ```html
* <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
* <igx-row-island [key]="'childData'" (gridInitialized)="gridInitialized($event)" #rowIsland>
* <!-- ... -->
* </igx-row-island>
* </igx-hierarchical-grid>
* ```
*
* @memberof IgxRowIslandComponent
*/
this.gridInitialized = new EventEmitter();
/**
* @hidden
*/
this.initialChanges = [];
/**
* @hidden
*/
this.rootGrid = null;
//#region inert, not-a-grid component
/** @hidden @internal */
this.tabindex = -1;
/** @hidden @internal */
this.hostRole = null;
this.baseClass = null;
this.displayStyle = 'none';
this.templateRows = null;
this.layout_id = `igx-row-island-`;
this.isInit = false;
this._childColumns = [];
}
/* blazorInclude,wcInclude TODO: Move to Elements-only component */
/**
* Sets the key of the row island by which child data would be taken from the row data if such is provided.
* @hidden @internal
*/
get childDataKey() {
return this.key;
}
/* blazorInclude,wcInclude */
set childDataKey(value) {
this.key = value;
}
/* csSuppress */
/**
* Sets/Gets the toolbar template for each child grid created from this row island.
*/
get toolbarTemplate() {
return this._toolbarTemplate || this.toolbarDirectiveTemplate;
}
set toolbarTemplate(template) {
this._toolbarTemplate = template;
}
/* csSuppress */
/**
* Sets/Gets the paginator template for each child grid created from this row island.
*/
get paginatorTemplate() {
return this._paginatorTemplate || this.paginatorDirectiveTemplate;
}
set paginatorTemplate(template) {
this._paginatorTemplate = template;
}
/** @hidden */
get hiddenColumnsCount() {
return 0;
}
/** @hidden */
get pinnedColumnsCount() {
return 0;
}
/** @hidden */
get lastSearchInfo() {
return null;
}
/** @hidden */
get filteredData() {
return [];
}
/** @hidden */
get filteredSortedData() {
return [];
}
/** @hidden */
get virtualizationState() {
return null;
}
/** @hidden */
get pinnedColumns() {
return [];
}
/** @hidden */
get unpinnedColumns() {
return [];
}
/** @hidden */
get visibleColumns() {
return [];
}
/** @hidden */
get dataView() {
return [];
}
/** @hidden @internal */
get hostWidth() {
return null;
}
/**
* Sets if all immediate children of the grids for this `IgxRowIslandComponent` should be expanded/collapsed.
* ```html
* <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
* <igx-row-island [key]="'childData'" [expandChildren]="true" #rowIsland>
* <!-- ... -->
* </igx-row-island>
* </igx-hierarchical-grid>
* ```
*
* @memberof IgxRowIslandComponent
*/
set expandChildren(value) {
this._defaultExpandState = value;
this.rowIslandAPI.getChildGrids().forEach((grid) => {
if (this.document.body.contains(grid.nativeElement)) {
// Detect changes right away if the grid is visible
grid.expandChildren = value;
grid.cdr.detectChanges();
}
else {
// Else defer the detection on changes when the grid gets into view for performance.
grid.updateOnRender = true;
}
});
}
/**
* Gets if all immediate children of the grids for this `IgxRowIslandComponent` have been set to be expanded/collapsed.
* ```typescript
* const expanded = this.rowIsland.expandChildren;
* ```
*
* @memberof IgxRowIslandComponent
*/
get expandChildren() {
return this._defaultExpandState;
}
/**
* @hidden
*/
get id() {
const pId = this.parentId ? this.parentId.substring(this.parentId.indexOf(this.layout_id) + this.layout_id.length) + '-' : '';
return this.layout_id + pId + this.key;
}
/**
* @hidden
*/
get parentId() {
return this.parentIsland ? this.parentIsland.id : null;
}
/**
* @hidden
*/
get level() {
let ptr = this.parentIsland;
let lvl = 0;
while (ptr) {
lvl++;
ptr = ptr.parentIsland;
}
return lvl + 1;
}
/**
* @hidden
*/
ngOnInit() {
this.filteringService.grid = this;
this.rootGrid = this.gridAPI.grid;
this.rowIslandAPI.rowIsland = this;
this.ri_columnListDiffer = this.differs.find([]).create(null);
}
/**
* @hidden
*/
ngAfterContentInit() {
this.updateChildren();
this.children.notifyOnChanges();
this.children.changes.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.updateChildren();
// update existing grids since their child ri have been changed.
this.rowIslandAPI.getChildGrids(false).forEach(grid => {
grid.onRowIslandChange(this.children);
});
});
const nestedColumns = this.children.map((layout) => layout.columnList.toArray());
const colsArray = [].concat.apply([], nestedColumns);
const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
this._childColumns = topCols;
this.updateColumns(this._childColumns);
this.columnList.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
Promise.resolve().then(() => {
this.updateColumnList();
});
});
// handle column changes so that they are passed to child grid instances when columnChange is emitted.
this.ri_columnListDiffer.diff(this.childColumns);
this._childColumns.forEach(x => x.columnChange.pipe(takeUntil(x.destroy$)).subscribe(() => this.updateColumnList()));
this.childColumns.changes.pipe(takeUntil(this.destroy$)).subscribe((change) => {
const diff = this.ri_columnListDiffer.diff(change);
if (diff) {
diff.forEachAddedItem((record) => {
record.item.columnChange.pipe(takeUntil(record.item.destroy$)).subscribe(() => this.updateColumnList());
});
}
});
if (this.actionStrip) {
this.actionStrip.menuOverlaySettings.outlet = this.outlet;
}
}
/**
* @hidden
*/
ngAfterViewInit() {
this.rowIslandAPI.register(this);
if (this.parentIsland) {
this.parentIsland.rowIslandAPI.registerChildRowIsland(this);
}
else {
this.rootGrid.gridAPI.registerChildRowIsland(this);
}
this._init = false;
// Create the child toolbar if the parent island has a toolbar definition
this.gridCreated.pipe(pluck('grid'), takeUntil(this.destroy$)).subscribe(grid => {
grid.rendered$.pipe(first(), filter(() => !!this.toolbarTemplate))
.subscribe(() => grid.toolbarOutlet.createEmbeddedView(this.toolbarTemplate, { $implicit: grid }, { injector: grid.toolbarOutlet.injector }));
grid.rendered$.pipe(first(), filter(() => !!this.paginatorTemplate))
.subscribe(() => {
this.rootGrid.paginatorList.changes.pipe(takeUntil(this.destroy$)).subscribe((changes) => {
changes.forEach(p => {
if (p.nativeElement.offsetParent?.id === grid.id) {
// Optimize update only for those grids that have related changed paginator.
grid.setUpPaginator();
}
});
});
grid.paginatorOutlet.createEmbeddedView(this.paginatorTemplate, { $implicit: grid });
});
});
}
/**
* @hidden
*/
ngOnChanges(changes) {
this.layoutChange.emit(changes);
if (!this.isInit) {
this.initialChanges.push(changes);
}
}
/**
* @hidden
*/
ngOnDestroy() {
// Override the base destroy because we have not rendered anything to use removeEventListener on
this.destroy$.next(true);
this.destroy$.complete();
this._destroyed = true;
this.rowIslandAPI.unset(this.id);
if (this.parentIsland) {
this.getGridsForIsland(this.key).forEach(grid => {
this.cleanGridState(grid);
grid.gridAPI.unsetChildRowIsland(this);
});
this.parentIsland.rowIslandAPI.unsetChildRowIsland(this);
}
else {
this.rootGrid.gridAPI.unsetChildRowIsland(this);
this.cleanGridState(this.rootGrid);
}
}
/**
* @hidden
*/
reflow() { }
/**
* @hidden
*/
calculateGridHeight() { }
/**
* @hidden
*/
calculateGridWidth() { }
updateColumnList() {
const nestedColumns = this.children.map((layout) => layout.columnList.toArray());
const colsArray = [].concat.apply([], nestedColumns);
const topCols = this.columnList.filter((item) => {
if (colsArray.indexOf(item) === -1) {
/* Reset the default width of the columns that come into this row island,
because the root catches them first during the detectChanges() and sets their defaultWidt