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

IgniteUI / igniteui-angular / 16193550997

10 Jul 2025 11:12AM UTC coverage: 4.657% (-87.0%) from 91.64%
16193550997

Pull #16028

github

web-flow
Merge f7a9963b8 into 87246e3ce
Pull Request #16028: fix(radio-group): dynamically added radio buttons do not initialize

178 of 15764 branches covered (1.13%)

18 of 19 new or added lines in 2 files covered. (94.74%)

25721 existing lines in 324 files now uncovered.

1377 of 29570 relevant lines covered (4.66%)

0.53 hits per line

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

0.32
/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts
1
import {
2
    AfterViewInit,
3
    ChangeDetectorRef,
4
    Component,
5
    Input,
6
    TemplateRef,
7
    ViewChild,
8
    ViewChildren,
9
    QueryList,
10
    ElementRef,
11
    HostBinding,
12
    ChangeDetectionStrategy,
13
    ViewRef,
14
    HostListener,
15
    OnDestroy
16
} from '@angular/core';
17
import { GridColumnDataType, DataUtil } from '../../../data-operations/data-util';
18
import { IgxDropDownComponent } from '../../../drop-down/drop-down.component';
19
import { IFilteringOperation } from '../../../data-operations/filtering-condition';
20
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
21
import { HorizontalAlignment, VerticalAlignment, OverlaySettings } from '../../../services/overlay/utilities';
22
import { ConnectedPositioningStrategy } from '../../../services/overlay/position/connected-positioning-strategy';
23
import { IgxDropDownItemComponent } from '../../../drop-down/drop-down-item.component';
24
import { ISelectionEventArgs } from '../../../drop-down/drop-down.common';
25
import { IgxFilteringService } from '../grid-filtering.service';
26
import { AbsoluteScrollStrategy } from '../../../services/overlay/scroll';
27
import { IgxDatePickerComponent } from '../../../date-picker/date-picker.component';
28
import { IgxTimePickerComponent } from '../../../time-picker/time-picker.component';
29
import { isEqual, PlatformUtil } from '../../../core/utils';
30
import { Subject } from 'rxjs';
31
import { takeUntil } from 'rxjs/operators';
32
import { ExpressionUI } from '../excel-style/common';
33
import { ColumnType } from '../../common/grid.interface';
34
import { IgxRippleDirective } from '../../../directives/ripple/ripple.directive';
35
import { IgxChipComponent, IBaseChipEventArgs } from '../../../chips/chip.component';
36
import { IgxChipsAreaComponent } from '../../../chips/chips-area.component';
37
import { IgxButtonDirective } from '../../../directives/button/button.directive';
38
import { IgxDateTimeEditorDirective } from '../../../directives/date-time-editor/date-time-editor.directive';
39
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../../../date-common/picker-icons.common';
40
import { IgxSuffixDirective } from '../../../directives/suffix/suffix.directive';
41
import { IgxInputDirective } from '../../../directives/input/input.directive';
42
import { IgxDropDownItemNavigationDirective } from '../../../drop-down/drop-down-navigation.directive';
43
import { IgxPrefixDirective } from '../../../directives/prefix/prefix.directive';
44
import { IgxInputGroupComponent } from '../../../input-group/input-group.component';
45
import { IgxIconComponent } from '../../../icon/icon.component';
46
import { NgTemplateOutlet, NgClass } from '@angular/common';
47
import { IgxIconButtonDirective } from '../../../directives/button/icon-button.directive';
48
import { Size } from '../../common/enums';
49

50
/**
51
 * @hidden
52
 */
53
@Component({
54
    changeDetection: ChangeDetectionStrategy.OnPush,
55
    selector: 'igx-grid-filtering-row',
56
    templateUrl: './grid-filtering-row.component.html',
57
    imports: [IgxDropDownComponent, IgxDropDownItemComponent, IgxChipsAreaComponent, IgxChipComponent, IgxIconComponent, IgxInputGroupComponent, IgxPrefixDirective, IgxDropDownItemNavigationDirective, IgxInputDirective, IgxSuffixDirective, IgxDatePickerComponent, IgxPickerToggleComponent, IgxPickerClearComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, NgTemplateOutlet, IgxButtonDirective, NgClass, IgxRippleDirective, IgxIconButtonDirective]
58
})
59
export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
3✔
60
    @Input()
61
    public get column(): ColumnType {
UNCOV
62
        return this._column;
×
63
    }
64

65
    public set column(val) {
UNCOV
66
        if (this._column) {
×
UNCOV
67
            this.expressionsList.forEach(exp => exp.isSelected = false);
×
68
        }
UNCOV
69
        if (val) {
×
UNCOV
70
            this._column = val;
×
71

UNCOV
72
            this.expressionsList = this.filteringService.getExpressions(this._column.field);
×
UNCOV
73
            this.resetExpression();
×
74

UNCOV
75
            this.chipAreaScrollOffset = 0;
×
UNCOV
76
            this.transform(this.chipAreaScrollOffset);
×
77
        }
78
    }
