• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 12010745303

25 Nov 2024 01:10PM CUT coverage: 91.818% (-0.02%) from 91.833%
12010745303

push

github

web-flow
fix(combo): move ngIf to search wrapper, not on input group (#15088)

12545 of 14643 branches covered (85.67%)

25666 of 27953 relevant lines covered (91.82%)

32963.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.64
/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts
1
import { DOCUMENT, formatNumber, getLocaleNumberFormat, NumberFormatStyle } from '@angular/common';
2
import {
3
    AfterContentInit,
4
    AfterViewInit,
5
    booleanAttribute,
6
    ChangeDetectorRef,
7
    ContentChild,
8
    ContentChildren,
9
    createComponent,
10
    Directive,
11
    DoCheck,
12
    ElementRef,
13
    EnvironmentInjector,
14
    EventEmitter,
15
    HostBinding,
16
    HostListener,
17
    Inject,
18
    Injector,
19
    Input,
20
    IterableChangeRecord,
21
    IterableDiffers,
22
    LOCALE_ID,
23
    NgZone,
24
    OnDestroy,
25
    OnInit,
26
    Optional,
27
    Output,
28
    QueryList,
29
    TemplateRef,
30
    ViewChild,
31
    ViewChildren,
32
    ViewContainerRef
33
} from '@angular/core';
34
import { formatDate, resizeObservable } from '../core/utils';
35
import { IgcTrialWatermark } from 'igniteui-trial-watermark';
36
import { Subject, pipe, fromEvent, animationFrameScheduler, merge } from 'rxjs';
37
import { takeUntil, first, filter, throttleTime, map, shareReplay, takeWhile } from 'rxjs/operators';
38
import { cloneArray, mergeObjects, compareMaps, resolveNestedPath, isObject, PlatformUtil } from '../core/utils';
39
import { GridColumnDataType } from '../data-operations/data-util';
40
import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface';
41
import { IGroupByRecord } from '../data-operations/groupby-record.interface';
42
import { IForOfDataChangingEventArgs, IgxGridForOfDirective } from '../directives/for-of/for_of.directive';
43
import { IgxTextHighlightService } from '../directives/text-highlight/text-highlight.service';
44
import { ISummaryExpression } from './summaries/grid-summary';
45
import { RowEditPositionStrategy } from './grid.common';
46
import type { IgxGridToolbarComponent } from './toolbar/grid-toolbar.component';
47
import { IgxToolbarToken } from './toolbar/token';
48
import { IgxRowDirective } from './row.directive';
49
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../directives/toggle/toggle.directive';
50
import {
51
    FilteringExpressionsTree, IFilteringExpressionsTree, FilteringExpressionsTreeType
52
} from '../data-operations/filtering-expressions-tree';
53
import { IFilteringOperation } from '../data-operations/filtering-condition';
54
import { Transaction, TransactionType, TransactionService, State } from '../services/public_api';
55
import {
56
    IgxRowAddTextDirective,
57
    IgxRowEditTemplateDirective,
58
    IgxRowEditTabStopDirective,
59
    IgxRowEditTextDirective,
60
    IgxRowEditActionsDirective
61
} from './grid.rowEdit.directive';
62
import { IgxGridNavigationService, IActiveNode } from './grid-navigation.service';
63
import { IDisplayDensityOptions, DisplayDensityToken, DisplayDensityBase, DisplayDensity } from '../core/density';
64
import { IgxFilteringService } from './filtering/grid-filtering.service';
65
import { IgxGridFilteringCellComponent } from './filtering/base/grid-filtering-cell.component';
66
import { WatchChanges } from './watch-changes';
67
import { IgxGridHeaderGroupComponent } from './headers/grid-header-group.component';
68
import { GridResourceStringsEN, IGridResourceStrings } from '../core/i18n/grid-resources';
69
import { IgxGridSummaryService } from './summaries/grid-summary.service';
70
import { IgxSummaryRowComponent } from './summaries/summary-row.component';
71
import { IgxGridSelectionService } from './selection/selection.service';
72
import { IgxEditRow, IgxCell, IgxAddRow } from './common/crud.service';
73
import { ICachedViewLoadedEventArgs, IgxTemplateOutletDirective } from '../directives/template-outlet/template_outlet.directive';
74
import { IgxExcelStyleLoadingValuesTemplateDirective } from './filtering/excel-style/excel-style-search.component';
75
import { IgxGridColumnResizerComponent } from './resizing/resizer.component';
76
import { CharSeparatedValueData } from '../services/csv/char-separated-value-data';
77
import { IgxColumnResizingService } from './resizing/resizing.service';
78
import { FilteringStrategy, IFilteringStrategy } from '../data-operations/filtering-strategy';
79
import {
80
    IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective, IgxHeaderExpandedIndicatorDirective,
81
    IgxHeaderCollapsedIndicatorDirective, IgxExcelStyleHeaderIconDirective, IgxSortAscendingHeaderIconDirective,
82
    IgxSortDescendingHeaderIconDirective, IgxSortHeaderIconDirective
83
} from './grid.directives';
84
import {
85
    GridKeydownTargetType,
86
    GridSelectionMode,
87
    GridSummaryPosition,
88
    GridSummaryCalculationMode,
89
    FilterMode,
90
    ColumnPinningPosition,
91
    RowPinningPosition,
92
    GridPagingMode,
93
    GridValidationTrigger
94
} from './common/enums';
95
import {
96
    IGridCellEventArgs,
97
    IRowSelectionEventArgs,
98
    IPinColumnEventArgs,
99
    IRowDataEventArgs,
100
    IColumnResizeEventArgs,
101
    IColumnMovingStartEventArgs,
102
    IColumnMovingEventArgs,
103
    IColumnMovingEndEventArgs,
104
    IGridKeydownEventArgs,
105
    IRowDragStartEventArgs,
106
    IRowDragEndEventArgs,
107
    IGridClipboardEvent,
108
    IGridToolbarExportEventArgs,
109
    ISearchInfo,
110
    ICellPosition,
111
    IRowToggleEventArgs,
112
    IColumnSelectionEventArgs,
113
    IPinRowEventArgs,
114
    IGridScrollEventArgs,
115
    IActiveNodeChangeEventArgs,
116
    ISortingEventArgs,
117
    IFilteringEventArgs,
118
    IColumnVisibilityChangedEventArgs,
119
    IColumnVisibilityChangingEventArgs,
120
    IPinColumnCancellableEventArgs,
121
    IGridEditEventArgs,
122
    IRowDataCancelableEventArgs,
123
    IGridEditDoneEventArgs,
124
    IGridRowEventArgs
125
} from './common/events';
126
import { IgxAdvancedFilteringDialogComponent } from './filtering/advanced-filtering/advanced-filtering-dialog.component';
127
import {
128
    ColumnType,
129
    GridServiceType,
130
    GridType,
131
    IGridFormGroupCreatedEventArgs,
132
    IGridValidationStatusEventArgs,
133
    IgxGridEmptyTemplateContext,
134
    IgxGridHeaderTemplateContext,
135
    IgxGridRowDragGhostContext,
136
    IgxGridRowEditActionsTemplateContext,
137
    IgxGridRowEditTemplateContext,
138
    IgxGridRowEditTextTemplateContext,
139
    IgxGridRowTemplateContext,
140
    IgxGridTemplateContext,
141
    IgxHeadSelectorTemplateContext,
142
    IgxRowSelectorTemplateContext,
143
    IGX_GRID_SERVICE_BASE,
144
    ISizeInfo,
145
    RowType,
146
    IPinningConfig,
147
    IClipboardOptions
148
} from './common/grid.interface';
149
import { DropPosition } from './moving/moving.service';
150
import { IgxHeadSelectorDirective, IgxRowSelectorDirective } from './selection/row-selectors';
151
import { IgxColumnComponent } from './columns/column.component';
152
import { IgxColumnGroupComponent } from './columns/column-group.component';
153
import { IgxRowDragGhostDirective, IgxDragIndicatorIconDirective } from './row-drag.directive';
154
import { IgxSnackbarComponent } from '../snackbar/snackbar.component';
155
import { v4 as uuidv4 } from 'uuid';
156
import { IgxActionStripToken } from '../action-strip/token';
157
import { IgxGridRowComponent } from './grid/grid-row.component';
158
import type { IgxPaginatorComponent } from '../paginator/paginator.component';
159
import { IgxPaginatorToken } from '../paginator/token';
160
import { IgxGridHeaderRowComponent } from './headers/grid-header-row.component';
161
import { IgxGridGroupByAreaComponent } from './grouping/grid-group-by-area.component';
162
import { IgxFlatTransactionFactory, TRANSACTION_TYPE } from '../services/transaction/transaction-factory.service';
163
import { ISortingOptions } from './columns/interfaces';
164
import { GridSelectionRange, IgxGridTransaction } from './common/types';
165
import { VerticalAlignment, HorizontalAlignment, PositionSettings, OverlaySettings } from '../services/overlay/utilities';
166
import { IgxOverlayService } from '../services/overlay/overlay';
167
import { ConnectedPositioningStrategy } from '../services/overlay/position/connected-positioning-strategy';
168
import { ContainerPositionStrategy } from '../services/overlay/position/container-position-strategy';
169
import { AbsoluteScrollStrategy } from '../services/overlay/scroll/absolute-scroll-strategy';
170
import { Action, StateUpdateEvent, TransactionEventOrigin } from '../services/transaction/transaction';
171
import { ISortingExpression } from '../data-operations/sorting-strategy';
172
import { IGridSortingStrategy } from './common/strategy';
173
import { IgxGridExcelStyleFilteringComponent } from './filtering/excel-style/excel-style-filtering.component';
174
import { IgxGridHeaderComponent } from './headers/grid-header.component';
175
import { IgxGridFilteringRowComponent } from './filtering/base/grid-filtering-row.component';
176
import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations/data-clone-strategy';
177
import { IgxGridCellComponent } from './cell.component';
178
import { IgxGridValidationService } from './grid/grid-validation.service';
179
import { getCurrentResourceStrings } from '../core/i18n/resources';
180

181
interface IMatchInfoCache {
182
    row: any;
183
    index: number;
184
    column: string;
185
    metadata: Map<string, boolean>;
186
}
187

188
let FAKE_ROW_ID = -1;
2✔
189
const DEFAULT_ITEMS_PER_PAGE = 15;
2✔
190
const MINIMUM_COLUMN_WIDTH = 136;
2✔
191
// By default row editing overlay outlet is inside grid body so that overlay is hidden below grid header when scrolling.
192
// In cases when grid has 1-2 rows there isn't enough space in grid body and row editing overlay should be shown above header.
193
// Default row editing overlay height is higher then row height that is why the case is valid also for row with 2 rows.
194
// More accurate calculation is not possible, cause row editing overlay is still not shown and we don't know its height,
195
// but in the same time we need to set row editing overlay outlet before opening the overlay itself.
196
const MIN_ROW_EDITING_COUNT_THRESHOLD = 2;
2✔
197

198
@Directive()
199
export abstract class IgxGridBaseDirective extends DisplayDensityBase implements GridType,
2✔
200
    OnInit, DoCheck, OnDestroy, AfterContentInit, AfterViewInit {
201

202
    /**
203
     * Gets/Sets the display time for the row adding snackbar notification.
204
     *
205
     * @remarks
206
     * By default it is 6000ms.
207
     */
208
    @Input()
209
    public snackbarDisplayTime = 6000;
3,996✔
210

211
    /**
212
     * Gets/Sets whether to auto-generate the columns.
213
     *
214
     * @remarks
215
     * The default value is false. When set to true, it will override all columns declared through code or in markup.
216
     * @example
217
     * ```html
218
     * <igx-grid [data]="Data" [autoGenerate]="true"></igx-grid>
219
     * ```
220
     */
221
    @Input({ transform: booleanAttribute })
222
    public autoGenerate = false;
3,996✔
223

224
    /**
225
     * Gets/Sets a list of property keys to be excluded from the generated column collection
226
     * @remarks
227
     * The collection is only used during initialization and changing it will not cause any changes in the generated columns at runtime
228
     * unless the grid is destroyed and recreated. To modify the columns visible in the UI at runtime, please use their
229
     * [hidden](https://www.infragistics.com/products/ignite-ui-angular/docs/typescript/latest/classes/IgxColumnComponent.html#hidden) property.
230
     * @example
231
     * ```html
232
     * <igx-grid data=[Data] [autoGenerate]="true" [autoGenerateExclude]="['ProductName', 'Count']"></igx-grid>
233
     * ```
234
     * ```typescript
235
     * const Data = [{ 'Id': '1', 'ProductName': 'name1', 'Description': 'description1', 'Count': 5 }]
236
     * ```
237
     */
238
    @Input()
239
    public autoGenerateExclude: string[] = [];
3,996✔
240

241
    /**
242
     * Controls whether columns moving is enabled in the grid.
243
     *
244
     */
245
    @Input({ transform: booleanAttribute })
246
    public moving = false;
3,996✔
247

248
    /**
249
     * Gets/Sets a custom template when empty.
250
     *
251
     * @example
252
     * ```html
253
     * <igx-grid [id]="'igx-grid-1'" [data]="Data" [emptyGridTemplate]="myTemplate" [autoGenerate]="true"></igx-grid>
254
     * ```
255
     */
256
    @Input()
257
    public emptyGridTemplate: TemplateRef<void>;
258

259
    /**
260
     * Gets/Sets a custom template for adding row UI when grid is empty.
261
     *
262
     * @example
263
     * ```html
264
     * <igx-grid [id]="'igx-grid-1'" [data]="Data" [addRowEmptyTemplate]="myTemplate" [autoGenerate]="true"></igx-grid>
265
     * ```
266
     */
267
    @Input()
268
    public addRowEmptyTemplate: TemplateRef<void>;
269

270
    /**
271
     * Gets/Sets a custom template when loading.
272
     *
273
     * @example
274
     * ```html
275
     * <igx-grid [id]="'igx-grid-1'" [data]="Data" [loadingGridTemplate]="myTemplate" [autoGenerate]="true"></igx-grid>
276
     * ```
277
     */
278
    @Input()
279
    public loadingGridTemplate: TemplateRef<void>;
280

281
    /**
282
     * Get/Set IgxSummaryRow height
283
     */
284
    @Input()
285
    public set summaryRowHeight(value: number) {
286
        this._summaryRowHeight = value | 0;
21✔
287
        this.summaryService.summaryHeight = value;
21✔
288
        if (!this._init) {
21✔
289
            this.reflow();
20✔
290
        }
291
    }
292

293
    public get summaryRowHeight(): number {
294
        if (this.hasSummarizedColumns && this.rootSummariesEnabled) {
237,136✔
295
            return this._summaryRowHeight || this.summaryService.calcMaxSummaryHeight();
156,905✔
296
        }
297
        return 0;
80,231✔
298
    }
299

300
    /** @hidden @internal */
301
    public get hasColumnsToAutosize() {
302
        return this._columns.some(x => x.width === 'fit-content');
86,103✔
303
    }
304

305
    /**
306
     * Gets/Sets the data clone strategy of the grid when in edit mode.
307
     *
308
     * @example
309
     * ```html
310
     *  <igx-grid #grid [data]="localData" [dataCloneStrategy]="customCloneStrategy"></igx-grid>
311
     * ```
312
     */
313
    @Input()
314
    public get dataCloneStrategy(): IDataCloneStrategy {
315
        return this._dataCloneStrategy;
51,068✔
316
    }
317

318
    public set dataCloneStrategy(strategy: IDataCloneStrategy) {
319
        if (strategy) {
1✔
320
            this._dataCloneStrategy = strategy;
1✔
321
            this._transactions.cloneStrategy = strategy;
1✔
322
        }
323
    }
324

325
    /**
326
     * Controls the copy behavior of the grid.
327
     */
328
    @Input()
329
    public clipboardOptions: IClipboardOptions = {
3,996✔
330
        /**
331
         * Enables/disables the copy behavior
332
         */
333
        enabled: true,
334
        /**
335
         * Include the columns headers in the clipboard output.
336
         */
337
        copyHeaders: true,
338
        /**
339
         * Apply the columns formatters (if any) on the data in the clipboard output.
340
         */
341
        copyFormatters: true,
342
        /**
343
         * The separator used for formatting the copy output. Defaults to `\t`.
344
         */
345
        separator: '\t'
346
    };
347

348
    /**
349
     * Emitted after filtering is performed.
350
     *
351
     * @remarks
352
     * Returns the filtering expressions tree of the column for which filtering was performed.
353
     * @example
354
     * ```html
355
     * <igx-grid #grid [data]="localData" [height]="'305px'" [autoGenerate]="true"
356
     *              (filteringExpressionsTreeChange)="filteringExprTreeChange($event)"></igx-grid>
357
     * ```
358
     */
359
    @Output()
360
    public filteringExpressionsTreeChange = new EventEmitter<IFilteringExpressionsTree>();
3,996✔
361

362
    /**
363
     * Emitted after advanced filtering is performed.
364
     *
365
     * @remarks
366
     * Returns the advanced filtering expressions tree.
367
     * @example
368
     * ```html
369
     * <igx-grid #grid [data]="localData" [height]="'305px'" [autoGenerate]="true"
370
     *           (advancedFilteringExpressionsTreeChange)="advancedFilteringExprTreeChange($event)"></igx-grid>
371
     * ```
372
     */
373
    @Output()
374
    public advancedFilteringExpressionsTreeChange = new EventEmitter<IFilteringExpressionsTree>();
3,996✔
375

376
    /**
377
     * Emitted when grid is scrolled horizontally/vertically.
378
     *
379
     * @example
380
     * ```html
381
     * <igx-grid #grid [data]="localData" [height]="'305px'" [autoGenerate]="true"
382
     *              (gridScroll)="onScroll($event)"></igx-grid>
383
     * ```
384
     */
385
    @Output()
386
    public gridScroll = new EventEmitter<IGridScrollEventArgs>();
3,996✔
387

388
    /**
389
     * Sets a conditional class selector to the grid's row element.
390
     * Accepts an object literal, containing key-value pairs,
391
     * where the key is the name of the CSS class and the value is
392
     * either a callback function that returns a boolean, or boolean, like so:
393
     * ```typescript
394
     * callback = (row: RowType) => { return row.selected > 6; }
395
     * rowClasses = { 'className' : this.callback };
396
     * ```
397
     * ```html
398
     * <igx-grid #grid [data]="Data" [rowClasses] = "rowClasses" [autoGenerate]="true"></igx-grid>
399
     * ```
400
     *
401
     * @memberof IgxColumnComponent
402
     */
403
    @Input()
404
    public rowClasses: any;
405

406
    /**
407
     * Sets conditional style properties on the grid row element.
408
     * It accepts an object literal where the keys are
409
     * the style properties and the value is an expression to be evaluated.
410
     * ```typescript
411
     * styles = {
412
     *  background: 'yellow',
413
     *  color: (row: RowType) => row.selected : 'red': 'white'
414
     * }
415
     * ```
416
     * ```html
417
     * <igx-grid #grid [data]="Data" [rowStyles]="styles" [autoGenerate]="true"></igx-grid>
418
     * ```
419
     *
420
     * @memberof IgxColumnComponent
421
     */
422
    @Input()
423
    public rowStyles = null;
3,996✔
424

425
    /**
426
     * Gets/Sets the primary key.
427
     *
428
     * @example
429
     * ```html
430
     * <igx-grid #grid [data]="localData" [primaryKey]="'ProductID'" [autoGenerate]="true"></igx-grid>
431
     * ```
432
     */
433
    @WatchChanges()
434
    @Input()
435
    public primaryKey: any;
436

437
    /**
438
     * Gets/Sets a unique values strategy used by the Excel Style Filtering
439
     *
440
     * @remarks
441
     * Provides a callback for loading unique column values on demand.
442
     * If this property is provided, the unique values it generates will be used by the Excel Style Filtering.
443
     * @example
444
     * ```html
445
     * <igx-grid [data]="localData" [filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy"></igx-grid>
446
     * ```
447
     */
448
    @Input()
449
    public uniqueColumnValuesStrategy: (column: ColumnType,
450
        filteringExpressionsTree: IFilteringExpressionsTree,
451
        done: (values: any[]) => void) => void;
452

453
    /** @hidden @internal */
454
    @ContentChildren(IgxGridExcelStyleFilteringComponent, { read: IgxGridExcelStyleFilteringComponent, descendants: false })
455
    public excelStyleFilteringComponents: QueryList<IgxGridExcelStyleFilteringComponent>;
456

457
    /** @hidden @internal */
458
    public get excelStyleFilteringComponent() {
459
        return this.excelStyleFilteringComponents?.first;
262✔
460
    }
461

462
    /** @hidden @internal */
463
    public get headerGroups() {
464
        return this.theadRow.groups;
3✔
465
    }
466

467
    /**
468
     * Emitted when a cell is clicked.
469
     *
470
     * @remarks
471
     * Returns the `IgxGridCell`.
472
     * @example
473
     * ```html
474
     * <igx-grid #grid (cellClick)="cellClick($event)" [data]="localData" [height]="'305px'" [autoGenerate]="true"></igx-grid>
475
     * ```
476
     */
477
    @Output()
478
    public cellClick = new EventEmitter<IGridCellEventArgs>();
3,996✔
479

480
    /**
481
     * Emitted when a row is clicked.
482
     *
483
     * @remarks
484
     * Returns the `IgxGridRow`.
485
     * @example
486
     * ```html
487
     * <igx-grid #grid (rowClick)="rowClick($event)" [data]="localData" [height]="'305px'" [autoGenerate]="true"></igx-grid>
488
     * ```
489
     */
490
    @Output()
491
    public rowClick = new EventEmitter<IGridRowEventArgs>();
3,996✔
492

493

494
    /**
495
     * Emitted when formGroup is created on edit of row/cell.
496
     *
497
     * @example
498
     * ```html
499
     * <igx-grid #grid (formGroupCreated)="formGroupCreated($event)" [data]="localData" [height]="'305px'" [autoGenerate]="true"></igx-grid>
500
     * ```
501
     */
502
    @Output()
503
    public formGroupCreated = new EventEmitter<IGridFormGroupCreatedEventArgs>();
3,996✔
504

505
    /**
506
     * Emitted when grid's validation status changes.
507
     *
508
     * @example
509
     * ```html
510
     * <igx-grid #grid (validationStatusChange)="validationStatusChange($event)" [data]="localData" [height]="'305px'" [autoGenerate]="true"></igx-grid>
511
     * ```
512
     */
513
    @Output()
514
    public validationStatusChange = new EventEmitter<IGridValidationStatusEventArgs>();
3,996✔
515

516
    /**
517
     * Emitted when a cell is selected.
518
     *
519
     * @remarks
520
     *  Returns the `IgxGridCell`.
521
     * @example
522
     * ```html
523
     * <igx-grid #grid (selected)="onCellSelect($event)" [data]="localData" [height]="'305px'" [autoGenerate]="true"></igx-grid>
524
     * ```
525
     */
526
    @Output()
527
    public selected = new EventEmitter<IGridCellEventArgs>();
3,996✔
528

529
    /**
530
     *  Emitted when `IgxGridRowComponent` is selected.
531
     *
532
     * @example
533
     * ```html
534
     * <igx-grid #grid (rowSelectionChanging)="rowSelectionChanging($event)" [data]="localData" [autoGenerate]="true"></igx-grid>
535
     * ```
536
     */
537
    @Output()
538
    public rowSelectionChanging = new EventEmitter<IRowSelectionEventArgs>();
3,996✔
539

540
    /**
541
     *  Emitted when `IgxColumnComponent` is selected.
542
     *
543
     * @example
544
     * ```html
545
     * <igx-grid #grid (columnSelectionChanging)="columnSelectionChanging($event)" [data]="localData" [autoGenerate]="true"></igx-grid>
546
     * ```
547
     */
548
    @Output()
549
    public columnSelectionChanging = new EventEmitter<IColumnSelectionEventArgs>();
3,996✔
550

551
    /**
552
     * Emitted before `IgxColumnComponent` is pinned.
553
     *
554
     * @remarks
555
     * The index at which to insert the column may be changed through the `insertAtIndex` property.
556
     * @example
557
     * ```typescript
558
     * public columnPinning(event) {
559
     *     if (event.column.field === "Name") {
560
     *       event.insertAtIndex = 0;
561
     *     }
562
     * }
563
     * ```
564
     */
565
    @Output()
566
    public columnPin = new EventEmitter<IPinColumnCancellableEventArgs>();
3,996✔
567

568
    /**
569
     * Emitted after `IgxColumnComponent` is pinned.
570
     *
571
     * @remarks
572
     * The index that the column is inserted at may be changed through the `insertAtIndex` property.
573
     * @example
574
     * ```typescript
575
     * public columnPinning(event) {
576
     *     if (event.column.field === "Name") {
577
     *       event.insertAtIndex = 0;
578
     *     }
579
     * }
580
     * ```
581
     */
582
    @Output()
583
    public columnPinned = new EventEmitter<IPinColumnEventArgs>();
3,996✔
584

585
    /**
586
     * Emitted when cell enters edit mode.
587
     *
588
     * @remarks
589
     * This event is cancelable.
590
     * @example
591
     * ```html
592
     * <igx-grid #grid3 (cellEditEnter)="editStart($event)" [data]="data" [primaryKey]="'ProductID'">
593
     * </igx-grid>
594
     * ```
595
     */
596
    @Output()
597
    public cellEditEnter = new EventEmitter<IGridEditEventArgs>();
3,996✔
598

599
    /**
600
     * Emitted when cell exits edit mode.
601
     *
602
     * @example
603
     * ```html
604
     * <igx-grid #grid3 (cellEditExit)="editExit($event)" [data]="data" [primaryKey]="'ProductID'">
605
     * </igx-grid>
606
     * ```
607
     */
608
    @Output()
609
    public cellEditExit = new EventEmitter<IGridEditDoneEventArgs>();
3,996✔
610

611
    /**
612
     * Emitted when cell has been edited.
613
     *
614
     * @remarks
615
     * Event is fired after editing is completed, when the cell is exiting edit mode.
616
     * This event is cancelable.
617
     * @example
618
     * ```html
619
     * <igx-grid #grid3 (cellEdit)="editDone($event)" [data]="data" [primaryKey]="'ProductID'">
620
     * </igx-grid>
621
     * ```
622
     */
623
    @Output()
624
    public cellEdit = new EventEmitter<IGridEditEventArgs>();
3,996✔
625

626
    /**
627
     * Emitted after cell has been edited and editing has been committed.
628
     *
629
     * @example
630
     * ```html
631
     * <igx-grid #grid3 (cellEditDone)="editDone($event)" [data]="data" [primaryKey]="'ProductID'">
632
     * </igx-grid>
633
     * ```
634
     */
635
    @Output()
636
    public cellEditDone = new EventEmitter<IGridEditDoneEventArgs>();
3,996✔
637

638
    /**
639
     * Emitted when a row enters edit mode.
640
     *
641
     * @remarks
642
     * Emitted when [rowEditable]="true".
643
     * This event is cancelable.
644
     * @example
645
     * ```html
646
     * <igx-grid #grid3 (rowEditEnter)="editStart($event)" [primaryKey]="'ProductID'" [rowEditable]="true">
647
     * </igx-grid>
648
     * ```
649
     */
650
    @Output()
651
    public rowEditEnter = new EventEmitter<IGridEditEventArgs>();
3,996✔
652

653
    /**
654
     * Emitted when exiting edit mode for a row.
655
     *
656
     * @remarks
657
     * Emitted when [rowEditable]="true" & `endEdit(true)` is called.
658
     * Emitted when changing rows during edit mode, selecting an un-editable cell in the edited row,
659
     * performing paging operation, column resizing, pinning, moving or hitting `Done`
660
     * button inside of the rowEditingOverlay, or hitting the `Enter` key while editing a cell.
661
     * This event is cancelable.
662
     * @example
663
     * ```html
664
     * <igx-grid #grid3 (rowEdit)="editDone($event)" [data]="data" [primaryKey]="'ProductID'" [rowEditable]="true">
665
     * </igx-grid>
666
     * ```
667
     */
668
    @Output()
669
    public rowEdit = new EventEmitter<IGridEditEventArgs>();
3,996✔
670

671
    /**
672
     * Emitted after exiting edit mode for a row and editing has been committed.
673
     *
674
     * @remarks
675
     * Emitted when [rowEditable]="true" & `endEdit(true)` is called.
676
     * Emitted when changing rows during edit mode, selecting an un-editable cell in the edited row,
677
     * performing paging operation, column resizing, pinning, moving or hitting `Done`
678
     * button inside of the rowEditingOverlay, or hitting the `Enter` key while editing a cell.
679
     * @example
680
     * ```html
681
     * <igx-grid #grid3 (rowEditDone)="editDone($event)" [data]="data" [primaryKey]="'ProductID'" [rowEditable]="true">
682
     * </igx-grid>
683
     * ```
684
     */
685
    @Output()
686
    public rowEditDone = new EventEmitter<IGridEditDoneEventArgs>();
3,996✔
687

688
    /**
689
     * Emitted when row editing is canceled.
690
     *
691
     * @remarks
692
     * Emits when [rowEditable]="true" & `endEdit(false)` is called.
693
     * Emitted when changing hitting `Esc` key during cell editing and when click on the `Cancel` button
694
     * in the row editing overlay.
695
     * @example
696
     * ```html
697
     * <igx-grid #grid3 (rowEditExit)="editExit($event)" [data]="data" [primaryKey]="'ProductID'" [rowEditable]="true">
698
     * </igx-grid>
699
     * ```
700
     */
701
    @Output()
702
    public rowEditExit = new EventEmitter<IGridEditDoneEventArgs>();
3,996✔
703

704
    /**
705
     * Emitted when a column is initialized.
706
     *
707
     * @remarks
708
     * Returns the column object.
709
     * @example
710
     * ```html
711
     * <igx-grid #grid [data]="localData" (columnInit)="initColumns($event)" [autoGenerate]="true"></igx-grid>
712
     * ```
713
     */
714
    @Output()
715
    public columnInit = new EventEmitter<IgxColumnComponent>();
3,996✔
716

717
    /**
718
     * Emitted before sorting expressions are applied.
719
     *
720
     * @remarks
721
     * Returns an `ISortingEventArgs` object. `sortingExpressions` key holds the sorting expressions.
722
     * @example
723
     * ```html
724
     * <igx-grid #grid [data]="localData" [autoGenerate]="true" (sorting)="sorting($event)"></igx-grid>
725
     * ```
726
     */
727
    @Output()
728
    public sorting = new EventEmitter<ISortingEventArgs>();
3,996✔
729

730
    /**
731
     * Emitted after sorting is completed.
732
     *
733
     * @remarks
734
     * Returns the sorting expression.
735
     * @example
736
     * ```html
737
     * <igx-grid #grid [data]="localData" [autoGenerate]="true" (sortingDone)="sortingDone($event)"></igx-grid>
738
     * ```
739
     */
740
    @Output()
741
    public sortingDone = new EventEmitter<ISortingExpression | ISortingExpression[]>();
3,996✔
742

743
    /**
744
     * Emitted before filtering expressions are applied.
745
     *
746
     * @remarks
747
     * Returns an `IFilteringEventArgs` object. `filteringExpressions` key holds the filtering expressions for the column.
748
     * @example
749
     * ```html
750
     * <igx-grid #grid [data]="localData" [height]="'305px'" [autoGenerate]="true" (filtering)="filtering($event)"></igx-grid>
751
     * ```
752
     */
753
    @Output()
754
    public filtering = new EventEmitter<IFilteringEventArgs>();
3,996✔
755

756
    /**
757
     * Emitted after filtering is performed through the UI.
758
     *
759
     * @remarks
760
     * Returns the filtering expressions tree of the column for which filtering was performed.
761
     * @example
762
     * ```html
763
     * <igx-grid #grid [data]="localData" [height]="'305px'" [autoGenerate]="true" (filteringDone)="filteringDone($event)"></igx-grid>
764
     * ```
765
     */
766
    @Output()
767
    public filteringDone = new EventEmitter<IFilteringExpressionsTree>();
3,996✔
768

769
    /**
770
     * Emitted when a row is added.
771
     *
772
     * @remarks
773
     * Returns the data for the new `IgxGridRowComponent` object.
774
     * @example
775
     * ```html
776
     * <igx-grid #grid [data]="localData" (rowAdded)="rowAdded($event)" [height]="'305px'" [autoGenerate]="true"></igx-grid>
777
     * ```
778
     */
779
    @Output()
780
    public rowAdded = new EventEmitter<IRowDataEventArgs>();
3,996✔
781

782
    /**
783
     * Emitted when a row is deleted.
784
     *
785
     * @remarks
786
     * Returns an `IRowDataEventArgs` object.
787
     * @example
788
     * ```html
789
     * <igx-grid #grid [data]="localData" (rowDeleted)="rowDeleted($event)" [height]="'305px'" [autoGenerate]="true"></igx-grid>
790
     * ```
791
     */
792
    @Output()
793
    public rowDeleted = new EventEmitter<IRowDataEventArgs>();
3,996✔
794

795
    /**
796
     * Emmited when deleting a row.
797
     *
798
     * @remarks
799
     * This event is cancelable.
800
     * Returns an IRowDataCancellableEventArgs` object.
801
     * @example
802
     * ```html
803
     * <igx-grid #grid [data]="localData" (rowDelete)="rowDelete($event)" [height]="'305px'" [autoGenerate]="true"></igx-grid>
804
     * ```
805
     */
806
    @Output()
807
    public rowDelete = new EventEmitter<IRowDataCancelableEventArgs>();
3,996✔
808

809
    /**
810
     * Emmited just before the newly added row is commited.
811
     *
812
     * @remarks
813
     * This event is cancelable.
814
     * Returns an IRowDataCancellableEventArgs` object.
815
     * @example
816
     * ```html
817
     * <igx-grid #grid [data]="localData" (rowAdd)="rowAdd($event)" [height]="'305px'" [autoGenerate]="true"></igx-grid>
818
     * ```
819
     */
820
    @Output()
821
    public rowAdd = new EventEmitter<IRowDataCancelableEventArgs>();
3,996✔
822

823
    /**
824
     * Emitted after column is resized.
825
     *
826
     * @remarks
827
     * Returns the `IgxColumnComponent` object's old and new width.
828
     * @example
829
     * ```html
830
     * <igx-grid #grid [data]="localData" (columnResized)="resizing($event)" [autoGenerate]="true"></igx-grid>
831
     * ```
832
     */
833
    @Output()
834
    public columnResized = new EventEmitter<IColumnResizeEventArgs>();
3,996✔
835

836
    /**
837
     * Emitted when a cell is right clicked.
838
     *
839
     * @remarks
840
     * Returns the `IgxGridCell` object.
841
     * ```html
842
     * <igx-grid #grid [data]="localData" (contextMenu)="contextMenu($event)" [autoGenerate]="true"></igx-grid>
843
     * ```
844
     */
845
    @Output()
846
    public contextMenu = new EventEmitter<IGridCellEventArgs>();
3,996✔
847

848
    /**
849
     * Emitted when a cell is double clicked.
850
     *
851
     * @remarks
852
     * Returns the `IgxGridCell` object.
853
     * @example
854
     * ```html
855
     * <igx-grid #grid [data]="localData" (doubleClick)="dblClick($event)" [autoGenerate]="true"></igx-grid>
856
     * ```
857
     */
858
    @Output()
859
    public doubleClick = new EventEmitter<IGridCellEventArgs>();
3,996✔
860

861
    /**
862
     * Emitted before column visibility is changed.
863
     *
864
     * @remarks
865
     * Args: { column: any, newValue: boolean }
866
     * @example
867
     * ```html
868
     * <igx-grid (columnVisibilityChanging)="visibilityChanging($event)"></igx-grid>
869
     * ```
870
     */
871
    @Output()
872
    public columnVisibilityChanging = new EventEmitter<IColumnVisibilityChangingEventArgs>();
3,996✔
873

874
    /**
875
     * Emitted after column visibility is changed.
876
     *
877
     * @remarks
878
     * Args: { column: IgxColumnComponent, newValue: boolean }
879
     * @example
880
     * ```html
881
     * <igx-grid (columnVisibilityChanged)="visibilityChanged($event)"></igx-grid>
882
     * ```
883
     */
884
    @Output()
885
    public columnVisibilityChanged = new EventEmitter<IColumnVisibilityChangedEventArgs>();
3,996✔
886

887
    /**
888
     * Emitted when column moving starts.
889
     *
890
     * @remarks
891
     * Returns the moved `IgxColumnComponent` object.
892
     * @example
893
     * ```html
894
     * <igx-grid (columnMovingStart)="movingStart($event)"></igx-grid>
895
     * ```
896
     */
897
    @Output()
898
    public columnMovingStart = new EventEmitter<IColumnMovingStartEventArgs>();
3,996✔
899

900
    /**
901
     * Emitted during the column moving operation.
902
     *
903
     * @remarks
904
     * Returns the source and target `IgxColumnComponent` objects. This event is cancelable.
905
     * @example
906
     * ```html
907
     * <igx-grid (columnMoving)="moving($event)"></igx-grid>
908
     * ```
909
     */
910
    @Output()
911
    public columnMoving = new EventEmitter<IColumnMovingEventArgs>();
3,996✔
912

913
    /**
914
     * Emitted when column moving ends.
915
     *
916
     * @remarks
917
     * Returns the source and target `IgxColumnComponent` objects.
918
     * @example
919
     * ```html
920
     * <igx-grid (columnMovingEnd)="movingEnds($event)"></igx-grid>
921
     * ```
922
     */
923
    @Output()
924
    public columnMovingEnd = new EventEmitter<IColumnMovingEndEventArgs>();
3,996✔
925

926
    /**
927
     * Emitted when keydown is triggered over element inside grid's body.
928
     *
929
     * @remarks
930
     * This event is fired only if the key combination is supported in the grid.
931
     * Return the target type, target object and the original event. This event is cancelable.
932
     * @example
933
     * ```html
934
     *  <igx-grid (gridKeydown)="customKeydown($event)"></igx-grid>
935
     * ```
936
     */
937
    @Output()
938
    public gridKeydown = new EventEmitter<IGridKeydownEventArgs>();
3,996✔
939

940
    /**
941
     * Emitted when start dragging a row.
942
     *
943
     * @remarks
944
     * Return the dragged row.
945
     */
946
    @Output()
947
    public rowDragStart = new EventEmitter<IRowDragStartEventArgs>();
3,996✔
948

949
    /**
950
     * Emitted when dropping a row.
951
     *
952
     * @remarks
953
     * Return the dropped row.
954
     */
955
    @Output()
956
    public rowDragEnd = new EventEmitter<IRowDragEndEventArgs>();
3,996✔
957

958
    /**
959
     * Emitted when a copy operation is executed.
960
     *
961
     * @remarks
962
     * Fired only if copy behavior is enabled through the [`clipboardOptions`]{@link IgxGridBaseDirective#clipboardOptions}.
963
     */
964
    @Output()
965
    public gridCopy = new EventEmitter<IGridClipboardEvent>();
3,996✔
966

967
    /**
968
     * @hidden @internal
969
     */
970
    @Output()
971
    public expansionStatesChange = new EventEmitter<Map<any, boolean>>();
3,996✔
972

973
    /**
974
     * Emitted when the expanded state of a row gets changed.
975
     *
976
     * @example
977
     * ```html
978
     * <igx-grid [data]="employeeData" (rowToggle)="rowToggle($event)" [autoGenerate]="true"></igx-grid>
979
     * ```
980
     */
981
    @Output()
982
    public rowToggle = new EventEmitter<IRowToggleEventArgs>();
3,996✔
983

984
    /**
985
     * Emitted when the pinned state of a row is changed.
986
     *
987
     * @example
988
     * ```html
989
     * <igx-grid [data]="employeeData" (rowPinning)="rowPin($event)" [autoGenerate]="true"></igx-grid>
990
     * ```
991
     */
992
    @Output()
993
    public rowPinning = new EventEmitter<IPinRowEventArgs>();
3,996✔
994

995
    /**
996
     * Emitted when the pinned state of a row is changed.
997
     *
998
     * @example
999
     * ```html
1000
     * <igx-grid [data]="employeeData" (rowPinned)="rowPin($event)" [autoGenerate]="true"></igx-grid>
1001
     * ```
1002
     */
1003
    @Output()
1004
    public rowPinned = new EventEmitter<IPinRowEventArgs>();
3,996✔
1005

1006
    /**
1007
     * Emmited when the active node is changed.
1008
     *
1009
     * @example
1010
     * ```
1011
     * <igx-grid [data]="data" [autoGenerate]="true" (activeNodeChange)="activeNodeChange($event)"></igx-grid>
1012
     * ```
1013
     */
1014
    @Output()
1015
    public activeNodeChange = new EventEmitter<IActiveNodeChangeEventArgs>();
3,996✔
1016

1017
    /**
1018
     * Emitted before sorting is performed.
1019
     *
1020
     * @remarks
1021
     * Returns the sorting expressions.
1022
     * @example
1023
     * ```html
1024
     * <igx-grid #grid [data]="localData" [autoGenerate]="true" (sortingExpressionsChange)="sortingExprChange($event)"></igx-grid>
1025
     * ```
1026
     */
1027
    @Output()
1028
    public sortingExpressionsChange = new EventEmitter<ISortingExpression[]>();
3,996✔
1029

1030

1031
    /**
1032
     * Emitted when an export process is initiated by the user.
1033
     *
1034
     * @example
1035
     * ```typescript
1036
     * toolbarExporting(event: IGridToolbarExportEventArgs){
1037
     *     const toolbarExporting = event;
1038
     * }
1039
     * ```
1040
     */
1041
    @Output()
1042
    public toolbarExporting = new EventEmitter<IGridToolbarExportEventArgs>();
3,996✔
1043

1044
    /* End of toolbar related definitions */
1045

1046
    /**
1047
     * Emitted when making a range selection.
1048
     *
1049
     * @remarks
1050
     * Range selection can be made either through drag selection or through keyboard selection.
1051
     */
1052
    @Output()
1053
    public rangeSelected = new EventEmitter<GridSelectionRange>();
3,996✔
1054

1055
    /** Emitted after the ngAfterViewInit hook. At this point the grid exists in the DOM */
1056
    @Output()
1057
    public rendered = new EventEmitter<boolean>();
3,996✔
1058

1059
    /**
1060
     * @hidden @internal
1061
     */
1062
    @Output()
1063
    public localeChange = new EventEmitter<boolean>();
3,996✔
1064

1065
    /**
1066
     * Emitted before the grid's data view is changed because of a data operation, rebinding, etc.
1067
     *
1068
     * @example
1069
     * ```typescript
1070
     *  <igx-grid #grid [data]="localData" [autoGenerate]="true" (dataChanging)='handleDataChangingEvent()'></igx-grid>
1071
     * ```
1072
     */
1073
    @Output()
1074
    public dataChanging = new EventEmitter<IForOfDataChangingEventArgs>();
3,996✔
1075

1076
    /**
1077
     * Emitted after the grid's data view is changed because of a data operation, rebinding, etc.
1078
     *
1079
     * @example
1080
     * ```typescript
1081
     *  <igx-grid #grid [data]="localData" [autoGenerate]="true" (dataChanged)='handleDataChangedEvent()'></igx-grid>
1082
     * ```
1083
     */
1084
    @Output()
1085
    public dataChanged = new EventEmitter<any>();
3,996✔
1086

1087

1088
    /**
1089
     * @hidden @internal
1090
     */
1091
    @ViewChild(IgxSnackbarComponent)
1092
    public addRowSnackbar: IgxSnackbarComponent;
1093

1094
    /**
1095
     * @hidden @internal
1096
     */
1097
    @ViewChild(IgxGridColumnResizerComponent)
1098
    public resizeLine: IgxGridColumnResizerComponent;
1099

1100
    /**
1101
     * @hidden @internal
1102
     */
1103
    @ViewChild('loadingOverlay', { read: IgxToggleDirective, static: true })
1104
    public loadingOverlay: IgxToggleDirective;
1105

1106
    /**
1107
     * @hidden @internal
1108
     */
1109
    @ViewChild('igxLoadingOverlayOutlet', { read: IgxOverlayOutletDirective, static: true })
1110
    public loadingOutlet: IgxOverlayOutletDirective;
1111

1112
    /**
1113
     * @hidden @internal
1114
     */
1115
    @ContentChildren(IgxColumnComponent, { read: IgxColumnComponent, descendants: true })
1116
    public columnList: QueryList<IgxColumnComponent> = new QueryList<IgxColumnComponent>();
3,996✔
1117

1118
    /** @hidden @internal */
1119
    @ContentChild(IgxActionStripToken)
1120
    public actionStrip: IgxActionStripToken;
1121

1122
    /**
1123
     * @hidden @internal
1124
     */
1125
    @ContentChild(IgxExcelStyleLoadingValuesTemplateDirective, { read: IgxExcelStyleLoadingValuesTemplateDirective, static: true })
1126
    public excelStyleLoadingValuesTemplateDirective: IgxExcelStyleLoadingValuesTemplateDirective;
1127

1128
    /** @hidden @internal */
1129
    @ViewChild('emptyFilteredGrid', { read: TemplateRef, static: true })
1130
    public emptyFilteredGridTemplate: TemplateRef<any>;
1131

1132
    /** @hidden @internal */
1133
    @ViewChild('defaultEmptyGrid', { read: TemplateRef, static: true })
1134
    public emptyGridDefaultTemplate: TemplateRef<any>;
1135

1136
    /**
1137
     * @hidden @internal
1138
     */
1139
    @ViewChild('defaultLoadingGrid', { read: TemplateRef, static: true })
1140
    public loadingGridDefaultTemplate: TemplateRef<any>;
1141

1142
    /**
1143
     * @hidden @internal
1144
     */
1145
    @ViewChild('scrollContainer', { read: IgxGridForOfDirective, static: true })
1146
    public parentVirtDir: IgxGridForOfDirective<any, any[]>;
1147

1148
    /**
1149
     * @hidden
1150
     * @internal
1151
     */
1152
    @ContentChildren(IgxHeadSelectorDirective, { read: TemplateRef, descendants: false })
1153
    public headSelectorsTemplates: QueryList<TemplateRef<IgxHeadSelectorTemplateContext>>;
1154

1155
    /**
1156
     * @hidden
1157
     * @internal
1158
     */
1159
    @ContentChildren(IgxRowSelectorDirective, { read: TemplateRef, descendants: false })
1160
    public rowSelectorsTemplates: QueryList<TemplateRef<IgxRowSelectorTemplateContext>>;
1161

1162
    /**
1163
     * @hidden
1164
     * @internal
1165
     */
1166
    @ContentChildren(IgxRowDragGhostDirective, { read: TemplateRef, descendants: false })
1167
    public dragGhostCustomTemplates: QueryList<TemplateRef<IgxGridRowDragGhostContext>>;
1168

1169

1170
    /**
1171
     * Gets the custom template, if any, used for row drag ghost.
1172
     */
1173
    @Input()
1174
    public get dragGhostCustomTemplate() {
1175
        return this._dragGhostCustomTemplate || this.dragGhostCustomTemplates?.first;
1,993✔
1176
    }
1177

1178
    /**
1179
     * Sets a custom template for the row drag ghost.
1180
     *```html
1181
     * <ng-template #template igxRowDragGhost>
1182
     *    <igx-icon>menu</igx-icon>
1183
     * </ng-template>
1184
     * ```
1185
     * ```typescript
1186
     * @ViewChild("'template'", {read: TemplateRef })
1187
     * public template: TemplateRef<any>;
1188
     * this.grid.dragGhostCustomTemplate = this.template;
1189
     * ```
1190
     */
1191
    public set dragGhostCustomTemplate(template: TemplateRef<IgxGridRowDragGhostContext>) {
1192
        this._dragGhostCustomTemplate = template;
1✔
1193
    }
1194

1195

1196
    /**
1197
     * @hidden @internal
1198
     */
1199
    @ViewChild('verticalScrollContainer', { read: IgxGridForOfDirective, static: true })
1200
    public verticalScrollContainer: IgxGridForOfDirective<any, any[]>;
1201

1202
    /**
1203
     * @hidden @internal
1204
     */
1205
    @ViewChild('verticalScrollHolder', { read: IgxGridForOfDirective, static: true })
1206
    public verticalScroll: IgxGridForOfDirective<any, any[]>;
1207

1208
    /**
1209
     * @hidden @internal
1210
     */
1211
    @ViewChild('scr', { read: ElementRef, static: true })
1212
    public scr: ElementRef;
1213

1214
    /** @hidden @internal */
1215
    @ViewChild('headSelectorBaseTemplate', { read: TemplateRef, static: true })
1216
    public headerSelectorBaseTemplate: TemplateRef<any>;
1217

1218
    /**
1219
     * @hidden @internal
1220
     */
1221
    @ViewChild('footer', { read: ElementRef })
1222
    public footer: ElementRef;
1223

1224
    /** @hidden @internal */
1225
    public get headerContainer() {
1226
        return this.theadRow?.headerForOf;
14,745✔
1227
    }
1228

1229
    /** @hidden @internal */
1230
    public get headerSelectorContainer() {
1231
        return this.theadRow?.headerSelectorContainer;
29,908✔
1232
    }
1233

1234
    /** @hidden @internal */
1235
    public get headerDragContainer() {
1236
        return this.theadRow?.headerDragContainer;
475✔
1237
    }
1238

1239
    /** @hidden @internal */
1240
    public get headerGroupContainer() {
1241
        return this.theadRow?.headerGroupContainer;
29,133✔
1242
    }
1243

1244
    /** @hidden @internal */
1245
    public get filteringRow(): IgxGridFilteringRowComponent {
1246
        return this.theadRow?.filterRow;
1,286✔
1247
    }
1248

1249
    /** @hidden @internal */
1250
    @ViewChild(IgxGridHeaderRowComponent, { static: true })
1251
    public theadRow: IgxGridHeaderRowComponent;
1252

1253
    /** @hidden @internal */
1254
    @ViewChild(IgxGridGroupByAreaComponent)
1255
    public groupArea: IgxGridGroupByAreaComponent;
1256

1257
    /**
1258
     * @hidden @internal
1259
     */
1260
    @ViewChild('tbody', { static: true })
1261
    public tbody: ElementRef;
1262

1263
    /**
1264
     * @hidden @internal
1265
     */
1266
    @ViewChild('pinContainer', { read: ElementRef })
1267
    public pinContainer: ElementRef;
1268

1269
    /**
1270
     * @hidden @internal
1271
     */
1272
    @ViewChild('tfoot', { static: true })
1273
    public tfoot: ElementRef<HTMLElement>;
1274

1275
    /**
1276
     * @hidden @internal
1277
     */
1278
    @ViewChild('igxRowEditingOverlayOutlet', { read: IgxOverlayOutletDirective, static: true })
1279
    public rowEditingOutletDirective: IgxOverlayOutletDirective;
1280

1281
    /**
1282
     * @hidden @internal
1283
     */
1284
    @ViewChildren(IgxTemplateOutletDirective, { read: IgxTemplateOutletDirective })
1285
    public tmpOutlets: QueryList<any> = new QueryList<any>();
3,996✔
1286

1287
    /**
1288
     * @hidden
1289
     * @internal
1290
     */
1291
    @ViewChild('dragIndicatorIconBase', { read: TemplateRef, static: true })
1292
    public dragIndicatorIconBase: TemplateRef<any>;
1293

1294
    /**
1295
     * @hidden @internal
1296
     */
1297
    @ContentChildren(IgxRowEditTemplateDirective, { descendants: false, read: TemplateRef })
1298
    public rowEditCustomDirectives: QueryList<TemplateRef<IgxGridRowEditTemplateContext>>;
1299

1300
    /**
1301
     * @hidden @internal
1302
     */
1303
    @ContentChildren(IgxRowEditTextDirective, { descendants: false, read: TemplateRef })
1304
    public rowEditTextDirectives: QueryList<TemplateRef<IgxGridRowEditTextTemplateContext>>;
1305

1306
    /**
1307
     * Gets the row edit text template.
1308
     */
1309
    @Input()
1310
    public get rowEditTextTemplate(): TemplateRef<IgxGridRowEditTextTemplateContext> {
1311
        return this._rowEditTextTemplate || this.rowEditTextDirectives?.first;
5,422✔
1312
    }
1313
    /**
1314
     * Sets the row edit text template.
1315
     *```html
1316
     * <ng-template #template igxRowEditText let-rowChangesCount>
1317
     * Changes: {{rowChangesCount}}
1318
     * </ng-template>
1319
     * ```
1320
     *```typescript
1321
     * @ViewChild('template', {read: TemplateRef })
1322
     * public template: TemplateRef<any>;
1323
     * this.grid.rowEditTextTemplate = this.template;
1324
     * ```
1325
     */
1326
    public set rowEditTextTemplate(template: TemplateRef<IgxGridRowEditTextTemplateContext>) {
1327
        this._rowEditTextTemplate = template;
1✔
1328
    }
1329

1330
    /**
1331
     * @hidden @internal
1332
     */
1333
    @ContentChild(IgxRowAddTextDirective, { read: TemplateRef })
1334
    public rowAddText: TemplateRef<IgxGridEmptyTemplateContext>;
1335

1336
    /**
1337
     * Gets the row add text template.
1338
     */
1339
    @Input()
1340
    public get rowAddTextTemplate(): TemplateRef<IgxGridEmptyTemplateContext> {
1341
        return this._rowAddTextTemplate || this.rowAddText;
365✔
1342
    }
1343
    /**
1344
     * Sets the row add text template.
1345
     *```html
1346
     * <ng-template #template igxRowAddText>
1347
     * Adding Row
1348
     * </ng-template>
1349
     * ```
1350
     *```typescript
1351
     * @ViewChild('template', {read: TemplateRef })
1352
     * public template: TemplateRef<any>;
1353
     * this.grid.rowAddTextTemplate = this.template;
1354
     * ```
1355
     */
1356
    public set rowAddTextTemplate(template: TemplateRef<IgxGridEmptyTemplateContext>) {
1357
        this._rowAddTextTemplate = template;
1✔
1358
    }
1359

1360
    /**
1361
     * @hidden @internal
1362
     */
1363
    @ContentChildren(IgxRowEditActionsDirective, { descendants: false, read: TemplateRef })
1364
    public rowEditActionsDirectives: QueryList<TemplateRef<IgxGridRowEditActionsTemplateContext>>;
1365

1366
    /**
1367
     * Gets the row edit actions template.
1368
     */
1369
    @Input()
1370
    public get rowEditActionsTemplate(): TemplateRef<IgxGridRowEditActionsTemplateContext> {
1371
        return this._rowEditActionsTemplate || this.rowEditActionsDirectives?.first;
5,791✔
1372
    }
1373
    /**
1374
     * Sets the row edit actions template.
1375
     *```html
1376
     * <ng-template #template igxRowEditActions let-endRowEdit>
1377
     *     <button type="button" igxButton igxRowEditTabStop (click)="endRowEdit(false)">Cancel</button>
1378
     *     <button type="button" igxButton igxRowEditTabStop (click)="endRowEdit(true)">Apply</button>
1379
     * </ng-template>
1380
     * ```
1381
     *```typescript
1382
     * @ViewChild('template', {read: TemplateRef })
1383
     * public template: TemplateRef<any>;
1384
     * this.grid.rowEditActionsTemplate = this.template;
1385
     * ```
1386
     */
1387
    public set rowEditActionsTemplate(template: TemplateRef<IgxGridRowEditActionsTemplateContext>) {
1388
        this._rowEditActionsTemplate = template;
1✔
1389
    }
1390

1391
    /**
1392
     * The custom template, if any, that should be used when rendering a row expand indicator.
1393
     */
1394
    @ContentChild(IgxRowExpandedIndicatorDirective, { read: TemplateRef })
1395
    protected rowExpandedIndicatorDirectiveTemplate: TemplateRef<IgxGridRowTemplateContext> = null;
3,996✔
1396

1397
    /**
1398
     * Gets the row expand indicator template.
1399
    */
1400
    @Input()
1401
    public get rowExpandedIndicatorTemplate(): TemplateRef<IgxGridRowTemplateContext> {
1402
        return this._rowExpandedIndicatorTemplate || this.rowExpandedIndicatorDirectiveTemplate;
12,040✔
1403
    }
1404

1405
    /**
1406
     * Sets the row expand indicator template.
1407
     *```html
1408
     *<ng-template igxRowExpandedIndicator>
1409
     *  <igx-icon role="button">remove</igx-icon>
1410
     *</ng-template>
1411
     * ```
1412
     *```typescript
1413
     * @ViewChild('template', {read: TemplateRef })
1414
     * public template: TemplateRef<any>;
1415
     * this.grid.rowExpandedIndicatorTemplate = this.template;
1416
     * ```
1417
    */
1418
    public set rowExpandedIndicatorTemplate(template: TemplateRef<IgxGridRowTemplateContext>) {
1419
        this._rowExpandedIndicatorTemplate = template;
855✔
1420
    }
1421

1422
    /**
1423
     * The custom template, if any, that should be used when rendering a row collapse indicator.
1424
     */
1425
    @ContentChild(IgxRowCollapsedIndicatorDirective, { read: TemplateRef })
1426
    protected rowCollapsedIndicatorDirectiveTemplate: TemplateRef<IgxGridRowTemplateContext> = null;
3,996✔
1427

1428
    /**
1429
     * Gets the row collapse indicator template.
1430
    */
1431
    @Input()
1432
    public get rowCollapsedIndicatorTemplate(): TemplateRef<IgxGridRowTemplateContext> {
1433
        return this._rowCollapsedIndicatorTemplate || this.rowCollapsedIndicatorDirectiveTemplate;
49,507✔
1434
    }
1435

1436
    /**
1437
     * Sets the row collapse indicator template.
1438
     *```html
1439
     *<ng-template igxRowCollapsedIndicator>
1440
     *  <igx-icon role="button">add</igx-icon>
1441
     *</ng-template>
1442
     * ```
1443
     *```typescript
1444
     * @ViewChild('template', {read: TemplateRef })
1445
     * public template: TemplateRef<any>;
1446
     * this.grid.rowCollapsedIndicatorTemplate = this.template;
1447
     * ```
1448
    */
1449
    public set rowCollapsedIndicatorTemplate(template: TemplateRef<IgxGridRowTemplateContext>) {
1450
        this._rowCollapsedIndicatorTemplate = template;
855✔
1451
    }
1452

1453
    /**
1454
     * The custom template, if any, that should be used when rendering a header expand indicator.
1455
     */
1456
    @ContentChild(IgxHeaderExpandedIndicatorDirective, { read: TemplateRef })
1457
    protected headerExpandedIndicatorDirectiveTemplate: TemplateRef<IgxGridTemplateContext> = null;
3,996✔
1458

1459
    /**
1460
     * Gets the header expand indicator template.
1461
    */
1462
    @Input()
1463
    public get headerExpandedIndicatorTemplate(): TemplateRef<IgxGridTemplateContext> {
1464
        return this._headerExpandIndicatorTemplate || this.headerExpandedIndicatorDirectiveTemplate;
13,305✔
1465
    }
1466

1467
    /**
1468
     * Sets the header expand indicator template.
1469
     *```html
1470
     *<ng-template igxHeaderExpandedIndicator>
1471
     *  <igx-icon role="button">remove</igx-icon>
1472
     *</ng-template>
1473
     * ```
1474
     *```typescript
1475
     * @ViewChild('template', {read: TemplateRef })
1476
     * public template: TemplateRef<any>;
1477
     * this.grid.headerExpandedIndicatorTemplate = this.template;
1478
     * ```
1479
    */
1480
    public set headerExpandedIndicatorTemplate(template: TemplateRef<IgxGridTemplateContext>) {
1481
        this._headerExpandIndicatorTemplate = template;
855✔
1482
    }
1483

1484
    /**
1485
     * The custom template, if any, that should be used when rendering a header collapse indicator.
1486
     */
1487
    @ContentChild(IgxHeaderCollapsedIndicatorDirective, { read: TemplateRef })
1488
    protected headerCollapsedIndicatorDirectiveTemplate: TemplateRef<IgxGridTemplateContext> = null;
3,996✔
1489

1490
    /**
1491
     * Gets the row collapse indicator template.
1492
    */
1493
    @Input()
1494
    public get headerCollapsedIndicatorTemplate(): TemplateRef<IgxGridTemplateContext> {
1495
        return this._headerCollapseIndicatorTemplate || this.headerCollapsedIndicatorDirectiveTemplate;
999✔
1496
    }
1497

1498
    /**
1499
     * Sets the row collapse indicator template.
1500
     *```html
1501
     *<ng-template igxHeaderCollapsedIndicator>
1502
     *  <igx-icon role="button">add</igx-icon>
1503
     *</ng-template>
1504
     * ```
1505
     *```typescript
1506
     * @ViewChild('template', {read: TemplateRef })
1507
     * public template: TemplateRef<any>;
1508
     * this.grid.headerCollapsedIndicatorTemplate = this.template;
1509
     * ```
1510
    */
1511
    public set headerCollapsedIndicatorTemplate(template: TemplateRef<IgxGridTemplateContext>) {
1512
        this._headerCollapseIndicatorTemplate = template;
855✔
1513
    }
1514

1515
    /** @hidden @internal */
1516
    @ContentChild(IgxExcelStyleHeaderIconDirective, { read: TemplateRef })
1517
    public excelStyleHeaderIconDirectiveTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
1518

1519
    /**
1520
     * Gets the excel style header icon.
1521
    */
1522
    @Input()
1523
    public get excelStyleHeaderIconTemplate(): TemplateRef<IgxGridHeaderTemplateContext> {
1524
        return this._excelStyleHeaderIconTemplate || this.excelStyleHeaderIconDirectiveTemplate;
10,425✔
1525
    }
1526

1527
    /**
1528
     * Sets the excel style header icon.
1529
     *```html
1530
     *<ng-template #template igxExcelStyleHeaderIcon>
1531
     * <igx-icon>filter_alt</igx-icon>
1532
     *</ng-template>
1533
     * ```
1534
     *```typescript
1535
     * @ViewChild('template', {read: TemplateRef })
1536
     * public template: TemplateRef<any>;
1537
     * this.grid.excelStyleHeaderIconTemplate = this.template;
1538
     * ```
1539
    */
1540
    public set excelStyleHeaderIconTemplate(template: TemplateRef<IgxGridHeaderTemplateContext>) {
1541
        this._excelStyleHeaderIconTemplate = template;
856✔
1542
    }
1543

1544

1545
    /**
1546
     * @hidden
1547
     * @internal
1548
     */
1549
    @ContentChild(IgxSortAscendingHeaderIconDirective, { read: TemplateRef })
1550
    public sortAscendingHeaderIconDirectiveTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
1551

1552
    /**
1553
     * The custom template, if any, that should be used when rendering a header sorting indicator when columns are sorted in asc order.
1554
     */
1555
    @Input()
1556
    public get sortAscendingHeaderIconTemplate(): TemplateRef<IgxGridHeaderTemplateContext> {
1557
        return this._sortAscendingHeaderIconTemplate;
1,007✔
1558
    }
1559

1560
    /**
1561
     * Sets a custom template that should be used when rendering a header sorting indicator when columns are sorted in asc order.
1562
     *```html
1563
     * <ng-template #template igxSortAscendingHeaderIcon>
1564
     *    <igx-icon>expand_less</igx-icon>
1565
     * </ng-template>
1566
     * ```
1567
     * ```typescript
1568
     * @ViewChild("'template'", {read: TemplateRef })
1569
     * public template: TemplateRef<any>;
1570
     * this.grid.sortAscendingHeaderIconTemplate = this.template;
1571
     * ```
1572
     */
1573
    public set sortAscendingHeaderIconTemplate(template: TemplateRef<IgxGridHeaderTemplateContext>) {
1574
        this._sortAscendingHeaderIconTemplate = template;
861✔
1575
    }
1576

1577
    /** @hidden @internal */
1578
    @ContentChild(IgxSortDescendingHeaderIconDirective, { read: TemplateRef })
1579
    public sortDescendingHeaderIconDirectiveTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
1580

1581
    /**
1582
     * The custom template, if any, that should be used when rendering a header sorting indicator when columns are sorted in desc order.
1583
     */
1584
    @Input()
1585
    public get sortDescendingHeaderIconTemplate() {
1586
        return this._sortDescendingHeaderIconTemplate;
913✔
1587
    }
1588

1589
    /**
1590
     * Sets a custom template that should be used when rendering a header sorting indicator when columns are sorted in desc order.
1591
     *```html
1592
     * <ng-template #template igxSortDescendingHeaderIcon>
1593
     *    <igx-icon>expand_more</igx-icon>
1594
     * </ng-template>
1595
     * ```
1596
     * ```typescript
1597
     * @ViewChild("'template'", {read: TemplateRef })
1598
     * public template: TemplateRef<any>;
1599
     * this.grid.sortDescendingHeaderIconTemplate = this.template;
1600
     * ```
1601
     */
1602
    public set sortDescendingHeaderIconTemplate(template: TemplateRef<IgxGridHeaderTemplateContext>) {
1603
        this._sortDescendingHeaderIconTemplate = template;
861✔
1604
    }
1605

1606
    /**
1607
     * @hidden
1608
     * @internal
1609
     */
1610
    @ContentChild(IgxSortHeaderIconDirective, { read: TemplateRef })
1611
    public sortHeaderIconDirectiveTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
1612

1613
    /**
1614
     * Gets custom template, if any, that should be used when rendering a header sorting indicator when columns are not sorted.
1615
     */
1616
    @Input()
1617
    public get sortHeaderIconTemplate(): TemplateRef<IgxGridHeaderTemplateContext> {
1618
        return this._sortHeaderIconTemplate;
26,874✔
1619
    }
1620

1621
    /**
1622
     * Sets a custom template that should be used when rendering a header sorting indicator when columns are not sorted.
1623
     *```html
1624
     * <ng-template #template igxSortHeaderIcon>
1625
     *    <igx-icon>unfold_more</igx-icon>
1626
     * </ng-template>
1627
     * ```
1628
     * ```typescript
1629
     * @ViewChild("'template'", {read: TemplateRef })
1630
     * public template: TemplateRef<any>;
1631
     * this.grid.sortHeaderIconTemplate = this.template;
1632
     * ```
1633
     */
1634
    public set sortHeaderIconTemplate(template: TemplateRef<IgxGridHeaderTemplateContext>) {
1635
        this._sortHeaderIconTemplate = template;
861✔
1636
    }
1637

1638
    /**
1639
     * @hidden
1640
     * @internal
1641
     */
1642
    @ContentChildren(IgxDragIndicatorIconDirective, { read: TemplateRef, descendants: false })
1643
    public dragIndicatorIconTemplates: QueryList<TemplateRef<IgxGridEmptyTemplateContext>>;
1644

1645
    /**
1646
     * @hidden @internal
1647
     */
1648
    @ViewChildren(IgxRowEditTabStopDirective)
1649
    public rowEditTabsDEFAULT: QueryList<IgxRowEditTabStopDirective>;
1650

1651
    /**
1652
     * @hidden @internal
1653
     */
1654
    @ContentChildren(IgxRowEditTabStopDirective, { descendants: true })
1655
    public rowEditTabsCUSTOM: QueryList<IgxRowEditTabStopDirective>;
1656

1657
    /**
1658
     * @hidden @internal
1659
     */
1660
    @ViewChild('rowEditingOverlay', { read: IgxToggleDirective })
1661
    public rowEditingOverlay: IgxToggleDirective;
1662

1663
    /**
1664
     * @hidden @internal
1665
     */
1666
    @HostBinding('attr.tabindex')
1667
    public tabindex = 0;
3,996✔
1668

1669
    /**
1670
     * @hidden @internal
1671
     */
1672
    @HostBinding('attr.role')
1673
    public hostRole = 'grid';
3,996✔
1674

1675
    /** @hidden @internal */
1676
    @ContentChildren(IgxToolbarToken)
1677
    public toolbar: QueryList<IgxGridToolbarComponent>;
1678

1679
    /** @hidden @internal */
1680
    @ContentChildren(IgxPaginatorToken)
1681
    protected paginationComponents: QueryList<IgxPaginatorComponent>;
1682

1683
    /**
1684
     * @hidden @internal
1685
     */
1686
    @ViewChild('igxFilteringOverlayOutlet', { read: IgxOverlayOutletDirective, static: true })
1687
    protected _outletDirective: IgxOverlayOutletDirective;
1688

1689
    /**
1690
     * @hidden @internal
1691
     */
1692
    @ViewChild('defaultExpandedTemplate', { read: TemplateRef, static: true })
1693
    protected defaultExpandedTemplate: TemplateRef<any>;
1694

1695
    /**
1696
     * @hidden @internal
1697
     */
1698
    @ViewChild('defaultCollapsedTemplate', { read: TemplateRef, static: true })
1699
    protected defaultCollapsedTemplate: TemplateRef<any>;
1700

1701
    /**
1702
     * @hidden @internal
1703
     */
1704
    @ViewChild('defaultESFHeaderIcon', { read: TemplateRef, static: true })
1705
    protected defaultESFHeaderIconTemplate: TemplateRef<any>;
1706

1707
    @ViewChildren('summaryRow', { read: IgxSummaryRowComponent })
1708
    protected _summaryRowList: QueryList<IgxSummaryRowComponent>;
1709

1710
    @ViewChildren('row')
1711
    private _rowList: QueryList<IgxGridRowComponent>;
1712

1713
    @ViewChildren('pinnedRow')
1714
    private _pinnedRowList: QueryList<IgxGridRowComponent>;
1715

1716
    /**
1717
     * @hidden @internal
1718
     */
1719
    @ViewChild('defaultRowEditTemplate', { read: TemplateRef, static: true })
1720
    private defaultRowEditTemplate: TemplateRef<IgxGridRowEditTemplateContext>;
1721

1722
    @ViewChildren(IgxRowDirective, { read: IgxRowDirective })
1723
    private _dataRowList: QueryList<IgxRowDirective>;
1724

1725
    @HostBinding('class.igx-grid')
1726
    protected baseClass = 'igx-grid';
3,996✔
1727

1728
    @HostBinding('style.--component-size')
1729
    protected get hostStyles(): string {
1730
        return this.getComponentSizeStyles();
32,031✔
1731
    }
1732

1733
    /**
1734
     * Gets/Sets the resource strings.
1735
     *
1736
     * @remarks
1737
     * By default it uses EN resources.
1738
     */
1739
    @Input()
1740
    public set resourceStrings(value: IGridResourceStrings) {
1741
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
2✔
1742
    }
1743

1744
    public get resourceStrings(): IGridResourceStrings {
1745
        return this._resourceStrings;
220,482✔
1746
    }
1747

1748
    /**
1749
     * Gets/Sets the filtering logic of the `IgxGridComponent`.
1750
     *
1751
     * @remarks
1752
     * The default is AND.
1753
     * @example
1754
     * ```html
1755
     * <igx-grid [data]="Data" [autoGenerate]="true" [filteringLogic]="filtering"></igx-grid>
1756
     * ```
1757
     */
1758
    @WatchChanges()
1759
    @Input()
1760
    public get filteringLogic() {
1761
        return this._filteringExpressionsTree.operator;
317✔
1762
    }
1763

1764
    public set filteringLogic(value: FilteringLogic) {
1765
        this._filteringExpressionsTree.operator = value;
3✔
1766
    }
1767

1768
    /**
1769
     * Gets/Sets the filtering state.
1770
     *
1771
     * @example
1772
     * ```html
1773
     * <igx-grid #grid [data]="Data" [autoGenerate]="true" [(filteringExpressionsTree)]="model.filteringExpressions"></igx-grid>
1774
     * ```
1775
     * @remarks
1776
     * Supports two-way binding.
1777
     */
1778
    @WatchChanges()
1779
    @Input()
1780
    public get filteringExpressionsTree() {
1781
        return this._filteringExpressionsTree;
117,510✔
1782
    }
1783

1784
    public set filteringExpressionsTree(value) {
1785
        if (value && value instanceof FilteringExpressionsTree) {
896✔
1786
            const val = (value as FilteringExpressionsTree);
896✔
1787
            for (let index = 0; index < val.filteringOperands.length; index++) {
896✔
1788
                if (!(val.filteringOperands[index] instanceof FilteringExpressionsTree)) {
475✔
1789
                    const newExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, val.filteringOperands[index].fieldName);
7✔
1790
                    newExpressionsTree.filteringOperands.push(val.filteringOperands[index] as IFilteringExpression);
7✔
1791
                    val.filteringOperands[index] = newExpressionsTree;
7✔
1792
                }
1793
            }
1794

1795
            value.type = FilteringExpressionsTreeType.Regular;
896✔
1796
            this._filteringExpressionsTree = value;
896✔
1797
            this.filteringPipeTrigger++;
896✔
1798
            this.filteringExpressionsTreeChange.emit(this._filteringExpressionsTree);
896✔
1799

1800
            if (this.filteringService.isFilteringExpressionsTreeEmpty(this._filteringExpressionsTree) &&
896✔
1801
                this.filteringService.isFilteringExpressionsTreeEmpty(this._advancedFilteringExpressionsTree)) {
1802
                this._filteredData = null;
442✔
1803
            }
1804

1805
            this.filteringService.refreshExpressions();
896✔
1806
            this.selectionService.clearHeaderCBState();
896✔
1807
            this.summaryService.clearSummaryCache();
896✔
1808
            this.notifyChanges();
896✔
1809
        }
1810
    }
1811

1812
    /**
1813
     * Gets/Sets the advanced filtering state.
1814
     *
1815
     * @example
1816
     * ```typescript
1817
     * let advancedFilteringExpressionsTree = this.grid.advancedFilteringExpressionsTree;
1818
     * this.grid.advancedFilteringExpressionsTree = logic;
1819
     * ```
1820
     */
1821
    @WatchChanges()
1822
    @Input()
1823
    public get advancedFilteringExpressionsTree() {
1824
        return this._advancedFilteringExpressionsTree;
65,971✔
1825
    }
1826

1827
    public set advancedFilteringExpressionsTree(value) {
1828
        const filteringEventArgs: IFilteringEventArgs = {
104✔
1829
            owner: this,
1830
            filteringExpressions: value,
1831
            cancel: false
1832
        };
1833

1834
        this.filtering.emit(filteringEventArgs);
104✔
1835

1836
        if (filteringEventArgs.cancel) {
104✔
1837
            return;
1✔
1838
        }
1839

1840
        if (value && value instanceof FilteringExpressionsTree) {
103✔
1841
            value.type = FilteringExpressionsTreeType.Advanced;
85✔
1842
            this._advancedFilteringExpressionsTree = value;
85✔
1843
            this.filteringPipeTrigger++;
85✔
1844
        } else {
1845
            this._advancedFilteringExpressionsTree = null;
18✔
1846
        }
1847
        this.advancedFilteringExpressionsTreeChange.emit(this._advancedFilteringExpressionsTree);
103✔
1848

1849
        if (this.filteringService.isFilteringExpressionsTreeEmpty(this._filteringExpressionsTree) &&
103✔
1850
            this.filteringService.isFilteringExpressionsTreeEmpty(this._advancedFilteringExpressionsTree)) {
1851
            this._filteredData = null;
20✔
1852
        }
1853

1854
        this.selectionService.clearHeaderCBState();
103✔
1855
        this.summaryService.clearSummaryCache();
103✔
1856
        this.notifyChanges();
103✔
1857

1858
        // Wait for the change detection to update filtered data through the pipes and then emit the event.
1859
        requestAnimationFrame(() => this.filteringDone.emit(this._advancedFilteringExpressionsTree));
103✔
1860
    }
1861

1862
    /**
1863
     * Gets/Sets the locale.
1864
     *
1865
     * @remarks
1866
     * If not set, returns browser's language.
1867
     */
1868
    @Input()
1869
    public get locale(): string {
1870
        return this._locale;
1,420,063✔
1871
    }
1872

1873
    public set locale(value: string) {
1874
        if (value !== this._locale) {
4,011✔
1875
            this._locale = value;
4,006✔
1876
            this._currencyPositionLeft = undefined;
4,006✔
1877
            this.summaryService.clearSummaryCache();
4,006✔
1878
            this.pipeTrigger++;
4,006✔
1879
            this.notifyChanges();
4,006✔
1880
            this.localeChange.emit();
4,006✔
1881
        }
1882
    }
1883

1884
    @Input()
1885
    public get pagingMode() {
1886
        return this._pagingMode;
8,421✔
1887
    }
1888

1889
    public set pagingMode(val: GridPagingMode) {
1890
        this._pagingMode = val;
2✔
1891
        this.pipeTrigger++;
2✔
1892
        this.notifyChanges(true);
2✔
1893
    }
1894

1895
    /** @hidden @internal */
1896
    public get page(): number {
1897
        return this.paginator?.page || 0;
2,041,020✔
1898
    }
1899

1900
    public set page(val: number) {
1901
        if (this.paginator) {
713✔
1902
            this.paginator.page = val;
330✔
1903
        }
1904
    }
1905

1906
    /** @hidden @internal */
1907
    public get perPage(): number {
1908
        return this.paginator?.perPage || DEFAULT_ITEMS_PER_PAGE;
2,040,933✔
1909
    }
1910

1911
    public set perPage(val: number) {
1912
        if (this.paginator) {
20✔
1913
            this.paginator.perPage = val;
20✔
1914
        }
1915
    }
1916

1917
    /**
1918
     * Gets/Sets if the row selectors are hidden.
1919
     *
1920
     * @remarks
1921
     *  By default row selectors are shown
1922
     */
1923
    @WatchChanges()
1924
    @Input({ transform: booleanAttribute })
1925
    public get hideRowSelectors() {
1926
        return this._hideRowSelectors;
31,236✔
1927
    }
1928

1929
    public set hideRowSelectors(value: boolean) {
1930
        this._hideRowSelectors = value;
21✔
1931
        this.notifyChanges(true);
21✔
1932
    }
1933

1934
    /**
1935
     * Gets/Sets whether rows can be moved.
1936
     *
1937
     * @example
1938
     * ```html
1939
     * <igx-grid #grid [rowDraggable]="true"></igx-grid>
1940
     * ```
1941
     */
1942
    @Input({ transform: booleanAttribute })
1943
    public get rowDraggable(): boolean {
1944
        return this._rowDrag && this.hasVisibleColumns;
309,463✔
1945
    }
1946

1947
    public set rowDraggable(val: boolean) {
1948
        this._rowDrag = val;
47✔
1949
        this.notifyChanges(true);
47✔
1950
    }
1951

1952
    /**
1953
     * Gets/Sets the trigger for validators used when editing the grid.
1954
     *
1955
     * @example
1956
     * ```html
1957
     * <igx-grid #grid validationTrigger='blur'></igx-grid>
1958
     * ```
1959
     */
1960
    @Input()
1961
    public validationTrigger: GridValidationTrigger = 'change';
3,996✔
1962

1963
    /**
1964
     * @hidden
1965
     * @internal
1966
     */
1967
    public rowDragging = false;
3,996✔
1968

1969
    /** @hidden @internal */
1970
    public dragRowID = null;
3,996✔
1971

1972
    /**
1973
     * Gets/Sets whether the rows are editable.
1974
     *
1975
     * @remarks
1976
     * By default it is set to false.
1977
     * @example
1978
     * ```html
1979
     * <igx-grid #grid [rowEditable]="true" [primaryKey]="'ProductID'" ></igx-grid>
1980
     * ```
1981
     */
1982
    @WatchChanges()
1983
    @Input({ transform: booleanAttribute })
1984
    public get rowEditable(): boolean {
1985
        return this._rowEditable;
12,278,803✔
1986
    }
1987

1988
    public set rowEditable(val: boolean) {
1989
        if (!this._init) {
528✔
1990
            this.refreshGridState();
34✔
1991
        }
1992
        this._rowEditable = val;
528✔
1993
        this.notifyChanges();
528✔
1994
    }
1995

1996
    /**
1997
     * Gets/Sets the height.
1998
     *
1999
     * @example
2000
     * ```html
2001
     * <igx-grid #grid [data]="Data" [height]="'305px'" [autoGenerate]="true"></igx-grid>
2002
     * ```
2003
     */
2004
    @WatchChanges()
2005
    @HostBinding('style.height')
2006
    @Input()
2007
    public get height(): string | null {
2008
        return this._height;
35,481✔
2009
    }
2010

2011
    public set height(value: string | null) {
2012
        if (this._height !== value) {
3,020✔
2013
            this._height = value;
3,018✔
2014
            this.nativeElement.style.height = value;
3,018✔
2015
            this.notifyChanges(true);
3,018✔
2016
        }
2017
    }
2018

2019
    /**
2020
     * @hidden @internal
2021
     */
2022
    @HostBinding('style.width')
2023
    public get hostWidth() {
2024
        return this._width || this._hostWidth;
32,037✔
2025
    }
2026

2027
    /**
2028
     * Gets/Sets the width of the grid.
2029
     *
2030
     * @example
2031
     * ```typescript
2032
     * let gridWidth = this.grid.width;
2033
     * ```
2034
     */
2035
    @WatchChanges()
2036
    @Input()
2037
    public get width(): string | null {
2038
        return this._width;
140,001✔
2039
    }
2040

2041
    public set width(value: string | null) {
2042
        if (this._width !== value) {
2,029✔
2043
            this._width = value;
2,029✔
2044
            this.nativeElement.style.width = value;
2,029✔
2045
            this.notifyChanges(true);
2,029✔
2046
        }
2047
    }
2048

2049
    /** @hidden @internal */
2050
    public get headerWidth() {
2051
        return parseInt(this.width, 10) - 17;
×
2052
    }
2053

2054
    /**
2055
     * Gets/Sets the row height.
2056
     *
2057
     * @example
2058
     * ```html
2059
     * <igx-grid #grid [data]="localData" [rowHeight]="100" [autoGenerate]="true"></igx-grid>
2060
     * ```
2061
     */
2062
    @WatchChanges()
2063
    @Input()
2064
    public get rowHeight(): number {
2065
        return this._rowHeight ? this._rowHeight : this.defaultRowHeight;
1,415,267✔
2066
    }
2067

2068
    public set rowHeight(value: number | string) {
2069
        if (typeof value !== 'number') {
1✔
2070
            value = parseInt(value, 10);
1✔
2071
        }
2072
        this._rowHeight = value;
1✔
2073
    }
2074

2075
    /**
2076
     * Gets/Sets the default width of the columns.
2077
     *
2078
     * @example
2079
     * ```html
2080
     * <igx-grid #grid [data]="localData" [columnWidth]="100" [autoGenerate]="true"></igx-grid>
2081
     * ```
2082
     */
2083
    @WatchChanges()
2084
    @Input()
2085
    public get columnWidth(): string {
2086
        return this._columnWidth;
610✔
2087
    }
2088
    public set columnWidth(value: string) {
2089
        this._columnWidth = value;
230✔
2090
        this.columnWidthSetByUser = true;
230✔
2091
        this.notifyChanges(true);
230✔
2092
    }
2093

2094
    /**
2095
     * Get/Sets the message displayed when there are no records.
2096
     *
2097
     * @example
2098
     * ```html
2099
     * <igx-grid #grid [data]="Data" [emptyGridMessage]="'The grid is empty'" [autoGenerate]="true"></igx-grid>
2100
     * ```
2101
     */
2102
    @Input()
2103
    public set emptyGridMessage(value: string) {
2104
        this._emptyGridMessage = value;
×
2105
    }
2106
    public get emptyGridMessage(): string {
2107
        return this._emptyGridMessage || this.resourceStrings.igx_grid_emptyGrid_message;
683✔
2108
    }
2109

2110
    /**
2111
     * Gets/Sets whether the grid is going to show a loading indicator.
2112
     *
2113
     * @example
2114
     * ```html
2115
     * <igx-grid #grid [data]="Data" [isLoading]="true" [autoGenerate]="true"></igx-grid>
2116
     * ```
2117
     */
2118
    @WatchChanges()
2119
    @Input({ transform: booleanAttribute })
2120
    public set isLoading(value: boolean) {
2121
        if (this._isLoading !== value) {
13✔
2122
            this._isLoading = value;
13✔
2123
            if (this.data) {
13✔
2124
                this.evaluateLoadingState();
7✔
2125
            }
2126
        }
2127
        Promise.resolve().then(() => {
13✔
2128
            // wait for the current detection cycle to end before triggering a new one.
2129
            this.notifyChanges();
13✔
2130
        });
2131
    }
2132

2133
    public get isLoading(): boolean {
2134
        return this._isLoading;
89,502✔
2135
    }
2136

2137
    /**
2138
     * Gets/Sets whether the columns should be auto-generated once again after the initialization of the grid
2139
     *
2140
     * @remarks
2141
     * This will allow to bind the grid to remote data and having auto-generated columns at the same time.
2142
     * Note that after generating the columns, this property would be disabled to avoid re-creating
2143
     * columns each time a new data is assigned.
2144
     * @example
2145
     * ```typescript
2146
     *  this.grid.shouldGenerate = true;
2147
     * ```
2148
     */
2149
    public shouldGenerate: boolean;
2150

2151
    /**
2152
     * Gets/Sets the message displayed when there are no records and the grid is filtered.
2153
     *
2154
     * @example
2155
     * ```html
2156
     * <igx-grid #grid [data]="Data" [emptyGridMessage]="'The grid is empty'" [autoGenerate]="true"></igx-grid>
2157
     * ```
2158
     */
2159
    @Input()
2160
    public set emptyFilteredGridMessage(value: string) {
2161
        this._emptyFilteredGridMessage = value;
×
2162
    }
2163

2164
    public get emptyFilteredGridMessage(): string {
2165
        return this._emptyFilteredGridMessage || this.resourceStrings.igx_grid_emptyFilteredGrid_message;
228✔
2166
    }
2167

2168
    /**
2169
     * Gets/Sets the initial pinning configuration.
2170
     *
2171
     * @remarks
2172
     * Allows to apply pinning the columns to the start or the end.
2173
     * Note that pinning to both sides at a time is not allowed.
2174
     * @example
2175
     * ```html
2176
     * <igx-grid [pinning]="pinningConfig"></igx-grid>
2177
     * ```
2178
     */
2179
    @Input()
2180
    public get pinning() {
2181
        return this._pinning;
2,705,417✔
2182
    }
2183
    public set pinning(value) {
2184
        if (value !== this._pinning) {
125✔
2185
            this.resetCaches();
125✔
2186
        }
2187
        this._pinning = value;
125✔
2188
    }
2189

2190
    /**
2191
     * Gets/Sets if the filtering is enabled.
2192
     *
2193
     * @example
2194
     * ```html
2195
     * <igx-grid #grid [data]="localData" [allowFiltering]="true" [height]="'305px'" [autoGenerate]="true"></igx-grid>
2196
     * ```
2197
     */
2198
    @Input({ transform: booleanAttribute })
2199
    public get allowFiltering() {
2200
        return this._allowFiltering;
385,081✔
2201
    }
2202

2203
    public set allowFiltering(value) {
2204
        if (this._allowFiltering !== value) {
714✔
2205
            this._allowFiltering = value;
703✔
2206
            this.filteringService.registerSVGIcons();
703✔
2207

2208
            if (!this._init) {
703✔
2209
                this.calcGridHeadRow();
18✔
2210
            }
2211

2212
            this.filteringService.isFilterRowVisible = false;
703✔
2213
            this.filteringService.filteredColumn = null;
703✔
2214

2215
            this.notifyChanges(true);
703✔
2216
        }
2217
    }
2218

2219
    /**
2220
     * Gets/Sets a value indicating whether the advanced filtering is enabled.
2221
     *
2222
     * @example
2223
     * ```html
2224
     * <igx-grid #grid [data]="localData" [allowAdvancedFiltering]="true" [autoGenerate]="true"></igx-grid>
2225
     * ```
2226
     */
2227
    @Input({ transform: booleanAttribute })
2228
    public get allowAdvancedFiltering() {
2229
        return this._allowAdvancedFiltering;
1,155✔
2230
    }
2231

2232
    public set allowAdvancedFiltering(value) {
2233
        if (this._allowAdvancedFiltering !== value) {
82✔
2234
            this._allowAdvancedFiltering = value;
82✔
2235
            this.filteringService.registerSVGIcons();
82✔
2236

2237
            if (!this._init) {
82✔
2238
                this.notifyChanges(true);
5✔
2239
            }
2240
        }
2241
    }
2242

2243
    /**
2244
     * Gets/Sets the filter mode.
2245
     *
2246
     * @example
2247
     * ```html
2248
     * <igx-grid #grid [data]="localData" [filterMode]="'quickFilter'" [height]="'305px'" [autoGenerate]="true"></igx-grid>
2249
     * ```
2250
     * @remarks
2251
     * By default it's set to FilterMode.quickFilter.
2252
     */
2253
    @Input()
2254
    public get filterMode() {
2255
        return this._filterMode;
76,573✔
2256
    }
2257

2258
    public set filterMode(value: FilterMode) {
2259
        switch (value) {
178!
2260
            case FilterMode.excelStyleFilter:
2261
            case FilterMode.quickFilter:
2262
                this._filterMode = value;
178✔
2263
                break;
178✔
2264
            default:
2265
                break;
×
2266
        }
2267

2268
        if (this.filteringService.isFilterRowVisible) {
178✔
2269
            this.filteringRow.close();
1✔
2270
        }
2271
        this.notifyChanges(true);
178✔
2272
    }
2273

2274
    /**
2275
     * Gets/Sets the summary position.
2276
     *
2277
     * @example
2278
     * ```html
2279
     * <igx-grid #grid [data]="localData" summaryPosition="top" [autoGenerate]="true"></igx-grid>
2280
     * ```
2281
     * @remarks
2282
     * By default it is bottom.
2283
     */
2284
    @Input()
2285
    public get summaryPosition() {
2286
        return this._summaryPosition;
30,873✔
2287
    }
2288

2289
    public set summaryPosition(value: GridSummaryPosition) {
2290
        this._summaryPosition = value;
18✔
2291
        this.notifyChanges();
18✔
2292
    }
2293

2294
    /**
2295
     * Gets/Sets the summary calculation mode.
2296
     *
2297
     * @example
2298
     * ```html
2299
     * <igx-grid #grid [data]="localData" summaryCalculationMode="rootLevelOnly" [autoGenerate]="true"></igx-grid>
2300
     * ```
2301
     * @remarks
2302
     * By default it is rootAndChildLevels which means the summaries are calculated for the root level and each child level.
2303
     */
2304
    @Input()
2305
    public get summaryCalculationMode() {
2306
        return this._summaryCalculationMode;
255,058✔
2307
    }
2308

2309
    public set summaryCalculationMode(value: GridSummaryCalculationMode) {
2310
        this._summaryCalculationMode = value;
63✔
2311
        if (!this._init) {
63✔
2312
            this.crudService.endEdit(false);
25✔
2313
            this.summaryService.resetSummaryHeight();
25✔
2314
            this.notifyChanges(true);
25✔
2315
        }
2316
    }
2317

2318
    /**
2319
     * Controls whether the summary row is visible when groupBy/parent row is collapsed.
2320
     *
2321
     * @example
2322
     * ```html
2323
     * <igx-grid #grid [data]="localData" [showSummaryOnCollapse]="true" [autoGenerate]="true"></igx-grid>
2324
     * ```
2325
     * @remarks
2326
     * By default showSummaryOnCollapse is set to 'false' which means that the summary row is not visible
2327
     * when the groupBy/parent row is collapsed.
2328
     */
2329
    @Input({ transform: booleanAttribute })
2330
    public get showSummaryOnCollapse() {
2331
        return this._showSummaryOnCollapse;
30,866✔
2332
    }
2333

2334
    public set showSummaryOnCollapse(value: boolean) {
2335
        this._showSummaryOnCollapse = value;
9✔
2336
        this.notifyChanges();
9✔
2337
    }
2338

2339
    /**
2340
     * Gets/Sets the filtering strategy of the grid.
2341
     *
2342
     * @example
2343
     * ```html
2344
     *  <igx-grid #grid [data]="localData" [filterStrategy]="filterStrategy"></igx-grid>
2345
     * ```
2346
     */
2347
    @Input()
2348
    public get filterStrategy(): IFilteringStrategy {
2349
        return this._filterStrategy;
44,086✔
2350
    }
2351

2352
    public set filterStrategy(classRef: IFilteringStrategy) {
2353
        this._filterStrategy = classRef;
37✔
2354
    }
2355

2356
    /**
2357
     * Gets/Sets the sorting strategy of the grid.
2358
     *
2359
     * @example
2360
     * ```html
2361
     *  <igx-grid #grid [data]="localData" [sortStrategy]="sortStrategy"></igx-grid>
2362
     * ```
2363
     */
2364
    @Input()
2365
    public get sortStrategy(): IGridSortingStrategy {
2366
        return this._sortingStrategy;
45,740✔
2367
    }
2368

2369
    public set sortStrategy(value: IGridSortingStrategy) {
2370
        this._sortingStrategy = value;
14✔
2371
    }
2372

2373
    /**
2374
     * Gets/Sets the sorting options - single or multiple sorting.
2375
     * Accepts an `ISortingOptions` object with any of the `mode` properties.
2376
     *
2377
     * @example
2378
     * ```typescript
2379
     * const _sortingOptions: ISortingOptions = {
2380
     *      mode: 'single'
2381
     * }
2382
     * ```html
2383
     * <igx-grid [sortingOptions]="sortingOptions"><igx-grid>
2384
     * ```
2385
     */
2386
    @Input()
2387
    public set sortingOptions(value: ISortingOptions) {
2388
        if (!this._init) {
3✔
2389
            // clear sort only if option is changed runtime. No need to clear on initial load.
2390
            this.clearSort();
2✔
2391
        }
2392
        this._sortingOptions = Object.assign(this._sortingOptions, value);
3✔
2393
    }
2394

2395
    public get sortingOptions() {
2396
        return this._sortingOptions;
26,106✔
2397
    }
2398

2399
    /**
2400
     * Gets/Sets the current selection state.
2401
     *
2402
     * @remarks
2403
     * Represents the selected rows' IDs (primary key or rowData)
2404
     * @example
2405
     * ```html
2406
     * <igx-grid [data]="localData" primaryKey="ID" rowSelection="multiple" [selectedRows]="[0, 1, 2]"><igx-grid>
2407
     * ```
2408
     */
2409
    @Input()
2410
    public set selectedRows(rowIDs: any[]) {
2411
        this.selectRows(rowIDs || [], true);
138!
2412
    }
2413

2414
    public get selectedRows(): any[] {
2415
        return this.selectionService.getSelectedRows();
731✔
2416
    }
2417

2418

2419
    /** @hidden @internal */
2420
    public get headerGroupsList(): IgxGridHeaderGroupComponent[] {
2421
        return this.theadRow.groups;
19,998✔
2422
    }
2423

2424
    /** @hidden @internal */
2425
    public get headerCellList(): IgxGridHeaderComponent[] {
2426
        return this.headerGroupsList.map(headerGroup => headerGroup.header).filter(header => header);
4,944✔
2427
    }
2428

2429
    /** @hidden @internal */
2430
    public get filterCellList(): IgxGridFilteringCellComponent[] {
2431
        return this.headerGroupsList.map(group => group.filter).filter(cell => cell);
14,643✔
2432
    }
2433

2434
    /**
2435
     * @hidden @internal
2436
     */
2437
    public get summariesRowList() {
2438
        const res = new QueryList<any>();
2,771✔
2439
        if (!this._summaryRowList) {
2,771!
2440
            return res;
×
2441
        }
2442
        const sumList = this._summaryRowList.filter((item) => item.element.nativeElement.parentElement !== null);
2,771✔
2443
        res.reset(sumList);
2,771✔
2444
        return res;
2,771✔
2445
    }
2446

2447
    /**
2448
     * A list of `IgxGridRowComponent`.
2449
     *
2450
     * @example
2451
     * ```typescript
2452
     * const rowList = this.grid.rowList;
2453
     * ```
2454
     */
2455
    public get rowList() {
2456
        const res = new QueryList<IgxRowDirective>();
10,984✔
2457
        if (!this._rowList) {
10,984!
2458
            return res;
×
2459
        }
2460
        const rList = this._rowList
10,984✔
2461
            .filter((item) => item.element.nativeElement.parentElement !== null)
85,792✔
2462
            .sort((a, b) => a.index - b.index);
95,426✔
2463
        res.reset(rList);
10,984✔
2464
        return res;
10,984✔
2465
    }
2466

2467
    /**
2468
     * A list of currently rendered `IgxGridRowComponent`'s.
2469
     *
2470
     * @example
2471
     * ```typescript
2472
     * const dataList = this.grid.dataRowList;
2473
     * ```
2474
     */
2475
    public get dataRowList(): QueryList<IgxRowDirective> {
2476
        const res = new QueryList<IgxRowDirective>();
52,355✔
2477
        if (!this._dataRowList) {
52,355✔
2478
            return res;
5,721✔
2479
        }
2480
        const rList = this._dataRowList.filter(item => item.element.nativeElement.parentElement !== null).sort((a, b) => a.index - b.index);
230,259✔
2481
        res.reset(rList);
46,634✔
2482
        return res;
46,634✔
2483
    }
2484

2485
    /**
2486
     * Gets the header row selector template.
2487
     */
2488
    @Input()
2489
    public get headSelectorTemplate(): TemplateRef<IgxHeadSelectorTemplateContext> {
2490
        return this._headSelectorTemplate || this.headSelectorsTemplates?.first;
3,319✔
2491
    }
2492

2493
    /**
2494
     * Sets the header row selector template.
2495
     * ```html
2496
     * <ng-template #template igxHeadSelector let-headContext>
2497
     * {{ headContext.selectedCount }} / {{ headContext.totalCount  }}
2498
     * </ng-template>
2499
     * ```
2500
     * ```typescript
2501
     * @ViewChild("'template'", {read: TemplateRef })
2502
     * public template: TemplateRef<any>;
2503
     * this.grid.headSelectorTemplate = this.template;
2504
     * ```
2505
     */
2506
    public set headSelectorTemplate(template: TemplateRef<IgxHeadSelectorTemplateContext>) {
2507
        this._headSelectorTemplate = template;
1✔
2508
    }
2509

2510
    /**
2511
     * @hidden
2512
     * @internal
2513
     */
2514
    public get isPinningToStart() {
2515
        return this.pinning.columns !== ColumnPinningPosition.End;
2,214,110✔
2516
    }
2517

2518
    /**
2519
     * @hidden
2520
     * @internal
2521
     */
2522
    public get isRowPinningToTop() {
2523
        return this.pinning.rows !== RowPinningPosition.Bottom;
637,382✔
2524
    }
2525

2526
    /**
2527
     * Gets the row selector template.
2528
     */
2529
    @Input()
2530
    public get rowSelectorTemplate(): TemplateRef<IgxRowSelectorTemplateContext> {
2531
        return this._rowSelectorTemplate || this.rowSelectorsTemplates?.first;
20,121✔
2532
    }
2533

2534
    /**
2535
         * Sets a custom template for the row selectors.
2536
         * ```html
2537
         * <ng-template #template igxRowSelector let-rowContext>
2538
         *    <igx-checkbox [checked]="rowContext.selected"></igx-checkbox>
2539
         * </ng-template>
2540
         * ```
2541
         * ```typescript
2542
         * @ViewChild("'template'", {read: TemplateRef })
2543
         * public template: TemplateRef<any>;
2544
         * this.grid.rowSelectorTemplate = this.template;
2545
         * ```
2546
         */
2547
    public set rowSelectorTemplate(template: TemplateRef<IgxRowSelectorTemplateContext>) {
2548
        this._rowSelectorTemplate = template;
1✔
2549
    }
2550

2551
    /**
2552
     * @hidden @internal
2553
     */
2554
    public get rowOutletDirective() {
2555
        return this.rowEditingOutletDirective;
4,272✔
2556
    }
2557

2558
    /**
2559
     * @hidden @internal
2560
     */
2561
    public get parentRowOutletDirective() {
2562
        return this.outlet;
1✔
2563
    }
2564

2565
    /**
2566
     * @hidden @internal
2567
     */
2568
    public get rowEditCustom(): TemplateRef<IgxGridRowEditTemplateContext> {
2569
        if (this.rowEditCustomDirectives && this.rowEditCustomDirectives.first) {
6,360✔
2570
            return this.rowEditCustomDirectives.first;
60✔
2571
        }
2572
        return null;
6,300✔
2573
    }
2574

2575
    /**
2576

2577
    /**
2578
     * @hidden @internal
2579
     */
2580
    public get rowEditContainer(): TemplateRef<IgxGridRowEditTemplateContext> {
2581
        return this.rowEditCustom ? this.rowEditCustom : this.defaultRowEditTemplate;
5,810✔
2582
    }
2583

2584
    /**
2585
     * The custom template, if any, that should be used when rendering the row drag indicator icon
2586
     */
2587
    @Input()
2588
    public get dragIndicatorIconTemplate(): TemplateRef<IgxGridEmptyTemplateContext> {
2589
        return this._customDragIndicatorIconTemplate || this.dragIndicatorIconTemplates?.first;
3,325✔
2590
    }
2591

2592
    /**
2593
     * Sets a custom template that should be used when rendering the row drag indicator icon.
2594
     *```html
2595
     * <ng-template #template igxDragIndicatorIcon>
2596
     *    <igx-icon>expand_less</igx-icon>
2597
     * </ng-template>
2598
     * ```
2599
     * ```typescript
2600
     * @ViewChild("'template'", {read: TemplateRef })
2601
     * public template: TemplateRef<any>;
2602
     * this.grid.dragIndicatorIconTemplate = this.template;
2603
     * ```
2604
     */
2605
    public set dragIndicatorIconTemplate(val: TemplateRef<IgxGridEmptyTemplateContext>) {
2606
        this._customDragIndicatorIconTemplate = val;
856✔
2607
    }
2608

2609
    /**
2610
     * @hidden @internal
2611
     */
2612
    public get firstEditableColumnIndex(): number {
2613
        const index = this.visibleColumns.filter(col => col.editable)
26✔
2614
            .map(c => c.visibleIndex).sort((a, b) => a - b);
21✔
2615
        return index.length ? index[0] : null;
4!
2616
    }
2617

2618
    /**
2619
     * @hidden @internal
2620
     */
2621
    public get lastEditableColumnIndex(): number {
2622
        const index = this.visibleColumns.filter(col => col.editable)
47✔
2623
            .map(c => c.visibleIndex).sort((a, b) => a > b ? -1 : 1);
55✔
2624
        return index.length ? index[0] : null;
6!
2625
    }
2626

2627
    /**
2628
     * @hidden @internal
2629
     * TODO: Nav service logic doesn't handle 0 results from this querylist
2630
     */
2631
    public get rowEditTabs(): QueryList<IgxRowEditTabStopDirective> {
2632
        return this.rowEditTabsCUSTOM.length ? this.rowEditTabsCUSTOM : this.rowEditTabsDEFAULT;
141✔
2633
    }
2634

2635
    /** @hidden @internal */
2636
    public get activeDescendant() {
2637
        const activeElem = this.navigation.activeNode;
127,796✔
2638

2639
        if (!activeElem || !Object.keys(activeElem).length) {
127,796✔
2640
            return this.id;
112,404✔
2641
        }
2642

2643
        return activeElem.row < 0 ?
15,392✔
2644
            `${this.id}_${activeElem.row}_${activeElem.mchCache.level}_${activeElem.column}` :
2645
            `${this.id}_${activeElem.row}_${activeElem.column}`;
2646
    }
2647

2648
    /** @hidden @internal */
2649
    public get bannerClass(): string {
2650
        const position = this.rowEditPositioningStrategy.isTop ? 'igx-banner__border-top' : 'igx-banner__border-bottom';
5,810✔
2651
        return `igx-banner ${position}`;
5,810✔
2652
    }
2653

2654
    /**
2655
     * Gets/Sets the sorting state.
2656
     *
2657
     * @remarks
2658
     * Supports two-way data binding.
2659
     * @example
2660
     * ```html
2661
     * <igx-grid #grid [data]="Data" [autoGenerate]="true" [(sortingExpressions)]="model.sortingExpressions"></igx-grid>
2662
     * ```
2663
     */
2664
    @WatchChanges()
2665
    @Input()
2666
    public get sortingExpressions(): ISortingExpression[] {
2667
        return this._sortingExpressions;
290,472✔
2668
    }
2669

2670
    public set sortingExpressions(value: ISortingExpression[]) {
2671
        this._sortingExpressions = cloneArray(value);
449✔
2672
        this.sortingExpressionsChange.emit(this._sortingExpressions);
449✔
2673
        this.notifyChanges();
449✔
2674
    }
2675

2676
    /**
2677
     * @hidden @internal
2678
     */
2679
    public get maxLevelHeaderDepth() {
2680
        if (this._maxLevelHeaderDepth === null) {
31,864✔
2681
            this._maxLevelHeaderDepth = this.hasColumnLayouts ?
2,604✔
2682
                this._columns.reduce((acc, col) => Math.max(acc, col.rowStart), 0) :
1,374✔
2683
                this._columns.reduce((acc, col) => Math.max(acc, col.level), 0);
15,645✔
2684
        }
2685
        return this._maxLevelHeaderDepth;
31,864✔
2686
    }
2687

2688
    /**
2689
     * Gets the number of hidden columns.
2690
     *
2691
     * @example
2692
     * ```typescript
2693
     * const hiddenCol = this.grid.hiddenColumnsCount;
2694
     * ``
2695
     */
2696
    public get hiddenColumnsCount() {
2697
        return this._columns.filter((col) => col.columnGroup === false && col.hidden === true).length;
28,010✔
2698
    }
2699

2700
    /**
2701
     * Gets the number of pinned columns.
2702
     */
2703
    public get pinnedColumnsCount() {
2704
        return this.pinnedColumns.filter(col => !col.columnLayout).length;
100✔
2705
    }
2706

2707
    /**
2708
     * Gets/Sets whether the grid has batch editing enabled.
2709
     * When batch editing is enabled, changes are not made directly to the underlying data.
2710
     * Instead, they are stored as transactions, which can later be committed w/ the `commit` method.
2711
     *
2712
     * @example
2713
     * ```html
2714
     * <igx-grid [batchEditing]="true" [data]="someData">
2715
     * </igx-grid>
2716
     * ```
2717
     */
2718
    @Input({ transform: booleanAttribute })
2719
    public get batchEditing(): boolean {
2720
        return this._batchEditing;
534,713✔
2721
    }
2722

2723
    public set batchEditing(val: boolean) {
2724
        if (val !== this._batchEditing) {
158✔
2725
            delete this._transactions;
147✔
2726
            this._batchEditing = val;
147✔
2727
            this.switchTransactionService(val);
147✔
2728
            this.subscribeToTransactions();
147✔
2729
        }
2730
    }
2731

2732
    /**
2733
     * Get transactions service for the grid.
2734
     */
2735
    public get transactions(): TransactionService<Transaction, State> {
2736
        if (this._diTransactions && !this.batchEditing) {
6,632,178✔
2737
            return this._diTransactions;
3,804✔
2738
        }
2739
        return this._transactions;
6,628,374✔
2740
    }
2741

2742
    /**
2743
     * @hidden @internal
2744
     */
2745
    public get currentRowState(): any {
2746
        return this._currentRowState;
×
2747
    }
2748

2749
    /**
2750
     * @hidden @internal
2751
     */
2752
    public get currencyPositionLeft(): boolean {
2753
        if (this._currencyPositionLeft !== undefined) {
8✔
2754
            return this._currencyPositionLeft;
6✔
2755
        }
2756
        const format = getLocaleNumberFormat(this.locale, NumberFormatStyle.Currency);
2✔
2757
        const formatParts = format.split(',');
2✔
2758
        const i = formatParts.indexOf(formatParts.find(c => c.includes('¤')));
3✔
2759
        return this._currencyPositionLeft = i < 1;
2✔
2760
    }
2761

2762
    /**
2763
     * Gets/Sets cell selection mode.
2764
     *
2765
     * @remarks
2766
     * By default the cell selection mode is multiple
2767
     * @param selectionMode: GridSelectionMode
2768
     */
2769
    @WatchChanges()
2770
    @Input()
2771
    public get cellSelection() {
2772
        return this._cellSelectionMode;
1,105,640✔
2773
    }
2774

2775
    public set cellSelection(selectionMode: GridSelectionMode) {
2776
        this._cellSelectionMode = selectionMode;
33✔
2777
        // if (this.gridAPI.grid) {
2778
        this.selectionService.clear(true);
33✔
2779
        this.notifyChanges();
33✔
2780
        // }
2781
    }
2782

2783
    /**
2784
     * Gets/Sets row selection mode
2785
     *
2786
     * @remarks
2787
     * By default the row selection mode is 'none'
2788
     * Note that in IgxGrid and IgxHierarchicalGrid 'multipleCascade' behaves like 'multiple'
2789
     */
2790
    @WatchChanges()
2791
    @Input()
2792
    public get rowSelection() {
2793
        return this._rowSelectionMode;
283,272✔
2794
    }
2795

2796
    public set rowSelection(selectionMode: GridSelectionMode) {
2797
        this._rowSelectionMode = selectionMode;
502✔
2798
        if (!this._init) {
502✔
2799
            this.selectionService.clearAllSelectedRows();
72✔
2800
            this.notifyChanges(true);
72✔
2801
        }
2802
    }
2803

2804
    /**
2805
     * Gets/Sets column selection mode
2806
     *
2807
     * @remarks
2808
     * By default the row selection mode is none
2809
     * @param selectionMode: GridSelectionMode
2810
     */
2811
    @WatchChanges()
2812
    @Input()
2813
    public get columnSelection() {
2814
        return this._columnSelectionMode;
208,139✔
2815
    }
2816

2817
    public set columnSelection(selectionMode: GridSelectionMode) {
2818
        this._columnSelectionMode = selectionMode;
137✔
2819
        // if (this.gridAPI.grid) {
2820
        this.selectionService.clearAllSelectedColumns();
137✔
2821
        this.notifyChanges(true);
137✔
2822
        // }
2823
    }
2824

2825
    /**
2826
     * @hidden @internal
2827
     */
2828
    public set pagingState(value) {
2829
        this._pagingState = value;
580✔
2830
        if (this.paginator && !this._init) {
580✔
2831
            this.paginator.totalRecords = value.metadata.countRecords;
428✔
2832
        }
2833
    }
2834

2835
    public get pagingState() {
2836
        return this._pagingState;
174✔
2837
    }
2838

2839
    /**
2840
     * @hidden @internal
2841
     */
2842
    public rowEditMessage;
2843

2844
    /**
2845
     * @hidden @internal
2846
     */
2847
    public calcWidth: number;
2848
    /**
2849
     * @hidden @internal
2850
     */
2851
    public calcHeight = 0;
3,996✔
2852
    /**
2853
     * @hidden @internal
2854
     */
2855
    public tfootHeight: number;
2856

2857
    /**
2858
     * @hidden @internal
2859
     */
2860
    public disableTransitions = false;
3,996✔
2861

2862
    /**
2863
     * Represents the last search information.
2864
     */
2865
    public get lastSearchInfo(): ISearchInfo {
2866
        return this._lastSearchInfo;
1,369,732✔
2867
    }
2868

2869
    /**
2870
     * @hidden @internal
2871
     */
2872
    public columnWidthSetByUser = false;
3,996✔
2873

2874
    /**
2875
     * @hidden @internal
2876
     */
2877
    public pinnedRecords: any[];
2878

2879
    /**
2880
     * @hidden @internal
2881
     */
2882
    public unpinnedRecords: any[];
2883

2884
    /**
2885
     * @hidden @internal
2886
     */
2887
    public rendered$ = this.rendered.asObservable().pipe(shareReplay({ bufferSize: 1, refCount: true }));
3,996✔
2888

2889
    /** @hidden @internal */
2890
    public resizeNotify = new Subject<void>();
3,996✔
2891

2892
    /** @hidden @internal */
2893
    public rowAddedNotifier = new Subject<IRowDataEventArgs>();
3,996✔
2894

2895
    /** @hidden @internal */
2896
    public rowDeletedNotifier = new Subject<IRowDataEventArgs>();
3,996✔
2897

2898
    /** @hidden @internal */
2899
    public pipeTriggerNotifier = new Subject();
3,996✔
2900

2901
    /** @hidden @internal */
2902
    public _filteredSortedPinnedData: any[];
2903

2904
    /** @hidden @internal */
2905
    public _filteredSortedUnpinnedData: any[];
2906

2907
    /** @hidden @internal */
2908
    public _filteredPinnedData: any[];
2909

2910
    /**
2911
     * @hidden
2912
     */
2913
    public _filteredUnpinnedData;
2914
    /**
2915
     * @hidden @internal
2916
     */
2917
    public _destroyed = false;
3,996✔
2918
    /**
2919
     * @hidden @internal
2920
     */
2921
    public _totalRecords = -1;
3,996✔
2922
    /**
2923
     * @hidden @internal
2924
     */
2925
    public columnsWithNoSetWidths = null;
3,996✔
2926
    /**
2927
     * @hidden @internal
2928
     */
2929
    public pipeTrigger = 0;
3,996✔
2930
    /**
2931
     * @hidden @internal
2932
     */
2933
    public filteringPipeTrigger = 0;
3,996✔
2934
    /**
2935
     * @hidden @internal
2936
     */
2937
    public summaryPipeTrigger = 0;
3,996✔
2938
    /**
2939
     * @hidden @internal
2940
     */
2941
    public groupablePipeTrigger = 0;
3,996✔
2942

2943
    /**
2944
    * @hidden @internal
2945
    */
2946
    public EMPTY_DATA = [];
3,996✔
2947

2948
    /** @hidden @internal */
2949
    public isPivot = false;
3,996✔
2950

2951
    /** @hidden @internal */
2952
    public _baseFontSize: number;
2953

2954
    /**
2955
     * @hidden
2956
     */
2957
    public destroy$ = new Subject<any>();
3,996✔
2958
    /**
2959
     * @hidden
2960
     */
2961
    protected _pagingMode = GridPagingMode.Local;
3,996✔
2962
    /**
2963
     * @hidden
2964
     */
2965
    protected _pagingState;
2966
    /**
2967
     * @hidden
2968
     */
2969
    protected _hideRowSelectors = false;
3,996✔
2970
    /**
2971
     * @hidden
2972
     */
2973
    protected _rowDrag = false;
3,996✔
2974
    /**
2975
     * @hidden
2976
     */
2977
    protected _columns: IgxColumnComponent[] = [];
3,996✔
2978
    /**
2979
     * @hidden
2980
     */
2981
    protected _pinnedColumns: IgxColumnComponent[] = [];
3,996✔
2982
    /**
2983
     * @hidden
2984
     */
2985
    protected _unpinnedColumns: IgxColumnComponent[] = [];
3,996✔
2986
    /**
2987
     * @hidden
2988
     */
2989
    protected _filteringExpressionsTree: IFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
3,996✔
2990
    /**
2991
     * @hidden
2992
     */
2993
    protected _advancedFilteringExpressionsTree: IFilteringExpressionsTree;
2994
    /**
2995
     * @hidden
2996
     */
2997
    protected _sortingExpressions: Array<ISortingExpression> = [];
3,996✔
2998
    /**
2999
     * @hidden
3000
     */
3001
    protected _maxLevelHeaderDepth = null;
3,996✔
3002
    /**
3003
     * @hidden
3004
     */
3005
    protected _columnHiding = false;
3,996✔
3006
    /**
3007
     * @hidden
3008
     */
3009
    protected _columnPinning = false;
3,996✔
3010

3011
    protected _pinnedRecordIDs = [];
3,996✔
3012

3013
    /**
3014
     * @hidden
3015
     */
3016
    protected _hasVisibleColumns;
3017
    protected _allowFiltering = false;
3,996✔
3018
    protected _allowAdvancedFiltering = false;
3,996✔
3019
    protected _filterMode: FilterMode = FilterMode.quickFilter;
3,996✔
3020

3021

3022
    protected _defaultTargetRecordNumber = 10;
3,996✔
3023
    protected _expansionStates: Map<any, boolean> = new Map<any, boolean>();
3,996✔
3024
    protected _defaultExpandState = false;
3,996✔
3025
    protected _headerFeaturesWidth = NaN;
3,996✔
3026
    protected _init = true;
3,996✔
3027
    protected _cdrRequestRepaint = false;
3,996✔
3028
    protected _userOutletDirective: IgxOverlayOutletDirective;
3029
    protected _transactions: TransactionService<Transaction, State>;
3030
    protected _batchEditing = false;
3,996✔
3031
    protected _sortingOptions: ISortingOptions = { mode: 'multiple' };
3,996✔
3032
    protected _filterStrategy: IFilteringStrategy = new FilteringStrategy();
3,996✔
3033
    protected _autoGeneratedCols = [];
3,996✔
3034
    protected _dataView = [];
3,996✔
3035
    protected _lastSearchInfo: ISearchInfo = {
3,996✔
3036
        searchText: '',
3037
        caseSensitive: false,
3038
        exactMatch: false,
3039
        activeMatchIndex: 0,
3040
        matchInfoCache: [],
3041
        matchCount: 0,
3042
        content: ''
3043
    };
3044

3045
    /** @hidden @internal */
3046
    public get paginator() {
3047
        return this.paginationComponents?.first;
3,378,230✔
3048
    }
3049

3050
    /**
3051
     * @hidden @internal
3052
     */
3053
    public get scrollSize() {
3054
        return this.verticalScrollContainer.getScrollNativeSize();
178,089✔
3055
    }
3056

3057
    private _rowEditable = false;
3,996✔
3058
    private _currentRowState: any;
3059
    private _filteredSortedData = null;
3,996✔
3060
    private _filteredData = null;
3,996✔
3061

3062
    private _customDragIndicatorIconTemplate: TemplateRef<IgxGridEmptyTemplateContext>;
3063
    private _excelStyleHeaderIconTemplate: TemplateRef<IgxGridHeaderTemplateContext>;
3064
    private _rowSelectorTemplate: TemplateRef<IgxRowSelectorTemplateContext>;
3065
    private _headSelectorTemplate: TemplateRef<IgxHeadSelectorTemplateContext>;
3066
    private _rowEditTextTemplate: TemplateRef<IgxGridRowEditTextTemplateContext>;
3067
    private _rowAddTextTemplate: TemplateRef<IgxGridEmptyTemplateContext>;
3068
    private _rowEditActionsTemplate: TemplateRef<IgxGridRowEditActionsTemplateContext>;
3069
    private _dragGhostCustomTemplate: TemplateRef<IgxGridRowDragGhostContext>;
3070
    private _rowExpandedIndicatorTemplate: TemplateRef<IgxGridRowTemplateContext>;
3071
    private _rowCollapsedIndicatorTemplate: TemplateRef<IgxGridRowTemplateContext>;
3072
    private _headerExpandIndicatorTemplate: TemplateRef<IgxGridTemplateContext>;
3073
    private _headerCollapseIndicatorTemplate: TemplateRef<IgxGridTemplateContext>;
3074

3075
    private _cdrRequests = false;
3,996✔
3076
    private _resourceStrings = getCurrentResourceStrings(GridResourceStringsEN);
3,996✔
3077
    private _emptyGridMessage = null;
3,996✔
3078
    private _emptyFilteredGridMessage = null;
3,996✔
3079
    private _isLoading = false;
3,996✔
3080
    private _locale: string;
3081
    private overlayIDs = [];
3,996✔
3082
    private _sortingStrategy: IGridSortingStrategy;
3083
    private _pinning: IPinningConfig = { columns: ColumnPinningPosition.Start };
3,996✔
3084

3085
    private _hostWidth;
3086
    private _advancedFilteringOverlayId: string;
3087
    private _advancedFilteringPositionSettings: PositionSettings = {
3,996✔
3088
        verticalDirection: VerticalAlignment.Middle,
3089
        horizontalDirection: HorizontalAlignment.Center,
3090
        horizontalStartPoint: HorizontalAlignment.Center,
3091
        verticalStartPoint: VerticalAlignment.Middle
3092
    };
3093

3094
    private _advancedFilteringOverlaySettings: OverlaySettings = {
3,996✔
3095
        closeOnOutsideClick: false,
3096
        modal: false,
3097
        positionStrategy: new ConnectedPositioningStrategy(this._advancedFilteringPositionSettings),
3098
    };
3099

3100
    private columnListDiffer;
3101
    private rowListDiffer;
3102
    private _height: string | null = '100%';
3,996✔
3103
    private _width: string | null = '100%';
3,996✔
3104
    private _rowHeight: number | undefined;
3105
    private _horizontalForOfs: Array<IgxGridForOfDirective<any, any[]>> = [];
3,996✔
3106
    private _multiRowLayoutRowSize = 1;
3,996✔
3107
    // Caches
3108
    private _totalWidth = NaN;
3,996✔
3109
    private _pinnedVisible = [];
3,996✔
3110
    private _unpinnedVisible = [];
3,996✔
3111
    private _pinnedWidth = NaN;
3,996✔
3112
    private _unpinnedWidth = NaN;
3,996✔
3113
    private _visibleColumns = [];
3,996✔
3114
    private _columnGroups = false;
3,996✔
3115

3116
    private _columnWidth: string;
3117

3118
    private _summaryPosition: GridSummaryPosition = GridSummaryPosition.bottom;
3,996✔
3119
    private _summaryCalculationMode: GridSummaryCalculationMode = GridSummaryCalculationMode.rootAndChildLevels;
3,996✔
3120
    private _showSummaryOnCollapse = false;
3,996✔
3121
    private _summaryRowHeight = 0;
3,996✔
3122
    private _cellSelectionMode: GridSelectionMode = GridSelectionMode.multiple;
3,996✔
3123
    private _rowSelectionMode: GridSelectionMode = GridSelectionMode.none;
3,996✔
3124
    private _selectRowOnClick = true;
3,996✔
3125
    private _columnSelectionMode: GridSelectionMode = GridSelectionMode.none;
3,996✔
3126

3127
    private lastAddedRowIndex;
3128
    private _currencyPositionLeft: boolean;
3129

3130
    private rowEditPositioningStrategy = new RowEditPositionStrategy({
3,996✔
3131
        horizontalDirection: HorizontalAlignment.Right,
3132
        verticalDirection: VerticalAlignment.Bottom,
3133
        horizontalStartPoint: HorizontalAlignment.Left,
3134
        verticalStartPoint: VerticalAlignment.Bottom,
3135
        closeAnimation: null
3136
    });
3137

3138
    private rowEditSettings: OverlaySettings = {
3,996✔
3139
        scrollStrategy: new AbsoluteScrollStrategy(),
3140
        modal: false,
3141
        closeOnOutsideClick: false,
3142
        outlet: this.rowOutletDirective,
3143
        positionStrategy: this.rowEditPositioningStrategy
3144
    };
3145

3146
    private transactionChange$ = new Subject<void>();
3,996✔
3147
    private _rendered = false;
3,996✔
3148
    private readonly DRAG_SCROLL_DELTA = 10;
3,996✔
3149
    private _dataCloneStrategy: IDataCloneStrategy = new DefaultDataCloneStrategy();
3,996✔
3150
    private _autoSize = false;
3,996✔
3151
    private _sortHeaderIconTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
3152
    private _sortAscendingHeaderIconTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
3153
    private _sortDescendingHeaderIconTemplate: TemplateRef<IgxGridHeaderTemplateContext> = null;
3,996✔
3154

3155
    /**
3156
     * @hidden @internal
3157
     */
3158
    protected get minColumnWidth() {
3159
        return MINIMUM_COLUMN_WIDTH;
64,283✔
3160
    }
3161

3162
    /**
3163
     * @hidden @internal
3164
     */
3165
    public abstract id: string;
3166
    public abstract data: any[] | null;
3167

3168
    /**
3169
     * Returns an array of objects containing the filtered data.
3170
     *
3171
     * @example
3172
     * ```typescript
3173
     * let filteredData = this.grid.filteredData;
3174
     * ```
3175
     */
3176
    public get filteredData() {
3177
        return this._filteredData;
46,611✔
3178
    }
3179

3180
    /**
3181
     * Returns an array containing the filtered sorted data.
3182
     *
3183
     * @example
3184
     * ```typescript
3185
     * const filteredSortedData = this.grid1.filteredSortedData;
3186
     * ```
3187
     */
3188
    public get filteredSortedData(): any[] {
3189
        return this._filteredSortedData;
123,956✔
3190
    }
3191

3192
    /**
3193
     * @hidden @internal
3194
     */
3195
    public get rowChangesCount() {
3196
        if (!this.crudService.row) {
16,625✔
3197
            return 0;
13,931✔
3198
        }
3199
        const f = (obj: any) => {
2,694✔
3200
            let changes = 0;
452✔
3201
            Object.keys(obj).forEach(key => isObject(obj[key]) ? changes += f(obj[key]) : changes++);
471✔
3202
            return changes;
452✔
3203
        };
3204
        if (this.transactions.getState(this.crudService.row.id)?.type === TransactionType.ADD) {
2,694✔
3205
            return this._columns.filter(c => c.field).length;
77✔
3206
        }
3207
        const rowChanges = this.transactions.getAggregatedValue(this.crudService.row.id, false);
2,675✔
3208
        return rowChanges ? f(rowChanges) : 0;
2,675✔
3209
    }
3210

3211
    /**
3212
     * @hidden @internal
3213
     */
3214
    public get dataWithAddedInTransactionRows() {
3215
        const result = cloneArray(this.gridAPI.get_all_data());
11,309✔
3216
        if (this.transactions.enabled) {
11,309✔
3217
            result.push(...this.transactions.getAggregatedChanges(true)
6,947✔
3218
                .filter(t => t.type === TransactionType.ADD)
1,222✔
3219
                .map(t => t.newValue));
356✔
3220
        }
3221

3222
        if (this.crudService.row && this.crudService.row.getClassName() === IgxAddRow.name) {
11,309✔
3223
            result.splice(this.crudService.row.index, 0, this.crudService.row.data);
379✔
3224
        }
3225

3226
        return result;
11,309✔
3227
    }
3228

3229
    /**
3230
     * @hidden @internal
3231
     */
3232
    public get dataLength() {
3233
        return this.transactions.enabled ? this.dataWithAddedInTransactionRows.length : this.gridAPI.get_all_data().length;
43,510✔
3234
    }
3235

3236
    /**
3237
     * @hidden @internal
3238
     */
3239
    public get template(): TemplateRef<IgxGridTemplateContext> {
3240
        if (this.isLoading && (this.hasZeroResultFilter || this.hasNoData)) {
43,135✔
3241
            return this.loadingGridTemplate ? this.loadingGridTemplate : this.loadingGridDefaultTemplate;
81✔
3242
        }
3243

3244
        if (this.hasZeroResultFilter) {
43,054✔
3245
            return this.emptyGridTemplate ? this.emptyGridTemplate : this.emptyFilteredGridTemplate;
216!
3246
        }
3247

3248
        if (this.hasNoData) {
42,838✔
3249
            return this.emptyGridTemplate ? this.emptyGridTemplate : this.emptyGridDefaultTemplate;
697✔
3250
        }
3251
    }
3252

3253
    /**
3254
     * @hidden @internal
3255
     */
3256
    private get hasZeroResultFilter(): boolean {
3257
        return this.filteredData && this.filteredData.length === 0;
43,330✔
3258
    }
3259

3260
    /**
3261
     * @hidden @internal
3262
     */
3263
    private get hasNoData(): boolean {
3264
        return !this.data || this.dataLength === 0;
43,183✔
3265
    }
3266

3267
    /**
3268
     * @hidden @internal
3269
     */
3270
    public get shouldOverlayLoading(): boolean {
3271
        return this.isLoading && !this.hasNoData && !this.hasZeroResultFilter;
46,354✔
3272
    }
3273

3274
    /**
3275
     * @hidden @internal
3276
     */
3277
    public get isMultiRowSelectionEnabled(): boolean {
3278
        return this.rowSelection === GridSelectionMode.multiple
3,374✔
3279
            || this.rowSelection === GridSelectionMode.multipleCascade;
3280
    }
3281

3282
    /**
3283
     * @hidden @internal
3284
     */
3285
    public get isRowSelectable(): boolean {
3286
        return this.rowSelection !== GridSelectionMode.none;
274,219✔
3287
    }
3288

3289
    /**
3290
     * @hidden @internal
3291
     */
3292
    public get isCellSelectable() {
3293
        return this.cellSelection !== GridSelectionMode.none;
8,355✔
3294
    }
3295

3296
    /**
3297
     * @hidden @internal
3298
     */
3299
    public get columnInDrag() {
3300
        return this.gridAPI.cms.column;
251,784✔
3301
    }
3302

3303
    constructor(
3304
        public readonly validation: IgxGridValidationService,
3,996✔
3305
        /** @hidden @internal */
3306
        public readonly selectionService: IgxGridSelectionService,
3,996✔
3307
        protected colResizingService: IgxColumnResizingService,
3,996✔
3308
        @Inject(IGX_GRID_SERVICE_BASE) public readonly gridAPI: GridServiceType,
3,996✔
3309
        protected transactionFactory: IgxFlatTransactionFactory,
3,996✔
3310
        private elementRef: ElementRef<HTMLElement>,
3,996✔
3311
        protected zone: NgZone,
3,996✔
3312
        /** @hidden @internal */
3313
        @Inject(DOCUMENT) public document: any,
3,996✔
3314
        public readonly cdr: ChangeDetectorRef,
3,996✔
3315
        protected differs: IterableDiffers,
3,996✔
3316
        protected viewRef: ViewContainerRef,
3,996✔
3317
        protected injector: Injector,
3,996✔
3318
        protected envInjector: EnvironmentInjector,
3,996✔
3319
        public navigation: IgxGridNavigationService,
3,996✔
3320
        /** @hidden @internal */
3321
        public filteringService: IgxFilteringService,
3,996✔
3322
        protected textHighlightService: IgxTextHighlightService,
3,996✔
3323
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
3,996✔
3324
        /** @hidden @internal */
3325
        public summaryService: IgxGridSummaryService,
3,996✔
3326
        @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions,
3,996✔
3327
        @Inject(LOCALE_ID) private localeId: string,
3,996✔
3328
        protected platform: PlatformUtil,
3,996✔
3329
        @Optional() @Inject(IgxGridTransaction) protected _diTransactions?: TransactionService<Transaction, State>
3,996✔
3330
    ) {
3331
        super(_displayDensityOptions, elementRef);
3,996✔
3332
        this.locale = this.locale || this.localeId;
3,996✔
3333
        this._transactions = this.transactionFactory.create(TRANSACTION_TYPE.None);
3,996✔
3334
        this._transactions.cloneStrategy = this.dataCloneStrategy;
3,996✔
3335
        this.cdr.detach();
3,996✔
3336
        IgcTrialWatermark.register();
3,996✔
3337
    }
3338

3339
    /**
3340
     * @hidden
3341
     * @internal
3342
     */
3343
    @HostListener('mouseleave')
3344
    public hideActionStrip() {
3345
        this.actionStrip?.hide();
4✔
3346
    }
3347

3348
    /**
3349
     * @hidden
3350
     * @internal
3351
     */
3352
    public get headerFeaturesWidth() {
3353
        return this._headerFeaturesWidth;
1,234✔
3354
    }
3355

3356
    /**
3357
     * @hidden
3358
     * @internal
3359
     */
3360
    public isDetailRecord(_rec) {
3361
        return false;
1,221✔
3362
    }
3363

3364
    /**
3365
     * @hidden
3366
     * @internal
3367
     */
3368
    public isGroupByRecord(_rec) {
3369
        return false;
1,221✔
3370
    }
3371

3372
    /**
3373
     * @hidden @internal
3374
     */
3375
    public isGhostRecord(record: any): boolean {
3376
        return record.ghostRecord !== undefined;
511,072✔
3377
    }
3378
    /**
3379
     * @hidden @internal
3380
     */
3381
    public isAddRowRecord(record: any): boolean {
3382
        return record.addRow !== undefined;
×
3383
    }
3384

3385
    /**
3386
     * @hidden
3387
     * Returns the row index of a row that takes into account the full view data like pinning.
3388
     */
3389
    public getDataViewIndex(rowIndex, pinned) {
3390
        if (pinned && !this.isRowPinningToTop) {
237,743✔
3391
            rowIndex = rowIndex + this.unpinnedDataView.length;
196✔
3392
        } else if (!pinned && this.isRowPinningToTop) {
237,547✔
3393
            rowIndex = rowIndex + this.pinnedDataView.length;
235,812✔
3394
        }
3395
        return rowIndex;
237,743✔
3396
    }
3397

3398
    /**
3399
     * @hidden
3400
     * @internal
3401
     */
3402
    public get hasDetails() {
3403
        return false;
371✔
3404
    }
3405

3406
    /**
3407
     * Returns the state of the grid virtualization.
3408
     *
3409
     * @remarks
3410
     * Includes the start index and how many records are rendered.
3411
     * @example
3412
     * ```typescript
3413
     * const gridVirtState = this.grid1.virtualizationState;
3414
     * ```
3415
     */
3416
    public get virtualizationState() {
3417
        return this.verticalScrollContainer.state;
1,240✔
3418
    }
3419

3420
    /**
3421
     * @hidden
3422
     * @internal
3423
     */
3424
    public hideOverlays() {
3425
        this.overlayIDs.forEach(overlayID => {
527✔
3426
            const overlay = this.overlayService.getOverlayById(overlayID);
1✔
3427

3428
            if (overlay?.visible && !overlay.closeAnimationPlayer?.hasStarted()) {
1✔
3429
                this.overlayService.hide(overlayID);
1✔
3430

3431
                this.nativeElement.focus();
1✔
3432
            }
3433
        });
3434
    }
3435

3436
    /**
3437
     * Returns whether the record is pinned or not.
3438
     *
3439
     * @param rowIndex Index of the record in the `dataView` collection.
3440
     *
3441
     * @hidden
3442
     * @internal
3443
     */
3444
    public isRecordPinnedByViewIndex(rowIndex: number) {
3445
        return this.hasPinnedRecords && (this.isRowPinningToTop && rowIndex < this.pinnedDataView.length) ||
286,247✔
3446
            (!this.isRowPinningToTop && rowIndex >= this.unpinnedDataView.length);
3447
    }
3448

3449
    /**
3450
     * Returns whether the record is pinned or not.
3451
     *
3452
     * @param rowIndex Index of the record in the `filteredSortedData` collection.
3453
     */
3454
    public isRecordPinnedByIndex(rowIndex: number) {
3455
        return this.hasPinnedRecords && (this.isRowPinningToTop && rowIndex < this._filteredSortedPinnedData.length) ||
2,167!
3456
            (!this.isRowPinningToTop && rowIndex >= this._filteredSortedUnpinnedData.length);
3457
    }
3458

3459
    /**
3460
     * @hidden
3461
     * @internal
3462
     */
3463
    public isRecordPinned(rec) {
3464
        return this.getInitialPinnedIndex(rec) !== -1;
999,135✔
3465
    }
3466

3467
    /**
3468
     * @hidden
3469
     * @internal
3470
     * Returns the record index in order of pinning by the user. Does not consider sorting/filtering.
3471
     */
3472
    public getInitialPinnedIndex(rec) {
3473
        const id = this.gridAPI.get_row_id(rec);
999,243✔
3474
        return this._pinnedRecordIDs.indexOf(id);
999,243✔
3475
    }
3476

3477
    /**
3478
     * @hidden
3479
     * @internal
3480
     */
3481
    public get hasPinnedRecords() {
3482
        return this._pinnedRecordIDs.length > 0;
469,050✔
3483
    }
3484

3485
    /**
3486
     * @hidden
3487
     * @internal
3488
     */
3489
    public get pinnedRecordsCount() {
3490
        return this._pinnedRecordIDs.length;
7,173✔
3491
    }
3492

3493
    /**
3494
     * @hidden
3495
     * @internal
3496
     */
3497
    public get crudService() {
3498
        return this.gridAPI.crudService;
4,396,487✔
3499
    }
3500

3501
    /**
3502
     * @hidden
3503
     * @internal
3504
     */
3505
    public _setupServices() {
3506
        this.gridAPI.grid = this as any;
3,491✔
3507
        this.crudService.grid = this as any;
3,491✔
3508
        this.selectionService.grid = this as any;
3,491✔
3509
        this.validation.grid = this as any;
3,491✔
3510
        this.navigation.grid = this as any;
3,491✔
3511
        this.filteringService.grid = this as any;
3,491✔
3512
        this.summaryService.grid = this as any;
3,491✔
3513
    }
3514

3515
    /**
3516
     * @hidden
3517
     * @internal
3518
     */
3519
    public _setupListeners() {
3520
        const destructor = takeUntil<any>(this.destroy$);
3,491✔
3521
        fromEvent(this.nativeElement, 'focusout').pipe(filter(() => !!this.navigation.activeNode), destructor).subscribe((event) => {
3,491✔
3522
            if (!this.crudService.cell &&
755!
3523
                !!this.navigation.activeNode &&
3524
                ((event.target === this.tbody.nativeElement && this.navigation.activeNode.row >= 0 &&
3525
                    this.navigation.activeNode.row < this.dataView.length)
3526
                    || (event.target === this.theadRow.nativeElement && this.navigation.activeNode.row === -1)
3527
                    || (event.target === this.tfoot.nativeElement && this.navigation.activeNode.row === this.dataView.length)) &&
3528
                !(this.rowEditable && this.crudService.rowEditingBlocked && this.crudService.rowInEditMode)) {
61!
3529
                this.navigation.lastActiveNode = this.navigation.activeNode;
61✔
3530
                this.navigation.activeNode = {} as IActiveNode;
61✔
3531
                this.notifyChanges();
61✔
3532
            }
3533
        });
3534
        this.rowAddedNotifier.pipe(destructor).subscribe(args => this.refreshGridState(args));
3,491✔
3535
        this.rowDeletedNotifier.pipe(destructor).subscribe(args => {
3,491✔
3536
            this.summaryService.deleteOperation = true;
185✔
3537
            this.summaryService.clearSummaryCache(args);
185✔
3538
        });
3539

3540
        this.subscribeToTransactions();
3,491✔
3541

3542
        this.resizeNotify.pipe(
3,491✔
3543
            filter(() => !this._init),
1,587✔
3544
            throttleTime(40, animationFrameScheduler, { leading: false, trailing: true }),
3545
            destructor
3546
        )
3547
            .subscribe(() => {
3548
                this.zone.run(() => {
910✔
3549
                    // do not trigger reflow if element is detached.
3550
                    if (this.document.contains(this.nativeElement)) {
910✔
3551
                        this.notifyChanges(true);
624✔
3552
                    }
3553
                });
3554
            });
3555

3556
        this.pipeTriggerNotifier.pipe(takeUntil(this.destroy$)).subscribe(() => this.pipeTrigger++);
3,491✔
3557
        this.columnMovingEnd.pipe(destructor).subscribe(() => this.crudService.endEdit(false));
3,491✔
3558

3559
        this.overlayService.opening.pipe(destructor).subscribe((event) => {
3,491✔
3560
            if (this._advancedFilteringOverlayId === event.id) {
864✔
3561
                const instance = event.componentRef.instance as IgxAdvancedFilteringDialogComponent;
88✔
3562
                if (instance) {
88✔
3563
                    instance.initialize(this as any, this.overlayService, event.id);
88✔
3564
                }
3565
            }
3566
        });
3567

3568
        this.overlayService.opened.pipe(destructor).subscribe((event) => {
3,491✔
3569
            const overlaySettings = this.overlayService.getOverlayById(event.id)?.settings;
599✔
3570

3571
            // do not hide the advanced filtering overlay on scroll
3572
            if (this._advancedFilteringOverlayId === event.id) {
599✔
3573
                const instance = event.componentRef.instance as IgxAdvancedFilteringDialogComponent;
84✔
3574
                if (instance) {
84✔
3575
                    instance.lastActiveNode = this.navigation.activeNode;
84✔
3576
                    instance.queryBuilder.setAddButtonFocus();
84✔
3577
                }
3578
                return;
84✔
3579
            }
3580

3581
            // do not hide the overlay if it's attached to a row
3582
            if (this.rowEditingOverlay?.overlayId === event.id) {
515✔
3583
                return;
20✔
3584
            }
3585

3586
            if (overlaySettings?.outlet === this.outlet && this.overlayIDs.indexOf(event.id) === -1) {
495✔
3587
                this.overlayIDs.push(event.id);
332✔
3588
            }
3589
        });
3590

3591
        this.overlayService.closed.pipe(filter(() => !this._init), destructor).subscribe((event) => {
3,491✔
3592
            if (this._advancedFilteringOverlayId === event.id) {
479✔
3593
                this.overlayService.detach(this._advancedFilteringOverlayId);
32✔
3594
                this._advancedFilteringOverlayId = null;
32✔
3595
                return;
32✔
3596
            }
3597

3598
            const ind = this.overlayIDs.indexOf(event.id);
447✔
3599
            if (ind !== -1) {
447✔
3600
                this.overlayIDs.splice(ind, 1);
192✔
3601
            }
3602
        });
3603

3604
        this.verticalScrollContainer.dataChanging.pipe(filter(() => !this._init), destructor).subscribe(($event) => {
6,595✔
3605
            const shouldRecalcSize = this.isPercentHeight &&
3,184✔
3606
                (!this.calcHeight || this.calcHeight === this.getDataBasedBodyHeight() ||
3607
                    this.calcHeight === this.renderedRowHeight * this._defaultTargetRecordNumber);
3608
            if (shouldRecalcSize) {
3,184✔
3609
                this.calculateGridHeight();
99✔
3610
                $event.containerSize = this.calcHeight;
99✔
3611
            }
3612
            this.evaluateLoadingState();
3,184✔
3613
        });
3614

3615
        this.verticalScrollContainer.scrollbarVisibilityChanged.pipe(filter(() => !this._init), destructor).subscribe(() => {
3,491✔
3616
            // called to recalc all widths that may have changes as a result of
3617
            // the vert. scrollbar showing/hiding
3618
            this.notifyChanges(true);
807✔
3619
            this.cdr.detectChanges();
807✔
3620
            Promise.resolve().then(() => this.headerContainer.updateScroll());
807✔
3621
        });
3622

3623

3624
        this.headerContainer?.scrollbarVisibilityChanged.pipe(filter(() => !this._init), destructor).subscribe(() => {
3,491✔
3625
            // the horizontal scrollbar showing/hiding
3626
            // update scrollbar visibility and recalc heights
3627
            this.notifyChanges(true);
1,026✔
3628
            this.cdr.detectChanges();
1,026✔
3629
        });
3630

3631
        this.verticalScrollContainer.contentSizeChange.pipe(filter(() => !this._init), throttleTime(30), destructor).subscribe(() => {
3,491✔
3632
            this.notifyChanges(true);
681✔
3633
        });
3634

3635
        this.densityChanged.pipe(destructor).subscribe(() => {
3,491✔
3636
            this._autoSize = this.isPercentHeight && this.calcHeight !== this.getDataBasedBodyHeight();
42✔
3637
            this.crudService.endEdit(false);
42✔
3638
            if (this._summaryRowHeight === 0) {
42✔
3639
                this.summaryService.summaryHeight = 0;
42✔
3640
            }
3641
            this.notifyChanges(true);
42✔
3642
        });
3643
    }
3644

3645
    /**
3646
     * @hidden
3647
     */
3648
    public override ngOnInit() {
3649
        super.ngOnInit();
3,491✔
3650
        this._setupServices();
3,491✔
3651
        this._setupListeners();
3,491✔
3652
        this.rowListDiffer = this.differs.find([]).create(null);
3,491✔
3653
        // compare based on field, not on object ref.
3654
        this.columnListDiffer = this.differs.find([]).create((index, col: ColumnType) => col.field);
17,192✔
3655
        this.calcWidth = this.width && this.width.indexOf('%') === -1 ? parseInt(this.width, 10) : 0;
3,491✔
3656
        this.shouldGenerate = this.autoGenerate;
3,491✔
3657
    }
3658

3659
    /**
3660
     * @hidden
3661
     * @internal
3662
     */
3663
    public resetColumnsCaches() {
3664
        this._columns.forEach(column => column.resetCaches());
205,836✔
3665
    }
3666

3667
    /**
3668
     * @hidden @internal
3669
     */
3670
    public generateRowID(): string | number {
3671
        const primaryColumn = this._columns.find(col => col.field === this.primaryKey);
60✔
3672
        const idType = this.data.length ?
58✔
3673
            this.resolveDataTypes(this.data[0][this.primaryKey]) : primaryColumn ? primaryColumn.dataType : 'string';
1!
3674
        return idType === 'string' ? uuidv4() : FAKE_ROW_ID--;
58✔
3675
    }
3676

3677
    /**
3678
     * @hidden
3679
     * @internal
3680
     */
3681
    public resetForOfCache() {
3682
        const firstVirtRow = this.dataRowList.first;
33,024✔
3683
        if (firstVirtRow) {
33,024✔
3684
            if (this._cdrRequests) {
16,170✔
3685
                firstVirtRow.virtDirRow.cdr.detectChanges();
6,268✔
3686
            }
3687
            firstVirtRow.virtDirRow.assumeMaster();
16,170✔
3688
        }
3689
    }
3690

3691
    /**
3692
     * @hidden
3693
     * @internal
3694
     */
3695
    public setFilteredData(data, pinned: boolean) {
3696
        if (this.hasPinnedRecords && pinned) {
1,540✔
3697
            this._filteredPinnedData = data || [];
49✔
3698
            const filteredUnpinned = this._filteredUnpinnedData || [];
49✔
3699
            const filteredData = [... this._filteredPinnedData, ...filteredUnpinned];
49✔
3700
            this._filteredData = filteredData.length > 0 ? filteredData : this._filteredUnpinnedData;
49✔
3701
        } else if (this.hasPinnedRecords && !pinned) {
1,491✔
3702
            this._filteredUnpinnedData = data;
47✔
3703
        } else {
3704
            this._filteredData = data;
1,444✔
3705
        }
3706
    }
3707

3708
    /**
3709
     * @hidden
3710
     * @internal
3711
     */
3712
    public resetColumnCollections() {
3713
        this._visibleColumns.length = 0;
33,164✔
3714
        this._pinnedVisible.length = 0;
33,164✔
3715
        this._unpinnedVisible.length = 0;
33,164✔
3716
    }
3717

3718
    /**
3719
     * @hidden
3720
     * @internal
3721
     */
3722
    public resetCachedWidths() {
3723
        this._unpinnedWidth = NaN;
44,412✔
3724
        this._pinnedWidth = NaN;
44,412✔
3725
        this._totalWidth = NaN;
44,412✔
3726
    }
3727

3728
    /**
3729
     * @hidden
3730
     * @internal
3731
     */
3732
    public resetCaches(recalcFeatureWidth = true) {
10,227✔
3733
        if (recalcFeatureWidth) {
33,024✔
3734
            this._headerFeaturesWidth = NaN;
33,015✔
3735
            this.summaryService.summaryHeight = 0;
33,015✔
3736
        }
3737
        this.resetForOfCache();
33,024✔
3738
        this.resetColumnsCaches();
33,024✔
3739
        this.resetColumnCollections();
33,024✔
3740
        this.resetCachedWidths();
33,024✔
3741
        this.hasVisibleColumns = undefined;
33,024✔
3742
        this._columnGroups = this._columns.some(col => col.columnGroup);
156,940✔
3743
    }
3744

3745
    /**
3746
     * @hidden
3747
     */
3748
    public ngAfterContentInit() {
3749
        if (this.sortHeaderIconDirectiveTemplate) {
3,384✔
3750
            this.sortHeaderIconTemplate = this.sortHeaderIconDirectiveTemplate;
5✔
3751
        }
3752

3753
        if (this.sortAscendingHeaderIconDirectiveTemplate) {
3,384✔
3754
            this.sortAscendingHeaderIconTemplate = this.sortAscendingHeaderIconDirectiveTemplate;
5✔
3755
        }
3756

3757
        if (this.sortDescendingHeaderIconDirectiveTemplate) {
3,384✔
3758
            this.sortDescendingHeaderIconTemplate = this.sortDescendingHeaderIconDirectiveTemplate;
5✔
3759
        }
3760

3761
        this.setupColumns();
3,384✔
3762
        this.toolbar.changes.pipe(filter(() => !this._init), takeUntil(this.destroy$)).subscribe(() => this.notifyChanges(true));
3,384✔
3763
        this.setUpPaginator();
3,384✔
3764
        this.paginationComponents.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,384✔
3765
            this.setUpPaginator();
64✔
3766
        });
