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

IgniteUI / igniteui-angular / 16026043880

02 Jul 2025 01:10PM UTC coverage: 91.488% (+0.08%) from 91.409%
16026043880

Pull #15982

github

web-flow
Merge 693a735da into 7e40671a1
Pull Request #15982: Address grids column headers accessibility issues - active descendant, what is announced by SRs

13459 of 15781 branches covered (85.29%)

38 of 39 new or added lines in 8 files covered. (97.44%)

131 existing lines in 3 files now uncovered.

27161 of 29688 relevant lines covered (91.49%)

39508.7 hits per line

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

95.95
/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
     * Returns the `aria-selected` of the header.
60
     */
61
    @HostBinding('attr.aria-selected')
62
    public get ariaSelected(): boolean {
63
        return this.column.selected;
224,469✔
64
    }
65

66
    /**
67
     * Returns the `aria-sort` of the header.
68
     */
69
    @HostBinding('attr.aria-sort')
70
    public get ariaSort() {
71
        return this.sortDirection === SortingDirection.Asc ? 'ascending'
224,469✔
72
                : this.sortDirection === SortingDirection.Desc ? 'descending' : null;
224,277✔
73
    }
74

75
    /**
76
     * @hidden
77
     */
78
    @HostBinding('attr.aria-colindex')
79
    public get ariaColIndx() {
80
        return this.column.index + 1;
224,469✔
81
    }
82

83
    /**
84
     * @hidden
85
     */
86
    @HostBinding('attr.aria-rowindex')
87
    public get ariaRowIndx() {
88
        return 1;
224,469✔
89
    }
90

91
    @HostBinding('class.igx-grid-th')
92
    public get columnGroupStyle() {
93
        return !this.column.columnGroup;
224,469✔
94
    }
95

96
    @HostBinding('class.asc')
97
    public get sortAscendingStyle() {
98
        return this.sortDirection === SortingDirection.Asc;
224,469✔
99
    }
100

101
    @HostBinding('class.desc')
102
    public get sortDescendingStyle() {
103
        return this.sortDirection === SortingDirection.Desc;
224,469✔
104
    }
105

106
    @HostBinding('class.igx-grid-th--number')
107
    public get numberStyle() {
108
        return this.column.dataType === GridColumnDataType.Number;
224,469✔
109
    }
110

111
    @HostBinding('class.igx-grid-th--sortable')
112
    public get sortableStyle() {
113
        return this.column.sortable;
224,469✔
114
    }
115

116
    @HostBinding('class.igx-grid-th--selectable')
117
    public get selectableStyle() {
118
        return this.selectable;
224,469✔
119
    }
120

121
    @HostBinding('class.igx-grid-th--filtrable')
122
    public get filterableStyle() {
123
        return this.column.filterable && this.grid.filteringService.isFilterRowVisible;
224,469✔
124
    }
125

126
    @HostBinding('class.igx-grid-th--sorted')
127
    public get sortedStyle() {
128
        return this.sorted;
224,469✔
129
    }
130

131
    @HostBinding('class.igx-grid-th--selected')
132
    public get selectedStyle() {
133
        return this.selected;
224,469✔
134
    }
135

136
    /**
137
     * @hidden
138
     */
139
    public get esfIconTemplate() {
140
        return this.grid.excelStyleHeaderIconTemplate || this.defaultESFHeaderIconTemplate;
11,494✔
141
    }
142

143
    /**
144
     * @hidden
145
     */
146
    public get sortIconTemplate() {
147
        if (this.sortDirection === SortingDirection.None && this.grid.sortHeaderIconTemplate) {
29,052✔
148
            return this.grid.sortHeaderIconTemplate;
110✔
149
        } else if (this.sortDirection === SortingDirection.Asc && this.grid.sortAscendingHeaderIconTemplate) {
28,942✔
150
            return this.grid.sortAscendingHeaderIconTemplate;
10✔
151
        } else if (this.sortDirection === SortingDirection.Desc && this.grid.sortDescendingHeaderIconTemplate) {
28,932✔
152
            return this.grid.sortDescendingHeaderIconTemplate;
3✔
153
        } else {
154
            return this.defaultSortHeaderIconTemplate;
28,929✔
155
        }
156
    }
157
    /**
158
     * @hidden
159
     */
160
    public get disabled() {
161
        const groupArea = this.grid.groupArea || this.grid.treeGroupArea;
29,141✔
162
        if (groupArea?.expressions && groupArea.expressions.length && groupArea.expressions.map(g => g.fieldName).includes(this.column.field)) {
29,141✔
163
            return true;
89✔
164
        }
165
        return false;
29,052✔
166
    }
167

168
    public get sorted() {
169
        return this.sortDirection !== SortingDirection.None;
224,469✔
170
    }
171

172
    public get filterIconClassName() {
173
        return this.column.filteringExpressionsTree || this.isAdvancedFilterApplied() ? 'igx-excel-filter__icon--filtered' : 'igx-excel-filter__icon';
11,494✔
174
    }
175

176
    public get selectable() {
177
        return this.grid.columnSelection !== GridSelectionMode.none &&
208,335✔
178
            this.column.applySelectableClass &&
179
            !this.column.selected &&
180
            !this.grid.filteringService.isFilterRowVisible;
181
    }
182

183
    public get selected() {
184
        return this.column.selected
224,469✔
185
            && (!this.grid.filteringService.isFilterRowVisible || this.grid.filteringService.filteredColumn !== this.column);
186
    }