79

80
    @Input()
81
    public get value(): any {
UNCOV
82
        return this._value;
×
83
    }
84

85
    public set value(val) {
UNCOV
86
        if (!val && val !== 0 && (this.expression.searchVal || this.expression.searchVal === 0)) {
×
UNCOV
87
            this.expression.searchVal = null;
×
UNCOV
88
            this._value = null;
×
UNCOV
89
            const index = this.expressionsList.findIndex(item => item.expression === this.expression);
×
UNCOV
90
            if (index === 0 && this.expressionsList.length === 1 && !this.expression.condition.isUnary) {
×
UNCOV
91
                this.filteringService.clearFilter(this.column.field);
×
92
            }
93
        } else {
UNCOV
94
            if (val === '') {
×
95
                return;
×
96
            }
UNCOV
97
            const oldValue = this.expression.searchVal;
×
UNCOV
98
            if (isEqual(oldValue, val)) {
×
UNCOV
99
                return;
×
100
            }
101

UNCOV
102
            this._value = val;
×
UNCOV
103
            this.expression.searchVal = DataUtil.parseValue(this.column.dataType, val);
×
UNCOV
104
            if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
×
UNCOV
105
                this.addExpression(true);
×
106
            }
UNCOV
107
            this.filter();
×
108
        }
109
    }
110

111
    protected get filteringElementsSize(): Size {
112
        // needed because we want the size of the chips to be either Medium or Small
UNCOV
113
        return this.column.grid.gridSize === Size.Large ? Size.Medium : this.column.grid.gridSize;
×
114
    }
115

116
    @HostBinding('class.igx-grid__filtering-row')
UNCOV
117
    public defaultCSSClass = true;
×
118

119
    @ViewChild('defaultFilterUI', { read: TemplateRef, static: true })
120
    protected defaultFilterUI: TemplateRef<any>;
121

122
    @ViewChild('defaultDateUI', { read: TemplateRef, static: true })
123
    protected defaultDateUI: TemplateRef<any>;
124

125
    @ViewChild('defaultTimeUI', { read: TemplateRef, static: true })
126
    protected defaultTimeUI: TemplateRef<any>;
127

128
    @ViewChild('defaultDateTimeUI', { read: TemplateRef, static: true })
129
    protected defaultDateTimeUI: TemplateRef<any>;
130

131
    @ViewChild('input', { read: ElementRef })
132
    protected input: ElementRef<HTMLInputElement>;
133

134
    @ViewChild('inputGroupConditions', { read: IgxDropDownComponent, static: true })
135
    protected dropDownConditions: IgxDropDownComponent;
136

137
    @ViewChild('chipsArea', { read: IgxChipsAreaComponent, static: true })
138
    protected chipsArea: IgxChipsAreaComponent;
139

140
    @ViewChildren('operators', { read: IgxDropDownComponent })
141
    protected dropDownOperators: QueryList<IgxDropDownComponent>;
142

143
    @ViewChild('inputGroup', { read: ElementRef })
144
    protected inputGroup: ElementRef<HTMLElement>;
145

146
    @ViewChild('picker')
147
    protected picker: IgxDatePickerComponent | IgxTimePickerComponent;
148

149
    @ViewChild('inputGroupPrefix', { read: ElementRef })
150
    protected inputGroupPrefix: ElementRef<HTMLElement>;
151

152
    @ViewChild('container', { static: true })
153
    protected container: ElementRef<HTMLElement>;
154

155
    @ViewChild('operand')
156
    protected operand: ElementRef<HTMLElement>;
157

158
    @ViewChild('closeButton', { static: true })
159
    protected closeButton: ElementRef<HTMLElement>;
160

161
    public get nativeElement() {
UNCOV
162
        return this.ref.nativeElement;
×
163
    }
164

165
    public showArrows: boolean;
166
    public expression: IFilteringExpression;
167
    public expressionsList: Array<ExpressionUI>;
168

UNCOV
169
    private _positionSettings = {
×
170
        horizontalStartPoint: HorizontalAlignment.Left,
171
        verticalStartPoint: VerticalAlignment.Bottom
172
    };
173

UNCOV
174
    private _conditionsOverlaySettings: OverlaySettings = {
×
175
        closeOnOutsideClick: true,
176
        modal: false,
177
        scrollStrategy: new AbsoluteScrollStrategy(),
178
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
179
    };
180

UNCOV
181
    private _operatorsOverlaySettings: OverlaySettings = {
×
182
        closeOnOutsideClick: true,
183
        modal: false,
184
        scrollStrategy: new AbsoluteScrollStrategy(),
185
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
186
    };