3767
        if (this.actionStrip) {
3,384✔
3768
            this.actionStrip.menuOverlaySettings.outlet = this.outlet;
108✔
3769
        }
3770
    }
3771

3772
    /**
3773
     * @hidden @internal
3774
     */
3775
    public dataRebinding(event: IForOfDataChangingEventArgs) {
3776
        this.dataChanging.emit(event);
6,339✔
3777
    }
3778

3779
    /**
3780
     * @hidden @internal
3781
     */
3782
    public dataRebound(event) {
3783
        this.selectionService.clearHeaderCBState();
6,339✔
3784
        this.dataChanged.emit(event);
6,339✔
3785
    }
3786

3787
    /** @hidden @internal */
3788
    public createFilterDropdown(column: ColumnType, options: OverlaySettings) {
3789
        options.outlet = this.outlet;
198✔
3790
        if (this.excelStyleFilteringComponent) {
198✔
3791
            this.excelStyleFilteringComponent.initialize(column, this.overlayService);
21✔
3792
            const id = this.overlayService.attach(this.excelStyleFilteringComponent.element, options);
21✔
3793
            this.excelStyleFilteringComponent.overlayComponentId = id;
21✔
3794
            return id;
21✔
3795
        }
3796
        const id = this.overlayService.attach(IgxGridExcelStyleFilteringComponent, this.viewRef, options);
177✔
3797
        return id;
177✔
3798
    }
