From e06d5869a1ff2f4c160c473d158c7849a713ad8e Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 23 Mar 2018 13:44:28 -0700 Subject: [PATCH 1/2] docs(focus-monitor): add documentation --- src/cdk/a11y/a11y.md | 76 ++++++++++++++++--- .../focus-monitor-directives.css | 19 +++++ .../focus-monitor-directives.html | 15 ++++ .../focus-monitor-directives.ts | 25 ++++++ .../focus-monitor-focus-via.css | 23 ++++++ .../focus-monitor-focus-via.html | 21 +++++ .../focus-monitor-focus-via.ts | 42 ++++++++++ .../focus-monitor-overview.css | 19 +++++ .../focus-monitor-overview.html | 11 +++ .../focus-monitor-overview.ts | 50 ++++++++++++ 10 files changed, 289 insertions(+), 12 deletions(-) create mode 100644 src/material-examples/focus-monitor-directives/focus-monitor-directives.css create mode 100644 src/material-examples/focus-monitor-directives/focus-monitor-directives.html create mode 100644 src/material-examples/focus-monitor-directives/focus-monitor-directives.ts create mode 100644 src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css create mode 100644 src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html create mode 100644 src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts create mode 100644 src/material-examples/focus-monitor-overview/focus-monitor-overview.css create mode 100644 src/material-examples/focus-monitor-overview/focus-monitor-overview.html create mode 100644 src/material-examples/focus-monitor-overview/focus-monitor-overview.ts diff --git a/src/cdk/a11y/a11y.md b/src/cdk/a11y/a11y.md index 548326f0bcc7..7b08ce5d9928 100644 --- a/src/cdk/a11y/a11y.md +++ b/src/cdk/a11y/a11y.md @@ -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 Tab 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
@@ -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. + + + +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. + + + +### 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. + + diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.css b/src/material-examples/focus-monitor-directives/focus-monitor-directives.css new file mode 100644 index 000000000000..f64065c60cea --- /dev/null +++ b/src/material-examples/focus-monitor-directives/focus-monitor-directives.css @@ -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); +} diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.html b/src/material-examples/focus-monitor-directives/focus-monitor-directives.html new file mode 100644 index 000000000000..e9b22e4b536b --- /dev/null +++ b/src/material-examples/focus-monitor-directives/focus-monitor-directives.html @@ -0,0 +1,15 @@ +
+ +
+ +
+
+

Focus Monitored Subtree ({{subtreeOrigin}})

+ + +
+
diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.ts b/src/material-examples/focus-monitor-directives/focus-monitor-directives.ts new file mode 100644 index 000000000000..c13412a97cf6 --- /dev/null +++ b/src/material-examples/focus-monitor-directives/focus-monitor-directives.ts @@ -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()); + } +} diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css new file mode 100644 index 000000000000..6c1a0481c0a4 --- /dev/null +++ b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css @@ -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; +} diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html new file mode 100644 index 000000000000..a007e62238f0 --- /dev/null +++ b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html @@ -0,0 +1,21 @@ +
+ + +
+ + + Simulated focus origin + + Mouse + Keyboard + Touch + Programmatic + + + + + diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts new file mode 100644 index 000000000000..6ad5dd511770 --- /dev/null +++ b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts @@ -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'; + } +} diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.css b/src/material-examples/focus-monitor-overview/focus-monitor-overview.css new file mode 100644 index 000000000000..f64065c60cea --- /dev/null +++ b/src/material-examples/focus-monitor-overview/focus-monitor-overview.css @@ -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); +} diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.html b/src/material-examples/focus-monitor-overview/focus-monitor-overview.html new file mode 100644 index 000000000000..6f3c733582ef --- /dev/null +++ b/src/material-examples/focus-monitor-overview/focus-monitor-overview.html @@ -0,0 +1,11 @@ +
+ +
+ +
+
+

Focus Monitored Subtree ({{subtreeOrigin}})