187

188
    private chipsAreaWidth: number;
UNCOV
189
    private chipAreaScrollOffset = 0;
×
UNCOV
190
    private _column = null;
×
UNCOV
191
    private isKeyPressed = false;
×
UNCOV
192
    private isComposing = false;
×
UNCOV
193
    private _cancelChipClick = false;
×
UNCOV
194
    private _value = null;
×
195

196
    /** switch to icon buttons when width is below 432px */
UNCOV
197
    private readonly NARROW_WIDTH_THRESHOLD = 432;
×
198

UNCOV
199
    private $destroyer = new Subject<void>();
×
200

201
    constructor(
UNCOV
202
        public filteringService: IgxFilteringService,
×
UNCOV
203
        public ref: ElementRef<HTMLElement>,
×
UNCOV
204
        public cdr: ChangeDetectorRef,
×
UNCOV
205
        protected platform: PlatformUtil,
×
206
    ) { }
207

208
    @HostListener('keydown', ['$event'])
209
    public onKeydownHandler(evt: KeyboardEvent) {
UNCOV
210
        if (this.platform.isFilteringKeyCombo(evt)) {
×
UNCOV
211
                evt.preventDefault();
×
UNCOV
212
                evt.stopPropagation();
×
UNCOV
213
                this.close();
×
214
        }
215
    }
216

217
    public ngAfterViewInit() {
UNCOV
218
        this._conditionsOverlaySettings.outlet = this.column.grid.outlet;
×
UNCOV
219
        this._operatorsOverlaySettings.outlet = this.column.grid.outlet;
×
220

UNCOV
221
        const selectedItem = this.expressionsList.find(expr => expr.isSelected === true);
×
UNCOV
222
        if (selectedItem) {
×
UNCOV
223
            this.expression = selectedItem.expression;
×
UNCOV
224
            this._value = this.expression.searchVal;
×
225
        }
226

UNCOV
227
        this.filteringService.grid.localeChange
×
228
        .pipe(takeUntil(this.$destroyer))
229
        .subscribe(() => {
230
            this.cdr.markForCheck();
×
231
        });
232

UNCOV
233
        requestAnimationFrame(() => this.focusEditElement());
×
234
    }
235

236
    public get disabled(): boolean {
UNCOV
237
        return !(this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0);
×
238
    }
239

240
    public get template(): TemplateRef<any> {
UNCOV
241
        if (this.column.dataType === GridColumnDataType.Date) {
×
UNCOV
242
            return this.defaultDateUI;
×
243
        }
UNCOV
244
        if (this.column.dataType === GridColumnDataType.Time) {
×
UNCOV
245
            return this.defaultTimeUI;
×
246
        }
UNCOV
247
        if (this.column.dataType === GridColumnDataType.DateTime) {
×
UNCOV
248
            return this.defaultDateTimeUI;
×
249
        }
UNCOV
250
        return this.defaultFilterUI;
×
251
    }
252

253
    public get type() {
UNCOV
254
        switch (this.column.dataType) {
×
255
            case GridColumnDataType.String:
256
            case GridColumnDataType.Boolean:
UNCOV
257
                return 'text';
×
258
            case GridColumnDataType.Number:
259
            case GridColumnDataType.Currency:
UNCOV
260
                return 'number';
×
261
        }
262
    }
263

264
    public get conditions(): any {
UNCOV
265
        return this.column.filters.conditionList();
×
266
    }
267

268
    public get isUnaryCondition(): boolean {
UNCOV
269
        if (this.expression.condition) {
×
UNCOV
270
            return this.expression.condition.isUnary;
×
271
        } else {
UNCOV
272
            return true;
×
273
        }
274
    }
275

276
    public get placeholder(): string {
UNCOV
277
        if (this.expression.condition && this.expression.condition.isUnary) {
×
UNCOV
278
            return this.filteringService.getChipLabel(this.expression);
×
UNCOV
279
        } else if (this.column.dataType === GridColumnDataType.Date) {
×
UNCOV
280
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_date_placeholder;
×
UNCOV
281
        } else if (this.column.dataType === GridColumnDataType.Boolean) {
×
UNCOV
282
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_boolean_placeholder;
×
283
        } else {
UNCOV
284
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_placeholder;
×
285
        }
286
    }
287

288
    /**
289
     * Event handler for keydown on the input group's prefix.
290
     */
291
    public onPrefixKeyDown(event: KeyboardEvent) {
UNCOV
292
        if (this.platform.isActivationKey(event) && this.dropDownConditions.collapsed) {
×
293
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
294
            event.stopImmediatePropagation();
×
UNCOV
295
        } else if (event.key === this.platform.KEYMAP.TAB && !this.dropDownConditions.collapsed) {
×
UNCOV
296
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
297
        }
298
    }
299