3799

3800
    /** @hidden @internal */
3801
    public setUpPaginator() {
3802
        if (this.paginator) {
3,451✔
3803
            this.paginator.pageChange.pipe(takeWhile(() => !!this.paginator), filter(() => !this._init))
219✔
3804
                .subscribe(() => {
3805
                    this.selectionService.clear(true);
114✔
3806
                    this.crudService.endEdit(false);
114✔
3807
                    this.pipeTrigger++;
114✔
3808
                    this.navigateTo(0);
114✔
3809
                    this.notifyChanges();
114✔
3810
                });
3811
            this.paginator.perPageChange.pipe(takeWhile(() => !!this.paginator), filter(() => !this._init))
219✔
3812
                .subscribe(() => {
3813
                    this.selectionService.clear(true);
70✔
3814
                    this.page = 0;
70✔
3815
                    this.crudService.endEdit(false);
70✔
3816
                    this.notifyChanges();
70✔
3817
                });
3818
        } else {
3819
            this.markForCheck();
3,232✔
3820
        }
3821
    }
3822

3823
    /**
3824
     * @hidden
3825
     * @internal
3826
     */
3827
    public setFilteredSortedData(data, pinned: boolean) {
3828
        data = data || [];
6,311✔
3829
        if (this.pinnedRecordsCount > 0) {
6,311✔
3830
            if (pinned) {
341✔
3831
                this._filteredSortedPinnedData = data;
174✔
3832
                this.pinnedRecords = data;
174✔
3833
                this._filteredSortedData = this.isRowPinningToTop ? [... this._filteredSortedPinnedData, ... this._filteredSortedUnpinnedData] :
174✔
3834
                    [... this._filteredSortedUnpinnedData, ... this._filteredSortedPinnedData];
3835
                this.refreshSearch(true, false);
174✔
3836
            } else {
3837
                this._filteredSortedUnpinnedData = data;
167✔
3838
            }
3839
        } else {
3840
            this._filteredSortedData = data;
5,970✔
3841
            this.refreshSearch(true, false);
5,970✔
3842
        }
3843
        this.buildDataView(data);
6,311✔
3844
    }
