mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs(focus-monitor): add documentation (#10547)
This commit is contained in:
+64
-12
@@ -1,10 +1,10 @@
|
||||
The `a11y` package provides a number of tools to improve accessibility, described below.
|
||||
|
||||
### ListKeyManager
|
||||
## ListKeyManager
|
||||
`ListKeyManager` manages the active option in a list of items based on keyboard interaction.
|
||||
Intended to be used with components that correspond to a `role="menu"` or `role="listbox"` pattern.
|
||||
|
||||
#### Basic usage
|
||||
### Basic usage
|
||||
Any component that uses a `ListKeyManager` will generally do three things:
|
||||
* Create a `@ViewChildren` query for the options being managed.
|
||||
* Initialize the `ListKeyManager`, passing in the options.
|
||||
@@ -18,16 +18,16 @@ interface ListKeyManagerOption {
|
||||
}
|
||||
```
|
||||
|
||||
#### Wrapping
|
||||
### Wrapping
|
||||
Navigation through options can be made to wrap via the `withWrap` method
|
||||
```ts
|
||||
this.keyManager = new FocusKeyManager(...).withWrap();
|
||||
```
|
||||
|
||||
#### Types of key managers
|
||||
### Types of key managers
|
||||
There are two varieties of `ListKeyManager`, `FocusKeyManager` and `ActiveDescendantKeyManager`.
|
||||
|
||||
##### FocusKeyManager
|
||||
#### FocusKeyManager
|
||||
Used when options will directly receive browser focus. Each item managed must implement the
|
||||
`FocusableOption` interface:
|
||||
```ts
|
||||
@@ -36,7 +36,7 @@ interface FocusableOption extends ListKeyManagerOption {
|
||||
}
|
||||
```
|
||||
|
||||
##### ActiveDescendantKeyManager
|
||||
#### ActiveDescendantKeyManager
|
||||
Used when options will be marked as active via `aria-activedescendant`.
|
||||
Each item managed must implement the
|
||||
`Highlightable` interface:
|
||||
@@ -50,7 +50,7 @@ interface Highlightable extends ListKeyManagerOption {
|
||||
Each item must also have an ID bound to the listbox's or menu's `aria-activedescendant`.
|
||||
|
||||
|
||||
### FocusTrap
|
||||
## FocusTrap
|
||||
The `cdkTrapFocus` directive traps <kbd>Tab</kbd> key focus within an element. This is intended to
|
||||
be used to create accessible experience for components like
|
||||
[modal dialogs](https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal), where focus must be
|
||||
@@ -58,7 +58,7 @@ constrained.
|
||||
|
||||
This directive is declared in `A11yModule`.
|
||||
|
||||
#### Example
|
||||
### Example
|
||||
```html
|
||||
<div class="my-inner-dialog-content" cdkTrapFocus>
|
||||
<!-- Tab and Shift + Tab will not leave this element. -->
|
||||
@@ -68,7 +68,7 @@ This directive is declared in `A11yModule`.
|
||||
This directive will not prevent focus from moving out of the trapped region due to mouse
|
||||
interaction.
|
||||
|
||||
#### Regions
|
||||
### Regions
|
||||
Regions can be declared explicitly with an initial focus element by using
|
||||
the `cdkFocusRegionStart`, `cdkFocusRegionEnd` and `cdkFocusInitial` DOM attributes.
|
||||
`cdkFocusInitial` specifies the element that will receive focus upon initialization of the region.
|
||||
@@ -85,18 +85,18 @@ For example:
|
||||
```
|
||||
|
||||
|
||||
### InteractivityChecker
|
||||
## InteractivityChecker
|
||||
`InteractivityChecker` is used to check the interactivity of an element, capturing disabled,
|
||||
visible, tabbable, and focusable states for accessibility purposes. See the API docs for more
|
||||
details.
|
||||
|
||||
|
||||
### LiveAnnouncer
|
||||
## LiveAnnouncer
|
||||
`LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region.
|
||||
See [the W3C's WAI-ARIA](https://www.w3.org/TR/wai-aria/states_and_properties#aria-live)
|
||||
for more information on aria-live regions.
|
||||
|
||||
#### Example
|
||||
### Example
|
||||
```ts
|
||||
@Component({...})
|
||||
export class MyComponent {
|
||||
@@ -106,3 +106,55 @@ export class MyComponent {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## FocusMonitor
|
||||
The `FocusMonitor` is an injectable service that can be used to listen for changes in the focus
|
||||
state of an element. It's more powerful than just listening for `focus` or `blur` events because it
|
||||
tells you how the element was focused (via mouse, keyboard, touch, or programmatically). It also
|
||||
allows listening for focus on descendant elements if desired.
|
||||
|
||||
To listen for focus changes on an element, use the `monitor` method which takes an element to
|
||||
monitor and an optional boolean flag `checkChildren`. Passing true for `checkChildren` will tell the
|
||||
`FocusMonitor` to consider the element focused if any of its descendants are focused. This option
|
||||
defaults to `false` if not specified. The `monitor` method will return an Observable that emits the
|
||||
`FocusOrigin` whenever the focus state changes. The `FocusOrigin` will be one of the following:
|
||||
|
||||
* `'mouse'` indicates the element was focused with the mouse
|
||||
* `'keyboard'` indicates the element was focused with the keyboard
|
||||
* `'touch'` indicates the element was focused by touching on a touchscreen
|
||||
* `'program'` indicates the element was focused programmatically
|
||||
* `null` indicates the element was blurred
|
||||
|
||||
In addition to emitting on the observable, the `FocusMonitor` will automatically apply CSS classes
|
||||
to the element when focused. It will add `.cdk-focused` if the element is focused and will further
|
||||
add `.cdk-${origin}-focused` (with `${origin}` being `mouse`, `keyboard`, `touch`, or `program`) to
|
||||
indicate how the element was focused.
|
||||
|
||||
Note: currently the `FocusMonitor` emits on the observable _outside_ of the Angular zone. Therefore
|
||||
if you `markForCheck` in the subscription you must put yourself back in the Angular zone.
|
||||
|
||||
```ts
|
||||
focusMonitor.monitor(el).subscribe(origin => this.ngZone.run(() => /* ... */ ));
|
||||
```
|
||||
|
||||
Any element that is monitored by calling `monitor` should eventually be unmonitored by calling
|
||||
`stopMonitoring` with the same element.
|
||||
|
||||
<!-- example(focus-monitor-overview) -->
|
||||
|
||||
It is possible to falsify the `FocusOrigin` when setting the focus programmatically by using the
|
||||
`focusVia` method of `FocusMonitor`. This method accepts an element to focus and the `FocusOrigin`
|
||||
to use. If the element being focused is currently being monitored by the `FocusMonitor` it will
|
||||
report the `FocusOrigin` that was passed in. If the element is not currently being monitored it will
|
||||
just be focused like normal.
|
||||
|
||||
<!-- example(focus-monitor-focus-via) -->
|
||||
|
||||
### cdkMonitorElementFocus and cdkMonitorSubtreeFocus
|
||||
For convenience, the CDK also provides two directives that allow for easily monitoring an element.
|
||||
`cdkMonitorElementFocus` is the equivalent of calling `monitor` on the host element with
|
||||
`checkChildren` set to `false`. `cdkMonitorSubtreeFocus` is the equivalent of calling `monitor` on
|
||||
the host element with `checkChildren` set to `true`. Each of these directives has an `@Output()`
|
||||
`cdkFocusChange` that will emit the new `FocusOrigin` whenever it changes.
|
||||
|
||||
<!-- example(focus-monitor-directives) -->
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.example-focus-monitor {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-mouse-focused {
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-keyboard-focused {
|
||||
background: rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-touch-focused {
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-program-focused {
|
||||
background: rgba(255, 0, 255, 0.5);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="example-focus-monitor">
|
||||
<button cdkMonitorSubtreeFocus
|
||||
(cdkFocusChange)="elementOrigin = formatOrigin($event); markForCheck()">
|
||||
Focus Monitored Element ({{elementOrigin}})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="example-focus-monitor">
|
||||
<div cdkMonitorSubtreeFocus
|
||||
(cdkFocusChange)="subtreeOrigin = formatOrigin($event); markForCheck()">
|
||||
<p>Focus Monitored Subtree ({{subtreeOrigin}})</p>
|
||||
<button>Child Button 1</button>
|
||||
<button>Child Button 2</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import {FocusOrigin} from '@angular/cdk/a11y';
|
||||
import {ChangeDetectorRef, Component, NgZone} from '@angular/core';
|
||||
|
||||
/** @title Monitoring focus with FocusMonitor */
|
||||
@Component({
|
||||
selector: 'focus-monitor-directives-example',
|
||||
templateUrl: 'focus-monitor-directives-example.html',
|
||||
styleUrls: ['focus-monitor-directives-example.css']
|
||||
})
|
||||
export class FocusMonitorDirectivesExample {
|
||||
elementOrigin: string = this.formatOrigin(null);
|
||||
subtreeOrigin: string = this.formatOrigin(null);
|
||||
|
||||
constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
|
||||
formatOrigin(origin: FocusOrigin): string {
|
||||
return origin ? origin + ' focused' : 'blurred';
|
||||
}
|
||||
|
||||
// Workaround for the fact that (cdkFocusChange) emits outside NgZone.
|
||||
markForCheck() {
|
||||
this.ngZone.run(() => this.cdr.markForCheck());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.example-focus-monitor {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-mouse-focused {
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-keyboard-focused {
|
||||
background: rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-touch-focused {
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-program-focused {
|
||||
background: rgba(255, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor button:focus {
|
||||
box-shadow: 0 0 30px cyan;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="example-focus-monitor">
|
||||
<button #monitored>1. Focus Monitored Element ({{origin}})</button>
|
||||
<button #unmonitored>2. Not Monitored</button>
|
||||
</div>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Simulated focus origin</mat-label>
|
||||
<mat-select #simulatedOrigin value="mouse">
|
||||
<mat-option value="mouse">Mouse</mat-option>
|
||||
<mat-option value="keyboard">Keyboard</mat-option>
|
||||
<mat-option value="touch">Touch</mat-option>
|
||||
<mat-option value="program">Programmatic</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button (click)="focusMonitor.focusVia(monitored, simulatedOrigin.value)">
|
||||
Focus button #1
|
||||
</button>
|
||||
<button (click)="focusMonitor.focusVia(unmonitored, simulatedOrigin.value)">
|
||||
Focus button #2
|
||||
</button>
|
||||
@@ -0,0 +1,42 @@
|
||||
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
/** @title Focusing with a specific FocusOrigin */
|
||||
@Component({
|
||||
selector: 'focus-monitor-focus-via-example',
|
||||
templateUrl: 'focus-monitor-focus-via-example.html',
|
||||
styleUrls: ['focus-monitor-focus-via-example.css']
|
||||
})
|
||||
export class FocusMonitorFocusViaExample implements OnDestroy, OnInit {
|
||||
@ViewChild('monitored') monitoredEl: ElementRef;
|
||||
|
||||
origin: string = this.formatOrigin(null);
|
||||
|
||||
constructor(public focusMonitor: FocusMonitor,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private ngZone: NgZone) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.focusMonitor.monitor(this.monitoredEl.nativeElement)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.origin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.monitoredEl.nativeElement);
|
||||
}
|
||||
|
||||
formatOrigin(origin: FocusOrigin): string {
|
||||
return origin ? origin + ' focused' : 'blurred';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.example-focus-monitor {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-mouse-focused {
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-keyboard-focused {
|
||||
background: rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-touch-focused {
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
.example-focus-monitor .cdk-program-focused {
|
||||
background: rgba(255, 0, 255, 0.5);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="example-focus-monitor">
|
||||
<button #element>Focus Monitored Element ({{elementOrigin}})</button>
|
||||
</div>
|
||||
|
||||
<div class="example-focus-monitor">
|
||||
<div #subtree>
|
||||
<p>Focus Monitored Subtree ({{subtreeOrigin}})</p>
|
||||
<button>Child Button 1</button>
|
||||
<button>Child Button 2</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
/** @title Monitoring focus with FocusMonitor */
|
||||
@Component({
|
||||
selector: 'focus-monitor-overview-example',
|
||||
templateUrl: 'focus-monitor-overview-example.html',
|
||||
styleUrls: ['focus-monitor-overview-example.css']
|
||||
})
|
||||
export class FocusMonitorOverviewExample implements OnDestroy, OnInit {
|
||||
@ViewChild('element') element: ElementRef;
|
||||
@ViewChild('subtree') subtree: ElementRef;
|
||||
|
||||
elementOrigin: string = this.formatOrigin(null);
|
||||
subtreeOrigin: string = this.formatOrigin(null);
|
||||
|
||||
constructor(private focusMonitor: FocusMonitor,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private ngZone: NgZone) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.focusMonitor.monitor(this.element.nativeElement)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.elementOrigin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
}));
|
||||
this.focusMonitor.monitor(this.subtree.nativeElement, true)
|
||||
.subscribe(origin => this.ngZone.run(() => {
|
||||
this.subtreeOrigin = this.formatOrigin(origin);
|
||||
this.cdr.markForCheck();
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.element.nativeElement);
|
||||
this.focusMonitor.stopMonitoring(this.subtree.nativeElement);
|
||||
}
|
||||
|
||||
formatOrigin(origin: FocusOrigin): string {
|
||||
return origin ? origin + ' focused' : 'blurred';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user