300
    /**
301
     * Event handler for keydown on the input.
302
     */
303
    public onInputKeyDown(event: KeyboardEvent) {
UNCOV
304
        this.isKeyPressed = true;
×
UNCOV
305
        event.stopPropagation();
×
UNCOV
306
        if (this.column.dataType === GridColumnDataType.Boolean) {
×
UNCOV
307
            if (this.platform.isActivationKey(event)) {
×
UNCOV
308
                this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
309
                this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
UNCOV
310
                return;
×
311
            }
312
        }
UNCOV
313
        if (event.key === this.platform.KEYMAP.ENTER) {
×
UNCOV
314
            if (this.isComposing) {
×
315
                return;
×
316
            }
UNCOV
317
            this.commitInput();
×
UNCOV
318
        } else if (event.altKey && (event.key === this.platform.KEYMAP.ARROW_DOWN)) {
×
UNCOV
319
            this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
320
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
UNCOV
321
        } else if (this.platform.isFilteringKeyCombo(event)) {
×
UNCOV
322
            event.preventDefault();
×
UNCOV
323
            this.close();
×
324
        }
325
    }
326

327
    /**
328
     * Event handler for keyup on the input.
329
     */
330
    public onInputKeyUp() {
UNCOV
331
        this.isKeyPressed = false;
×
332
    }
333

334
    /**
335
     * Event handler for input on the input.
336
     */
337
    public onInput(eventArgs) {
UNCOV
338
        if (!eventArgs) {
×
UNCOV
339
            return;
×
340
        }
341

342
        // The 'iskeyPressed' flag is needed for a case in IE, because the input event is fired on focus and for some reason,
343
        // when you have a japanese character as a placeholder, on init the value here is empty string .
UNCOV
344
        const target = eventArgs.target;
×
UNCOV
345
        if (this.column.dataType === GridColumnDataType.DateTime) {
×
346
            this.value = eventArgs;
×
347
            return;
×
348
        }
UNCOV
349
        if (this.platform.isEdge && target.type !== 'number'
×
350
            || this.isKeyPressed || target.value || target.checkValidity()) {
UNCOV
351
            this.value = target.value;
×
352
        }
353
    }
354

355
    /**
356
     * Event handler for compositionstart on the input.
357
     */
358
    public onCompositionStart() {
359
        this.isComposing = true;
×
360
    }
361

362
    /**
363
     * Event handler for compositionend on the input.
364
     */
365
    public onCompositionEnd() {
366
        this.isComposing = false;
×
367
    }
368

369
    /**
370
     * Event handler for input click event.
371
     */
372
    public onInputClick() {
UNCOV
373
        if (this.column.dataType === GridColumnDataType.Boolean && this.dropDownConditions.collapsed) {
×
UNCOV
374
            this.inputGroupPrefix.nativeElement.focus();
×
UNCOV
375
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
×
376
        }
377
    }
378

379
    /**
380
     * Returns the filtering operation condition for a given value.
381
     */
382
    public getCondition(value: string): IFilteringOperation {
UNCOV
383
        return this.column.filters.condition(value);
×
384
    }
385

386
    /**
387
     * Returns the translated condition name for a given value.
388
     */
389
    public translateCondition(value: string): string {
UNCOV
390
        return this.filteringService.grid.resourceStrings[`igx_grid_filter_${this.getCondition(value).name}`] || value;
×
391
    }
392

393
    /**
394
     * Returns the icon name of the current condition.
395
     */
396
    public getIconName(): string {
UNCOV
397
        if (this.column.dataType === GridColumnDataType.Boolean && this.expression.condition === null) {
×
UNCOV
398
            return this.getCondition(this.conditions[0]).iconName;
×
399
        } else {
UNCOV
400
            return this.expression.condition.iconName;
×
401
        }
402
    }
403

404
    /**
405
     * Returns whether a given condition is selected in dropdown.
406
     */
407
    public isConditionSelected(conditionName: string): boolean {
UNCOV
408
        if (this.expression.condition) {
×
UNCOV
409
            return this.expression.condition.name === conditionName;
×
410
        } else {
UNCOV
411
            return false;
×
412
        }
413
    }
414

415
    /**
416
     * Clears the current filtering.
417
     */
418
    public clearFiltering() {
UNCOV
419
        this.filteringService.clearFilter(this.column.field);
×
UNCOV
420
        this.resetExpression();
×
UNCOV
421
        if (this.input) {
×
UNCOV
422
            this.input.nativeElement.focus();
×
423
        }
UNCOV
424
        this.cdr.detectChanges();
×
425

UNCOV
426
        this.chipAreaScrollOffset = 0;
×
UNCOV
427
        this.transform(this.chipAreaScrollOffset);
×
428
    }
429

430
    /**
431
     * Commits the value of the input.
432
     */