+ + +
+
diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.ts b/src/material-examples/focus-monitor-overview/focus-monitor-overview.ts new file mode 100644 index 000000000000..4f0e8475f64a --- /dev/null +++ b/src/material-examples/focus-monitor-overview/focus-monitor-overview.ts @@ -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'; + } +} From 40c3eb42e999824e3dcb2ff11952e4481865e2ec Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 23 Mar 2018 15:12:40 -0700 Subject: [PATCH 2/2] fix misnamed files --- ...onitor-directives.css => focus-monitor-directives-example.css} | 0 ...itor-directives.html => focus-monitor-directives-example.html} | 0 ...-monitor-directives.ts => focus-monitor-directives-example.ts} | 0 ...-monitor-focus-via.css => focus-monitor-focus-via-example.css} | 0 ...onitor-focus-via.html => focus-monitor-focus-via-example.html} | 0 ...us-monitor-focus-via.ts => focus-monitor-focus-via-example.ts} | 0 ...us-monitor-overview.css => focus-monitor-overview-example.css} | 0 ...-monitor-overview.html => focus-monitor-overview-example.html} | 0 ...ocus-monitor-overview.ts => focus-monitor-overview-example.ts} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/material-examples/focus-monitor-directives/{focus-monitor-directives.css => focus-monitor-directives-example.css} (100%) rename src/material-examples/focus-monitor-directives/{focus-monitor-directives.html => focus-monitor-directives-example.html} (100%) rename src/material-examples/focus-monitor-directives/{focus-monitor-directives.ts => focus-monitor-directives-example.ts} (100%) rename src/material-examples/focus-monitor-focus-via/{focus-monitor-focus-via.css => focus-monitor-focus-via-example.css} (100%) rename src/material-examples/focus-monitor-focus-via/{focus-monitor-focus-via.html => focus-monitor-focus-via-example.html} (100%) rename src/material-examples/focus-monitor-focus-via/{focus-monitor-focus-via.ts => focus-monitor-focus-via-example.ts} (100%) rename src/material-examples/focus-monitor-overview/{focus-monitor-overview.css => focus-monitor-overview-example.css} (100%) rename src/material-examples/focus-monitor-overview/{focus-monitor-overview.html => focus-monitor-overview-example.html} (100%) rename src/material-examples/focus-monitor-overview/{focus-monitor-overview.ts => focus-monitor-overview-example.ts} (100%) diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.css b/src/material-examples/focus-monitor-directives/focus-monitor-directives-example.css similarity index 100% rename from src/material-examples/focus-monitor-directives/focus-monitor-directives.css rename to src/material-examples/focus-monitor-directives/focus-monitor-directives-example.css diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.html b/src/material-examples/focus-monitor-directives/focus-monitor-directives-example.html similarity index 100% rename from src/material-examples/focus-monitor-directives/focus-monitor-directives.html rename to src/material-examples/focus-monitor-directives/focus-monitor-directives-example.html diff --git a/src/material-examples/focus-monitor-directives/focus-monitor-directives.ts b/src/material-examples/focus-monitor-directives/focus-monitor-directives-example.ts similarity index 100% rename from src/material-examples/focus-monitor-directives/focus-monitor-directives.ts rename to src/material-examples/focus-monitor-directives/focus-monitor-directives-example.ts diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.css similarity index 100% rename from src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.css rename to src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.css diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.html similarity index 100% rename from src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.html rename to src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.html diff --git a/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts b/src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.ts similarity index 100% rename from src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via.ts rename to src/material-examples/focus-monitor-focus-via/focus-monitor-focus-via-example.ts diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.css b/src/material-examples/focus-monitor-overview/focus-monitor-overview-example.css similarity index 100% rename from src/material-examples/focus-monitor-overview/focus-monitor-overview.css rename to src/material-examples/focus-monitor-overview/focus-monitor-overview-example.css diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.html b/src/material-examples/focus-monitor-overview/focus-monitor-overview-example.html similarity index 100% rename from src/material-examples/focus-monitor-overview/focus-monitor-overview.html rename to src/material-examples/focus-monitor-overview/focus-monitor-overview-example.html diff --git a/src/material-examples/focus-monitor-overview/focus-monitor-overview.ts b/src/material-examples/focus-monitor-overview/focus-monitor-overview-example.ts similarity index 100% rename from src/material-examples/focus-monitor-overview/focus-monitor-overview.ts rename to src/material-examples/focus-monitor-overview/focus-monitor-overview-example.ts