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

IgniteUI / igniteui-angular / 16804852387

07 Aug 2025 12:45PM UTC coverage: 91.52% (+0.08%) from 91.44%
16804852387

Pull #16096

github

web-flow
Merge 22e4fbe2e into 6d3657091
Pull Request #16096: Address grids column headers accessibility issues - active descendant, what is announced by SRs - master

13509 of 15836 branches covered (85.31%)

41 of 42 new or added lines in 8 files covered. (97.62%)

13 existing lines in 2 files now uncovered.

27228 of 29751 relevant lines covered (91.52%)

34752.07 hits per line

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

95.83
/projects/igniteui-angular/src/lib/grids/headers/grid-header.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    ChangeDetectorRef,
4
    Component,
5
    DoCheck,
6
    ElementRef,
7
    HostBinding,
8
    HostListener,
9
    Inject,
10
    Input,
11
    OnDestroy,
12
    TemplateRef,
13
    ViewChild
14
} from '@angular/core';
15
import { GridColumnDataType } from '../../data-operations/data-util';
16
import { IgxColumnResizingService } from '../resizing/resizing.service';
17
import { Subject } from 'rxjs';
18
import { ColumnType, GridType, IGX_GRID_BASE } from '../common/grid.interface';
19
import { GridSelectionMode } from '../common/enums';
20
import { SortingDirection } from '../../data-operations/sorting-strategy';
21
import { SortingIndexPipe } from './pipes';
22
import { NgTemplateOutlet, NgClass } from '@angular/common';
23
import { IgxIconComponent } from '../../icon/icon.component';
24
import { ExpressionsTreeUtil } from '../../data-operations/expressions-tree-util';
25

26
/**
27
 * @hidden
28
 */