433
    public commitInput() {
UNCOV
434
        const selectedItem = this.expressionsList.filter(ex => ex.isSelected === true);
×
UNCOV
435
        selectedItem.forEach(e => e.isSelected = false);
×
436

UNCOV
437
        let indexToDeselect = -1;
×
UNCOV
438
        for (let index = 0; index < this.expressionsList.length; index++) {
×
UNCOV
439
            const expression = this.expressionsList[index].expression;
×
UNCOV
440
            if (expression.searchVal === null && !expression.condition.isUnary) {
×
UNCOV
441
                indexToDeselect = index;
×
442
            }
443
        }
UNCOV
444
        if (indexToDeselect !== -1) {
×
UNCOV
445
            this.removeExpression(indexToDeselect, this.expression);
×
446
        }
UNCOV
447
        this.resetExpression();
×
UNCOV
448
        this._value = this.expression.searchVal;
×
UNCOV
449
        this.scrollChipsWhenAddingExpression();
×
450
    }
451

452
    /**
453
     * Clears the value of the input.
454
     */
455
    public clearInput(event?: MouseEvent) {
UNCOV
456
        event?.stopPropagation();
×
UNCOV
457
        this.value = null;
×
458
    }
459

460
    /**
461
     * Event handler for keydown on clear button.
462
     */
463
    public onClearKeyDown(eventArgs: KeyboardEvent) {
UNCOV
464
        if (this.platform.isActivationKey(eventArgs)) {
×
UNCOV
465
            eventArgs.preventDefault();
×
UNCOV
466
            this.clearInput();
×
UNCOV
467
            this.focusEditElement();
×
468
        }
469
    }
470

471
    /**
472
     * Event handler for click on clear button.
473
     */
474
    public onClearClick() {
UNCOV
475
        this.clearInput();
×
UNCOV
476
        this.focusEditElement();
×
477
    }
478

479
    /**
480
     * Event handler for keydown on commit button.
481
     */
482
    public onCommitKeyDown(eventArgs: KeyboardEvent) {
UNCOV
483
        if (this.platform.isActivationKey(eventArgs)) {
×
UNCOV
484
            eventArgs.preventDefault();
×
UNCOV
485
            this.commitInput();
×
UNCOV
486
            this.focusEditElement();
×
487
        }
488
    }
489

490
    /**
491
     * Event handler for click on commit button.
492
     */
493
    public onCommitClick(event?: MouseEvent) {
UNCOV
494
        event?.stopPropagation();
×
UNCOV
495
        this.commitInput();
×
UNCOV
496
        this.focusEditElement();
×
497
    }
498

499
    /**
500
     * Event handler for focusout on the input group.
501
     */
502
    public onInputGroupFocusout() {
UNCOV
503
        if (!this.value && this.value !== 0 &&
×
504
            this.expression.condition && !this.expression.condition.isUnary) {
UNCOV
505
            return;
×
506
        }
UNCOV
507
        requestAnimationFrame(() => {
×
UNCOV
508
            const focusedElement = this.column?.grid.document.activeElement;
×
509

UNCOV
510
            if (focusedElement.classList.contains('igx-chip__remove')) {
×
511
                return;
×
512
            }
513

UNCOV
514
            if (!(focusedElement && this.editorFocused(focusedElement))
×
515
                && this.dropDownConditions.collapsed) {
UNCOV
516
                this.commitInput();
×
517
            }
518
        });
519
    }
520

521
    /**
522
     * Closes the filtering edit row.
523
     */
524
    public close() {
UNCOV
525
        if (this.expressionsList.length === 1 &&
×
526
            this.expressionsList[0].expression.searchVal === null &&
527
            this.expressionsList[0].expression.condition.isUnary === false) {
528
            this.filteringService.getExpressions(this.column.field).pop();
×
529

530
            this.filter();
×
531
        } else {
UNCOV
532
            const condToRemove = this.expressionsList.filter(ex => ex.expression.searchVal === null && !ex.expression.condition.isUnary);
×
UNCOV
533
            if (condToRemove && condToRemove.length > 0) {
×
534
                condToRemove.forEach(c => this.filteringService.removeExpression(this.column.field, this.expressionsList.indexOf(c)));
×
535
                this.filter();
×
536
            }
537
        }
538

UNCOV
539
        this.filteringService.isFilterRowVisible = false;
×
UNCOV
540
        this.filteringService.updateFilteringCell(this.column);
×
UNCOV
541
        this.filteringService.filteredColumn = null;
×
UNCOV
542
        this.filteringService.selectedExpression = null;
×
UNCOV
543
        this.filteringService.grid.theadRow.nativeElement.focus();
×
544

UNCOV
545
        this.chipAreaScrollOffset = 0;
×
UNCOV
546
        this.transform(this.chipAreaScrollOffset);
×
547
    }
548

549
    /**
550
     *  Event handler for date picker's selection.
551
     */