3845

3846
    /**
3847
     * @hidden @internal
3848
     */
3849
    public resetHorizontalVirtualization() {
3850
        const elementFilter = (item: IgxRowDirective | IgxSummaryRowComponent) => this.isDefined(item.nativeElement.parentElement);
48,773✔
3851
        this._horizontalForOfs = [
6,616✔
3852
            ...this._dataRowList.filter(elementFilter).map(item => item.virtDirRow),
45,574✔
3853
            ...this._summaryRowList.filter(elementFilter).map(item => item.virtDirRow)
3,199✔
3854
        ];
3855
    }
3856

3857
    /**
3858
     * @hidden @internal
3859
     */
3860
    public _setupRowObservers() {
3861
        const elementFilter = (item: IgxRowDirective | IgxSummaryRowComponent) => this.isDefined(item.nativeElement.parentElement);
17,428✔
3862
        const extractForOfs = pipe(map((collection: any[]) => collection.filter(elementFilter).map(item => item.virtDirRow)));
17,428✔
3863
        const rowListObserver = extractForOfs(this._dataRowList.changes);
3,504✔
3864
        const summaryRowObserver = extractForOfs(this._summaryRowList.changes);
3,504✔
3865
        rowListObserver.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,504✔
3866
            this.resetHorizontalVirtualization();
2,700✔
3867
        });
3868
        summaryRowObserver.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,504✔
3869
            this.resetHorizontalVirtualization();
314✔
3870
        });
3871
        this.resetHorizontalVirtualization();
3,504✔
3872
    }
3873

3874
    /**
3875
     * @hidden @internal
3876
     */
3877
    public _zoneBegoneListeners() {
3878
        this.zone.runOutsideAngular(() => {
3,504✔
3879
            this.verticalScrollContainer.getScroll().addEventListener('scroll', this.verticalScrollHandler.bind(this));
3,504✔
3880
            this.headerContainer?.getScroll().addEventListener('scroll', this.horizontalScrollHandler.bind(this));
3,504✔
3881
            if (this.hasColumnsToAutosize) {
3,504✔
3882
                this.headerContainer?.dataChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
13✔
3883
                    this.cdr.detectChanges();
21✔
3884
                    this.zone.onStable.pipe(first()).subscribe(() => {
21✔
3885
                        this.autoSizeColumnsInView();
20✔
3886
                    });
3887
                });
3888
            }
3889
            fromEvent(window, 'resize').pipe(takeUntil(this.destroy$)).subscribe(() => this.resizeNotify.next());
3,504✔
3890
            resizeObservable(this.nativeElement).pipe(takeUntil(this.destroy$)).subscribe(() => this.resizeNotify.next());
3,504✔
3891
        });
3892
    }
3893

3894
    /**
3895
     * @hidden
3896
     */
3897
    public ngAfterViewInit() {
3898
        this.initPinning();
3,504✔
3899
        this.calculateGridSizes();
3,504✔
3900
        this._init = false;
3,504✔
3901
        this.cdr.reattach();
3,504✔
3902
        this._setupRowObservers();
3,504✔
3903
        this._zoneBegoneListeners();
3,504✔
3904

3905
        const vertScrDC = this.verticalScrollContainer.displayContainer;
3,504✔
3906
        vertScrDC.addEventListener('scroll', this.preventContainerScroll.bind(this));
3,504✔
3907

3908
        this._pinnedRowList.changes
3,504✔
3909
            .pipe(takeUntil(this.destroy$))
3910
            .subscribe((change: QueryList<IgxGridRowComponent>) => {
3911
                this.onPinnedRowsChanged(change);
182✔
3912
            });
3913

3914
        this.addRowSnackbar?.clicked.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,504✔
3915
            const rec = this.filteredSortedData[this.lastAddedRowIndex];
4✔
3916
            this.scrollTo(rec, 0);
4✔
3917
            this.addRowSnackbar.close();
4✔
3918
        });
3919

3920
        // Keep the stream open for future subscribers
3921
        this.rendered$.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,504✔
3922
            if (this.paginator) {
1,949✔
3923
                this.paginator.totalRecords = this.totalRecords ? this.totalRecords : this.paginator.totalRecords;
73✔
3924
                this.paginator.overlaySettings = { outlet: this.outlet };
73✔
3925
            }
3926
            if (this.hasColumnsToAutosize) {
1,949✔
3927
                this.autoSizeColumnsInView();
12✔
3928
            }
3929
            this._rendered = true;
1,949✔
3930
        });
3931
        Promise.resolve().then(() => this.rendered.next(true));
3,504✔
3932
    }
3933

3934
    /**
3935
     * @hidden @internal
3936
     */
3937
    public notifyChanges(repaint = false) {
14,713✔
3938
        this._cdrRequests = true;
461,559✔
3939
        this._cdrRequestRepaint = repaint;
461,559✔
3940
        this.cdr.markForCheck();
461,559✔
3941
    }
3942