29
@Component({
30
    changeDetection: ChangeDetectionStrategy.OnPush,
31
    selector: 'igx-grid-header',
32
    templateUrl: 'grid-header.component.html',
33
    imports: [IgxIconComponent, NgTemplateOutlet, NgClass, SortingIndexPipe]
34
})
35
export class IgxGridHeaderComponent implements DoCheck, OnDestroy {
3✔
36

37
    @Input()
38
    public column: ColumnType;
39

40
    /**
41
     * @hidden
42
     */
43
    @ViewChild('defaultESFHeaderIconTemplate', { read: TemplateRef, static: true })
44
    protected defaultESFHeaderIconTemplate: TemplateRef<any>;
45

46
    /**
47
     * @hidden
48
     */
49
    @ViewChild('defaultSortHeaderIconTemplate', { read: TemplateRef, static: true })
50
    protected defaultSortHeaderIconTemplate;
51

52
    /**
53
     * @hidden
54
     */
55
    @ViewChild('sortIconContainer', { read: ElementRef })
56
    protected sortIconContainer: ElementRef;
57

58
    /**
59
     * @hidden
60
     */
61
    @Input()
62
    @HostBinding('attr.id')
63
    public id: string;
64

65
    /**
66
     * Returns the `aria-selected` of the header.
67
     */
68
    @HostBinding('attr.aria-selected')
69
    public get ariaSelected(): boolean {
70
        return this.column.selected;
226,366✔
71
    }
72

73
    /**
74
     * Returns the `aria-sort` of the header.
75
     */
76
    @HostBinding('attr.aria-sort')
77
    public get ariaSort() {
78
        return this.sortDirection === SortingDirection.Asc ? 'ascending'
226,366✔
79
                : this.sortDirection === SortingDirection.Desc ? 'descending' : null;
226,174✔
80
    }
81

82
    @HostBinding('class.igx-grid-th')
83
    public get columnGroupStyle() {
84
        return !this.column.columnGroup;
226,366✔
85
    }
86

87
    @HostBinding('class.asc')
88
    public get sortAscendingStyle() {
89
        return this.sortDirection === SortingDirection.Asc;
226,366✔
90
    }
91

92
    @HostBinding('class.desc')
93
    public get sortDescendingStyle() {
94
        return this.sortDirection === SortingDirection.Desc;
226,366✔
95
    }
96

97
    @HostBinding('class.igx-grid-th--number')
98
    public get numberStyle() {
99
        return this.column.dataType === GridColumnDataType.Number;
226,366✔
100
    }
101

102
    @HostBinding('class.igx-grid-th--sortable')
103
    public get sortableStyle() {
104
        return this.column.sortable;
226,366✔
105
    }
106

107
    @HostBinding('class.igx-grid-th--selectable')
108
    public get selectableStyle() {
109
        return this.selectable;
226,366✔
110
    }
111

112
    @HostBinding('class.igx-grid-th--filtrable')
113
    public get filterableStyle() {
114
        return this.column.filterable && this.grid.filteringService.isFilterRowVisible;
226,366✔
115
    }
116

117
    @HostBinding('class.igx-grid-th--sorted')
118
    public get sortedStyle() {
119
        return this.sorted;
226,366✔
120
    }
121

122
    @HostBinding('class.igx-grid-th--selected')
123
    public get selectedStyle() {
124
        return this.selected;
226,366✔
125
    }
126

127
    /**
128
     * @hidden
129
     */
130
    public get esfIconTemplate() {
131
        return this.grid.excelStyleHeaderIconTemplate || this.defaultESFHeaderIconTemplate;
11,584✔
132
    }
133

134
    /**
135
     * @hidden
136
     */
137
    public get sortIconTemplate() {
138
        if (this.sortDirection === SortingDirection.None && this.grid.sortHeaderIconTemplate) {
29,164✔
139
            return this.grid.sortHeaderIconTemplate;
110✔
140
        } else if (this.sortDirection === SortingDirection.Asc && this.grid.sortAscendingHeaderIconTemplate) {
29,054✔
141
            return this.grid.sortAscendingHeaderIconTemplate;
10✔
142
        } else if (this.sortDirection === SortingDirection.Desc && this.grid.sortDescendingHeaderIconTemplate) {
29,044✔
143
            return this.grid.sortDescendingHeaderIconTemplate;
3✔
144
        } else {
145
            return this.defaultSortHeaderIconTemplate;
29,041✔
146
        }
147
    }
148
    /**
149
     * @hidden
150
     */
151
    public get disabled() {
152
        const groupArea = this.grid.groupArea || this.grid.treeGroupArea;
29,253✔
153
        if (groupArea?.expressions && groupArea.expressions.length && groupArea.expressions.map(g => g.fieldName).includes(this.column.field)) {
29,253✔
154
            return true;
89✔
155
        }
156
        return false;
29,164✔
157
    }
158

159
    public get sorted() {
160
        return this.sortDirection !== SortingDirection.None;
226,366✔
161
    }
162

163
    public get filterIconClassName() {
164
        return this.column.filteringExpressionsTree || this.isAdvancedFilterApplied() ? 'igx-excel-filter__icon--filtered' : 'igx-excel-filter__icon';
11,584✔
165
    }
166

167
    public get selectable() {
168
        return this.grid.columnSelection !== GridSelectionMode.none &&
210,078✔
169
            this.column.applySelectableClass &&
170
            !this.column.selected &&
171
            !this.grid.filteringService.isFilterRowVisible;
172
    }
173

174
    public get selected() {
175
        return this.column.selected
226,366✔
176
            && (!this.grid.filteringService.isFilterRowVisible || this.grid.filteringService.filteredColumn !== this.column);
177
    }
178

179
    public get title() {
180
        return this.column.title || this.column.header || this.column.field;
224,496✔
181
    }
182

183
    public get nativeElement() {
184
        return this.ref.nativeElement;
1,278✔
185
    }
186

187
    public sortDirection = SortingDirection.None;
20,673✔
188
    protected _destroy$ = new Subject<boolean>();
20,673✔
189

190
    constructor(
191
        @Inject(IGX_GRID_BASE) public grid: GridType,
20,673✔
192
        public colResizingService: IgxColumnResizingService,
20,673✔
193
        public cdr: ChangeDetectorRef,
20,673✔
194
        private ref: ElementRef<HTMLElement>
20,673✔
195
    ) { }
196

197
    @HostListener('click', ['$event'])
198
    public onClick(event: MouseEvent) {
199
        if (!this.colResizingService.isColumnResizing) {
77✔
200

201
            if (this.grid.filteringService.isFilterRowVisible) {
77✔
202
                if (this.column.filterCellTemplate) {
6✔
203
                    this.grid.filteringRow.close();
1✔
204
                    return;
1✔
205
                }
206

207
                if (this.column.filterable && !this.column.columnGroup &&
5✔
208
                    !this.grid.filteringService.isFilterComplex(this.column.field)) {
209
                    this.grid.filteringService.filteredColumn = this.column;
5✔
210
                }
211
            } else if (this.grid.columnSelection !== GridSelectionMode.none && this.column.selectable) {
71✔
212
                const clearSelection = this.grid.columnSelection === GridSelectionMode.single || !event.ctrlKey;
33✔
213
                const rangeSelection = this.grid.columnSelection === GridSelectionMode.multiple && event.shiftKey;
33✔
214

215
                if (!this.column.selected || (this.grid.selectionService.getSelectedColumns().length > 1 && clearSelection)) {
33✔
216
                    this.grid.selectionService.selectColumn(this.column.field, clearSelection, rangeSelection, event);
29✔
217
                } else {
218
                    this.grid.selectionService.deselectColumn(this.column.field, event);
4✔
219
                }
220
            }
221
        }
222
        this.grid.theadRow.nativeElement.focus();
76✔
223
    }
224

225
    /**
226
     * @hidden
227
     */
228
    @HostListener('pointerenter')
229
    public onPinterEnter() {
230
        this.column.applySelectableClass = true;
4✔
231
    }
232

233
    /**
234
     * @hidden
235
     */
236
    @HostListener('pointerleave')
237
    public onPointerLeave() {
238
        this.column.applySelectableClass = false;
3✔
239
    }
240

241
    /**
242
     * @hidden @internal
243
     */
244
    public ngDoCheck() {
245
        this.getSortDirection();
226,366✔
246
        this.cdr.markForCheck();
226,366✔
247
    }
248

249
    /**
250
     * @hidden @internal
251
     */
252
    public ngOnDestroy(): void {
253
        this._destroy$.next(true);
20,544✔
254
        this._destroy$.complete();
20,544✔
255
    }
256

257
    /**
258
     * @hidden @internal
259
     */
260
    public onPointerDownIndicator(event) {
261
        // Stop propagation of pointer events to now allow column dragging using the header indicators.
262
        event.stopPropagation();
113✔
263
    }
264

265
    /**
266
     * @hidden @internal
267
     */
268
    public onFilteringIconClick(event) {
269
        event.stopPropagation();
200✔
270
        this.grid.filteringService.toggleFilterDropdown(this.nativeElement, this.column);
200✔
271
    }
272

273
    /**
274
     * @hidden @internal
275
     */
276
    public onSortingIconClick(event) {
277
        event.stopPropagation();
47✔
278
        this.triggerSort();
47✔
279
    }
280

281
    protected getSortDirection() {
282
        const expr = this.grid.sortingExpressions.find((x) => x.fieldName === this.column.field);
210,078✔
283
        this.sortDirection = expr ? expr.dir : SortingDirection.None;
210,078✔
284
    }
285

286
    protected isAdvancedFilterApplied() {
287
        if(!this.grid.advancedFilteringExpressionsTree) {
11,443✔
288
            return false;
11,443✔
289
        }
UNCOV
290
        return !!ExpressionsTreeUtil.find(this.grid.advancedFilteringExpressionsTree, this.column.field);
×
291
    }
292

293
    private triggerSort() {
294
        const groupingExpr = this.grid.groupingExpressions ?
47✔
UNCOV
295
            this.grid.groupingExpressions.find((expr) => expr.fieldName === this.column.field) :
×
UNCOV
296
            this.grid.groupArea?.expressions ? this.grid.groupArea?.expressions.find((expr) => expr.fieldName === this.column.field) : null;
×
297
        const sortDir = groupingExpr ?
47!
298
            this.sortDirection + 1 > SortingDirection.Desc ? SortingDirection.Asc : SortingDirection.Desc
×
299
            : this.sortDirection + 1 > SortingDirection.Desc ? SortingDirection.None : this.sortDirection + 1;
47✔
300
        this.sortDirection = sortDir;
47✔
301
        this.grid.sort({
47✔
302
            fieldName: this.column.field, dir: this.sortDirection, ignoreCase: this.column.sortingIgnoreCase,
303
            strategy: this.column.sortStrategy
304
        });
305
    }
306
}
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