552
    public onDateSelected(value: Date) {
553
        this.value = value;
×
554
    }
555

556
    /** @hidden @internal */
557
    public inputGroupPrefixClick(event: MouseEvent) {
UNCOV
558
        event.stopPropagation();
×
UNCOV
559
        (event.currentTarget as HTMLElement).focus();
×
UNCOV
560
        this.toggleConditionsDropDown(event.currentTarget);
×
561
    }
562

563
    /**
564
     * Opens the conditions dropdown.
565
     */
566
    public toggleConditionsDropDown(target: any) {
UNCOV
567
        this._conditionsOverlaySettings.target = target;
×
UNCOV
568
        this._conditionsOverlaySettings.excludeFromOutsideClick = [target as HTMLElement];
×
UNCOV
569
        this.dropDownConditions.toggle(this._conditionsOverlaySettings);
×
570
    }
571

572
    /**
573
     * Opens the logic operators dropdown.
574
     */
575
    public toggleOperatorsDropDown(eventArgs, index) {
UNCOV
576
        this._operatorsOverlaySettings.target = eventArgs.target.parentElement;
×
UNCOV
577
        this._operatorsOverlaySettings.excludeFromOutsideClick = [eventArgs.target.parentElement as HTMLElement];
×
UNCOV
578
        this.dropDownOperators.toArray()[index].toggle(this._operatorsOverlaySettings);
×
579
    }
580

581
    /**
582
     * Event handler for change event in conditions dropdown.
583
     */
584
    public onConditionsChanged(eventArgs) {
UNCOV
585
        const value = (eventArgs.newSelection as IgxDropDownItemComponent).value;
×
UNCOV
586
        this.expression.condition = this.getCondition(value);
×
UNCOV
587
        if (this.expression.condition.isUnary) {
×
588
            // update grid's filtering on the next cycle to ensure the drop-down is closed
589
            // if the drop-down is not closed this event handler will be invoked multiple times
UNCOV
590
            requestAnimationFrame(() => this.unaryConditionChangedCallback());
×
591
        } else {
UNCOV
592
            requestAnimationFrame(() => this.conditionChangedCallback());
×
593
        }
594

595
        // Add requestAnimationFrame because of an issue in IE, where you are still able to write in the input,
596
        // if it has been focused and then set to readonly.
UNCOV
597
        requestAnimationFrame(() => this.focusEditElement());
×
598
    }
599

600

601
    public onChipPointerdown(args, chip: IgxChipComponent) {
UNCOV
602
        const activeElement = this.column?.grid.document.activeElement;
×
UNCOV
603
        this._cancelChipClick = chip.selected
×
604
            && activeElement && this.editorFocused(activeElement);
605
    }
606

607
    public onChipClick(args, item: ExpressionUI) {
UNCOV
608
        if (this._cancelChipClick) {
×
609
            this._cancelChipClick = false;
×
610
            return;
×
611
        }
612

UNCOV
613
        this.expressionsList.forEach(ex => ex.isSelected = false);
×
614

UNCOV
615
        this.toggleChip(item);
×
616
    }
617

618
    public toggleChip(item: ExpressionUI) {
UNCOV
619
        item.isSelected = !item.isSelected;
×
UNCOV
620
        if (item.isSelected) {
×
UNCOV
621
            this.expression = item.expression;
×
UNCOV
622
            this._value = this.expression.searchVal;
×
UNCOV
623
            this.focusEditElement();
×
624
        }
625
    }
626

627
    /**
628
     * Event handler for chip keydown event.
629
     */
630
    public onChipKeyDown(eventArgs: KeyboardEvent, item: ExpressionUI) {
UNCOV
631
        if (eventArgs.key === this.platform.KEYMAP.ENTER) {
×
UNCOV
632
            eventArgs.preventDefault();
×
633

UNCOV
634
            this.toggleChip(item);
×
635
        }
636
    }
637

638
    /**
639
     * Scrolls the first chip into view if the tab key is pressed on the left arrow.
640
     */
641
    public onLeftArrowKeyDown(event: KeyboardEvent) {
UNCOV
642
        if (event.key === this.platform.KEYMAP.TAB) {
×
UNCOV
643
            this.chipAreaScrollOffset = 0;
×
UNCOV
644
            this.transform(this.chipAreaScrollOffset);
×
645
        }
646
    }
647

648
    /**
649
     * Event handler for chip removed event.
650
     */
651
    public onChipRemoved(eventArgs: IBaseChipEventArgs, item: ExpressionUI) {
UNCOV
652
        const indexToRemove = this.expressionsList.indexOf(item);
×
UNCOV
653
        this.removeExpression(indexToRemove, item.expression);
×
654

UNCOV
655
        this.scrollChipsOnRemove();
×
656
    }
657