3943
    /**
3944
     * @hidden @internal
3945
     */
3946
    public override ngDoCheck() {
3947
        super.ngDoCheck();
17,338✔
3948
        if (this._init) {
17,338✔
3949
            return;
4,020✔
3950
        }
3951

3952
        if (this._cdrRequestRepaint) {
13,318✔
3953
            this.resetNotifyChanges();
3,800✔
3954
            this.calculateGridSizes();
3,800✔
3955
            this.refreshSearch(true);
3,800✔
3956
            return;
3,800✔
3957
        }
3958

3959
        if (this._cdrRequests) {
9,518✔
3960
            this.resetNotifyChanges();
4,162✔
3961
            this.cdr.detectChanges();
4,162✔
3962
        }
3963
    }
3964

3965
    /**
3966
     * @hidden
3967
     * @internal
3968
     */
3969
    public getDragGhostCustomTemplate() {
3970

3971
        return this.dragGhostCustomTemplate;
1,993✔
3972
    }
3973

3974
    /**
3975
     * @hidden @internal
3976
     */
3977
    public ngOnDestroy() {
3978
        this.tmpOutlets.forEach((tmplOutlet) => {
3,305✔
3979
            tmplOutlet.cleanCache();
24,444✔
3980
        });
3981

3982
        this.destroy$.next(true);
3,305✔
3983
        this.destroy$.complete();
3,305✔
3984
        this.transactionChange$.next();
3,305✔
3985
        this.transactionChange$.complete();
3,305✔
3986
        this._destroyed = true;
3,305✔
3987

3988
        this.textHighlightService.destroyGroup(this.id);
3,305✔
3989

3990
        if (this._advancedFilteringOverlayId) {
3,305!
3991
            this.overlayService.detach(this._advancedFilteringOverlayId);
×
3992
            delete this._advancedFilteringOverlayId;
×
3993
        }
3994

3995
        this.overlayIDs.forEach(overlayID => {
3,305✔
3996
            const overlay = this.overlayService.getOverlayById(overlayID);
23✔
3997

3998
            if (overlay && !overlay.detached) {
23✔
3999
                this.overlayService.detach(overlayID);
13✔
4000
            }
4001
        });
4002

4003
        this.zone.runOutsideAngular(() => {
3,305✔
4004
            this.verticalScrollContainer?.getScroll()?.removeEventListener('scroll', this.verticalScrollHandler);
3,305✔
4005
            this.headerContainer?.getScroll()?.removeEventListener('scroll', this.horizontalScrollHandler);
3,305✔
4006
            const vertScrDC = this.verticalScrollContainer?.displayContainer;
3,305✔
4007
            vertScrDC?.removeEventListener('scroll', this.preventContainerScroll);
3,305✔
4008
        });
4009
    }
4010

4011
    /**
4012
     * Toggles the specified column's visibility.
4013
     *
4014
     * @example
4015
     * ```typescript
4016
     * this.grid1.toggleColumnVisibility({
4017
     *       column: this.grid1.columns[0],
4018
     *       newValue: true
4019
     * });
4020
     * ```
4021
     */
4022
    public toggleColumnVisibility(args: IColumnVisibilityChangedEventArgs) {
4023
        const col = args.column ? this._columns.find((c) => c === args.column) : undefined;
×
4024

4025
        if (!col) {
×
4026
            return;
×
4027
        }
4028
        col.toggleVisibility(args.newValue);
×
4029
    }
4030

4031
    /**
4032
     * Gets/Sets a list of key-value pairs [row ID, expansion state].
4033
     *
4034
     * @remarks
4035
     * Includes only states that differ from the default one.
4036
     * Supports two-way binding.
4037
     * @example
4038
     * ```html
4039
     * <igx-grid #grid [data]="data" [(expansionStates)]="model.expansionStates">
4040
     * </igx-grid>
4041
     * ```
4042
     */
4043
    @Input()
4044
    public get expansionStates() {
4045
        return this._expansionStates;
219,505✔
4046
    }
4047

4048
    public set expansionStates(value) {
4049
        this._expansionStates = new Map<any, boolean>(value);
664✔
4050
        this.expansionStatesChange.emit(this._expansionStates);
664✔
4051
        this.notifyChanges(true);
664✔
4052
        if (this.gridAPI.grid) {
664✔
4053
            this.cdr.detectChanges();
573✔
4054
        }
4055
    }
4056

4057
    /**
4058
     * Expands all rows.
4059
     *
4060
     * @example
4061
     * ```typescript
4062
     * this.grid.expandAll();
4063
     * ```
4064
     */
4065
    public expandAll() {
4066
        this._defaultExpandState = true;
8✔
4067
        this.expansionStates = new Map<any, boolean>();
8✔
4068
    }
4069

4070
    /**
4071
     * Collapses all rows.
4072
     *
4073
     * @example
4074
     * ```typescript
4075
     * this.grid.collapseAll();
4076
     * ```
4077
     */
4078
    public collapseAll() {
4079
        this._defaultExpandState = false;
2✔
4080
        this.expansionStates = new Map<any, boolean>();
2✔
4081
    }
4082

4083
    /**
4084
     * Expands the row by its id.
4085
     *
4086
     * @remarks
4087
     * ID is either the primaryKey value or the data record instance.
4088
     * @example
4089
     * ```typescript
4090
     * this.grid.expandRow(rowID);
4091
     * ```
4092
     * @param rowID The row id - primaryKey value or the data record instance.
4093
     */
4094
    public expandRow(rowID: any) {
4095
        this.gridAPI.set_row_expansion_state(rowID, true);
54✔
4096
    }
4097

4098
    /**
4099
     * Collapses the row by its id.
4100
     *
4101
     * @remarks
4102
     * ID is either the primaryKey value or the data record instance.
4103
     * @example
4104
     * ```typescript
4105
     * this.grid.collapseRow(rowID);
4106
     * ```
4107
     * @param rowID The row id - primaryKey value or the data record instance.
4108
     */
4109
    public collapseRow(rowID: any) {
4110
        this.gridAPI.set_row_expansion_state(rowID, false);
8✔
4111
    }
4112

4113

4114
    /**
4115
     * Toggles the row by its id.
4116
     *
4117
     * @remarks
4118
     * ID is either the primaryKey value or the data record instance.
4119
     * @example
4120
     * ```typescript
4121
     * this.grid.toggleRow(rowID);
4122
     * ```
4123
     * @param rowID The row id - primaryKey value or the data record instance.
4124
     */
4125
    public toggleRow(rowID: any) {
4126
        const rec = this.gridAPI.get_rec_by_id(rowID);
79✔
4127
        const state = this.gridAPI.get_row_expansion_state(rec);
79✔
4128
        this.gridAPI.set_row_expansion_state(rowID, !state);
79✔
4129
    }
4130

4131
    /**
4132
     * @hidden
4133
     * @internal
4134
     */
4135
    public getDefaultExpandState(_rec: any) {
4136
        return this._defaultExpandState;
8,436✔
4137
    }
4138

4139
    /**
4140
     * Gets the native element.
4141
     *
4142
     * @example
4143
     * ```typescript
4144
     * const nativeEl = this.grid.nativeElement.
4145
     * ```
4146
     */
4147
    public get nativeElement() {
4148
        return this.elementRef.nativeElement;
36,399✔
4149
    }
4150

4151
    /**
4152
     * Gets/Sets the outlet used to attach the grid's overlays to.
4153
     *
4154
     * @remark
4155
     * If set, returns the outlet defined outside the grid. Otherwise returns the grid's internal outlet directive.
4156
     */
4157
    @Input()
4158
    public get outlet() {
4159
        return this.resolveOutlet();
182,471✔
4160
    }
4161

4162
    public set outlet(val: IgxOverlayOutletDirective) {
4163
        this._userOutletDirective = val;
×
4164
    }
4165

4166

4167
    /**
4168
     * Gets the default row height.
4169
     *
4170
     * @example
4171
     * ```typescript
4172
     * const rowHeigh = this.grid.defaultRowHeight;
4173
     * ```
4174
     */
4175
    public get defaultRowHeight(): number {
4176
        switch (this.displayDensity) {
1,444,293✔
4177
            case DisplayDensity.cosy:
4178
                return 40;
68,293✔
4179
            case DisplayDensity.compact:
4180
                return 32;
19,709✔
4181
            default:
4182
                return 50;
1,356,291✔
4183
        }
4184
    }
4185

4186
    /**
4187
     * @hidden @internal
4188
     */
4189
    public get defaultSummaryHeight(): number {
4190
        switch (this.displayDensity) {
15,684✔
4191
            case DisplayDensity.cosy:
4192
                return 30;
57✔
4193
            case DisplayDensity.compact:
4194
                return 24;
146✔
4195
            default:
4196
                return 36;
15,481✔
4197
        }
4198
    }
4199

4200
    /**
4201
     * Returns the `IgxGridHeaderGroupComponent`'s minimum allowed width.
4202
     *
4203
     * @remarks
4204
     * Used internally for restricting header group component width.
4205
     * The values below depend on the header cell default right/left padding values.
4206
     */
4207
    public get defaultHeaderGroupMinWidth(): number {
4208
        switch (this.displayDensity) {
421,066✔
4209
            case DisplayDensity.cosy:
4210
                return 32;
28,654✔
4211
            case DisplayDensity.compact:
4212
                return 24;
3,088✔
4213
            default:
4214
                return 48;
389,324✔
4215
        }
4216
    }
4217

4218
    /** @hidden @internal */
4219
    public get pinnedWidth() {
4220
        if (!isNaN(this._pinnedWidth)) {
214,459✔
4221
            return this._pinnedWidth;
193,766✔
4222
        }
4223
        this._pinnedWidth = this.getPinnedWidth();
20,693✔
4224
        return this._pinnedWidth;
20,693✔
4225
    }
4226

4227
    /** @hidden @internal */
4228
    public get unpinnedWidth() {
4229
        if (!isNaN(this._unpinnedWidth)) {
345,331✔
4230
            return this._unpinnedWidth;
322,745✔
4231
        }
4232
        this._unpinnedWidth = this.getUnpinnedWidth();
22,586✔
4233
        return this._unpinnedWidth;
22,586✔
4234
    }
4235

4236
    /**
4237
     * @hidden @internal
4238
     */
4239
    public isHorizontalScrollHidden = false;
3,996✔
4240

4241
    /**
4242
     * @hidden @internal
4243
     * Gets the header cell inner width for auto-sizing.
4244
     */
4245
    public getHeaderCellWidth(element: HTMLElement): ISizeInfo {
4246
        const range = this.document.createRange();
35✔
4247
        const headerWidth = this.platform.getNodeSizeViaRange(range,
35✔
4248
            element,
4249
            element.parentElement);
4250

4251
        const headerStyle = this.document.defaultView.getComputedStyle(element);
35✔
4252
        const headerPadding = parseFloat(headerStyle.paddingLeft) + parseFloat(headerStyle.paddingRight) +
35✔
4253
            parseFloat(headerStyle.borderRightWidth);
4254

4255
        // Take into consideration the header group element, since column pinning applies borders to it if its not a columnGroup.
4256
        const headerGroupStyle = this.document.defaultView.getComputedStyle(element.parentElement);
35✔
4257
        const borderSize = parseFloat(headerGroupStyle.borderRightWidth) + parseFloat(headerGroupStyle.borderLeftWidth);
35✔
4258
        return { width: Math.ceil(headerWidth), padding: Math.ceil(headerPadding + borderSize) };
35✔
4259
    }
4260

4261
    /**
4262
     * @hidden @internal
4263
     * Gets the combined width of the columns that are specific to the enabled grid features. They are fixed.
4264
     */
4265
    public featureColumnsWidth(expander?: ElementRef) {
4266
        if (Number.isNaN(this._headerFeaturesWidth)) {
126,461✔
4267
            // TODO: platformUtil.isBrowser check
4268
            const rowSelectArea = this.headerSelectorContainer?.nativeElement?.getBoundingClientRect ?
28,167✔
4269
                this.headerSelectorContainer.nativeElement.getBoundingClientRect().width : 0;
4270
            const rowDragArea = this.rowDraggable && this.headerDragContainer?.nativeElement?.getBoundingClientRect ?
28,167✔
4271
                this.headerDragContainer.nativeElement.getBoundingClientRect().width : 0;
4272
            const groupableArea = this.headerGroupContainer?.nativeElement?.getBoundingClientRect ?
28,167✔
4273
                this.headerGroupContainer.nativeElement.getBoundingClientRect().width : 0;
4274
            const expanderWidth = expander?.nativeElement?.getBoundingClientRect ? expander.nativeElement.getBoundingClientRect().width : 0;
28,167✔
4275
            this._headerFeaturesWidth = rowSelectArea + rowDragArea + groupableArea + expanderWidth;
28,167✔
4276
        }
4277
        return this._headerFeaturesWidth;
126,461✔
4278
    }
4279

4280
    /**
4281
     * @hidden @internal
4282
     */
4283
    public get summariesMargin() {
4284
        return this.featureColumnsWidth();
20,985✔
4285
    }
4286

4287
    /**
4288
     * Gets an array of `IgxColumnComponent`s.
4289
     *
4290
     * @example
4291
     * ```typescript
4292
     * const colums = this.grid.columns.
4293
     * ```
4294
     */
4295
    public get columns(): IgxColumnComponent[] {
4296
        return this._columns || [];
33,247!
4297
    }
4298

4299
    /**
4300
     * Gets an array of the pinned `IgxColumnComponent`s.
4301
     *
4302
     * @example
4303
     * ```typescript
4304
     * const pinnedColumns = this.grid.pinnedColumns.
4305
     * ```
4306
     */
4307
    public get pinnedColumns(): IgxColumnComponent[] {
4308
        if (this._pinnedVisible.length) {
3,750,311✔
4309
            return this._pinnedVisible;
332,554✔
4310
        }
4311
        this._pinnedVisible = this._pinnedColumns.filter(col => !col.hidden);
3,417,757✔
4312
        return this._pinnedVisible;
3,417,757✔
4313
    }
4314

4315
    /**
4316
     * Gets an array of the pinned `IgxRowComponent`s.
4317
     *
4318
     * @example
4319
     * ```typescript
4320
     * const pinnedRow = this.grid.pinnedRows;
4321
     * ```
4322
     */
4323
    public get pinnedRows(): IgxGridRowComponent[] {
4324
        return this._pinnedRowList.toArray().sort((a, b) => a.index - b.index);
147✔
4325
    }
4326

4327
    /**
4328
     * Gets an array of unpinned `IgxColumnComponent`s.
4329
     *
4330
     * @example
4331
     * ```typescript
4332
     * const unpinnedColumns = this.grid.unpinnedColumns.
4333
     * ```
4334
     */
4335
    public get unpinnedColumns(): IgxColumnComponent[] {
4336
        if (this._unpinnedVisible.length) {
723,533✔
4337
            return this._unpinnedVisible;
698,103✔
4338
        }
4339
        this._unpinnedVisible = this._unpinnedColumns.filter((col) => !col.hidden);
144,772✔
4340
        return this._unpinnedVisible;
25,430✔
4341
    }
4342

4343
    /**
4344
     * Gets the `width` to be set on `IgxGridHeaderGroupComponent`.
4345
     */
4346
    public getHeaderGroupWidth(column: IgxColumnComponent): string {
4347
        return this.hasColumnLayouts
×
4348
            ? ''
4349
            : `${Math.max(parseFloat(column.calcWidth), this.defaultHeaderGroupMinWidth)}px`;
4350
    }
4351

4352
    /**
4353
     * Returns the `IgxColumnComponent` by field name.
4354
     *
4355
     * @example
4356
     * ```typescript
4357
     * const myCol = this.grid1.getColumnByName("ID");
4358
     * ```
4359
     * @param name
4360
     */
4361
    public getColumnByName(name: string): IgxColumnComponent {
4362
        return this._columns.find((col) => col.field === name);
122,162✔
4363
    }
4364

4365
    public getColumnByVisibleIndex(index: number): IgxColumnComponent {
4366
        return this.visibleColumns.find((col) =>
2,116✔
4367
            !col.columnGroup && !col.columnLayout &&
11,900✔
4368
            col.visibleIndex === index
4369
        );
4370
    }
4371

4372
    /**
4373
     * Recalculates all widths of columns that have size set to `auto`.
4374
     *
4375
     * @example
4376
     * ```typescript
4377
     * this.grid1.recalculateAutoSizes();
4378
     * ```
4379
     */
4380
    public recalculateAutoSizes() {
4381
        // reset auto-size and calculate it again.
4382
        this._columns.forEach(x => x.autoSize = undefined);
12✔
4383
        this.resetCaches();
2✔
4384
        this.zone.onStable.pipe(first()).subscribe(() => {
2✔
4385
            this.cdr.detectChanges();
2✔
4386
            this.autoSizeColumnsInView();
2✔
4387
        });
4388
    }
4389

4390
    /**
4391
     * Returns an array of visible `IgxColumnComponent`s.
4392
     *
4393
     * @example
4394
     * ```typescript
4395
     * const visibleColumns = this.grid.visibleColumns.
4396
     * ```
4397
     */
4398
    public get visibleColumns(): IgxColumnComponent[] {
4399
        if (this._visibleColumns.length) {
157,414✔
4400
            return this._visibleColumns;
131,498✔
4401
        }
4402
        this._visibleColumns = this._columns.filter(c => !c.hidden);
145,187✔
4403
        return this._visibleColumns;
25,916✔
4404
    }
4405

4406
    /**
4407
     * Returns the total number of records.
4408
     *
4409
     * @remarks
4410
     * Only functions when paging is enabled.
4411
     * @example
4412
     * ```typescript
4413
     * const totalRecords = this.grid.totalRecords;
4414
     * ```
4415
     */
4416
    @Input()
4417
    public get totalRecords(): number {
4418
        return this._totalRecords >= 0 ? this._totalRecords : this.pagingState?.metadata.countRecords;
145✔
4419
    }
4420

4421
    public set totalRecords(total: number) {
4422
        if (total >= 0) {
1✔
4423
            if (this.paginator) {
1✔
4424
                this.paginator.totalRecords = total;
1✔
4425
            }
4426
            this._totalRecords = total;
1✔
4427
            this.pipeTrigger++;
1✔
4428
            this.notifyChanges();
1✔
4429
        }
4430
    }
4431

4432
    /** @hidden @internal */
4433
    public get totalWidth(): number {
4434
        if (!isNaN(this._totalWidth)) {
10,597✔
4435
            return this._totalWidth;
2,784✔
4436
        }
4437
        // Take only top level columns
4438
        const cols = this.visibleColumns.filter(col => col.level === 0 && !col.pinned);
47,748✔
4439
        let totalWidth = 0;
7,813✔
4440
        let i = 0;
7,813✔
4441
        for (i; i < cols.length; i++) {
7,813✔
4442
            totalWidth += parseInt(cols[i].calcWidth, 10) || 0;
38,812!
4443
        }
4444
        this._totalWidth = totalWidth;
7,813✔
4445
        return totalWidth;
7,813✔
4446
    }
4447

4448
    /**
4449
     * @hidden
4450
     * @internal
4451
     */
4452
    public get showRowSelectors(): boolean {
4453
        return this.isRowSelectable && this.hasVisibleColumns && !this.hideRowSelectors;
263,133✔
4454
    }
4455

4456
    /**
4457
     * @hidden
4458
     * @internal
4459
     */
4460
    public get showAddButton() {
4461
        return this.rowEditable && this.dataView.length === 0 && this._columns.length > 0;
895✔
4462
    }
4463

4464
    /**
4465
     * @hidden
4466
     * @internal
4467
     */
4468
    public get showDragIcons(): boolean {
4469
        return this.rowDraggable && this._columns.length > this.hiddenColumnsCount;
×
4470
    }
4471

4472
    /**
4473
     * @hidden
4474
     * @internal
4475
     */
4476
    protected _getDataViewIndex(index: number): number {
4477
        let newIndex = index;
56,256✔
4478
        if ((index < 0 || index >= this.dataView.length) && this.pagingMode === 1 && this.page !== 0) {
56,256!
4479
            newIndex = index - this.perPage * this.page;
×
4480
        } else if (this.gridAPI.grid.verticalScrollContainer.isRemote) {
56,256!
4481
            newIndex = index - this.gridAPI.grid.virtualizationState.startIndex;
×
4482
        }
4483
        return newIndex;
56,256✔
4484
    }
4485

4486
    /**
4487
     * @hidden
4488
     * @internal
4489
     */
4490
    protected getDataIndex(dataViewIndex: number): number {
4491
        let newIndex = dataViewIndex;
23✔
4492
        if (this.gridAPI.grid.verticalScrollContainer.isRemote) {
23!
4493
            newIndex = dataViewIndex + this.gridAPI.grid.virtualizationState.startIndex;
×
4494
        }
4495
        return newIndex;
23✔
4496
    }
4497

4498
    /**
4499
     * Places a column before or after the specified target column.
4500
     *
4501
     * @example
4502
     * ```typescript
4503
     * grid.moveColumn(column, target);
4504
     * ```
4505
     */
4506
    public moveColumn(column: IgxColumnComponent, target: IgxColumnComponent, pos: DropPosition = DropPosition.AfterDropTarget) {
51✔
4507
        // M.A. May 11th, 2021 #9508 Make the event cancelable
4508
        const eventArgs: IColumnMovingEndEventArgs = { source: column, target, cancel: false };
150✔
4509

4510
        this.columnMovingEnd.emit(eventArgs);
150✔
4511

4512
        if (eventArgs.cancel) {
150!
4513
            return;
×
4514
        }
4515

4516
        if (column === target || (column.level !== target.level) ||
150✔
4517
            (column.topLevelParent !== target.topLevelParent)) {
4518
            return;
22✔
4519
        }
4520

4521
        if (column.level) {
128✔
4522
            this._moveChildColumns(column.parent, column, target, pos);
16✔
4523
        }
4524

4525
        // let columnPinStateChanged;
4526
        // pinning and unpinning will work correctly even without passing index
4527
        // but is easier to calclulate the index here, and later use it in the pinning event args
4528
        if (target.pinned && !column.pinned) {
128✔
4529
            const pinnedIndex = this._pinnedColumns.indexOf(target);
8✔
4530
            const index = pos === DropPosition.AfterDropTarget ? pinnedIndex + 1 : pinnedIndex;
8✔
4531
            column.pin(index);
8✔
4532
        }
4533

4534
        if (!target.pinned && column.pinned) {
128✔
4535
            const unpinnedIndex = this._unpinnedColumns.indexOf(target);
3✔
4536
            const index = pos === DropPosition.AfterDropTarget ? unpinnedIndex + 1 : unpinnedIndex;
3✔
4537
            column.unpin(index);
3✔
4538
        }
4539

4540
        // if (target.pinned && column.pinned && !columnPinStateChanged) {
4541
        //     this._reorderColumns(column, target, pos, this._pinnedColumns);
4542
        // }
4543

4544
        // if (!target.pinned && !column.pinned && !columnPinStateChanged) {
4545
        //     this._reorderColumns(column, target, pos, this._unpinnedColumns);
4546
        // }
4547

4548
        this._moveColumns(column, target, pos);
128✔
4549
        this._columnsReordered(column);
128✔
4550
    }
4551

4552
    /**
4553
     * Triggers change detection for the `IgxGridComponent`.
4554
     * Calling markForCheck also triggers the grid pipes explicitly, resulting in all updates being processed.
4555
     * May degrade performance if used when not needed, or if misused:
4556
     * ```typescript
4557
     * // DON'Ts:
4558
     * // don't call markForCheck from inside a loop
4559
     * // don't call markForCheck when a primitive has changed
4560
     * grid.data.forEach(rec => {
4561
     *  rec = newValue;
4562
     *  grid.markForCheck();
4563
     * });
4564
     *
4565
     * // DOs
4566
     * // call markForCheck after updating a nested property
4567
     * grid.data.forEach(rec => {
4568
     *  rec.nestedProp1.nestedProp2 = newValue;
4569
     * });
4570
     * grid.markForCheck();
4571
     * ```
4572
     *
4573
     * @example
4574
     * ```typescript
4575
     * grid.markForCheck();
4576
     * ```
4577
     */
4578
    public markForCheck() {
4579
        this.pipeTrigger++;
3,235✔
4580
        this.cdr.detectChanges();
3,235✔
4581
    }
4582

4583
    /**
4584
     * Creates a new `IgxGridRowComponent` and adds the data record to the end of the data source.
4585
     *
4586
     * @example
4587
     * ```typescript
4588
     * this.grid1.addRow(record);
4589
     * ```
4590
     * @param data
4591
     */
4592
    public addRow(data: any): void {
4593
        // commit pending states prior to adding a row
4594
        this.crudService.endEdit(true);
171✔
4595
        this.gridAPI.addRowToData(data);
171✔
4596

4597
        this.pipeTrigger++;
171✔
4598
        this.rowAddedNotifier.next({ data: data, rowData: data, owner: this, primaryKey: data[this.primaryKey], rowKey: data[this.primaryKey] });
171✔
4599
        this.notifyChanges();
171✔
4600
    }
4601

4602
    /**
4603
     * Removes the `IgxGridRowComponent` and the corresponding data record by primary key.
4604
     *
4605
     * @remarks
4606
     * Requires that the `primaryKey` property is set.
4607
     * The method accept rowSelector as a parameter, which is the rowID.
4608
     * @example
4609
     * ```typescript
4610
     * this.grid1.deleteRow(0);
4611
     * ```
4612
     * @param rowSelector
4613
     */
4614
    public deleteRow(rowSelector: any): any {
4615
        if (this.primaryKey !== undefined && this.primaryKey !== null) {
82✔
4616
            return this.deleteRowById(rowSelector);
82✔
4617
        }
4618
    }
4619

4620
    /** @hidden */
4621
    public deleteRowById(rowId: any): any {
4622
        const args: IRowDataCancelableEventArgs = {
79✔
4623
            rowID: rowId,
4624
            primaryKey: rowId,
4625
            rowKey: rowId,
4626
            rowData: this.getRowData(rowId),
4627
            data: this.getRowData(rowId),
4628
            oldValue: this.getRowData(rowId),
4629
            owner: this,
4630
            isAddRow: false,
4631
            cancel: false
4632
        };
4633
        this.rowDelete.emit(args);
79✔
4634
        if (args.cancel) {
79!
4635
            return;
×
4636
        }
4637

4638
        const record = this.gridAPI.deleteRowById(rowId);
79✔
4639
        if (record !== null && record !== undefined) {
79✔
4640
            const rowDeletedEventArgs: IRowDataEventArgs = {
78✔
4641
                data: record,
4642
                rowData: record,
4643
                owner: this,
4644
                primaryKey: record[this.primaryKey],
4645
                rowKey: record[this.primaryKey]
4646
            };
4647
            this.rowDeleted.emit(rowDeletedEventArgs);
78✔
4648
        }
4649
        return record;
79✔
4650
    }
4651

4652
    /**
4653
     * Updates the `IgxGridRowComponent` and the corresponding data record by primary key.
4654
     *
4655
     * @remarks
4656
     * Requires that the `primaryKey` property is set.
4657
     * @example
4658
     * ```typescript
4659
     * this.gridWithPK.updateCell('Updated', 1, 'ProductName');
4660
     * ```
4661
     * @param value the new value which is to be set.
4662
     * @param rowSelector corresponds to rowID.
4663
     * @param column corresponds to column field.
4664
     */
4665
    public updateCell(value: any, rowSelector: any, column: string): void {
4666
        if (this.isDefined(this.primaryKey)) {
19✔
4667
            const col = this._columns.find(c => c.field === column);
47✔
4668
            if (col) {
19✔
4669
                // Simplify
4670
                const rowData = this.gridAPI.getRowData(rowSelector);
19✔
4671
                const index = this.gridAPI.get_row_index_in_data(rowSelector);
19✔
4672
                // If row passed is invalid
4673
                if (index < 0) {
19✔
4674
                    return;
1✔
4675
                }
4676

4677
                const id = {
18✔
4678
                    rowID: rowSelector,
4679
                    columnID: col.index,
4680
                    rowIndex: index
4681
                };
4682

4683
                const cell = new IgxCell(id, index, col, rowData[col.field], value, rowData, this as any);
18✔
4684
                const formControl = this.validation.getFormControl(cell.id.rowID, cell.column.field);
18✔
4685
                formControl.setValue(value);
18✔
4686
                this.gridAPI.update_cell(cell);
18✔
4687
                this.cdr.detectChanges();
18✔
4688
            }
4689
        }
4690
    }
4691

4692
    /**
4693
     * Updates the `IgxGridRowComponent`
4694
     *
4695
     * @remarks
4696
     * The row is specified by
4697
     * rowSelector parameter and the data source record with the passed value.
4698
     * This method will apply requested update only if primary key is specified in the grid.
4699
     * @example
4700
     * ```typescript
4701
     * grid.updateRow({
4702
     *       ProductID: 1, ProductName: 'Spearmint', InStock: true, UnitsInStock: 1, OrderDate: new Date('2005-03-21')
4703
     *   }, 1);
4704
     * ```
4705
     * @param value–
4706
     * @param rowSelector correspond to rowID
4707
     */
4708
    // TODO: prevent event invocation
4709
    public updateRow(value: any, rowSelector: any): void {
4710
        if (this.isDefined(this.primaryKey)) {
32✔
4711
            const editableCell = this.crudService.cell;
32✔
4712
            if (editableCell && editableCell.id.rowID === rowSelector) {
32!
4713
                this.crudService.endCellEdit();
×
4714
            }
4715
            const row = new IgxEditRow(rowSelector, -1, this.gridAPI.getRowData(rowSelector), this as any);
32✔
4716
            this.gridAPI.update_row(row, value);
32✔
4717

4718
            // TODO: fix for #5934 and probably break for #5763
4719
            // consider adding of third optional boolean parameter in updateRow.
4720
            // If developer set this parameter to true we should call notifyChanges(true), and
4721
            // vise-versa if developer set it to false we should call notifyChanges(false).
4722
            // The parameter should default to false
4723
            this.notifyChanges();
32✔
4724
        }
4725
    }
4726

4727
    /**
4728
     * Returns the data that is contained in the row component.
4729
     *
4730
     * @remarks
4731
     * If the primary key is not specified the row selector match the row data.
4732
     * @example
4733
     * ```typescript
4734
     * const data = grid.getRowData(94741);
4735
     * ```
4736
     * @param rowSelector correspond to rowID
4737
     */
4738
    public getRowData(rowSelector: any) {
4739
        if (!this.primaryKey) {
427✔
4740
            return rowSelector;
19✔
4741
        }
4742
        const data = this.gridAPI.get_all_data(this.transactions.enabled);
408✔
4743
        const index = this.gridAPI.get_row_index_in_data(rowSelector);
408✔
4744
        return index < 0 ? {} : data[index];
408✔
4745
    }
4746

4747
    /**
4748
     * Sort a single `IgxColumnComponent`.
4749
     *
4750
     * @remarks
4751
     * Sort the `IgxGridComponent`'s `IgxColumnComponent` based on the provided array of sorting expressions.
4752
     * @example
4753
     * ```typescript
4754
     * this.grid.sort({ fieldName: name, dir: SortingDirection.Asc, ignoreCase: false });
4755
     * ```
4756
     */
4757
    public sort(expression: ISortingExpression | Array<ISortingExpression>): void {
4758
        const sortingState = cloneArray(this.sortingExpressions);
155✔
4759

4760
        if (expression instanceof Array) {
155✔
4761
            for (const each of expression) {
3✔
4762
                this.gridAPI.prepare_sorting_expression([sortingState], each);
6✔
4763
            }
4764
        } else {
4765
            if (this._sortingOptions.mode === 'single') {
152✔
4766
                this._columns.forEach((col) => {
4✔
4767
                    if (!(col.field === expression.fieldName)) {
12✔
4768
                        this.clearSort(col.field);
8✔
4769
                    }
4770
                });
4771
            }
4772
            this.gridAPI.prepare_sorting_expression([sortingState], expression);
152✔
4773
        }
4774

4775
        const eventArgs: ISortingEventArgs = { owner: this, sortingExpressions: sortingState, cancel: false };
155✔
4776
        this.sorting.emit(eventArgs);
155✔
4777

4778
        if (eventArgs.cancel) {
155!
4779
            return;
×
4780
        }
4781

4782
        this.crudService.endEdit(false);
155✔
4783
        if (expression instanceof Array) {
155✔
4784
            this.gridAPI.sort_multiple(expression);
3✔
4785
        } else {
4786
            this.gridAPI.sort(expression);
152✔
4787
        }
4788
        requestAnimationFrame(() => this.sortingDone.emit(expression));
155✔
4789
    }
4790

4791
    /**
4792
     * Filters a single `IgxColumnComponent`.
4793
     *
4794
     * @example
4795
     * ```typescript
4796
     * public filter(term) {
4797
     *      this.grid.filter("ProductName", term, IgxStringFilteringOperand.instance().condition("contains"));
4798
     * }
4799
     * ```
4800
     * @param name
4801
     * @param value
4802
     * @param conditionOrExpressionTree
4803
     * @param ignoreCase
4804
     */
4805
    public filter(name: string, value: any, conditionOrExpressionTree?: IFilteringOperation | IFilteringExpressionsTree,
4806
        ignoreCase?: boolean) {
4807
        this.filteringService.filter(name, value, conditionOrExpressionTree, ignoreCase);
275✔
4808
    }
4809

4810
    /**
4811
     * Filters all the `IgxColumnComponent` in the `IgxGridComponent` with the same condition.
4812
     *
4813
     * @example
4814
     * ```typescript
4815
     * grid.filterGlobal('some', IgxStringFilteringOperand.instance().condition('contains'));
4816
     * ```
4817
     * @param value
4818
     * @param condition
4819
     * @param ignoreCase
4820
     */
4821
    public filterGlobal(value: any, condition, ignoreCase?) {
4822
        this.filteringService.filterGlobal(value, condition, ignoreCase);
3✔
4823
    }
4824

4825
    /**
4826
     * Enables summaries for the specified column and applies your customSummary.
4827
     *
4828
     * @remarks
4829
     * If you do not provide the customSummary, then the default summary for the column data type will be applied.
4830
     * @example
4831
     * ```typescript
4832
     * grid.enableSummaries([{ fieldName: 'ProductName' }, { fieldName: 'ID' }]);
4833
     * ```
4834
     * Enable summaries for the listed columns.
4835
     * @example
4836
     * ```typescript
4837
     * grid.enableSummaries('ProductName');
4838
     * ```
4839
     * @param rest
4840
     */
4841
    public enableSummaries(...rest) {
4842
        if (rest.length === 1 && Array.isArray(rest[0])) {
7✔
4843
            this._multipleSummaries(rest[0], true);
4✔
4844
        } else {
4845
            this._summaries(rest[0], true, rest[1]);
3✔
4846
        }
4847
    }
4848

4849
    /**
4850
     * Disable summaries for the specified column.
4851
     *
4852
     * @example
4853
     * ```typescript
4854
     * grid.disableSummaries('ProductName');
4855
     * ```
4856
     * @remarks
4857
     * Disable summaries for the listed columns.
4858
     * @example
4859
     * ```typescript
4860
     * grid.disableSummaries([{ fieldName: 'ProductName' }]);
4861
     * ```
4862
     */
4863
    public disableSummaries(...rest) {
4864
        if (rest.length === 1 && Array.isArray(rest[0])) {
7✔
4865
            this._disableMultipleSummaries(rest[0]);
5✔
4866
        } else {
4867
            this._summaries(rest[0], false);
2✔
4868
        }
4869
    }
4870

4871
    /**
4872
     * If name is provided, clears the filtering state of the corresponding `IgxColumnComponent`.
4873
     *
4874
     * @remarks
4875
     * Otherwise clears the filtering state of all `IgxColumnComponent`s.
4876
     * @example
4877
     * ```typescript
4878
     * this.grid.clearFilter();
4879
     * ```
4880
     * @param name
4881
     */
4882
    public clearFilter(name?: string) {
4883
        this.filteringService.clearFilter(name);
147✔
4884
    }
4885

4886
    /**
4887
     * If name is provided, clears the sorting state of the corresponding `IgxColumnComponent`.
4888
     *
4889
     * @remarks
4890
     * otherwise clears the sorting state of all `IgxColumnComponent`.
4891
     * @example
4892
     * ```typescript
4893
     * this.grid.clearSort();
4894
     * ```
4895
     * @param name
4896
     */
4897
    public clearSort(name?: string) {
4898
        if (!name) {
34✔
4899
            this.sortingExpressions = [];
21✔
4900
            return;
21✔
4901
        }
4902
        if (!this.gridAPI.get_column_by_name(name)) {
13!
4903
            return;
×
4904
        }
4905
        this.gridAPI.clear_sort(name);
13✔
4906
    }
4907

4908
    /**
4909
     * @hidden @internal
4910
     */
4911
    public refreshGridState(_args?) {
4912
        this.crudService.endEdit(true);
285✔
4913
        this.selectionService.clearHeaderCBState();
285✔
4914
        this.summaryService.clearSummaryCache();
285✔
4915
        this.summaryPipeTrigger++;
285✔
4916
        this.cdr.detectChanges();
285✔
4917
    }
4918

4919
    // TODO: We have return values here. Move them to event args ??
4920

4921
    /**
4922
     * Pins a column by field name.
4923
     *
4924
     * @remarks
4925
     * Returns whether the operation is successful.
4926
     * @example
4927
     * ```typescript
4928
     * this.grid.pinColumn("ID");
4929
     * ```
4930
     * @param columnName
4931
     * @param index
4932
     */
4933
    public pinColumn(columnName: string | IgxColumnComponent, index?: number): boolean {
4934
        const col = columnName instanceof IgxColumnComponent ? columnName : this.getColumnByName(columnName);
37✔
4935
        return col.pin(index);
37✔
4936
    }
4937

4938
    /**
4939
     * Unpins a column by field name. Returns whether the operation is successful.
4940
     *
4941
     * @example
4942
     * ```typescript
4943
     * this.grid.pinColumn("ID");
4944
     * ```
4945
     * @param columnName
4946
     * @param index
4947
     */
4948
    public unpinColumn(columnName: string | IgxColumnComponent, index?: number): boolean {
4949
        const col = columnName instanceof IgxColumnComponent ? columnName : this.getColumnByName(columnName);
19✔
4950
        return col.unpin(index);
19✔
4951
    }
4952

4953
    /**
4954
     * Pin the row by its id.
4955
     *
4956
     * @remarks
4957
     * ID is either the primaryKey value or the data record instance.
4958
     * @example
4959
     * ```typescript
4960
     * this.grid.pinRow(rowID);
4961
     * ```
4962
     * @param rowID The row id - primaryKey value or the data record instance.
4963
     * @param index The index at which to insert the row in the pinned collection.
4964
     */
4965
    public pinRow(rowID: any, index?: number, row?: RowType): boolean {
4966
        if (this._pinnedRecordIDs.indexOf(rowID) !== -1) {
134✔
4967
            return false;
1✔
4968
        }
4969
        const eventArgs = this.gridAPI.get_pin_row_event_args(rowID, index, row, true);
133✔
4970
        this.rowPinning.emit(eventArgs);
133✔
4971

4972
        if (eventArgs.cancel) {
133✔
4973
            return;
1✔
4974
        }
4975
        this.crudService.endEdit(false);
132✔
4976

4977
        const insertIndex = typeof eventArgs.insertAtIndex === 'number' ? eventArgs.insertAtIndex : this._pinnedRecordIDs.length;
132✔
4978
        this._pinnedRecordIDs.splice(insertIndex, 0, rowID);
132✔
4979
        this.pipeTrigger++;
132✔
4980
        if (this.gridAPI.grid) {
132✔
4981
            this.cdr.detectChanges();
130✔
4982
            this.rowPinned.emit(eventArgs);
130✔
4983
        }
4984

4985
        return true;
132✔
4986
    }
