Skip to content

Commit 191357a

Browse files
crisbetommalerba
authored andcommitted
fix(sort): no focus indication for active header (#17735)
Currently we rely on the arrow being toggled as focus indication in the sort header, but if the header is active, the arrow will always be visible so the user won't have a way of telling where focus is. These changes add an outline around the text for keyboard and programmatic focus. Fixes #17716.
1 parent c8abc5f commit 191357a

File tree

5 files changed

+32
-5
lines changed

5 files changed

+32
-5
lines changed

src/material/sort/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ ng_module(
1919
assets = [":sort-header.css"] + glob(["**/*.html"]),
2020
module_name = "@angular/material/sort",
2121
deps = [
22+
"//src/cdk/a11y",
2223
"//src/cdk/coercion",
2324
"//src/material/core",
2425
"@npm//@angular/animations",

src/material/sort/sort-header.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
[class.mat-sort-header-position-before]="arrowPosition == 'before'">
44
<button class="mat-sort-header-button" type="button"
55
[attr.disabled]="_isDisabled() || null"
6-
[attr.aria-label]="_intl.sortButtonLabel(id)"
7-
(focus)="_setIndicatorHintVisible(true)"
8-
(blur)="_setIndicatorHintVisible(false)">
6+
[attr.aria-label]="_intl.sortButtonLabel(id)">
97
<ng-content></ng-content>
108
</button>
119

src/material/sort/sort-header.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ $mat-sort-header-arrow-hint-opacity: 0.38;
3232
font: inherit;
3333
color: currentColor;
3434

35+
// Usually we could rely on the arrow showing up to be focus indication, but if a header is
36+
// active, the arrow will always be shown so the user has no way of telling the difference.
37+
[mat-sort-header].cdk-keyboard-focused &,
38+
[mat-sort-header].cdk-program-focused & {
39+
border-bottom: solid 1px currentColor;
40+
}
41+
3542
// The `outline: 0` from above works on all browsers, however Firefox also
3643
// adds a special `focus-inner` which we have to disable explicitly. See:
3744
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Firefox

src/material/sort/sort-header.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import {
1717
Optional,
1818
ViewEncapsulation,
1919
Inject,
20+
ElementRef,
2021
} from '@angular/core';
2122
import {CanDisable, CanDisableCtor, mixinDisabled} from '@angular/material/core';
23+
import {FocusMonitor} from '@angular/cdk/a11y';
2224
import {merge, Subscription} from 'rxjs';
2325
import {MatSort, MatSortable} from './sort';
2426
import {matSortAnimations} from './sort-animations';
@@ -139,7 +141,13 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
139141
changeDetectorRef: ChangeDetectorRef,
140142
@Optional() public _sort: MatSort,
141143
@Inject('MAT_SORT_HEADER_COLUMN_DEF') @Optional()
142-
public _columnDef: MatSortHeaderColumnDef) {
144+
public _columnDef: MatSortHeaderColumnDef,
145+
/**
146+
* @deprecated _focusMonitor and _elementRef to become required parameters.
147+
* @breaking-change 10.0.0
148+
*/
149+
private _focusMonitor?: FocusMonitor,
150+
private _elementRef?: ElementRef<HTMLElement>) {
143151
// Note that we use a string token for the `_columnDef`, because the value is provided both by
144152
// `material/table` and `cdk/table` and we can't have the CDK depending on Material,
145153
// and we want to avoid having the sort header depending on the CDK table because
@@ -164,6 +172,13 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
164172

165173
changeDetectorRef.markForCheck();
166174
});
175+
176+
if (_focusMonitor && _elementRef) {
177+
// We use the focus monitor because we also want to style
178+
// things differently based on the focus origin.
179+
_focusMonitor.monitor(_elementRef, true)
180+
.subscribe(origin => this._setIndicatorHintVisible(!!origin));
181+
}
167182
}
168183

169184
ngOnInit() {
@@ -180,6 +195,11 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
180195
}
181196

182197
ngOnDestroy() {
198+
// @breaking-change 10.0.0 Remove null check for _focusMonitor and _elementRef.
199+
if (this._focusMonitor && this._elementRef) {
200+
this._focusMonitor.stopMonitoring(this._elementRef);
201+
}
202+
183203
this._sort.deregister(this);
184204
this._rerenderSubscription.unsubscribe();
185205
}

tools/public_api_guard/material/sort.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export declare class MatSortHeader extends _MatSortHeaderMixinBase implements Ca
6161
disableClear: boolean;
6262
id: string;
6363
start: 'asc' | 'desc';
64-
constructor(_intl: MatSortHeaderIntl, changeDetectorRef: ChangeDetectorRef, _sort: MatSort, _columnDef: MatSortHeaderColumnDef);
64+
constructor(_intl: MatSortHeaderIntl, changeDetectorRef: ChangeDetectorRef, _sort: MatSort, _columnDef: MatSortHeaderColumnDef,
65+
_focusMonitor?: FocusMonitor | undefined, _elementRef?: ElementRef<HTMLElement> | undefined);
6566
_getAriaSortAttribute(): "ascending" | "descending" | null;
6667
_getArrowDirectionState(): string;
6768
_getArrowViewState(): string;

0 commit comments

Comments
 (0)