658
    /**
659
     * Event handler for logic operator changed event.
660
     */
661
    public onLogicOperatorChanged(eventArgs: ISelectionEventArgs, expression: ExpressionUI) {
UNCOV
662
        if (eventArgs.oldSelection) {
×
UNCOV
663
            expression.afterOperator = (eventArgs.newSelection as IgxDropDownItemComponent).value;
×
UNCOV
664
            this.expressionsList[this.expressionsList.indexOf(expression) + 1].beforeOperator = expression.afterOperator;
×
665

666
            // update grid's filtering on the next cycle to ensure the drop-down is closed
667
            // if the drop-down is not closed this event handler will be invoked multiple times
UNCOV
668
            requestAnimationFrame(() => this.filter());
×
669
        }
670
    }
671

672
    /**
673
     * Scrolls the chips into the chip area when left or right arrows are pressed.
674
     */
675
    public scrollChipsOnArrowPress(arrowPosition: string) {
UNCOV
676
        let count = 0;
×
UNCOV
677
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
678
        const containerRect = this.container.nativeElement.getBoundingClientRect();
×
679

UNCOV
680
        if (arrowPosition === 'right') {
×
UNCOV
681
            for (const chip of chipAraeChildren) {
×
UNCOV
682
                if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.right)) {
×
UNCOV
683
                    count++;
×
684
                }
685
            }
686

UNCOV
687
            if (count < chipAraeChildren.length) {
×
UNCOV
688
                this.chipAreaScrollOffset -= Math.ceil(chipAraeChildren[count].getBoundingClientRect().right) -
×
689
                    Math.ceil(containerRect.right) + 1;
UNCOV
690
                this.transform(this.chipAreaScrollOffset);
×
691
            }
692
        }
693

UNCOV
694
        if (arrowPosition === 'left') {
×
UNCOV
695
            for (const chip of chipAraeChildren) {
×
UNCOV
696
                if (Math.ceil(chip.getBoundingClientRect().left) < Math.ceil(containerRect.left)) {
×
UNCOV
697
                    count++;
×
698
                }
699
            }
700

UNCOV
701
            if (count > 0) {
×
UNCOV
702
                this.chipAreaScrollOffset += Math.ceil(containerRect.left) -
×
703
                    Math.ceil(chipAraeChildren[count - 1].getBoundingClientRect().left) + 1;
UNCOV
704
                this.transform(this.chipAreaScrollOffset);
×
705
            }
706
        }
707
    }
708

709
    /**
710
     * @hidden
711
     * Resets the chips area
712
     * @memberof IgxGridFilteringRowComponent
713
     */
714
    public resetChipsArea() {
UNCOV
715
        this.chipAreaScrollOffset = 0;
×
UNCOV
716
        this.transform(this.chipAreaScrollOffset);
×
UNCOV
717
        this.showHideArrowButtons();
×
718
    }
719

720
    /** @hidden @internal */
721
    public focusEditElement() {
UNCOV
722
        if (this.input) {
×
UNCOV
723
            this.input.nativeElement.focus();
×
UNCOV
724
        } else if (this.picker) {
×
UNCOV
725
            this.picker.getEditElement().focus();
×
726
        }
727
    }
728

729
    public ngOnDestroy() {
UNCOV
730
        this.$destroyer.next();
×
731
    }
732

733
    private showHideArrowButtons() {
UNCOV
734
        requestAnimationFrame(() => {
×
UNCOV
735
            if (this.filteringService.isFilterRowVisible) {
×
UNCOV
736
                const containerWidth = this.container.nativeElement.getBoundingClientRect().width;
×
UNCOV
737
                this.chipsAreaWidth = this.chipsArea.element.nativeElement.getBoundingClientRect().width;
×
738

UNCOV
739
                this.showArrows = this.chipsAreaWidth >= containerWidth && this.isColumnFiltered;
×
740

741
                // TODO: revise the cdr.detectChanges() usage here
UNCOV
742
                if (!(this.cdr as ViewRef).destroyed) {
×
UNCOV
743
                    this.cdr.detectChanges();
×
744
                }
745
            }
746
        });
747
    }
748

749
    private addExpression(isSelected: boolean) {
UNCOV
750
        const exprUI = new ExpressionUI();
×
UNCOV
751
        exprUI.expression = this.expression;
×
UNCOV
752
        exprUI.beforeOperator = this.expressionsList.length > 0 ? FilteringLogic.And : null;
×
UNCOV
753
        exprUI.isSelected = isSelected;
×
754

UNCOV
755
        this.expressionsList.push(exprUI);
×
756

UNCOV
757
        const length = this.expressionsList.length;
×
UNCOV
758
        if (this.expressionsList[length - 2]) {
×
UNCOV
759
            this.expressionsList[length - 2].afterOperator = this.expressionsList[length - 1].beforeOperator;
×
760
        }
761

UNCOV
762
        this.showHideArrowButtons();
×
763
    }