4987

4988
    /**
4989
     * Unpin the row by its id.
4990
     *
4991
     * @remarks
4992
     * ID is either the primaryKey value or the data record instance.
4993
     * @example
4994
     * ```typescript
4995
     * this.grid.unpinRow(rowID);
4996
     * ```
4997
     * @param rowID The row id - primaryKey value or the data record instance.
4998
     */
4999
    public unpinRow(rowID: any, row?: RowType): boolean {
5000
        const index = this._pinnedRecordIDs.indexOf(rowID);
24✔
5001
        if (index === -1) {
24!
5002
            return false;
×
5003
        }
5004

5005
        const eventArgs = this.gridAPI.get_pin_row_event_args(rowID, null, row, false);
24✔
5006
        this.rowPinning.emit(eventArgs);
24✔
5007

5008
        if (eventArgs.cancel) {
24✔
5009
            return;
1✔
5010
        }
5011

5012
        this.crudService.endEdit(false);
23✔
5013
        this._pinnedRecordIDs.splice(index, 1);
23✔
5014
        this.pipeTrigger++;
23✔
5015
        if (this.gridAPI.grid) {
23✔
5016
            this.cdr.detectChanges();
23✔
5017
            this.rowPinned.emit(eventArgs);
23✔
5018
        }
5019

5020
        return true;
23✔
5021
    }
5022

5023
    /** @hidden @internal */
5024
    public get pinnedRowHeight() {
5025
        const containerHeight = this.pinContainer ? this.pinContainer.nativeElement.offsetHeight : 0;
78,305✔
5026
        return this.hasPinnedRecords ? containerHeight : 0;
78,305✔
5027
    }
5028

5029
    /** @hidden @internal */
5030
    public get totalHeight() {
5031
        return this.calcHeight ? this.calcHeight + this.pinnedRowHeight : this.calcHeight;
41,470✔
5032
    }
5033

5034
    /**
5035
     * Recalculates grid width/height dimensions.
5036
     *
5037
     * @remarks
5038
     * Should be run when changing DOM elements dimentions manually that affect the grid's size.
5039
     * @example
5040
     * ```typescript
5041
     * this.grid.reflow();
5042
     * ```
5043
     */
5044
    public reflow() {
5045
        this.calculateGridSizes();
274✔
5046
    }
5047

5048
    /**
5049
     * Finds the next occurrence of a given string in the grid and scrolls to the cell if it isn't visible.
5050
     *
5051
     * @remarks
5052
     * Returns how many times the grid contains the string.
5053
     * @example
5054
     * ```typescript
5055
     * this.grid.findNext("financial");
5056
     * ```
5057
     * @param text the string to search.
5058
     * @param caseSensitive optionally, if the search should be case sensitive (defaults to false).
5059
     * @param exactMatch optionally, if the text should match the entire value  (defaults to false).
5060
     */
5061
    public findNext(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
5062
        return this.find(text, 1, caseSensitive, exactMatch);
155✔
5063
    }
5064

5065
    /**
5066
     * Finds the previous occurrence of a given string in the grid and scrolls to the cell if it isn't visible.
5067
     *
5068
     * @remarks
5069
     * Returns how many times the grid contains the string.
5070
     * @example
5071
     * ```typescript
5072
     * this.grid.findPrev("financial");
5073
     * ```
5074
     * @param text the string to search.
5075
     * @param caseSensitive optionally, if the search should be case sensitive (defaults to false).
5076
     * @param exactMatch optionally, if the text should match the entire value (defaults to false).
5077
     */
5078
    public findPrev(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
5079
        return this.find(text, -1, caseSensitive, exactMatch);
40✔
5080
    }
5081

5082
    /**
5083
     * Reapplies the existing search.
5084
     *
5085
     * @remarks
5086
     * Returns how many times the grid contains the last search.
5087
     * @example
5088
     * ```typescript
5089
     * this.grid.refreshSearch();
5090
     * ```
5091
     * @param updateActiveInfo
5092
     */
5093
    public refreshSearch(updateActiveInfo?: boolean, endEdit = true): number {
3,955✔
5094
        if (this._lastSearchInfo.searchText) {
9,828✔
5095
            this.rebuildMatchCache();
122✔
5096

5097
            if (updateActiveInfo) {
122✔
5098
                const activeInfo = this.textHighlightService.highlightGroupsMap.get(this.id);
121✔
5099
                this._lastSearchInfo.matchInfoCache.forEach((match, i) => {
121✔
5100
                    if (match.column === activeInfo.column &&
1,300✔
5101
                        match.row === activeInfo.row &&
5102
                        match.index === activeInfo.index &&
5103
                        compareMaps(match.metadata, activeInfo.metadata)) {
5104
                        this._lastSearchInfo.activeMatchIndex = i;
106✔
5105
                    }
5106
                });
5107
            }
5108

5109
            return this.find(this._lastSearchInfo.searchText,
122✔
5110
                0,
5111
                this._lastSearchInfo.caseSensitive,
5112
                this._lastSearchInfo.exactMatch,
5113
                false,
5114
                endEdit);
5115
        } else {
5116
            return 0;
9,706✔
5117
        }
5118
    }
5119

5120
    /**
5121
     * Removes all the highlights in the cell.
5122
     *
5123
     * @example
5124
     * ```typescript
5125
     * this.grid.clearSearch();
5126
     * ```
5127
     */
5128
    public clearSearch() {
5129
        this._lastSearchInfo = {
1✔
5130
            searchText: '',
5131
            caseSensitive: false,
5132
            exactMatch: false,
5133
            activeMatchIndex: 0,
5134
            matchInfoCache: [],
5135
            matchCount: 0,
5136
            content: ''
5137
        };
5138

5139
        this.rowList.forEach((row) => {
1✔
5140
            if (row.cells) {
10✔
5141
                row.cells.forEach((c: IgxGridCellComponent) => {
10✔
5142
                    c.clearHighlight();
40✔
5143
                });
5144
            }
5145
        });
5146
    }
5147

5148
    /** @hidden @internal */
5149
    public get hasEditableColumns(): boolean {
5150
        return this._columns.some((col) => col.editable);
6✔
5151
    }
5152

5153
    /** @hidden @internal */
5154
    public get hasSummarizedColumns(): boolean {
5155
        const summarizedColumns = this._columns.filter(col => col.hasSummary && !col.hidden);
1,911,962✔
5156
        return summarizedColumns.length > 0;
309,671✔
5157
    }
5158

5159
    /**
5160
     * @hidden @internal
5161
     */
5162
    public get rootSummariesEnabled(): boolean {
5163
        return this.summaryCalculationMode !== GridSummaryCalculationMode.childLevelsOnly;
224,156✔
5164
    }
5165

5166
    /**
5167
     * @hidden @internal
5168
     */
5169
    public get hasVisibleColumns(): boolean {
5170
        if (this._hasVisibleColumns === undefined) {
75,714✔
5171
            return this._columns ? this._columns.some(c => !c.hidden) : false;
77,010!
5172
        }
5173
        return this._hasVisibleColumns;
×
5174
    }
5175

5176
    public set hasVisibleColumns(value) {
5177
        this._hasVisibleColumns = value;
33,024✔
5178
    }
5179

5180
    /** @hidden @internal */
5181
    public get hasMovableColumns(): boolean {
5182
        return this.moving;
×
5183
    }
5184

5185
    /** @hidden @internal */
5186
    public get hasColumnGroups(): boolean {
5187
        return this._columnGroups;
206,661✔
5188
    }
5189

5190
    /** @hidden @internal */
5191
    public get hasColumnLayouts() {
5192
        return !!this._columns.some(col => col.columnLayout);
19,944,320✔
5193
    }
5194

5195

5196
    /**
5197
     * @hidden @internal
5198
     */
5199
    public get multiRowLayoutRowSize() {
5200
        return this._multiRowLayoutRowSize;
19,485✔
5201
    }
5202

5203
    /**
5204
     * @hidden
5205
     */
5206
    protected get rowBasedHeight() {
5207
        return this.dataLength * this.rowHeight;
×
5208
    }
5209

5210
    /**
5211
     * @hidden
5212
     */
5213
    protected get isPercentWidth() {
5214
        return this.width && this.width.indexOf('%') !== -1;
40,691✔
5215
    }
5216

5217
    /**
5218
     * @hidden @internal
5219
     */
5220
    public get isPercentHeight() {
5221
        return this._height && this._height.indexOf('%') !== -1;
15,012✔
5222
    }
5223

5224
    /**
5225
     * @hidden
5226
     */
5227
    protected get defaultTargetBodyHeight(): number {
5228
        const allItems = this.dataLength;
293✔
5229
        return this.renderedRowHeight * Math.min(this._defaultTargetRecordNumber,
293✔
5230
            this.paginator ? Math.min(allItems, this.paginator.perPage) : allItems);
293✔
5231
    }
5232

5233
    /**
5234
     * @hidden @internal
5235
     * The rowHeight input is bound to min-height css prop of rows that adds a 1px border in all cases
5236
     */
5237
    public get renderedRowHeight(): number {
5238
        return this.rowHeight + 1;
76,723✔
5239
    }
5240

5241
    /**
5242
     * @hidden @internal
5243
     */
5244
    public get outerWidth() {
5245
        return this.hasVerticalScroll() ? this.calcWidth + this.scrollSize : this.calcWidth;
3,773✔
5246
    }
5247

5248
    /**
5249
     * @hidden @internal
5250
     * Gets the visible content height that includes header + tbody + footer.
5251
     */
5252
    public getVisibleContentHeight() {
5253
        let height = this.theadRow.nativeElement.clientHeight + this.tbody.nativeElement.clientHeight;
46✔
5254
        if (this.hasSummarizedColumns) {
46✔
5255
            height += this.tfoot.nativeElement.clientHeight;
5✔
5256
        }
5257
        return height;
46✔
5258
    }
5259

5260
    /**
5261
     * @hidden @internal
5262
     */
5263
    public getPossibleColumnWidth(baseWidth: number = null) {
70,523✔
5264
        let computedWidth;
5265
        if (baseWidth !== null) {
70,523!
5266
            computedWidth = baseWidth;
×
5267
        } else {
5268
            computedWidth = this.calcWidth ||
70,523✔
5269
                parseInt(this.document.defaultView.getComputedStyle(this.nativeElement).getPropertyValue('width'), 10);
5270
        }
5271

5272
        const visibleChildColumns = this.visibleColumns.filter(c => !c.columnGroup);
659,534✔
5273

5274

5275
        // Column layouts related
5276
        let visibleCols = [];
70,523✔
5277
        const columnBlocks = this.visibleColumns.filter(c => c.columnGroup);
659,534✔
5278
        const colsPerBlock = columnBlocks.map(block => block.getInitialChildColumnSizes(block.children));
117,116✔
5279
        const combinedBlocksSize = colsPerBlock.reduce((acc, item) => acc + item.length, 0);
117,116✔
5280
        colsPerBlock.forEach(blockCols => visibleCols = visibleCols.concat(blockCols));
117,116✔
5281
        //
5282

5283
        const columnsWithSetWidths = this.hasColumnLayouts ?
70,523✔
5284
            visibleCols.filter(c => c.widthSetByUser) :
162,824✔
5285
            visibleChildColumns.filter(c => c.widthSetByUser && c.width !== 'fit-content');
283,056✔
5286

5287
        const columnsToSize = this.hasColumnLayouts ?
70,523✔
5288
            combinedBlocksSize - columnsWithSetWidths.length :
5289
            visibleChildColumns.length - columnsWithSetWidths.length;
5290
        const sumExistingWidths = columnsWithSetWidths
70,523✔
5291
            .reduce((prev, curr) => {
5292
                const colWidth = curr.width;
36,191✔
5293
                let widthValue = parseInt(colWidth, 10);
36,191✔
5294
                if (isNaN(widthValue)) {
36,191!
5295
                    widthValue = MINIMUM_COLUMN_WIDTH;
×
5296
                }
5297
                const currWidth = colWidth && typeof colWidth === 'string' && colWidth.indexOf('%') !== -1 ?
36,191✔
5298
                    widthValue / 100 * computedWidth :
5299
                    widthValue;
5300
                return prev + currWidth;
36,191✔
5301
            }, 0);
5302

5303
        // When all columns are hidden, return 0px width
5304
        if (!sumExistingWidths && !columnsToSize) {
70,523✔
5305
            return '0px';
1,427✔
5306
        }
5307
        computedWidth -= this.featureColumnsWidth();
69,096✔
5308

5309
        const columnWidth = Math.floor(!Number.isFinite(sumExistingWidths) ?
69,096!
5310
            Math.max(computedWidth / columnsToSize, this.minColumnWidth) :
5311
            Math.max((computedWidth - sumExistingWidths) / columnsToSize, this.minColumnWidth));
5312

5313
        return columnWidth + 'px';
69,096✔
5314
    }
5315

5316
    /**
5317
     * @hidden @internal
5318
     */
5319
    public hasVerticalScroll() {
5320
        if (this._init) {
179,726✔
5321
            return false;
79,723✔
5322
        }
5323
        const isScrollable = this.verticalScrollContainer ? this.verticalScrollContainer.isScrollable() : false;
100,003✔
5324
        return !!(this.calcWidth && this.dataView && this.dataView.length > 0 && isScrollable);
100,003✔
5325
    }
5326

5327
    /**
5328
     * Gets calculated width of the pinned area.
5329
     *
5330
     * @example
5331
     * ```typescript
5332
     * const pinnedWidth = this.grid.getPinnedWidth();
5333
     * ```
5334
     * @param takeHidden If we should take into account the hidden columns in the pinned area.
5335
     */
5336
    public getPinnedWidth(takeHidden = false) {
20,058✔
5337
        const fc = takeHidden ? this._pinnedColumns : this.pinnedColumns;
43,279!
5338
        let sum = 0;
43,279✔
5339
        for (const col of fc) {
43,279✔
5340
            if (col.level === 0) {
6,541✔
5341
                sum += parseInt(col.calcWidth, 10);
3,842✔
5342
            }
5343
        }
5344
        if (this.isPinningToStart) {
43,279✔
5345
            sum += this.featureColumnsWidth();
43,163✔
5346
        }
5347

5348
        return sum;
43,279✔
5349
    }
5350

5351
    /**
5352
     * @hidden @internal
5353
     */
5354
    public isColumnGrouped(_fieldName: string): boolean {
5355
        return false;
×
5356
    }
5357

5358
    /**
5359
     * @hidden @internal
5360
     * TODO: REMOVE
5361
     */
5362
    public onHeaderSelectorClick(event) {
5363
        if (!this.isMultiRowSelectionEnabled) {
7!
5364
            return;
×
5365
        }
5366
        if (this.selectionService.areAllRowSelected()) {
7✔
5367
            this.selectionService.clearRowSelection(event);
1✔
5368
        } else {
5369
            this.selectionService.selectAllRows(event);
6✔
5370
        }
5371
    }
5372

5373
    /**
5374
     * @hidden @internal
5375
     */
5376
    public get headSelectorBaseAriaLabel() {
5377
        if (this._filteringExpressionsTree.filteringOperands.length > 0) {
3,154✔
5378
            return this.selectionService.areAllRowSelected() ? 'Deselect all filtered' : 'Select all filtered';
112✔
5379
        }
5380

5381
        return this.selectionService.areAllRowSelected() ? 'Deselect all' : 'Select all';
3,042✔
5382
    }
5383

5384
    /**
5385
     * @hidden
5386
     * @internal
5387
     */
5388
    public get totalRowsCountAfterFilter() {
5389
        if (this.data) {
3,319✔
5390
            return this.selectionService.allData.length;
3,319✔
5391
        }
5392

5393
        return 0;
×
5394
    }
5395

5396
    /** @hidden @internal */
5397
    public get pinnedDataView(): any[] {
5398
        return this.pinnedRecords ? this.pinnedRecords : [];
248,935✔
5399
    }
5400

5401
    /** @hidden @internal */
5402
    public get unpinnedDataView(): any[] {
5403
        return this.unpinnedRecords ? this.unpinnedRecords : this.verticalScrollContainer?.igxForOf || [];
15,462✔
5404
    }
5405

5406
    /**
5407
     * Returns the currently transformed paged/filtered/sorted/grouped/pinned/unpinned row data, displayed in the grid.
5408
     *
5409
     * @example
5410
     * ```typescript
5411
     *      const dataView = this.grid.dataView;
5412
     * ```
5413
     */
5414
    public get dataView() {
5415
        return this._dataView;
387,418✔
5416
    }
5417

5418
    /**
5419
     * Gets/Sets whether clicking over a row should select/deselect it
5420
     *
5421
     * @remarks
5422
     * By default it is set to true
5423
     * @param enabled: boolean
5424
     */
5425
    @WatchChanges()
5426
    @Input({ transform: booleanAttribute })
5427
    public get selectRowOnClick() {
5428
        return this._selectRowOnClick;
110✔
5429
    }
5430

5431
    public set selectRowOnClick(enabled: boolean) {
5432
        this._selectRowOnClick = enabled;
13✔
5433
    }
5434

5435
    /**
5436
     * Select specified rows by ID.
5437
     *
5438
     * @example
5439
     * ```typescript
5440
     * this.grid.selectRows([1,2,5], true);
5441
     * ```
5442
     * @param rowIDs
5443
     * @param clearCurrentSelection if true clears the current selection
5444
     */
5445
    public selectRows(rowIDs: any[], clearCurrentSelection?: boolean) {
5446
        this.selectionService.selectRowsWithNoEvent(rowIDs, clearCurrentSelection);
265✔
5447
        this.notifyChanges();
265✔
5448
    }
5449

5450
    /**
5451
     * Deselect specified rows by ID.
5452
     *
5453
     * @example
5454
     * ```typescript
5455
     * this.grid.deselectRows([1,2,5]);
5456
     * ```
5457
     * @param rowIDs
5458
     */
5459
    public deselectRows(rowIDs: any[]) {
5460
        this.selectionService.deselectRowsWithNoEvent(rowIDs);
19✔
5461
        this.notifyChanges();
19✔
5462
    }
5463

5464
    /**
5465
     * Selects all rows
5466
     *
5467
     * @remarks
5468
     * By default if filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows.
5469
     * If you set the parameter onlyFilterData to false that will select all rows in the grid exept deleted rows.
5470
     * @example
5471
     * ```typescript
5472
     * this.grid.selectAllRows();
5473
     * this.grid.selectAllRows(false);
5474
     * ```
5475
     * @param onlyFilterData
5476
     */
5477
    public selectAllRows(onlyFilterData = true) {
29✔
5478
        const data = onlyFilterData && this.filteredData ? this.filteredData : this.gridAPI.get_all_data(true);
30✔
5479
        const rowIDs = this.selectionService.getRowIDs(data).filter(rID => !this.gridAPI.row_deleted_transaction(rID));
429✔
5480
        this.selectRows(rowIDs);
30✔
5481
    }
5482

5483
    /**
5484
     * Deselects all rows
5485
     *
5486
     * @remarks
5487
     * By default if filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows.
5488
     * If you set the parameter onlyFilterData to false that will deselect all rows in the grid exept deleted rows.
5489
     * @example
5490
     * ```typescript
5491
     * this.grid.deselectAllRows();
5492
     * ```
5493
     * @param onlyFilterData
5494
     */
5495
    public deselectAllRows(onlyFilterData = true) {
5✔
5496
        if (onlyFilterData && this.filteredData && this.filteredData.length > 0) {
6✔
5497
            this.deselectRows(this.selectionService.getRowIDs(this.filteredData));
1✔
5498
        } else {
5499
            this.selectionService.clearAllSelectedRows();
5✔
5500
            this.notifyChanges();
5✔
5501
        }
5502
    }
5503

5504
    /**
5505
     * Deselect selected cells.
5506
     * @example
5507
     * ```typescript
5508
     * this.grid.clearCellSelection();
5509
     * ```
5510
     */
5511
    public clearCellSelection(): void {
5512
        this.selectionService.clear(true);
7✔
5513
        this.notifyChanges();
7✔
5514
    }
5515

5516
    /**
5517
     * @hidden @internal
5518
     */
5519
    public dragScroll(delta: { left: number; top: number }): void {
5520
        const horizontal = this.headerContainer.getScroll();
1✔
5521
        const vertical = this.verticalScrollContainer.getScroll();
1✔
5522
        const { left, top } = delta;
1✔
5523

5524
        horizontal.scrollLeft += left * this.DRAG_SCROLL_DELTA;
1✔
5525
        vertical.scrollTop += top * this.DRAG_SCROLL_DELTA;
1✔
5526
    }
5527

5528
    /**
5529
     * @hidden @internal
5530
     */
5531
    public isDefined(arg: any): boolean {
5532
        return arg !== undefined && arg !== null;
66,425✔
5533
    }
5534

5535
    /**
5536
     * Select range(s) of cells between certain rows and columns of the grid.
5537
     */
5538
    public selectRange(arg: GridSelectionRange | GridSelectionRange[] | null | undefined): void {
5539
        if (!this.isDefined(arg)) {
173✔
5540
            this.clearCellSelection();
6✔
5541
            return;
6✔
5542
        }
5543
        if (arg instanceof Array) {
167✔
5544
            arg.forEach(range => this.setSelection(range));
6✔
5545
        } else {
5546
            this.setSelection(arg);
164✔
5547
        }
5548
        this.notifyChanges();
164✔
5549
    }
5550

5551
    /**
5552
     * @hidden @internal
5553
     */
5554
    public columnToVisibleIndex(field: string | number): number {
5555
        const visibleColumns = this.visibleColumns;
347✔
5556
        if (typeof field === 'number') {
347✔
5557
            return field;
214✔
5558
        }
5559
        return visibleColumns.find(column => column.field === field).visibleIndex;
322✔
5560
    }
5561

5562
    /**
5563
     * @hidden @internal
5564
     */
5565
    public setSelection(range: GridSelectionRange): void {
5566
        const startNode = { row: range.rowStart, column: this.columnToVisibleIndex(range.columnStart) };
172✔
5567
        const endNode = { row: range.rowEnd, column: this.columnToVisibleIndex(range.columnEnd) };
170✔
5568

5569
        this.selectionService.pointerState.node = startNode;
169✔
5570
        this.selectionService.selectRange(endNode, this.selectionService.pointerState);
169✔
5571
        this.selectionService.addRangeMeta(endNode, this.selectionService.pointerState);
169✔
5572
        this.selectionService.initPointerState();
169✔
5573
    }
5574

5575
    /**
5576
     * Get the currently selected ranges in the grid.
5577
     */
5578
    public getSelectedRanges(): GridSelectionRange[] {
5579
        return this.selectionService.ranges;
348✔
5580
    }
5581

5582
    /**
5583
     *
5584
     * Returns an array of the current cell selection in the form of `[{ column.field: cell.value }, ...]`.
5585
     *
5586
     * @remarks
5587
     * If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
5588
     * If `headers` is enabled, it will use the column header (if any) instead of the column field.
5589
     */
5590
    public getSelectedData(formatters = false, headers = false) {
4✔
5591
        const source = this.filteredSortedData;
167✔
5592
        return this.extractDataFromSelection(source, formatters, headers);
167✔
5593
    }
5594

5595
    /**
5596
     * Get current selected columns.
5597
     *
5598
     * @example
5599
     * Returns an array with selected columns
5600
     * ```typescript
5601
     * const selectedColumns = this.grid.selectedColumns();
5602
     * ```
5603
     */
5604
    public selectedColumns(): ColumnType[] {
5605
        const fields = this.selectionService.getSelectedColumns();
67✔
5606
        return fields.map(field => this.getColumnByName(field)).filter(field => field);
67✔
5607
    }
5608

5609
    /**
5610
     * Select specified columns.
5611
     *
5612
     * @example
5613
     * ```typescript
5614
     * this.grid.selectColumns(['ID','Name'], true);
5615
     * ```
5616
     * @param columns
5617
     * @param clearCurrentSelection if true clears the current selection
5618
     */
5619
    public selectColumns(columns: string[] | ColumnType[], clearCurrentSelection?: boolean) {
5620
        let fieldToSelect: string[] = [];
13✔
5621
        if (columns.length === 0 || typeof columns[0] === 'string') {
13✔
5622
            fieldToSelect = columns as string[];
8✔
5623
        } else {
5624
            (columns as ColumnType[]).forEach(col => {
5✔
5625
                if (col.columnGroup) {
18!
5626
                    const children = col.allChildren.filter(c => !c.columnGroup).map(c => c.field);
×
5627
                    fieldToSelect = [...fieldToSelect, ...children];
×
5628
                } else {
5629
                    fieldToSelect.push(col.field);
18✔
5630
                }
5631
            });
5632
        }
5633

5634
        this.selectionService.selectColumnsWithNoEvent(fieldToSelect, clearCurrentSelection);
13✔
5635
        this.notifyChanges();
13✔
5636
    }
5637

5638
    /**
5639
     * Deselect specified columns by field.
5640
     *
5641
     * @example
5642
     * ```typescript
5643
     * this.grid.deselectColumns(['ID','Name']);
5644
     * ```
5645
     * @param columns
5646
     */
5647
    public deselectColumns(columns: string[] | ColumnType[]) {
5648
        let fieldToDeselect: string[] = [];
3✔
5649
        if (columns.length === 0 || typeof columns[0] === 'string') {
3✔
5650
            fieldToDeselect = columns as string[];
2✔
5651
        } else {
5652
            (columns as ColumnType[]).forEach(col => {
1✔
5653
                if (col.columnGroup) {
2!
5654
                    const children = col.allChildren.filter(c => !c.columnGroup).map(c => c.field);
×
5655
                    fieldToDeselect = [...fieldToDeselect, ...children];
×
5656
                } else {
5657
                    fieldToDeselect.push(col.field);
2✔
5658
                }
5659
            });
5660
        }
5661
        this.selectionService.deselectColumnsWithNoEvent(fieldToDeselect);
3✔
5662
        this.notifyChanges();
3✔
5663
    }
5664

5665
    /**
5666
     * Deselects all columns
5667
     *
5668
     * @example
5669
     * ```typescript
5670
     * this.grid.deselectAllColumns();
5671
     * ```
5672
     */
5673
    public deselectAllColumns() {
5674
        this.selectionService.clearAllSelectedColumns();
4✔
5675
        this.notifyChanges();
4✔
5676
    }
5677

5678
    /**
5679
     * Selects all columns
5680
     *
5681
     * @example
5682
     * ```typescript
5683
     * this.grid.deselectAllColumns();
5684
     * ```
5685
     */
5686
    public selectAllColumns() {
5687
        this.selectColumns(this._columns.filter(c => !c.columnGroup));
15✔
5688
    }
5689

5690
    /**
5691
     *
5692
     * Returns an array of the current columns selection in the form of `[{ column.field: cell.value }, ...]`.
5693
     *
5694
     * @remarks
5695
     * If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
5696
     * If `headers` is enabled, it will use the column header (if any) instead of the column field.
5697
     */
5698
    public getSelectedColumnsData(formatters = false, headers = false) {
16✔
5699
        const source = this.filteredSortedData ? this.filteredSortedData : this.data;
20!
5700
        return this.extractDataFromColumnsSelection(source, formatters, headers);
20✔
5701
    }
5702

5703

5704
    /** @hidden @internal **/
5705
    public combineSelectedCellAndColumnData(columnData: any[], formatters = false, headers = false) {
×
5706
        const source = this.filteredSortedData;
×
5707
        return this.extractDataFromSelection(source, formatters, headers, columnData);
×
5708
    }
5709

5710
    /**
5711
     * @hidden @internal
5712
     */
5713
    public preventContainerScroll = (evt) => {
3,996✔
5714
        if (evt.target.scrollTop !== 0) {
×
5715
            this.verticalScrollContainer.addScroll(evt.target.scrollTop);
×
5716
            evt.target.scrollTop = 0;
×
5717
        }
5718
        if (evt.target.scrollLeft !== 0) {
×
5719
            this.headerContainer.scrollPosition += evt.target.scrollLeft;
×
5720
            evt.target.scrollLeft = 0;
×
5721
        }
5722
    };
5723

5724
    /**
5725
     * @hidden
5726
     * @internal
5727
     */
5728
    public copyHandler(event) {
5729
        const eventPathElements = event.composedPath().map(el => el.tagName?.toLowerCase());
97✔
5730
        if (eventPathElements.includes('igx-grid-filtering-row') ||
13✔
5731
            eventPathElements.includes('igx-grid-filtering-cell')) {
5732
            return;
1✔
5733
        }
5734

5735
        const selectedColumns = this.gridAPI.grid.selectedColumns();
12✔
5736
        const columnData = this.getSelectedColumnsData(this.clipboardOptions.copyFormatters, this.clipboardOptions.copyHeaders);
12✔
5737
        let selectedData;
5738
        if (event.type === 'copy') {
12✔
5739
            selectedData = this.getSelectedData(this.clipboardOptions.copyFormatters, this.clipboardOptions.copyHeaders);
12✔
5740
        }
5741

5742
        let data = [];
12✔
5743
        let result;
5744

5745
        if (event.code === 'KeyC' && (event.ctrlKey || event.metaKey) && event.currentTarget.className === 'igx-grid-thead__wrapper') {
12!
5746
            if (selectedData.length) {
×
5747
                if (columnData.length === 0) {
×
5748
                    result = this.prepareCopyData(event, selectedData);
×
5749
                } else {
5750
                    data = this.combineSelectedCellAndColumnData(columnData, this.clipboardOptions.copyFormatters,
×
5751
                        this.clipboardOptions.copyHeaders);
5752
                    result = this.prepareCopyData(event, data[0], data[1]);
×
5753
                }
5754
            } else {
5755
                data = columnData;
×
5756
                result = this.prepareCopyData(event, data);
×
5757
            }
5758

5759
            navigator.clipboard.writeText(result).then().catch(e => console.error(e));
×
5760
        } else if (!this.clipboardOptions.enabled || this.crudService.cellInEditMode || event.type === 'keydown') {
12✔
5761
            return;
2✔
5762
        } else {
5763
            if (selectedColumns.length) {
10!
5764
                data = this.combineSelectedCellAndColumnData(columnData, this.clipboardOptions.copyFormatters,
×
5765
                    this.clipboardOptions.copyHeaders);
5766
                result = this.prepareCopyData(event, data[0], data[1]);
×
5767
            } else {
5768
                data = selectedData;
10✔
5769
                result = this.prepareCopyData(event, data);
10✔
5770
            }
5771
            event.clipboardData.setData('text/plain', result);
10✔
5772
        }
5773
    }
5774

5775
    /**
5776
     * @hidden @internal
5777
     */
5778
    public prepareCopyData(event, data, keys?) {
5779
        const ev = { data, cancel: false } as IGridClipboardEvent;
10✔
5780
        this.gridCopy.emit(ev);
10✔
5781

5782
        if (ev.cancel) {
10✔
5783
            return;
1✔
5784
        }
5785

5786
        const transformer = new CharSeparatedValueData(ev.data, this.clipboardOptions.separator);
9✔
5787
        let result = keys ? transformer.prepareData(keys) : transformer.prepareData();
9!
5788

5789
        if (!this.clipboardOptions.copyHeaders) {
9✔
5790
            result = result.substring(result.indexOf('\n') + 1);
2✔
5791
        }
5792

5793
        if (data && data.length > 0 && Object.values(data[0]).length === 1) {
9✔
5794
            result = result.slice(0, -2);
4✔
5795
        }
5796

5797
        event.preventDefault();
9✔
5798

5799
        /* Necessary for the hiearachical case but will probably have to
5800
           change how getSelectedData is propagated in the hiearachical grid
5801
        */
5802
        event.stopPropagation();
9✔
5803

5804
        return result;
9✔
5805
    }
5806

5807
    /**
5808
     * @hidden @internal
5809
     */
5810
    public showSnackbarFor(index: number) {
5811
        this.addRowSnackbar.actionText = index === -1 ? '' : this.resourceStrings.igx_grid_snackbar_addrow_actiontext;
28✔
5812
        this.lastAddedRowIndex = index;
28✔
5813
        this.addRowSnackbar.open();
28✔
5814
    }
5815

5816
    /**
5817
     * Navigates to a position in the grid based on provided `rowindex` and `visibleColumnIndex`.
5818
     *
5819
     * @remarks
5820
     * Also can execute a custom logic over the target element,
5821
     * through a callback function that accepts { targetType: GridKeydownTargetType, target: Object }
5822
     * @example
5823
     * ```typescript
5824
     *  this.grid.navigateTo(10, 3, (args) => { args.target.nativeElement.focus(); });
5825
     * ```
5826
     */
5827
    public navigateTo(rowIndex: number, visibleColIndex = -1, cb: (args: any) => void = null) {
409✔
5828
        const totalItems = (this as any).totalItemCount ?? this.dataView.length - 1;
834✔
5829
        if (rowIndex < 0 || rowIndex > totalItems || (visibleColIndex !== -1
834!
5830
            && this._columns.map(col => col.visibleIndex).indexOf(visibleColIndex) === -1)) {
5,057✔
5831
            return;
×
5832
        }
5833
        if (this.dataView.slice(rowIndex, rowIndex + 1).find(rec => rec.expression || rec.childGridsData)) {
834✔
5834
            visibleColIndex = -1;
43✔
5835
        }
5836
        // If the target row is pinned no need to scroll as well.
5837
        const shouldScrollVertically = this.navigation.shouldPerformVerticalScroll(rowIndex, visibleColIndex);
834✔
5838
        const shouldScrollHorizontally = this.navigation.shouldPerformHorizontalScroll(visibleColIndex, rowIndex);
834✔
5839
        if (shouldScrollVertically) {
834✔
5840
            this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex, () => {
143✔
5841
                if (shouldScrollHorizontally) {
140✔
5842
                    this.navigation.performHorizontalScrollToCell(visibleColIndex, () =>
16✔
5843
                        this.executeCallback(rowIndex, visibleColIndex, cb));
16✔
5844
                } else {
5845
                    this.executeCallback(rowIndex, visibleColIndex, cb);
124✔
5846
                }
5847
            });
5848
        } else if (shouldScrollHorizontally) {
691✔
5849
            this.navigation.performHorizontalScrollToCell(visibleColIndex, () => {
93✔
5850
                if (shouldScrollVertically) {
93!
5851
                    this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex, () =>
×
5852
                        this.executeCallback(rowIndex, visibleColIndex, cb));
×
5853
                } else {
5854
                    this.executeCallback(rowIndex, visibleColIndex, cb);
93✔
5855
                }
5856
            });
5857
        } else {
5858
            this.executeCallback(rowIndex, visibleColIndex, cb);
598✔
5859
        }
5860
    }
5861

5862
    /**
5863
     * Returns `ICellPosition` which defines the next cell,
5864
     * according to the current position, that match specific criteria.
5865
     *
5866
     * @remarks
5867
     * You can pass callback function as a third parameter of `getPreviousCell` method.
5868
     * The callback function accepts IgxColumnComponent as a param
5869
     * @example
5870
     * ```typescript
5871
     *  const nextEditableCellPosition = this.grid.getNextCell(0, 3, (column) => column.editable);
5872
     * ```
5873
     */
5874
    public getNextCell(currRowIndex: number, curVisibleColIndex: number,
5875
        callback: (IgxColumnComponent) => boolean = null): ICellPosition {
×
5876
        const columns = this._columns.filter(col => !col.columnGroup && col.visibleIndex >= 0);
477✔
5877
        const dataViewIndex = this._getDataViewIndex(currRowIndex);
56✔
5878
        if (!this.isValidPosition(dataViewIndex, curVisibleColIndex)) {
56✔
5879
            return { rowIndex: currRowIndex, visibleColumnIndex: curVisibleColIndex };
2✔
5880
        }
5881
        const colIndexes = callback ? columns.filter((col) => callback(col)).map(editCol => editCol.visibleIndex).sort((a, b) => a - b) :
460!
5882
            columns.map(editCol => editCol.visibleIndex).sort((a, b) => a - b);
×
5883
        const nextCellIndex = colIndexes.find(index => index > curVisibleColIndex);
147✔
5884
        if (this.dataView.slice(dataViewIndex, dataViewIndex + 1)
54✔
5885
            .find(rec => !rec.expression && !rec.summaries && !rec.childGridsData && !rec.detailsData) && nextCellIndex !== undefined) {
54✔
5886
            return { rowIndex: currRowIndex, visibleColumnIndex: nextCellIndex };
39✔
5887
        } else {
5888
            const nextIndex = this.getNextDataRowIndex(currRowIndex)
15✔
5889
            if (colIndexes.length === 0 || nextIndex === currRowIndex) {
15!
5890
                return { rowIndex: currRowIndex, visibleColumnIndex: curVisibleColIndex };
×
5891
            } else {
5892
                return { rowIndex: nextIndex, visibleColumnIndex: colIndexes[0] };
15✔
5893
            }
5894
        }
5895
    }
5896

5897
    /**
5898
     * Returns `ICellPosition` which defines the previous cell,
5899
     * according to the current position, that match specific criteria.
5900
     *
5901
     * @remarks
5902
     * You can pass callback function as a third parameter of `getPreviousCell` method.
5903
     * The callback function accepts IgxColumnComponent as a param
5904
     * @example
5905
     * ```typescript
5906
     *  const previousEditableCellPosition = this.grid.getPreviousCell(0, 3, (column) => column.editable);
5907
     * ```
5908
     */
5909
    public getPreviousCell(currRowIndex: number, curVisibleColIndex: number,
5910
        callback: (IgxColumnComponent) => boolean = null): ICellPosition {
1✔
5911
        const columns = this._columns.filter(col => !col.columnGroup && col.visibleIndex >= 0);
300✔
5912
        const dataViewIndex = this._getDataViewIndex(currRowIndex);
41✔
5913
        if (!this.isValidPosition(dataViewIndex, curVisibleColIndex)) {
41✔
5914
            return { rowIndex: currRowIndex, visibleColumnIndex: curVisibleColIndex };
2✔
5915
        }
5916
        const colIndexes = callback ? columns.filter((col) => callback(col)).map(editCol => editCol.visibleIndex).sort((a, b) => b - a) :
237✔
5917
            columns.map(editCol => editCol.visibleIndex).sort((a, b) => b - a);
4✔
5918
        const prevCellIndex = colIndexes.find(index => index < curVisibleColIndex);
141✔
5919
        if (this.dataView.slice(dataViewIndex, dataViewIndex + 1)
39✔
5920
            .find(rec => !rec.expression && !rec.summaries && !rec.childGridsData && !rec.detailsData) && prevCellIndex !== undefined) {
39✔
5921
            return { rowIndex: currRowIndex, visibleColumnIndex: prevCellIndex };
24✔
5922
        } else {
5923
            const prevIndex = this.getNextDataRowIndex(currRowIndex, true);
15✔
5924
            if (colIndexes.length === 0 || prevIndex === currRowIndex) {
15✔
5925
                return { rowIndex: currRowIndex, visibleColumnIndex: curVisibleColIndex };
7✔
5926
            } else {
5927
                return { rowIndex: prevIndex, visibleColumnIndex: colIndexes[0] };
8✔
5928
            }
5929
        }
5930
    }