187

188
    public get title() {
189
        return this.column.title || this.column.header || this.column.field;
222,613✔
190
    }
191

192
    public get nativeElement() {
193
        return this.ref.nativeElement;
1,276✔
194
    }
195

196
    public sortDirection = SortingDirection.None;
20,393✔
197
    protected _destroy$ = new Subject<boolean>();
20,393✔
198

199
    constructor(
200
        @Inject(IGX_GRID_BASE) public grid: GridType,
20,393✔
201
        public colResizingService: IgxColumnResizingService,
20,393✔
202
        public cdr: ChangeDetectorRef,
20,393✔
203
        private ref: ElementRef<HTMLElement>
20,393✔
204
    ) { }
205

206
    @HostListener('click', ['$event'])
207
    public onClick(event: MouseEvent) {
208
        if (!this.colResizingService.isColumnResizing) {
77✔
209

210
            if (this.grid.filteringService.isFilterRowVisible) {
77✔
211
                if (this.column.filterCellTemplate) {
6✔
212
                    this.grid.filteringRow.close();
1✔
213
                    return;
1✔
214
                }
215

216
                if (this.column.filterable && !this.column.columnGroup &&
5✔
217
                    !this.grid.filteringService.isFilterComplex(this.column.field)) {
218
                    this.grid.filteringService.filteredColumn = this.column;
5✔
219
                }
220
            } else if (this.grid.columnSelection !== GridSelectionMode.none && this.column.selectable) {
71✔
221
                const clearSelection = this.grid.columnSelection === GridSelectionMode.single || !event.ctrlKey;
33✔
222
                const rangeSelection = this.grid.columnSelection === GridSelectionMode.multiple && event.shiftKey;
33✔
223

224
                if (!this.column.selected || (this.grid.selectionService.getSelectedColumns().length > 1 && clearSelection)) {
33✔
225
                    this.grid.selectionService.selectColumn(this.column.field, clearSelection, rangeSelection, event);
29✔
226
                } else {
227
                    this.grid.selectionService.deselectColumn(this.column.field, event);
4✔
228
                }
229
            }
230
        }
231
        this.grid.theadRow.nativeElement.focus();
76✔
232
    }
233

234
    /**
235
     * @hidden
236
     */
237
    @HostListener('pointerenter')
238
    public onPinterEnter() {
239
        this.column.applySelectableClass = true;
4✔
240
    }
241

242
    /**
243
     * @hidden
244
     */
245
    @HostListener('pointerleave')
246
    public onPointerLeave() {
247
        this.column.applySelectableClass = false;
3✔
248
    }
249

250
    /**
251
     * @hidden @internal
252
     */
253
    public ngDoCheck() {
254
        this.getSortDirection();
224,469✔
255
        this.cdr.markForCheck();
224,469✔
256
    }
257

258
    /**
259
     * @hidden @internal
260
     */
261
    public ngOnDestroy(): void {
262
        this._destroy$.next(true);
20,264✔
263
        this._destroy$.complete();
20,264✔
264
    }
265

266
    /**
267
     * @hidden @internal
268
     */
269
    public onPointerDownIndicator(event) {
270
        // Stop propagation of pointer events to now allow column dragging using the header indicators.
271
        event.stopPropagation();
111✔
272
    }
273

274
    /**
275
     * @hidden @internal
276
     */
277
    public onFilteringIconClick(event) {
278
        event.stopPropagation();
198✔
279
        this.grid.filteringService.toggleFilterDropdown(this.nativeElement, this.column);
198✔
280
    }
281

282
    /**
283
     * @hidden @internal
284
     */
285
    public onSortingIconClick(event) {
286
        event.stopPropagation();
47✔
287
        this.triggerSort();
47✔
288
    }
289

290
    protected getSortDirection() {
291
        const expr = this.grid.sortingExpressions.find((x) => x.fieldName === this.column.field);
208,335✔
292
        this.sortDirection = expr ? expr.dir : SortingDirection.None;
208,335✔
293
    }
294

295
    protected isAdvancedFilterApplied() {
296
        if(!this.grid.advancedFilteringExpressionsTree) {
11,357✔
297
            return false;
11,357✔
298
        }
UNCOV
299
        return !!ExpressionsTreeUtil.find(this.grid.advancedFilteringExpressionsTree, this.column.field);
×
300
    }
301

302
    private triggerSort() {
303
        const groupingExpr = this.grid.groupingExpressions ?
47✔
UNCOV
304
            this.grid.groupingExpressions.find((expr) => expr.fieldName === this.column.field) :
×
UNCOV
305
            this.grid.groupArea?.expressions ? this.grid.groupArea?.expressions.find((expr) => expr.fieldName === this.column.field) : null;
×
306
        const sortDir = groupingExpr ?
47!
307
            this.sortDirection + 1 > SortingDirection.Desc ? SortingDirection.Asc : SortingDirection.Desc
×
308
            : this.sortDirection + 1 > SortingDirection.Desc ? SortingDirection.None : this.sortDirection + 1;
47✔
309
        this.sortDirection = sortDir;
47✔
310
        this.grid.sort({
47✔
311
            fieldName: this.column.field, dir: this.sortDirection, ignoreCase: this.column.sortingIgnoreCase,
312
            strategy: this.column.sortStrategy
313
        });
314
    }
315
}
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