764

765
    private removeExpression(indexToRemove: number, expression: IFilteringExpression) {
UNCOV
766
        if (indexToRemove === 0 && this.expressionsList.length === 1) {
×
UNCOV
767
            this.clearFiltering();
×
UNCOV
768
            return;
×
769
        }
770

UNCOV
771
        this.filteringService.removeExpression(this.column.field, indexToRemove);
×
772

UNCOV
773
        this.filter();
×
774

UNCOV
775
        if (this.expression === expression) {
×
UNCOV
776
            this.resetExpression();
×
777
        }
778

UNCOV
779
        this.showHideArrowButtons();
×
780
    }
781

782
    private resetExpression(condition?: string) {
UNCOV
783
        this.expression = {
×
784
            fieldName: this.column.field,
785
            condition: null,
786
            conditionName: null,
787
            searchVal: null,
788
            ignoreCase: this.column.filteringIgnoreCase
789
        };
790

UNCOV
791
        if (this.column.dataType !== GridColumnDataType.Boolean) {
×
UNCOV
792
            this.expression.condition = this.getCondition(condition ?? this.conditions[0]);
×
793
        }
794

UNCOV
795
        if (this.column.dataType === GridColumnDataType.Date && this.input) {
×
796
            this.input.nativeElement.value = null;
×
797
        }
798

UNCOV
799
        this.showHideArrowButtons();
×
800
    }
801

802
    private scrollChipsWhenAddingExpression() {
UNCOV
803
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
804
        if (!chipAraeChildren || chipAraeChildren.length === 0) {
×
UNCOV
805
            return;
×
806
        }
807

UNCOV
808
        const chipsContainerWidth = this.container.nativeElement.offsetWidth;
×
UNCOV
809
        const chipsAreaWidth = this.chipsArea.element.nativeElement.offsetWidth;
×
810

UNCOV
811
        if (chipsAreaWidth > chipsContainerWidth) {
×
UNCOV
812
            this.chipAreaScrollOffset = chipsContainerWidth - chipsAreaWidth;
×
UNCOV
813
            this.transform(this.chipAreaScrollOffset);
×
814
        }
815
    }
816

817
    private transform(offset: number) {
UNCOV
818
        requestAnimationFrame(() => {
×
UNCOV
819
            this.chipsArea.element.nativeElement.style.transform = `translate(${offset}px)`;
×
820
        });
821
    }
822

823
    private scrollChipsOnRemove() {
UNCOV
824
        let count = 0;
×
UNCOV
825
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
×
UNCOV
826
        const containerRect = this.container.nativeElement.getBoundingClientRect();
×
827

UNCOV
828
        for (const chip of chipAraeChildren) {
×
UNCOV
829
            if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.left)) {
×
830
                count++;
×
831
            }
832
        }
833

UNCOV
834
        if (count <= 2) {
×
UNCOV
835
            this.chipAreaScrollOffset = 0;
×
836
        } else {
837
            const dif = chipAraeChildren[count].id === 'chip' ? count - 2 : count - 1;
×
838
            this.chipAreaScrollOffset += Math.ceil(containerRect.left) - Math.ceil(chipAraeChildren[dif].getBoundingClientRect().left) + 1;
×
839
        }
840

UNCOV
841
        this.transform(this.chipAreaScrollOffset);
×
842
    }
843

844
    private conditionChangedCallback() {
UNCOV
845
        if (!!this.expression.searchVal || this.expression.searchVal === 0) {
×
UNCOV
846
            this.filter();
×
UNCOV
847
        } else if (this.value) {
×
848
            this.value = null;
×
849
        }
850
    }
851

852
    private unaryConditionChangedCallback() {
UNCOV
853
        if (this.value) {
×
UNCOV
854
            this.value = null;
×
855
        }
UNCOV
856
        if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
×
UNCOV
857
            this.addExpression(true);
×
858
        }
UNCOV
859
        this.filter();
×
860
    }
861

862
    private filter() {
UNCOV
863
        this.filteringService.filterInternal(this.column.field);
×
864
    }
865

866
    private editorFocused(activeElement: Element): boolean {
867
        // if the first check is false and the second is undefined this will return undefined
868
        // make sure it always returns boolean
UNCOV
869
        return !!(this.inputGroup && this.inputGroup.nativeElement.contains(activeElement)
×
870
            || this.picker && this.picker.isFocused);
871
    }
872

873
    private get isColumnFiltered() {
UNCOV
874
        return this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0;
×
875
    }
876

877
    public get isNarrowWidth(): boolean {
UNCOV
878
        return this.nativeElement.offsetWidth < this.NARROW_WIDTH_THRESHOLD;
×
879
    }
880
}
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

© 2026 Coveralls, Inc