5931

5932
    /**
5933
     * @hidden
5934
     * @internal
5935
     */
5936
    public endRowEditTabStop(commit = true, event?: Event) {
×
5937
        const canceled = this.crudService.endEdit(commit, event);
15✔
5938

5939
        if (canceled) {
15✔
5940
            return true;
3✔
5941
        }
5942

5943
        const activeCell = this.gridAPI.grid.navigation.activeNode;
12✔
5944
        if (activeCell && activeCell.row !== -1) {
12✔
5945
            this.tbody.nativeElement.focus();
12✔
5946
        }
5947
    }
5948

5949
    /**
5950
     * @hidden @internal
5951
     */
5952
    public trackColumnChanges(index, col) {
5953
        return col.field + col._calcWidth;
1,558,252✔
5954
    }
5955

5956
    /**
5957
     * @hidden
5958
     */
5959
    public isExpandedGroup(_group: IGroupByRecord): boolean {
5960
        return undefined;
×
5961
    }
5962

5963
    /**
5964
     * @hidden @internal
5965
     * TODO: MOVE to CRUD
5966
     */
5967
    public openRowOverlay(id) {
5968
        this.configureRowEditingOverlay(id, this.rowList.length <= MIN_ROW_EDITING_COUNT_THRESHOLD);
206✔
5969

5970
        this.rowEditingOverlay.open(this.rowEditSettings);
206✔
5971
        this.rowEditingOverlay.element.addEventListener('wheel', this.rowEditingWheelHandler.bind(this));
206✔
5972
    }
5973

5974
    /**
5975
     * @hidden @internal
5976
     */
5977
    public closeRowEditingOverlay() {
5978
        this.rowEditingOverlay.element.removeEventListener('wheel', this.rowEditingWheelHandler);
129✔
5979
        this.rowEditPositioningStrategy.isTopInitialPosition = null;
129✔
5980
        this.rowEditingOverlay.close();
129✔
5981
        this.rowEditingOverlay.element.parentElement.style.display = '';
129✔
5982
    }
5983

5984
    /**
5985
     * @hidden @internal
5986
     */
5987
    public toggleRowEditingOverlay(show) {
5988
        const rowStyle = this.rowEditingOverlay.element.style;
277✔
5989
        if (show) {
277!
5990
            rowStyle.display = 'block';
277✔
5991
        } else {
5992
            rowStyle.display = 'none';
×
5993
        }
5994
    }
5995

5996
    /**
5997
     * @hidden @internal
5998
     */
5999
    public repositionRowEditingOverlay(row: RowType) {
6000
        if (row && !this.rowEditingOverlay.collapsed) {
864✔
6001
            const rowStyle = this.rowEditingOverlay.element.parentElement.style;
71✔
6002
            if (row) {
71!
6003
                rowStyle.display = '';
71✔
6004
                this.configureRowEditingOverlay(row.key);
71✔
6005
                this.rowEditingOverlay.reposition();
71✔
6006
            } else {
6007
                rowStyle.display = 'none';
×
6008
            }
6009
        }
6010
    }
6011

6012
    /**
6013
     * @hidden @internal
6014
     */
6015
    public cachedViewLoaded(args: ICachedViewLoadedEventArgs) {
6016
        if (this.hasHorizontalScroll()) {
626✔
6017
            const tmplId = args.context.templateID.type;
447✔
6018
            const index = args.context.index;
447✔
6019
            args.view.detectChanges();
447✔
6020
            this.zone.onStable.pipe(first()).subscribe(() => {
447✔
6021
                const row = tmplId === 'dataRow' ? this.gridAPI.get_row_by_index(index) : null;
447✔
6022
                const summaryRow = tmplId === 'summaryRow' ? this.summariesRowList.find((sr) => sr.dataRowIndex === index) : null;
447✔
6023
                if (row && row instanceof IgxRowDirective) {
447✔
6024
                    this._restoreVirtState(row);
265✔
6025
                } else if (summaryRow) {
182✔
6026
                    this._restoreVirtState(summaryRow);
43✔
6027
                }
6028
            });
6029
        }
6030
    }
6031

6032
    /**
6033
     * Opens the advanced filtering dialog.
6034
     */
6035
    public openAdvancedFilteringDialog(overlaySettings?: OverlaySettings) {
6036
        const settings = overlaySettings ? overlaySettings : this._advancedFilteringOverlaySettings;
91!
6037
        if (!this._advancedFilteringOverlayId) {
91✔
6038
            this._advancedFilteringOverlaySettings.target =
88✔
6039
                (this as any).rootGrid ? (this as any).rootGrid.nativeElement : this.nativeElement;
88!
6040
            this._advancedFilteringOverlaySettings.outlet = this.outlet;
88✔
6041

6042
            this._advancedFilteringOverlayId = this.overlayService.attach(
88✔
6043
                IgxAdvancedFilteringDialogComponent,
6044
                this.viewRef,
6045
                settings);
6046
            this.overlayService.show(this._advancedFilteringOverlayId);
88✔
6047
        }
6048
    }
6049

6050
    /**
6051
     * Closes the advanced filtering dialog.
6052
     *
6053
     * @param applyChanges indicates whether the changes should be applied
6054
     */
6055
    public closeAdvancedFilteringDialog(applyChanges: boolean) {
6056
        if (this._advancedFilteringOverlayId) {
4✔
6057
            const advancedFilteringOverlay = this.overlayService.getOverlayById(this._advancedFilteringOverlayId);
4✔
6058
            const advancedFilteringDialog = advancedFilteringOverlay.componentRef.instance as IgxAdvancedFilteringDialogComponent;
4✔
6059

6060
            if (applyChanges) {
4✔
6061
                advancedFilteringDialog.applyChanges();
2✔
6062
            }
6063
            advancedFilteringDialog.closeDialog();
4✔
6064
        }
6065
    }
6066

6067
    /**
6068
     * @hidden @internal
6069
     */
6070
    public getEmptyRecordObjectFor(inRow: RowType) {
6071
        const row = { ...inRow?.data };
49✔
6072
        Object.keys(row).forEach(key => row[key] = undefined);
213✔
6073
        const id = this.generateRowID();
49✔
6074
        row[this.primaryKey] = id;
49✔
6075
        return { rowID: id, data: row, recordRef: row };
49✔
6076
    }
6077

6078
    /**
6079
     * @hidden @internal
6080
     */
6081
    public hasHorizontalScroll() {
6082
        return this.totalWidth - this.unpinnedWidth > 0 && this.width !== null;
10,597✔
6083
    }
6084

6085
    /**
6086
     * @hidden @internal
6087
     */
6088
    public isSummaryRow(rowData): boolean {
6089
        return rowData && rowData.summaries && (rowData.summaries instanceof Map);
411,251✔
6090
    }
6091

6092
    /**
6093
     * @hidden @internal
6094
     */
6095
    public triggerPipes() {
6096
        this.pipeTrigger++;
125✔
6097
        this.cdr.detectChanges();
125✔
6098
    }
6099

6100
    /**
6101
     * @hidden
6102
     */
6103
    public rowEditingWheelHandler(event: WheelEvent) {
6104
        if (event.deltaY > 0) {
×
6105
            this.verticalScrollContainer.scrollNext();
×
6106
        } else {
6107
            this.verticalScrollContainer.scrollPrev();
×
6108
        }
6109
    }
6110

6111
    /**
6112
     * @hidden
6113
     */
6114
    public getUnpinnedIndexById(id) {
6115
        return this.unpinnedRecords.findIndex(x => x[this.primaryKey] === id);
102✔
6116
    }
6117

6118
    /**
6119
     * Finishes the row transactions on the current row and returns whether the grid editing was canceled.
6120
     *
6121
     * @remarks
6122
     * If `commit === true`, passes them from the pending state to the data (or transaction service)
6123
     * @example
6124
     * ```html
6125
     * <button type="button" igxButton (click)="grid.endEdit(true)">Commit Row</button>
6126
     * ```
6127
     * @param commit
6128
     */
6129
    // TODO: Facade for crud service refactoring. To be removed
6130
    // TODO: do not remove this, as it is used in rowEditTemplate, but mark is as internal and hidden
6131
    public endEdit(commit = true, event?: Event): boolean {
1✔
6132
        return this.crudService.endEdit(commit, event);
5✔
6133
    }
6134

6135
    /**
6136
     * Enters add mode by spawning the UI under the specified row by rowID.
6137
     *
6138
     * @remarks
6139
     * If null is passed as rowID, the row adding UI is spawned as the first record in the data view
6140
     * @remarks
6141
     * Spawning the UI to add a child for a record only works if you provide a rowID
6142
     * @example
6143
     * ```typescript
6144
     * this.grid.beginAddRowById('ALFKI');
6145
     * this.grid.beginAddRowById('ALFKI', true);
6146
     * this.grid.beginAddRowById(null);
6147
     * ```
6148
     * @param rowID - The rowID to spawn the add row UI for, or null to spawn it as the first record in the data view
6149
     * @param asChild - Whether the record should be added as a child. Only applicable to igxTreeGrid.
6150
     */
6151
    public beginAddRowById(rowID: any, asChild?: boolean): void {
6152
        let index = rowID;
4✔
6153
        if (rowID == null) {
4✔
6154
            if (asChild) {
3!
6155
                console.warn('The record cannot be added as a child to an unspecified record.');
×
6156
                return;
×
6157
            }
6158
            index = null;
3✔
6159
        } else {
6160
            // find the index of the record with that PK
6161
            index = this.gridAPI.get_rec_index_by_id(rowID, this.dataView);
1✔
6162
            if (index === -1) {
1!
6163
                console.warn('No row with the specified ID was found.');
×
6164
                return;
×
6165
            }
6166
        }
6167

6168
        this._addRowForIndex(index, asChild);
4✔
6169
    }
6170

6171
    protected _addRowForIndex(index: number, asChild?: boolean) {
6172
        if (!this.dataView.length) {
4!
6173
            this.beginAddRowForIndex(index, asChild);
×
6174
            return;
×
6175
        }
6176
        // check if the index is valid - won't support anything outside the data view
6177
        if (index >= 0 && index < this.dataView.length) {
4!
6178
            // check if the index is in the view port
6179
            if ((index < this.virtualizationState.startIndex ||
4✔
6180
                index >= this.virtualizationState.startIndex + this.virtualizationState.chunkSize) &&
6181
                !this.isRecordPinnedByViewIndex(index)) {
6182
                this.verticalScrollContainer.chunkLoad
1✔
6183
                    .pipe(first(), takeUntil(this.destroy$))
6184
                    .subscribe(() => {
6185
                        this.beginAddRowForIndex(index, asChild);
1✔
6186
                    });
6187
                this.navigateTo(index);
1✔
6188
                this.notifyChanges(true);
1✔
6189
                return;
1✔
6190
            }
6191
            this.beginAddRowForIndex(index, asChild);
3✔
6192
        } else {
6193
            console.warn('The row with the specified PK or index is outside of the current data view.');
×
6194
        }
6195
    }
6196

6197
    /**
6198
     * Enters add mode by spawning the UI at the specified index.
6199
     *
6200
     * @remarks
6201
     * Accepted values for index are integers from 0 to this.grid.dataView.length
6202
     * @example
6203
     * ```typescript
6204
     * this.grid.beginAddRowByIndex(0);
6205
     * ```
6206
     * @param index - The index to spawn the UI at. Accepts integers from 0 to this.grid.dataView.length
6207
     */
6208
    public beginAddRowByIndex(index: number): void {
6209
        if (index === 0) {
1✔
6210
            return this.beginAddRowById(null);
1✔
6211
        }
6212
        return this._addRowForIndex(index - 1);
×
6213
    }
6214

6215
    /**
6216
     * @hidden
6217
     */
6218
    public preventHeaderScroll(args) {
6219
        if (args.target.scrollLeft !== 0) {
×
6220
            (this.navigation as any).forOfDir().getScroll().scrollLeft = args.target.scrollLeft;
×
6221
            args.target.scrollLeft = 0;
×
6222
        }
6223
    }
6224

6225
    protected beginAddRowForIndex(index: number, asChild = false) {
3✔
6226
        // TODO is row from rowList suitable for enterAddRowMode
6227
        const row = index == null ?
4✔
6228
            null : this.rowList.find(r => r.index === index);
2✔
6229
        if (row !== undefined) {
4!
6230
            this.crudService.enterAddRowMode(row, asChild);
4✔
6231
        } else {
6232
            console.warn('No row with the specified PK or index was found.');
×
6233
        }
6234
    }
6235

6236
    protected switchTransactionService(val: boolean) {
6237
        if (val) {
239✔
6238
            this._transactions = this.transactionFactory.create(TRANSACTION_TYPE.Base);
238✔
6239
        } else {
6240
            this._transactions = this.transactionFactory.create(TRANSACTION_TYPE.None);
1✔
6241
        }
6242

6243
        if (this.dataCloneStrategy) {
239✔
6244
            this._transactions.cloneStrategy = this.dataCloneStrategy;
239✔
6245
        }
6246
    }
6247

6248
    protected subscribeToTransactions(): void {
6249
        this.transactionChange$.next();
3,730✔
6250
        this.transactions.onStateUpdate.pipe(takeUntil(merge(this.destroy$, this.transactionChange$)))
3,730✔
6251
            .subscribe(this.transactionStatusUpdate.bind(this));
6252
    }
6253

6254
    protected transactionStatusUpdate(event: StateUpdateEvent) {
6255
        let actions: Action<Transaction>[] = [];
319✔
6256
        if (event.origin === TransactionEventOrigin.REDO) {
319✔
6257
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.DELETE) : [];
44!
6258
        } else if (event.origin === TransactionEventOrigin.UNDO) {
280✔
6259
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.ADD) : [];
54!
6260
        }
6261
        if (actions.length > 0) {
319✔
6262
            for (const action of actions) {
22✔
6263
                if (this.selectionService.isRowSelected(action.transaction.id)) {
27!
6264
                    this.selectionService.deselectRow(action.transaction.id);
×
6265
                }
6266
            }
6267
        }
6268
        if (event.origin === TransactionEventOrigin.REDO || event.origin === TransactionEventOrigin.UNDO) {
319✔
6269
            event.actions.forEach(x => {
88✔
6270
                if (x.transaction.type === TransactionType.UPDATE) {
98✔
6271
                    const value = this.transactions.getAggregatedValue(x.transaction.id, true);
43✔
6272
                    this.validation.update(x.transaction.id, value ?? x.recordRef);
43✔
6273
                } else if (x.transaction.type === TransactionType.DELETE || x.transaction.type === TransactionType.ADD) {
55✔
6274
                    const value = this.transactions.getAggregatedValue(x.transaction.id, true);
55✔
6275
                    if (value) {
55✔
6276
                        this.validation.create(x.transaction.id, value ?? x.recordRef);
29!
6277
                        this.validation.update(x.transaction.id, value ?? x.recordRef);
29!
6278
                        this.validation.markAsTouched(x.transaction.id);
29✔
6279
                    } else {
6280
                        this.validation.clear(x.transaction.id);
26✔
6281
                    }
6282
                }
6283

6284
            });
6285
        }
6286

6287
        this.selectionService.clearHeaderCBState();
319✔
6288
        this.summaryService.clearSummaryCache();
319✔
6289
        this.pipeTrigger++;
319✔
6290
        this.notifyChanges();
319✔
6291
    }
6292

6293
    protected writeToData(rowIndex: number, value: any) {
6294
        mergeObjects(this.gridAPI.get_all_data()[rowIndex], value);
×
6295
    }
6296

6297
    protected _restoreVirtState(row) {
6298
        // check virtualization state of data record added from cache
6299
        // in case state is no longer valid - update it.
6300
        const rowForOf = row.virtDirRow;
308✔
6301
        const gridScrLeft = rowForOf.getScroll().scrollLeft;
308✔
6302
        rowForOf.onHScroll(gridScrLeft);
308✔
6303
        rowForOf.cdr.detectChanges();
308✔
6304
    }
6305

6306
    protected changeRowEditingOverlayStateOnScroll(row: RowType) {
6307
        if (!this.rowEditable || !this.rowEditingOverlay || this.rowEditingOverlay.collapsed) {
14✔
6308
            return;
12✔
6309
        }
6310
        if (!row) {
2!
6311
            this.toggleRowEditingOverlay(false);
×
6312
        } else {
6313
            this.repositionRowEditingOverlay(row);
2✔
6314
        }
6315
    }
6316

6317
    /**
6318
     * Should be called when data and/or isLoading input changes so that the overlay can be
6319
     * hidden/shown based on the current value of shouldOverlayLoading
6320
     */
6321
    protected evaluateLoadingState() {
6322
        if (this.shouldOverlayLoading) {
3,191✔
6323
            // a new overlay should be shown
6324
            const overlaySettings: OverlaySettings = {
15✔
6325
                outlet: this.loadingOutlet,
6326
                closeOnOutsideClick: false,
6327
                positionStrategy: new ContainerPositionStrategy()
6328
            };
6329
            this.loadingOverlay.open(overlaySettings);
15✔
6330
        } else {
6331
            this.loadingOverlay.close();
3,176✔
6332
        }
6333
    }
6334

6335
    /**
6336
     * @hidden
6337
     * Sets grid width i.e. this.calcWidth
6338
     */
6339
    protected calculateGridWidth() {
6340
        let width;
6341

6342
        if (this.isPercentWidth) {
11,370✔
6343
            /* width in %*/
6344
            const computed = this.document.defaultView.getComputedStyle(this.nativeElement).getPropertyValue('width');
5,183✔
6345
            width = computed.indexOf('%') === -1 ? parseFloat(computed) : null;
5,183✔
6346
        } else {
6347
            width = parseInt(this.width, 10);
6,187✔
6348
        }
6349

6350
        if (!width && this.nativeElement) {
11,370✔
6351
            width = this.nativeElement.offsetWidth;
38✔
6352
        }
6353

6354

6355
        if (this.width === null || !width) {
11,370✔
6356
            width = this.getColumnWidthSum();
38✔
6357
        }
6358

6359
        if (this.hasVerticalScroll() && this.width !== null) {
11,370✔
6360
            width -= this.scrollSize;
2,376✔
6361
        }
6362
        if ((Number.isFinite(width) || width === null) && width !== this.calcWidth) {
11,370!
6363
            this.calcWidth = width;
3,658✔
6364
        }
6365
        this._derivePossibleWidth();
11,370✔
6366
    }
6367

6368
    /**
6369
     * @hidden
6370
     * Sets columns defaultWidth property
6371
     */
6372
    protected _derivePossibleWidth() {
6373
        if (!this.columnWidthSetByUser) {
11,370✔
6374
            this._columnWidth = this.width !== null ? this.getPossibleColumnWidth() : this.minColumnWidth + 'px';
10,632✔
6375
        }
6376
        this._columns.forEach((column: IgxColumnComponent) => {
11,370✔
6377
            if (this.hasColumnLayouts && parseInt(this._columnWidth, 10)) {
70,699✔
6378
                const columnWidthCombined = parseInt(this._columnWidth, 10) * (column.colEnd ? column.colEnd - column.colStart : 1);
3,448✔
6379
                column.defaultWidth = columnWidthCombined + 'px';
3,448✔
6380
            } else {
6381
                // D.K. March 29th, 2021 #9145 Consider min/max width when setting defaultWidth property
6382
                column.defaultWidth = this.getExtremumBasedColWidth(column);
67,251✔
6383
                column.resetCaches();
67,251✔
6384
            }
6385
        });
6386
        this.resetCachedWidths();
11,370✔
6387
    }
6388

6389
    /**
6390
     * @hidden
6391
     * @internal
6392
     */
6393
    protected getExtremumBasedColWidth(column: IgxColumnComponent): string {
6394
        let width = this._columnWidth;
67,251✔
6395
        if (width && typeof width !== 'string') {
67,251!
6396
            width = String(width);
×
6397
        }
6398
        const minWidth = width.indexOf('%') === -1 ? column.minWidthPx : column.minWidthPercent;
67,251✔
6399
        const maxWidth = width.indexOf('%') === -1 ? column.maxWidthPx : column.maxWidthPercent;
67,251✔
6400
        if (column.hidden) {
67,251✔
6401
            return width;
1,368✔
6402
        }
6403

6404
        if (minWidth > parseFloat(width)) {
65,883!
6405
            width = String(column.minWidth);
×
6406
        } else if (maxWidth < parseFloat(width)) {
65,883✔
6407
            width = String(column.maxWidth);
1,115✔
6408
        }
6409

6410
        // if no px or % are defined in maxWidth/minWidth consider it px
6411
        if (width.indexOf('%') === -1 && width.indexOf('px') === -1) {
65,883✔
6412
            width += 'px';
248✔
6413
        }
6414
        return width;
65,883✔
6415
    }
6416

6417
    protected resetNotifyChanges() {
6418
        this._cdrRequestRepaint = false;
7,962✔
6419
        this._cdrRequests = false;
7,962✔
6420
    }
6421

6422
    /** @hidden @internal */
6423
    public resolveOutlet() {
6424
        return this._userOutletDirective ? this._userOutletDirective : this._outletDirective;
220,214!
6425
    }
6426

6427
    /**
6428
     * Reorder columns in the main columnList and _columns collections.
6429
     *
6430
     * @hidden
6431
     */
6432
    protected _moveColumns(from: IgxColumnComponent, to: IgxColumnComponent, pos: DropPosition) {
6433
        const orderedList = this._pinnedColumns.concat(this._unpinnedColumns);
138✔
6434
        const list = orderedList;
138✔
6435
        this._reorderColumns(from, to, pos, list);
138✔
6436
        const newList = this._resetColumnList(list);
138✔
6437
        this.updateColumns(newList);
138✔
6438
    }
6439

6440

6441
    /**
6442
     * Update internal column's collection.
6443
     * @hidden
6444
     */
6445
    public updateColumns(newColumns: IgxColumnComponent[]) {
6446
        // update internal collections to retain order.
6447
        this._pinnedColumns = newColumns
5,512✔
6448
            .filter((c) => c.pinned);
33,197✔
6449
        this._unpinnedColumns = newColumns.filter((c) => !c.pinned);
33,197✔
6450
        this._columns = newColumns;
5,512✔
6451
        this.resetCaches();
5,512✔
6452
    }
6453

6454
    /**
6455
     * @hidden
6456
     */
6457
    protected _resetColumnList(list?) {
6458
        if (!list) {
138!
6459
            list = this._columns;
×
6460
        }
6461
        let newList = [];
138✔
6462
        list.filter(c => c.level === 0).forEach(p => {
1,189✔
6463
            newList.push(p);
814✔
6464
            if (p.columnGroup) {
814✔
6465
                newList = newList.concat(p.allChildren);
138✔
6466
            }
6467
        });
6468
        return newList;
138✔
6469
    }
6470

6471
    /**
6472
     * Reorders columns inside the passed column collection.
6473
     * When reordering column group collection, the collection is not flattened.
6474
     * In all other cases, the columns collection is flattened, this is why adittional calculations on the dropIndex are done.
6475
     *
6476
     * @hidden
6477
     */
6478
    protected _reorderColumns(from: IgxColumnComponent, to: IgxColumnComponent, position: DropPosition, columnCollection: any[],
6479
        inGroup = false) {
138✔
6480
        const fromIndex = columnCollection.indexOf(from);
154✔
6481
        const childColumnsCount = inGroup ? 1 : from.allChildren.length + 1;
154✔
6482
        columnCollection.splice(fromIndex, childColumnsCount);
154✔
6483
        let dropIndex = columnCollection.indexOf(to);
154✔
6484
        if (position === DropPosition.AfterDropTarget) {
154✔
6485
            dropIndex++;
87✔
6486
            if (!inGroup && to.columnGroup) {
87✔
6487
                dropIndex += to.allChildren.length;
21✔
6488
            }
6489
        }
6490
        columnCollection.splice(dropIndex, 0, from);
154✔
6491
    }
6492

6493
    /**
6494
     * Reorder column group collection.
6495
     *
6496
     * @hidden
6497
     */
6498
    protected _moveChildColumns(parent: IgxColumnComponent, from: IgxColumnComponent, to: IgxColumnComponent, pos: DropPosition) {
6499
        const buffer = parent.children.toArray();
16✔
6500
        this._reorderColumns(from, to, pos, buffer, true);
16✔
6501
        parent.children.reset(buffer);
16✔
6502
    }
6503

6504
    /**
6505
     * @hidden @internal
6506
     */
6507
    protected setupColumns() {
6508
        if (this.autoGenerate) {
3,278✔
6509
            this.autogenerateColumns();
833✔
6510
        } else {
6511
            this._columns = this.getColumnList();
2,445✔
6512
        }
6513

6514
        this.initColumns(this._columns, (col: IgxColumnComponent) => this.columnInit.emit(col));
20,974✔
6515
        this.columnListDiffer.diff(this.columnList);
3,278✔
6516

6517
        this.columnList.changes
3,278✔
6518
            .pipe(takeUntil(this.destroy$))
6519
            .subscribe((change: QueryList<IgxColumnComponent>) => {
6520
                this.onColumnsChanged(change);
78✔
6521
            });
6522
    }
6523

6524
    protected getColumnList() {
6525
        return this.columnList.toArray();
2,261✔
6526
    }
6527

6528
    /**
6529
     * @hidden
6530
     */
6531
    protected deleteRowFromData(rowID: any, index: number) {
6532
        //  if there is a row (index !== 0) delete it
6533
        //  if there is a row in ADD or UPDATE state change it's state to DELETE
6534
        if (index !== -1) {
×
6535
            if (this.transactions.enabled) {
×
6536
                const transaction: Transaction = { id: rowID, type: TransactionType.DELETE, newValue: null };
×
6537
                this.transactions.add(transaction, this.data[index]);
×
6538
            } else {
6539
                this.data.splice(index, 1);
×
6540
            }
6541
        } else {
6542
            const state: State = this.transactions.getState(rowID);
×
6543
            this.transactions.add({ id: rowID, type: TransactionType.DELETE, newValue: null }, state && state.recordRef);
×
6544
        }
6545
    }
6546

6547

6548
    /**
6549
     * @hidden @internal
6550
     */
6551
    protected getDataBasedBodyHeight(): number {
6552
        return !this.data || (this.data.length < this._defaultTargetRecordNumber) ?
1,030✔
6553
            0 : this.defaultTargetBodyHeight;
6554
    }
6555

6556
    /**
6557
     * @hidden @internal
6558
     */
6559
    protected onPinnedRowsChanged(change: QueryList<IgxGridRowComponent>) {
6560
        const diff = this.rowListDiffer.diff(change);
182✔
6561
        if (diff) {
182✔
6562
            this.notifyChanges(true);
182✔
6563
        }
6564
    }
6565

6566
    /**
6567
     * @hidden
6568
     */
6569
    protected onColumnsChanged(change: QueryList<IgxColumnComponent>) {
6570
        const diff = this.columnListDiffer.diff(change);
67✔
6571

6572
        if (this.autoGenerate && this._columns.length === 0 && this._autoGeneratedCols.length > 0) {
67!
6573
            // In Ivy if there are nested conditional templates the content children are re-evaluated
6574
            // hence autogenerated columns are cleared and need to be reset.
6575
            this.updateColumns(this._autoGeneratedCols);
×
6576
            return;
×
6577
        }
6578
        if (diff) {
67✔
6579
            let added = false;
67✔
6580
            let removed = false;
67✔
6581
            let pinning = false;
67✔
6582
            diff.forEachAddedItem((record: IterableChangeRecord<IgxColumnComponent>) => {
67✔
6583
                added = true;
1,035✔
6584
                if (record.item.pinned) {
1,035✔
6585
                    this._pinnedColumns.push(record.item);
1✔
6586
                    pinning = true;
1✔
6587
                } else {
6588
                    this._unpinnedColumns.push(record.item);
1,034✔
6589
                }
6590
            });
6591

6592
            this.initColumns(this.columnList.toArray(), (col: IgxColumnComponent) => this.columnInit.emit(col));
1,297✔
6593
            if (pinning) {
67✔
6594
                this.initPinning();
1✔
6595
            }
6596

6597
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxColumnComponent | IgxColumnGroupComponent>) => {
67✔
6598
                const isColumnGroup = record.item instanceof IgxColumnGroupComponent;
134✔
6599
                if (!isColumnGroup) {
134✔
6600
                    // Clear Grouping
6601
                    this.gridAPI.clear_groupby(record.item.field);
131✔
6602

6603
                    // Clear Filtering
6604
                    this.filteringService.clear_filter(record.item.field);
131✔
6605

6606
                    // Close filter row
6607
                    if (this.filteringService.isFilterRowVisible
131!
6608
                        && this.filteringService.filteredColumn
6609
                        && this.filteringService.filteredColumn.field === record.item.field) {
6610
                        this.filteringRow.close();
×
6611
                    }
6612

6613
                    // Clear Sorting
6614
                    this.gridAPI.clear_sort(record.item.field);
131✔
6615

6616
                    // Remove column selection
6617
                    this.selectionService.deselectColumnsWithNoEvent([record.item.field]);
131✔
6618
                }
6619
                removed = true;
134✔
6620
            });
6621

6622
            this.resetCaches();
67✔
6623

6624
            if (added || removed) {
67✔
6625
                this.onColumnsAddedOrRemoved();
63✔
6626
            }
6627
        }
6628
    }
6629

6630
    /**
6631
     * @hidden @internal
6632
     */
6633
    protected onColumnsAddedOrRemoved() {
6634
        this.summaryService.clearSummaryCache();
63✔
6635
        Promise.resolve().then(() => {
63✔
6636
            // `onColumnsChanged` can be executed midway a current detectChange cycle and markForCheck will be ignored then.
6637
            // This ensures that we will wait for the current cycle to end so we can trigger a new one and ngDoCheck to fire.
6638
            this.notifyChanges(true);
63✔
6639
        });
6640
    }
6641

6642
    /**
6643
     * @hidden
6644
     */
6645
    protected calculateGridSizes(recalcFeatureWidth = true) {
7,596✔
6646
        /*
6647
            TODO: (R.K.) This layered lasagne should be refactored
6648
            ASAP. The reason I have to reset the caches so many times is because
6649
            after teach `detectChanges` call they are filled with invalid
6650
            state. Of course all of this happens midway through the grid
6651
            sizing process which of course, uses values from the caches, thus resulting
6652
            in a broken layout.
6653
        */
6654
        this.cdr.detectChanges();
7,599✔
6655
        this.resetCaches(recalcFeatureWidth);
7,599✔
6656

6657
        const hasScroll = this.hasVerticalScroll();
7,599✔
6658
        const hasHScroll = !this.isHorizontalScrollHidden;
7,599✔
6659
        this.calculateGridWidth();
7,599✔
6660
        this.resetCaches(recalcFeatureWidth);
7,599✔
6661
        this.cdr.detectChanges();
7,599✔
6662
        this.calculateGridHeight();
7,599✔
6663

6664
        if (this.rowEditable) {
7,599✔
6665
            this.repositionRowEditingOverlay(this.crudService.rowInEditMode);
855✔
6666
        }
6667

6668
        if (this.filteringService.isFilterRowVisible) {
7,599✔
6669
            this.filteringRow.resetChipsArea();
115✔
6670
        }
6671

6672
        this.cdr.detectChanges();
7,599✔
6673
        // in case scrollbar has appeared recalc to size correctly.
6674
        if (hasScroll !== this.hasVerticalScroll()) {
7,599✔
6675
            this.calculateGridWidth();
86✔
6676
            this.cdr.detectChanges();
86✔
6677
        }
6678

6679
        // in case horizontal scrollbar has appeared recalc to size correctly.
6680
        if (hasHScroll !== this.hasHorizontalScroll()) {
7,599✔
6681
            this.isHorizontalScrollHidden = !this.hasHorizontalScroll();
2,363✔
6682
            this.cdr.detectChanges();
2,363✔
6683
            this.calculateGridHeight();
2,363✔
6684
            this.cdr.detectChanges();
2,363✔
6685
        }
6686
        if (this.zone.isStable) {
7,599✔
6687
            this.zone.run(() => {
203✔
6688
                this._applyWidthHostBinding();
203✔
6689
                this.cdr.detectChanges();
203✔
6690
            });
6691
        } else {
6692
            this.zone.onStable.pipe(first()).subscribe(() => {
7,396✔
6693
                this.zone.run(() => {
7,396✔
6694
                    this._applyWidthHostBinding();
7,396✔
6695
                });
6696
            });
6697
        }
6698
        this.resetCaches(recalcFeatureWidth);
7,599✔
6699
        if (this.hasColumnsToAutosize) {
7,599✔
6700
            this.cdr.detectChanges();
18✔
6701
            this.zone.onStable.pipe(first()).subscribe(() => {
18✔
6702
                this.autoSizeColumnsInView();
18✔
6703
            });
6704
        }
6705
    }
6706

6707
    /**
6708
     * @hidden
6709
     * @internal
6710
     */
6711
    protected calcGridHeadRow() {
6712
        if (this.maxLevelHeaderDepth) {
9,290✔
6713
            this._baseFontSize = parseFloat(getComputedStyle(this.document.documentElement).getPropertyValue('font-size'));
804✔
6714
            const hasFilterRow = this._allowFiltering && this._filterMode === FilterMode.quickFilter;
804✔
6715
            const minSize = (this.maxLevelHeaderDepth + 1 + (hasFilterRow ? 1 : 0)) * this.defaultRowHeight / this._baseFontSize;
804✔
6716
            this.theadRow.nativeElement.style.minHeight = `${minSize}rem`;
804✔
6717
        }
6718
    }
6719

6720
    /**
6721
     * @hidden
6722
     * Sets TBODY height i.e. this.calcHeight
6723
     */
6724
    protected calculateGridHeight() {
6725
        this.calcGridHeadRow();
9,728✔
6726

6727
        this.calcHeight = this._calculateGridBodyHeight();
9,728✔
6728
        if (this.pinnedRowHeight && this.calcHeight) {
9,728✔
6729
            this.calcHeight -= this.pinnedRowHeight;
106✔
6730
        }
6731
    }
6732

6733
    /**
6734
     * @hidden
6735
     */
6736
    protected getGroupAreaHeight(): number {
6737
        return 0;
2,512✔
6738
    }
6739

6740
    /**
6741
     * @hidden
6742
     */
6743
    protected getComputedHeight(elem) {
6744
        return elem.offsetHeight ? parseFloat(this.document.defaultView.getComputedStyle(elem).getPropertyValue('height')) : 0;
34,822✔
6745
    }
6746
    /**
6747
     * @hidden
6748
     */
6749
    protected getFooterHeight(): number {
6750
        return this.summaryRowHeight || this.getComputedHeight(this.tfoot.nativeElement);
8,975✔
6751
    }
6752
    /**
6753
     * @hidden
6754
     */
6755
    protected getTheadRowHeight(): number {
6756
        const height = this.getComputedHeight(this.theadRow.nativeElement);
8,975✔
6757
        return (!this.allowFiltering || (this.allowFiltering && this.filterMode !== FilterMode.quickFilter)) ?
8,975✔
6758
            height - this.getFilterCellHeight() :
6759
            height;
6760
    }
6761

6762
    /**
6763
     * @hidden
6764
     */
6765
    protected getToolbarHeight(): number {
6766
        let toolbarHeight = 0;
8,975✔
6767
        if (this.toolbar.first) {
8,975✔
6768
            toolbarHeight = this.getComputedHeight(this.toolbar.first.nativeElement);
393✔
6769
        }
6770
        return toolbarHeight;
8,975✔
6771
    }
6772

6773
    /**
6774
     * @hidden
6775
     */
6776
    protected getPagingFooterHeight(): number {
6777
        let pagingHeight = 0;
8,975✔
6778
        if (this.footer) {
8,975✔
6779
            const height = this.getComputedHeight(this.footer.nativeElement);
8,975✔
6780
            pagingHeight = this.footer.nativeElement.firstElementChild ?
8,975✔
6781
                height : 0;
6782
        }
6783
        return pagingHeight;
8,975✔
6784
    }
6785

6786
    /**
6787
     * @hidden
6788
     */
6789
    protected getFilterCellHeight(): number {
6790
        const headerGroupNativeEl = (this.headerGroupsList.length !== 0) ?
7,698✔
6791
            this.headerGroupsList[0].nativeElement : null;
6792
        const filterCellNativeEl = (headerGroupNativeEl) ?
7,698✔
6793
            headerGroupNativeEl.querySelector('igx-grid-filtering-cell') as HTMLElement : null;
6794
        return (filterCellNativeEl) ? filterCellNativeEl.offsetHeight : 0;
7,698✔
6795
    }
6796

6797
    /**
6798
     * @hidden
6799
     */
6800
    protected _calculateGridBodyHeight(): number {
6801
        if (!this._height) {
9,728✔
6802
            return null;
753✔
6803
        }
6804
        const actualTheadRow = this.getTheadRowHeight();
8,975✔
6805
        const footerHeight = this.getFooterHeight();
8,975✔
6806
        const toolbarHeight = this.getToolbarHeight();
8,975✔
6807
        const pagingHeight = this.getPagingFooterHeight();
8,975✔
6808
        const groupAreaHeight = this.getGroupAreaHeight();
8,975✔
6809
        const scrHeight = this.getComputedHeight(this.scr.nativeElement);
8,975✔
6810
        const renderedHeight = toolbarHeight + actualTheadRow +
8,975✔
6811
            footerHeight + pagingHeight + groupAreaHeight +
6812
            scrHeight;
6813

6814
        let gridHeight = 0;
8,975✔
6815

6816
        if (this.isPercentHeight) {
8,975✔
6817
            const computed = this.document.defaultView.getComputedStyle(this.nativeElement).getPropertyValue('height');
1,890✔
6818
            const autoSize = this._shouldAutoSize(renderedHeight);
1,890✔
6819
            if (autoSize || computed.indexOf('%') !== -1) {
1,890✔
6820
                const bodyHeight = this.getDataBasedBodyHeight();
833✔
6821
                return bodyHeight > 0 ? bodyHeight : null;
833✔
6822
            }
6823
            gridHeight = parseFloat(computed);
1,057✔
6824
        } else {
6825
            gridHeight = parseInt(this._height, 10);
7,085✔
6826
        }
6827
        const height = Math.abs(gridHeight - renderedHeight);
8,142✔
6828

6829
        if (Math.round(height) === 0 || isNaN(gridHeight)) {
8,142✔
6830
            const bodyHeight = this.defaultTargetBodyHeight;
16✔
6831
            return bodyHeight > 0 ? bodyHeight : null;
16✔
6832
        }
6833
        return height;
8,126✔
6834
    }
6835

6836
    protected checkContainerSizeChange() {
6837
        const parentElement = this.nativeElement.parentElement || (this.nativeElement.getRootNode() as any).host;
94!
6838
        const origHeight = parentElement.offsetHeight;
94✔
6839
        this.nativeElement.style.display = 'none';
94✔
6840
        const height = parentElement.offsetHeight;
94✔
6841
        this.nativeElement.style.display = '';
94✔
6842
        return origHeight !== height;
94✔
6843
    }
6844

