Skip to content

Commit 1673a63

Browse files
committed
fix(sort): no focus indication for active header
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 93dc69f commit 1673a63

File tree

5 files changed

+33
-6
lines changed

5 files changed

+33
-6
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+
outline: 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';
@@ -138,7 +140,13 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
138140
changeDetectorRef: ChangeDetectorRef,
139141
@Optional() public _sort: MatSort,
140142
@Inject('MAT_SORT_HEADER_COLUMN_DEF') @Optional()
141-
public _columnDef: MatSortHeaderColumnDef) {
143+
public _columnDef: MatSortHeaderColumnDef,
144+
/**
145+
* @deprecated _focusMonitor and _elementRef to become required parameters.
146+
* @breaking-change 10.0.0
147+
*/
148+
private _focusMonitor?: FocusMonitor,
149+
private _elementRef?: ElementRef<HTMLElement>) {
142150
// Note that we use a string token for the `_columnDef`, because the value is provided both by
143151
// `material/table` and `cdk/table` and we can't have the CDK depending on Material,
144152
// and we want to avoid having the sort header depending on the CDK table because
@@ -163,6 +171,13 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
163171

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

168183
ngOnInit() {
@@ -179,6 +194,11 @@ export class MatSortHeader extends _MatSortHeaderMixinBase
179194
}
180195

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

tools/public_api_guard/material/sort.d.ts

Lines changed: 3 additions & 2 deletions
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;
@@ -89,7 +90,7 @@ export declare class MatSortHeaderIntl {
8990

9091
export declare class MatSortModule {
9192
static ɵinj: i0.ɵɵInjectorDef<MatSortModule>;
92-
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MatSortModule, [typeof i1.MatSort, typeof i2.MatSortHeader], [typeof i3.CommonModule], [typeof i1.MatSort, typeof i2.MatSortHeader]>;
93+
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MatSortModule, [typeof i1.MatSort, typeof i2.MatSortHeader], [typeof i3.A11yModule, typeof i4.CommonModule], [typeof i1.MatSort, typeof i2.MatSortHeader]>;
9394
}
9495

9596
export interface Sort {

0 commit comments

Comments
 (0)