6845
    protected _shouldAutoSize(renderedHeight) {
6846
        this.tbody.nativeElement.style.display = 'none';
1,166✔
6847
        const parentElement = this.nativeElement.parentElement || (this.nativeElement.getRootNode() as any).host;
1,166✔
6848
        let res = !parentElement ||
1,166✔
6849
            parentElement.clientHeight === 0 ||
6850
            parentElement.clientHeight === renderedHeight;
6851
        if (parentElement && (res || this._autoSize)) {
1,166✔
6852
            // If grid causes the parent container to extend (for example when container is flex)
6853
            // we should always auto-size since the actual size of the container will continuously change as the grid renders elements.
6854
            this._autoSize = false;
94✔
6855
            res = this.checkContainerSizeChange();
94✔
6856
        }
6857
        this.tbody.nativeElement.style.display = '';
1,166✔
6858
        return res;
1,166✔
6859
    }
6860

6861
    /**
6862
     * @hidden
6863
     * Gets calculated width of the unpinned area
6864
     * @param takeHidden If we should take into account the hidden columns in the pinned area.
6865
     */
6866
    protected getUnpinnedWidth(takeHidden = false) {
22,586✔
6867
        let width = this.isPercentWidth ?
22,586✔
6868
            this.calcWidth :
6869
            parseInt(this.width, 10) || parseInt(this.hostWidth, 10) || this.calcWidth;
12,754✔
6870
        if (this.hasVerticalScroll() && !this.isPercentWidth) {
22,586✔
6871
            width -= this.scrollSize;
4,218✔
6872
        }
6873
        if (!this.isPinningToStart) {
22,586✔
6874
            width -= this.featureColumnsWidth();
58✔
6875
        }
6876

6877
        return width - this.getPinnedWidth(takeHidden);
22,586✔
6878
    }
6879

6880
    /**
6881
     * @hidden
6882
     */
6883
    protected _summaries(fieldName: string, hasSummary: boolean, summaryOperand?: any) {
6884
        const column = this.gridAPI.get_column_by_name(fieldName);
24✔
6885
        if (column) {
24✔
6886
            column.hasSummary = hasSummary;
24✔
6887
            if (summaryOperand) {
24✔
6888
                if (this.rootSummariesEnabled) {
2✔
6889
                    this.summaryService.retriggerRootPipe++;
2✔
6890
                }
6891
                column.summaries = summaryOperand;
2✔
6892
            }
6893
        }
6894
    }
6895

6896
    /**
6897
     * @hidden
6898
     */
6899
    protected _multipleSummaries(expressions: ISummaryExpression[], hasSummary: boolean) {
6900
        expressions.forEach((element) => {
4✔
6901
            this._summaries(element.fieldName, hasSummary, element.customSummary);
6✔
6902
        });
6903
    }
6904
    /**
6905
     * @hidden
6906
     */
6907
    protected _disableMultipleSummaries(expressions) {
6908
        expressions.forEach((column) => {
5✔
6909
            const columnName = column && column.fieldName ? column.fieldName : column;
13✔
6910
            this._summaries(columnName, false);
13✔
6911
        });
6912
    }
6913

6914
    /**
6915
     * @hidden
6916
     */
6917
    public resolveDataTypes(rec) {
6918
        if (typeof rec === 'number') {
5,933✔
6919
            return GridColumnDataType.Number;
3,449✔
6920
        } else if (typeof rec === 'boolean') {
2,484✔
6921
            return GridColumnDataType.Boolean;
152✔
6922
        } else if (typeof rec === 'object' && rec instanceof Date) {
2,332✔
6923
            return GridColumnDataType.Date;
142✔
6924
        } else if (typeof rec === 'string' && (/\.(gif|jpe?g|tiff?|png|webp|bmp)$/i).test(rec)) {
2,190✔
6925
            return GridColumnDataType.Image;
1✔
6926
        }
6927
        return GridColumnDataType.String;
2,189✔
6928
    }
6929

6930
    /**
6931
     * @hidden
6932
     */
6933
    protected autogenerateColumns() {
6934
        const data = this.gridAPI.get_data();
623✔
6935
        const fields = this.generateDataFields(data);
623✔
6936
        const columns = [];
623✔
6937

6938
        fields.forEach((field) => {
623✔
6939
            const ref = createComponent(IgxColumnComponent, { environmentInjector: this.envInjector, elementInjector: this.injector });
4,045✔
6940
            ref.instance.field = field;
4,045✔
6941
            ref.instance.dataType = this.resolveDataTypes(data[0][field]);
4,045✔
6942
            ref.changeDetectorRef.detectChanges();
4,045✔
6943
            columns.push(ref.instance);
4,045✔
6944
        });
6945
        this._autoGeneratedCols = columns;
623✔
6946

6947
        this.updateColumns(columns);
623✔
6948
        if (data && data.length > 0) {
623✔
6949
            this.shouldGenerate = false;
595✔
6950
        }
6951
    }
6952

6953
    protected generateDataFields(data: any[]): string[] {
6954
        return Object.keys(data && data.length !== 0 ? data[0] : [])
627✔
6955
            .filter(key => !this.autoGenerateExclude.includes(key));
4,293✔
6956
    }
6957

6958
    /**
6959
     * @hidden
6960
     */
6961
    protected initColumns(collection: IgxColumnComponent[], cb: (args: any) => void = null) {
×
6962
        this._columnGroups = collection.some(col => col.columnGroup);
17,199✔
6963
        if (this.hasColumnLayouts) {
3,539✔
6964
            // Set overall row layout size
6965
            collection.forEach((col) => {
145✔
6966
                if (col.columnLayout) {
1,384✔
6967
                    const layoutSize = col.children ?
287!
6968
                        col.children.reduce((acc, val) => Math.max(val.rowStart + val.gridRowSpan - 1, acc), 1) :
1,087✔
6969
                        1;
6970
                    this._multiRowLayoutRowSize = Math.max(layoutSize, this._multiRowLayoutRowSize);
287✔
6971
                }
6972
            });
6973
        }
6974
        if (this.hasColumnLayouts && this.hasColumnGroups) {
3,539✔
6975
            // invalid configuration - multi-row and column groups
6976
            // remove column groups
6977
            const columnLayoutColumns = collection.filter((col) => col.columnLayout || col.columnLayoutChild);
1,384✔
6978
            collection = columnLayoutColumns;
145✔
6979
        }
6980
        this._maxLevelHeaderDepth = null;
3,539✔
6981
        collection.forEach((column: IgxColumnComponent) => {
3,539✔
6982
            column.defaultWidth = this.columnWidthSetByUser ? this._columnWidth : column.defaultWidth ? column.defaultWidth : '';
22,911✔
6983

6984
            if (cb) {
22,911✔
6985
                cb(column);
22,911✔
6986
            }
6987
        });
6988

6989
        this.updateColumns(collection);
3,539✔
6990

6991
        if (this.hasColumnLayouts) {
3,539✔
6992
            collection.forEach((column: IgxColumnComponent) => {
145✔
6993
                column.populateVisibleIndexes();
1,374✔
6994
            });
6995
        }
6996
    }
6997

6998
    /**
6999
     * @hidden
7000
     */
7001
    protected reinitPinStates() {
7002
        this._pinnedColumns = this._columns
183✔
7003
            .filter((c) => c.pinned).sort((a, b) => this._pinnedColumns.indexOf(a) - this._pinnedColumns.indexOf(b));
2,374✔
7004
        this._unpinnedColumns = this.hasColumnGroups ? this._columns.filter((c) => !c.pinned) :
2,109✔
7005
            this._columns.filter((c) => !c.pinned)
265✔
7006
                .sort((a, b) => this._unpinnedColumns.indexOf(a) - this._unpinnedColumns.indexOf(b));
187✔
7007
    }
7008

7009
    protected extractDataFromSelection(source: any[], formatters = false, headers = false, columnData?: any[]): any[] {
×
7010
        let columnsArray: IgxColumnComponent[];
7011
        let record = {};
246✔
7012
        let selectedData = [];
246✔
7013
        let keys = [];
246✔
7014
        const selectionCollection = new Map();
246✔
7015
        const keysAndData = [];
246✔
7016
        const activeEl = this.selectionService.activeElement;
246✔
7017

7018
        if (this.nativeElement.tagName.toLowerCase() === 'igx-hierarchical-grid') {
246✔
7019
            const expansionRowIndexes = [];
2✔
7020
            for (const [key, value] of this.expansionStates.entries()) {
2✔
7021
                if (value) {
×
7022
                    const rowIndex = this.gridAPI.get_rec_index_by_id(key, this.dataView);
×
7023
                    expansionRowIndexes.push(rowIndex);
×
7024
                }
7025
            }
7026
            if (this.selectionService.selection.size > 0) {
2!
7027
                if (expansionRowIndexes.length > 0) {
2!
7028
                    for (const [key, value] of this.selectionService.selection.entries()) {
×
7029
                        const updatedKey = key;
×
7030
                        let subtract = 0;
×
7031
                        expansionRowIndexes.forEach((row) => {
×
7032
                            if (updatedKey > Number(row)) {
×
7033
                                subtract++;
×
7034
                            }
7035
                        });
7036
                        selectionCollection.set(updatedKey - subtract, value);
×
7037
                    }
7038
                }
7039
            } else if (activeEl) {
×
7040
                let subtract = 0;
×
7041
                if (expansionRowIndexes.length > 0) {
×
7042
                    expansionRowIndexes.forEach(row => {
×
7043
                        if (activeEl.row > Number(row)) {
×
7044
                            subtract++;
×
7045
                        }
7046
                    });
7047
                    activeEl.row -= subtract;
×
7048
                }
7049
            }
7050
        }
7051

7052
        const totalItems = (this as any).totalItemCount ?? 0;
246✔
7053
        const isRemote = totalItems && totalItems > this.dataView.length;
246!
7054
        let selectionMap;
7055
        if (this.nativeElement.tagName.toLowerCase() === 'igx-hierarchical-grid' && selectionCollection.size > 0) {
246!
7056
            selectionMap = isRemote ? Array.from(selectionCollection) :
×
7057
                Array.from(selectionCollection).filter((tuple) => tuple[0] < source.length);
×
7058
        } else {
7059
            selectionMap = isRemote ? Array.from(this.selectionService.selection) :
246!
7060
                Array.from(this.selectionService.selection).filter((tuple) => tuple[0] < source.length);
883✔
7061
        }
7062

7063
        if (this.cellSelection === GridSelectionMode.single && activeEl) {
246✔
7064
            selectionMap.push([activeEl.row, new Set<number>().add(activeEl.column)]);
10✔
7065
        }
7066

7067
        if (this.cellSelection === GridSelectionMode.none && activeEl) {
246✔
7068
            selectionMap.push([activeEl.row, new Set<number>().add(activeEl.column)]);
5✔
7069
        }
7070

7071
        if (columnData) {
246!
7072
            selectedData = columnData;
×
7073
        }
7074

7075
        // eslint-disable-next-line prefer-const
7076
        for (let [row, set] of selectionMap) {
246✔
7077
            row = this.paginator && (this.pagingMode === GridPagingMode.Local && source === this.filteredSortedData) ? row + (this.perPage * this.page) : row;
788✔
7078
            row = isRemote ? row - this.virtualizationState.startIndex : row;
788!
7079
            if (!source[row] || source[row].detailsData !== undefined) {
788✔
7080
                continue;
40✔
7081
            }
7082
            const temp = Array.from(set);
748✔
7083
            for (const each of temp) {
748✔
7084
                columnsArray = this.getSelectableColumnsAt(each);
2,512✔
7085
                columnsArray.forEach((col) => {
2,512✔
7086
                    if (col) {
2,518✔
7087
                        const key = !this.isPivot && headers ? col.header || col.field : col.field;
2,316!
7088
                        const rowData = source[row].ghostRecord ? source[row].recordRef : source[row];
2,316!
7089
                        const value = this.isPivot ? rowData.aggregationValues.get(col.field)
2,316!
7090
                            : resolveNestedPath(rowData, col.field);
7091
                        record[key] = formatters && col.formatter ? col.formatter(value, rowData) : value;
2,316✔
7092
                        if (columnData) {
2,316!
7093
                            if (!record[key]) {
×
7094
                                record[key] = '';
×
7095
                            }
7096
                            record[key] = record[key].toString().concat('recordRow-' + row);
×
7097
                        }
7098
                    }
7099
                });
7100
            }
7101
            if (Object.keys(record).length) {
748✔
7102
                if (columnData) {
746!
7103
                    if (!keys.length) {
×
7104
                        keys = Object.keys(columnData[0]);
×
7105
                    }
7106
                    for (const [key, value] of Object.entries(record)) {
×
7107
                        if (!keys.includes(key)) {
×
7108
                            keys.push(key);
×
7109
                        }
7110
                        let c: any = value;
×
7111
                        const rowNumber = +c.split('recordRow-')[1];
×
7112
                        c = c.split('recordRow-')[0];
×
7113
                        record[key] = c;
×
7114
                        const mergedObj = Object.assign(selectedData[rowNumber], record);
×
7115
                        selectedData[rowNumber] = mergedObj;
×
7116
                    }
7117
                } else {
7118
                    selectedData.push(record);
746✔
7119
                }
7120
            }
7121
            record = {};
748✔
7122
        }
7123

7124
        if (keys.length) {
246!
7125
            keysAndData.push(selectedData);
×
7126
            keysAndData.push(keys);
×
7127
            return keysAndData;
×
7128
        } else {
7129
            return selectedData;
246✔
7130
        }
7131
    }
7132

7133
    protected getSelectableColumnsAt(index) {
7134
        if (this.hasColumnLayouts) {
2,512✔
7135
            const visibleLayoutColumns = this.visibleColumns
2✔
7136
                .filter(col => col.columnLayout)
20✔
7137
                .sort((a, b) => a.visibleIndex - b.visibleIndex);
2✔
7138
            const colLayout = visibleLayoutColumns[index];
2✔
7139
            return colLayout ? colLayout.children.toArray() : [];
2!
7140
        } else {
7141
            const visibleColumns = this.visibleColumns
2,510✔
7142
                .filter(col => !col.columnGroup)
14,120✔
7143
                .sort((a, b) => a.visibleIndex - b.visibleIndex);
11,790✔
7144
            return [visibleColumns[index]];
2,510✔
7145
        }
7146
    }
7147

7148
    protected autoSizeColumnsInView() {
7149
        if (!this.hasColumnsToAutosize) return;
368✔
7150
        const vState = this.headerContainer.state;
29✔
7151
        let colResized = false;
29✔
7152
        const unpinnedInView = this.headerContainer.igxGridForOf.slice(vState.startIndex, vState.startIndex + vState.chunkSize).flatMap(x => x.columnGroup ? x.allChildren : x);
115✔
7153
        const columnsInView = this.pinnedColumns.concat(unpinnedInView as IgxColumnComponent[]);
29✔
7154
        for (const col of columnsInView) {
29✔
7155
            if (!col.autoSize && col.headerCell) {
118✔
7156
                const cellsContentWidths = [];
78✔
7157
                if (col._cells.length !== this.rowList.length) {
78!
7158
                    this.rowList.forEach(x => x.cdr.detectChanges());
×
7159
                }
7160
                const cells = this._dataRowList.map(x => x.cells.find(c => c.column === col));
2,321✔
7161
                cells.forEach((cell) => cellsContentWidths.push(cell?.nativeElement?.offsetWidth || 0));
582✔
7162
                const header = this.headerCellList.find(x => x.column === col);
299✔
7163
                cellsContentWidths.push(header.nativeElement.offsetWidth);
78✔
7164
                const max = Math.max(...cellsContentWidths);
78✔
7165
                if (max === 0) {
78✔
7166
                    // cells not in DOM yet...
7167
                    continue;
8✔
7168
                }
7169
                let maxSize = Math.ceil(Math.max(...cellsContentWidths)) + 1;
70✔
7170
                if (col.maxWidth && maxSize > col.maxWidthPx) {
70✔
7171
                    maxSize = col.maxWidthPx;
3✔
7172
                } else if (maxSize < col.minWidthPx) {
67✔
7173
                    maxSize = col.minWidthPx;
6✔
7174
                }
7175
                col.autoSize = maxSize;
70✔
7176
                col.resetCaches();
70✔
7177
                colResized = true;
70✔
7178
            }
7179
        }
7180
        if (colResized) {
29✔
7181
            this.resetCachedWidths();
18✔
7182
            this.cdr.detectChanges();
18✔
7183
        }
7184
    }
7185

7186
    protected extractDataFromColumnsSelection(source: any[], formatters = false, headers = false): any[] {
×
7187
        let record = {};
20✔
7188
        const selectedData = [];
20✔
7189
        const selectedColumns = this.selectedColumns();
20✔
7190
        if (selectedColumns.length === 0) {
20✔
7191
            return [];
12✔
7192
        }
7193

7194
        for (const data of source) {
8✔
7195
            selectedColumns.forEach((col) => {
71✔
7196
                const key = headers ? col.header || col.field : col.field;
142!
7197
                record[key] = formatters && col.formatter ? col.formatter(data[col.field], data)
142!
7198
                    : data[col.field];
7199
            });
7200

7201
            if (Object.keys(record).length) {
71✔
7202
                selectedData.push(record);
71✔
7203
            }
7204
            record = {};
71✔
7205
        }
7206
        return selectedData;
8✔
7207
    }
7208

7209
    /**
7210
     * @hidden
7211
     */
7212
    protected initPinning() {
7213
        this.calculateGridWidth();
3,858✔
7214
        this.resetCaches();
3,858✔
7215
        this.handleColumnPinningForGroups();
3,858✔
7216
        this.notifyChanges();
3,858✔
7217
    }
7218

7219
    /**
7220
     * @hidden
7221
     */
7222
    protected scrollTo(row: any | number, column: any | number, inCollection = this._filteredSortedUnpinnedData): void {
3✔
7223
        let delayScrolling = false;
106✔
7224

7225
        if (this.paginator && typeof (row) !== 'number') {
106✔
7226
            const rowIndex = inCollection.indexOf(row);
16✔
7227
            const page = Math.floor(rowIndex / this.perPage);
16✔
7228

7229
            if (this.page !== page) {
16✔
7230
                delayScrolling = true;
5✔
7231
                this.page = page;
5✔
7232
            }
7233
        }
7234

7235
        if (delayScrolling) {
106✔
7236
            this.verticalScrollContainer.dataChanged.pipe(first()).subscribe(() => {
5✔
7237
                this.scrollDirective(this.verticalScrollContainer,
5✔
7238
                    typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(row));
5!
7239
            });
7240
        } else {
7241
            this.scrollDirective(this.verticalScrollContainer,
101✔
7242
                typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(row));
101✔
7243
        }
7244

7245
        this.scrollToHorizontally(column);
106✔
7246
    }
7247

7248
    /**
7249
     * @hidden
7250
     */
7251
    protected scrollToHorizontally(column: any | number) {
7252
        let columnIndex = typeof column === 'number' ? column : this.getColumnByName(column).visibleIndex;
191✔
7253
        const scrollRow = this.rowList.find(r => !!r.virtDirRow);
233✔
7254
        const virtDir = scrollRow ? scrollRow.virtDirRow : null;
191✔
7255
        if (this.isPinningToStart && this.pinnedColumns.length) {
191✔
7256
            if (columnIndex >= this.pinnedColumns.length) {
1!
7257
                columnIndex -= this.pinnedColumns.length;
×
7258
                this.scrollDirective(virtDir, columnIndex);
×
7259
            }
7260
        } else {
7261
            this.scrollDirective(virtDir, columnIndex);
190✔
7262
        }
7263
    }
7264

7265
    /**
7266
     * @hidden
7267
     */
7268
    protected scrollDirective(directive: IgxGridForOfDirective<any, any[]>, goal: number): void {
7269
        if (!directive) {
381✔
7270
            return;
1✔
7271
        }
7272
        directive.scrollTo(goal);
380✔
7273
    }
7274

7275

7276
    /**
7277
     * @hidden
7278
     */
7279
    protected getColumnWidthSum(): number {
7280
        let colSum = 0;
38✔
7281
        const cols = this.hasColumnLayouts ?
38!
7282
            this.visibleColumns.filter(x => x.columnLayout) : this.visibleColumns.filter(x => !x.columnGroup);
248✔
7283
        cols.forEach((item) => {
38✔
7284
            colSum += parseInt((item.calcWidth || item.defaultWidth), 10) || this.minColumnWidth;
218!
7285
        });
7286
        if (!colSum) {
38!
7287
            return null;
×
7288
        }
7289
        this.cdr.detectChanges();
38✔
7290
        colSum += this.featureColumnsWidth();
38✔
7291
        return colSum;
38✔
7292
    }
7293

7294
    /**
7295
     * Notify changes, reset cache and populateVisibleIndexes.
7296
     *
7297
     * @hidden
7298
     */
7299
    private _columnsReordered(column: IgxColumnComponent) {
7300
        this.notifyChanges();
128✔
7301
        if (this.hasColumnLayouts) {
128✔
7302
            this._columns.filter(x => x.columnLayout).forEach(x => x.populateVisibleIndexes());
10✔
7303
        }
7304
        // after reordering is done reset cached column collections.
7305
        this.resetColumnCollections();
128✔
7306
        column.resetCaches();
128✔
7307
    }
7308

7309
    protected buildDataView(_data: any[]) {
7310
        this._dataView = this.isRowPinningToTop ?
13,472✔
7311
            [...this.pinnedDataView, ...this.unpinnedDataView] :
7312
            [...this.unpinnedDataView, ...this.pinnedDataView];
7313
    }
7314

7315
    private _applyWidthHostBinding() {
7316
        let width = this._width;
7,599✔
7317
        if (width === null) {
7,599✔
7318
            let currentWidth = this.calcWidth;
2✔
7319
            if (this.hasVerticalScroll()) {
2!
7320
                currentWidth += this.scrollSize;
×
7321
            }
7322
            width = currentWidth + 'px';
2✔
7323
            this.resetCaches();
2✔
7324
        }
7325
        this._hostWidth = width;
7,599✔
7326
        this.cdr.markForCheck();
7,599✔
7327
    }
7328

7329
    protected verticalScrollHandler(event) {
7330
        this.verticalScrollContainer.onScroll(event);
319✔
7331
        this.disableTransitions = true;
319✔
7332

7333
        this.zone.run(() => {
319✔
7334
            this.zone.onStable.pipe(first()).subscribe(() => {
319✔
7335
                this.verticalScrollContainer.chunkLoad.emit(this.verticalScrollContainer.state);
319✔
7336
                if (this.rowEditable) {
319✔
7337
                    this.changeRowEditingOverlayStateOnScroll(this.crudService.rowInEditMode);
14✔
7338
                }
7339
            });
7340
        });
7341
        this.disableTransitions = false;
319✔
7342

7343
        this.hideOverlays();
319✔
7344
        this.actionStrip?.hide();
319✔
7345
        if (this.actionStrip) {
319✔
7346
            this.actionStrip.context = null;
9✔
7347
        }
7348
        const args: IGridScrollEventArgs = {
319✔
7349
            direction: 'vertical',
7350
            event,
7351
            scrollPosition: this.verticalScrollContainer.scrollPosition
7352
        };
7353
        this.gridScroll.emit(args);
319✔
7354
    }
7355

7356
    protected horizontalScrollHandler(event) {
7357
        const scrollLeft = event.target.scrollLeft;
318✔
7358
        this.headerContainer.onHScroll(scrollLeft);
318✔
7359
        this._horizontalForOfs.forEach(vfor => vfor.onHScroll(scrollLeft));
2,010✔
7360
        this.cdr.markForCheck();
318✔
7361

7362
        this.zone.run(() => {
318✔
7363
            this.zone.onStable.pipe(first()).subscribe(() => {
318✔
7364
                this.parentVirtDir.chunkLoad.emit(this.headerContainer.state);
316✔
7365
                requestAnimationFrame(() => {
316✔
7366
                    this.autoSizeColumnsInView();
316✔
7367
                });
7368
            });
7369
        });
7370
        if (!this.navigation.isColumnFullyVisible(this.navigation.lastColumnIndex)) {
318✔
7371
            this.hideOverlays();
208✔
7372
        }
7373
        const args: IGridScrollEventArgs = { direction: 'horizontal', event, scrollPosition: this.headerContainer.scrollPosition };
318✔
7374
        this.gridScroll.emit(args);
318✔
7375
    }
7376

7377
    private executeCallback(rowIndex, visibleColIndex = -1, cb: (args: any) => void = null) {
×
7378
        if (!cb) {
831✔
7379
            return;
280✔
7380
        }
7381
        let row = this.summariesRowList.filter(s => s.index !== 0).concat(this.rowList.toArray()).find(r => r.index === rowIndex);
1,622✔
7382
        if (!row) {
551✔
7383
            if ((this as any).totalItemCount) {
8!
7384
                this.verticalScrollContainer.dataChanged.pipe(first()).subscribe(() => {
×
7385
                    this.cdr.detectChanges();
×
7386
                    row = this.summariesRowList.filter(s => s.index !== 0).concat(this.rowList.toArray()).find(r => r.index === rowIndex);
×
7387
                    const cbArgs = this.getNavigationArguments(row, visibleColIndex);
×
7388
                    cb(cbArgs);
×
7389
                });
7390
            }
7391
            const dataViewIndex = this._getDataViewIndex(rowIndex);
8✔
7392
            if (this.dataView[dataViewIndex].detailsData) {
8✔
7393
                this.navigation.setActiveNode({ row: rowIndex });
8✔
7394
                this.cdr.detectChanges();
8✔
7395
            }
7396

7397
            return;
8✔
7398
        }
7399
        const args = this.getNavigationArguments(row, visibleColIndex);
543✔
7400
        cb(args);
543✔
7401
    }
7402

7403
    private getNavigationArguments(row, visibleColIndex) {
7404
        let targetType: GridKeydownTargetType; let target;
7405
        switch (row.nativeElement.tagName.toLowerCase()) {
543!
7406
            case 'igx-grid-groupby-row':
7407
                targetType = 'groupRow';
30✔
7408
                target = row;
30✔
7409
                break;
30✔
7410
            case 'igx-grid-summary-row':
7411
                targetType = 'summaryCell';
50✔
7412
                target = visibleColIndex !== -1 ?
50!
7413
                    row.summaryCells.find(c => c.visibleColumnIndex === visibleColIndex) : row.summaryCells.first;
139✔
7414
                break;
50✔
7415
            case 'igx-child-grid-row':
7416
                targetType = 'hierarchicalRow';
×
7417
                target = row;
×
7418
                break;
×
7419
            default:
7420
                targetType = 'dataCell';
463✔
7421
                target = visibleColIndex !== -1 ? row.cells.find(c => c.visibleColumnIndex === visibleColIndex) : row.cells.first;
1,286!
7422
                break;
463✔
7423
        }
7424
        return { targetType, target };
543✔
7425
    }
7426

7427
    private getNextDataRowIndex(currentRowIndex, previous = false): number {
15✔
7428
        const resolvedIndex = this._getDataViewIndex(currentRowIndex);
30✔
7429
        if (currentRowIndex < 0 || (currentRowIndex === 0 && previous) || (resolvedIndex >= this.dataView.length - 1 && !previous)) {
30!
7430
            return currentRowIndex;
7✔
7431
        }
7432
        // find next/prev record that is editable.
7433
        const nextRowIndex = previous ? this.findPrevEditableDataRowIndex(currentRowIndex) :
23✔
7434
            this.dataView.findIndex((rec, index) =>
7435
                index > resolvedIndex && this.isEditableDataRecordAtIndex(index));
56✔
7436
        const nextDataIndex = this.getDataIndex(nextRowIndex);
23✔
7437
        return nextDataIndex !== -1 ? nextDataIndex : currentRowIndex;
23!
7438
    }
7439

7440
    /**
7441
     * Returns the previous editable row index or -1 if no such row is found.
7442
     *
7443
     * @param currentIndex The index of the current editable record.
7444
     */
7445
    private findPrevEditableDataRowIndex(currentIndex): number {
7446
        let i = this.dataView.length;
8✔
7447
        const resolvedIndex = this._getDataViewIndex(currentIndex);
8✔
7448
        while (i--) {
8✔
7449
            if (i < resolvedIndex && this.isEditableDataRecordAtIndex(i)) {
178✔
7450
                return i;
8✔
7451
            }
7452
        }
7453
        return -1;
×
7454
    }
7455

7456

7457
    /**
7458
     * Returns if the record at the specified data view index is a an editable data record.
7459
     * If record is group rec, summary rec, child rec, ghost rec. etc. it is not editable.
7460
     *
7461
     * @param dataViewIndex The index of that record in the data view.
7462
     *
7463
     */
7464
    // TODO: Consider moving it into CRUD
7465
    private isEditableDataRecordAtIndex(dataViewIndex) {
7466
        const rec = this.dataView[dataViewIndex];
29✔
7467
        return !rec.expression && !rec.summaries && !rec.childGridsData && !rec.detailsData &&
29✔
7468
            !this.isGhostRecordAtIndex(dataViewIndex);
7469
    }
7470

7471
    /**
7472
     * Returns if the record at the specified data view index is a ghost.
7473
     * If record is pinned but is not in pinned area then it is a ghost record.
7474
     *
7475
     * @param dataViewIndex The index of that record in the data view.
7476
     */
7477
    private isGhostRecordAtIndex(dataViewIndex) {
7478
        const isPinned = this.isRecordPinned(this.dataView[dataViewIndex]);
27✔
7479
        const isInPinnedArea = this.isRecordPinnedByViewIndex(dataViewIndex);
27✔
7480
        return isPinned && !isInPinnedArea;
27✔
7481
    }
7482

7483
    private isValidPosition(rowIndex, colIndex): boolean {
7484
        const rows = this.summariesRowList.filter(s => s.index !== 0).concat(this.rowList.toArray()).length;
97✔
7485
        const cols = this._columns.filter(col => !col.columnGroup && col.visibleIndex >= 0 && !col.hidden).length;
777✔
7486
        if (rows < 1 || cols < 1) {
97✔
7487
            return false;
2✔
7488
        }
7489
        if (rowIndex > -1 && rowIndex < this.dataView.length &&
95✔
7490
            colIndex > - 1 && colIndex <= Math.max(...this.visibleColumns.map(c => c.visibleIndex))) {
694✔
7491
            return true;
93✔
7492
        }
7493
        return false;
2✔
7494
    }
7495

7496
    private find(text: string, increment: number, caseSensitive?: boolean, exactMatch?: boolean, scroll?: boolean, endEdit = true) {
195✔
7497
        if (!this.rowList) {
317!
7498
            return 0;
×
7499
        }
7500

7501
        if (endEdit) {
317✔
7502
            this.crudService.endEdit(false);
278✔
7503
        }
7504

7505
        if (!text) {
317!
7506
            this.clearSearch();
×
7507
            return 0;
×
7508
        }
7509

7510
        const caseSensitiveResolved = caseSensitive ? true : false;
317✔
7511
        const exactMatchResolved = exactMatch ? true : false;
317✔
7512
        let rebuildCache = false;
317✔
7513

7514
        if (this._lastSearchInfo.searchText !== text ||
317✔
7515
            this._lastSearchInfo.caseSensitive !== caseSensitiveResolved ||
7516
            this._lastSearchInfo.exactMatch !== exactMatchResolved) {
7517
            this._lastSearchInfo = {
84✔
7518
                searchText: text,
7519
                activeMatchIndex: 0,
7520
                caseSensitive: caseSensitiveResolved,
7521
                exactMatch: exactMatchResolved,
7522
                matchInfoCache: [],
7523
                matchCount: 0,
7524
                content: ''
7525
            };
7526

7527
            rebuildCache = true;
84✔
7528
        } else {
7529
            this._lastSearchInfo.activeMatchIndex += increment;
233✔
7530
        }
7531

7532
        if (rebuildCache) {
317✔
7533
            this.rowList.forEach((row) => {
84✔
7534
                if (row.cells) {
783✔
7535
                    row.cells.forEach((c: IgxGridCellComponent) => {
739✔
7536
                        c.highlightText(text, caseSensitiveResolved, exactMatchResolved);
3,091✔
7537
                    });
7538
                }
7539
            });
7540

7541
            this.rebuildMatchCache();
84✔
7542
        }
7543

7544
        if (this._lastSearchInfo.activeMatchIndex >= this._lastSearchInfo.matchCount) {
317✔
7545
            this._lastSearchInfo.activeMatchIndex = 0;
28✔
7546
        } else if (this._lastSearchInfo.activeMatchIndex < 0) {
289✔
7547
            this._lastSearchInfo.activeMatchIndex = this._lastSearchInfo.matchCount - 1;
8✔
7548
        }
7549

7550
        if (this._lastSearchInfo.matchCount > 0) {
317✔
7551
            const matchInfo = this._lastSearchInfo.matchInfoCache[this._lastSearchInfo.activeMatchIndex];
299✔
7552
            this._lastSearchInfo = { ...this._lastSearchInfo };
299✔
7553

7554
            if (scroll !== false) {
299✔
7555
                this.scrollTo(matchInfo.row, matchInfo.column);
182✔
7556
            }
7557

7558
            this.textHighlightService.setActiveHighlight(this.id, {
299✔
7559
                column: matchInfo.column,
7560
                row: matchInfo.row,
7561
                index: matchInfo.index,
7562
                metadata: matchInfo.metadata,
7563
            });
7564

7565
        } else {
7566
            this.textHighlightService.clearActiveHighlight(this.id);
18✔
7567
        }
7568

7569
        return this._lastSearchInfo.matchCount;
317✔
7570
    }
7571

7572
    private rebuildMatchCache() {
7573
        this._lastSearchInfo.matchInfoCache = [];
206✔
7574

7575
        const caseSensitive = this._lastSearchInfo.caseSensitive;
206✔
7576
        const exactMatch = this._lastSearchInfo.exactMatch;
206✔
7577
        const searchText = caseSensitive ? this._lastSearchInfo.searchText : this._lastSearchInfo.searchText.toLowerCase();
206✔
7578
        const data = this.filteredSortedData;
206✔
7579
        const columnItems = this.visibleColumns.filter((c) => !c.columnGroup).sort((c1, c2) => c1.visibleIndex - c2.visibleIndex);
888✔
7580

7581
        data.forEach((dataRow, rowIndex) => {
206✔
7582
            columnItems.forEach((c) => {
3,384✔
7583
                const pipeArgs = this.getColumnByName(c.field).pipeArgs;
14,431✔
7584
                const value = c.formatter ? c.formatter(resolveNestedPath(dataRow, c.field), dataRow) :
14,431!
7585
                    c.dataType === 'number' ? formatNumber(resolveNestedPath(dataRow, c.field), this.locale, pipeArgs.digitsInfo) :
14,431✔
7586
                        c.dataType === 'date'
10,755✔
7587
                            ? formatDate(resolveNestedPath(dataRow, c.field), pipeArgs.format, this.locale, pipeArgs.timezone)
7588
                            : resolveNestedPath(dataRow, c.field);
7589
                if (value !== undefined && value !== null && c.searchable) {
14,431✔
7590
                    let searchValue = caseSensitive ? String(value) : String(value).toLowerCase();
14,216✔
7591

7592
                    if (exactMatch) {
14,216✔
7593
                        if (searchValue === searchText) {
536✔
7594
                            const mic: IMatchInfoCache = {
9✔
7595
                                row: dataRow,
7596
                                column: c.field,
7597
                                index: 0,
7598
                                metadata: new Map<string, boolean>([['pinned', this.isRecordPinnedByIndex(rowIndex)]])
7599
                            };
7600

7601
                            this._lastSearchInfo.matchInfoCache.push(mic);
9✔
7602
                        }
7603
                    } else {
7604
                        let occurrenceIndex = 0;
13,680✔
7605
                        let searchIndex = searchValue.indexOf(searchText);
13,680✔
7606

7607
                        while (searchIndex !== -1) {
13,680✔
7608
                            const mic: IMatchInfoCache = {
2,158✔
7609
                                row: dataRow,
7610
                                column: c.field,
7611
                                index: occurrenceIndex++,
7612
                                metadata: new Map<string, boolean>([['pinned', this.isRecordPinnedByIndex(rowIndex)]])
7613
                            };
7614

7615
                            this._lastSearchInfo.matchInfoCache.push(mic);
2,158✔
7616

7617
                            searchValue = searchValue.substring(searchIndex + searchText.length);
2,158✔
7618
                            searchIndex = searchValue.indexOf(searchText);
2,158✔
7619
                        }
7620
                    }
7621
                }
7622
            });
7623
        });
7624

7625
        this._lastSearchInfo.matchCount = this._lastSearchInfo.matchInfoCache.length;
206✔
7626
    }
7627

7628
    // TODO: About to Move to CRUD
7629
    private configureRowEditingOverlay(rowID: any, useOuter = false) {
71✔
7630
        let settings = this.rowEditSettings;
277✔
7631
        const overlay = this.overlayService.getOverlayById(this.rowEditingOverlay.overlayId);
277✔
7632
        if (overlay) {
277✔
7633
            settings = overlay.settings;
72✔
7634
        }
7635
        settings.outlet = useOuter ? this.parentRowOutletDirective : this.rowOutletDirective;
277✔
7636
        this.rowEditPositioningStrategy.settings.container = this.tbody.nativeElement;
277✔
7637
        const pinned = this._pinnedRecordIDs.indexOf(rowID) !== -1;
277✔
7638
        const targetRow = !pinned ?
277✔
7639
            this.gridAPI.get_row_by_key(rowID) as IgxRowDirective
7640
            : this.pinnedRows.find(x => x.key === rowID) as IgxRowDirective;
8✔
7641
        if (!targetRow) {
277!
7642
            return;
×
7643
        }
7644
        settings.target = targetRow.element.nativeElement;
277✔
7645
        this.toggleRowEditingOverlay(true);
277✔
7646
    }
7647

7648
    private handleColumnPinningForGroups(): void {
7649
        // When a column is a group or is inside a group, pin all related.
7650
        const pinnedColumns = [];
3,858✔
7651
        const unpinnedColumns = [];
3,858✔
7652

7653
        this._pinnedColumns.forEach(col => {
3,858✔
7654
            if (col.parent) {
491✔
7655
                col.parent.pinned = true;
113✔
7656
            }
7657
            if (col.columnGroup) {
491✔
7658
                col.children.forEach(child => child.pinned = true);
112✔
7659
            }
7660
        });
7661

7662
        // Make sure we don't exceed unpinned area min width and get pinned and unpinned col collections.
7663
        // We take into account top level columns (top level groups and non groups).
7664
        // If top level is unpinned the pinning handles all children to be unpinned as well.
7665
        for (const column of this._columns) {
3,858✔
7666
            if (column.pinned && !column.parent) {
22,638✔
7667
                pinnedColumns.push(column);
379✔
7668
            } else if (column.pinned && column.parent) {
22,259✔
7669
                if (column.topLevelParent.pinned) {
115!
7670
                    pinnedColumns.push(column);
115✔
7671
                } else {
7672
                    column.pinned = false;
×
7673
                    unpinnedColumns.push(column);
×
7674
                }
7675
            } else {
7676
                unpinnedColumns.push(column);
22,144✔
7677
            }
7678
        }
7679
        // Assign the applicable collections.
7680
        this._pinnedColumns = pinnedColumns;
3,858✔
7681
        this._unpinnedColumns = unpinnedColumns;
3,858✔
7682
    }
7683
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc