diff --git a/public/docs/_examples/cb-a11y/ts/.gitignore b/public/docs/_examples/cb-a11y/ts/.gitignore new file mode 100644 index 0000000000..0390f46835 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/.gitignore @@ -0,0 +1,11 @@ +styles.css +typings +typings.json +*.js.map +package.json +karma.conf.js +karma-test-shim.js +tsconfig.json +npm-debug*. +**/protractor.config.js + diff --git a/public/docs/_examples/cb-a11y/ts/a11y.css b/public/docs/_examples/cb-a11y/ts/a11y.css new file mode 100644 index 0000000000..8541969447 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/a11y.css @@ -0,0 +1,105 @@ +label { + color: #424242; + width: 100%; +} + +hr { + border-top: 3px double #8c8b8b; +} + +h3 { + outline: 0; +} + +.hide-element { + display: none !important; +} + +.btn { + margin-bottom: 15px; +} + +.background-contrast { + background-color: #0143A3; + color: #fff; +} + +nav a:visited, a:link { + color: darkblue; +} + +.label-default { + background-color: #3F3F3F; +} + +.skiplink { + min-height: 20px; +} + +/* #docregion cb-a11y-managing-focus-skiplinks-style */ +.skiplink a { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + color: darkblue; +} + +.skiplink a:active, +.skiplink a:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} + +/* #enddocregion */ + +/* #docregion cb-a11y-managing-focus-custom-outline */ +.custom-outline:focus { + border-color: #7F0037; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(127, 0, 55, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(127, 0, 55, .6); +} + +/* #enddocregion */ + +/* #docregion cb-a11y-form-controls-visually-hidden-style*/ +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +/* #enddocregion */ + +.like-bootstrap { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} diff --git a/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.html b/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.html new file mode 100644 index 0000000000..b6ac43137d --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.html @@ -0,0 +1,13 @@ + diff --git a/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.ts b/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.ts new file mode 100644 index 0000000000..e4e76d7d2a --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/a11y-index.component.ts @@ -0,0 +1,11 @@ +import {Component} from "angular2/core"; +import {ROUTER_DIRECTIVES} from "angular2/router"; + +@Component({ + selector: 'a11y-index', + templateUrl: './app/a11y-index.component.html', + directives: [ROUTER_DIRECTIVES] +}) +export class A11yIndex{ + +} diff --git a/public/docs/_examples/cb-a11y/ts/app/app.component.html b/public/docs/_examples/cb-a11y/ts/app/app.component.html new file mode 100644 index 0000000000..f4c9d73c8d --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/app.component.html @@ -0,0 +1,6 @@ + +
+ +
diff --git a/public/docs/_examples/cb-a11y/ts/app/app.component.ts b/public/docs/_examples/cb-a11y/ts/app/app.component.ts new file mode 100644 index 0000000000..5c3f52453b --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/app.component.ts @@ -0,0 +1,29 @@ +import {Component} from "angular2/core"; +import {ROUTER_DIRECTIVES, RouteConfig, ROUTER_PROVIDERS} from "angular2/router"; +import {A11yFormControls} from "./form-controls/a11y-form-controls.component"; +import {A11yIndex} from "./a11y-index.component"; +import {A11yHelper} from "./services/a11y-helper.service"; +import {A11yManagingFocus} from "./managing-focus/a11y-managing-focus.component"; +import {A11yComponentRoles} from "./component-roles/a11y-component-roles.component"; + +@Component({ + selector: 'app', + templateUrl: 'app/app.component.html', + directives:[ + ROUTER_DIRECTIVES, + A11yIndex + ], + providers: [ + ROUTER_PROVIDERS, + A11yHelper + ] +}) +@RouteConfig([ + {path:'/', name: 'Index', component: A11yIndex}, + {path:'/form-controls', name: 'FormControls', component: A11yFormControls}, + {path:'/managing-focus', name: 'ManagingFocus', component: A11yManagingFocus}, + {path:'/component-roles', name: 'ComponentRoles', component: A11yComponentRoles} +]) +export class AppComponent { + +} diff --git a/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.html b/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.html new file mode 100644 index 0000000000..7b68ed2c83 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.html @@ -0,0 +1,47 @@ + + +
+
+

Roles for custom component widgets

+
+ +
+
+

Roles in the template

+
+
+ + + I set the role in my template: + + + + +
+ +
+
+

Roles of the host element

+
+
+ +
+
+ + Do something... + +
+
+ +
+
+ +
+
+ +
+ +
diff --git a/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.ts b/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.ts new file mode 100644 index 0000000000..247d991917 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/component-roles/a11y-component-roles.component.ts @@ -0,0 +1,35 @@ +import {Component} from "angular2/core"; +import {A11yHelper} from "../services/a11y-helper.service"; +import {A11yCustomControl} from "../shared/a11y-custom-control.component"; +import {A11yValueHelper} from "../shared/a11y-value-helper.component"; +import {A11yCustomButton} from "../shared/a11y-custom-button.component"; + +@Component({ + selector: 'a11y-component-roles', + templateUrl: './app/component-roles/a11y-component-roles.component.html', + directives: [ + A11yCustomControl, + A11yValueHelper, + A11yCustomButton + ] +}) +export class A11yComponentRoles { + + inputDivModel: string = ''; + buttonClicks: number = 0; + + constructor(private _a11yHelper: A11yHelper){} + + onClick():void { + this.buttonClicks++; + } + + generateSkiplink(hash:string){ + return this._a11yHelper.getInternalLink(hash, 'ComponentRoles'); + } + + generateButtonString(): string{ + return `Button has been clicked ${this.buttonClicks} times`; + } + +} diff --git a/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.html b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.html new file mode 100644 index 0000000000..480006d0d5 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.html @@ -0,0 +1,179 @@ + + +
+
+

Accessible form control labels

+
+ +
+
+

Implicit labeling

+
+
+ + +
+ +
+ + + + + +
+ +
+ + + + + +
+ + What do you like most about Angular 2? + +
+ +
+
+ + + + + +
+ + Choose your favourite Angular 2 language: + +
+ +
+
+ + + + + +
+ +
+ + + +
+ +
+
+

Explicit labeling

+
+
+ + +
+ + +
+ + + + +
+ +
+
+

Hiding labels

+
+
+ + +
+ + +
+ + + + + +
+ +
+ + + + +
+ +
+
+

Labeling custom controls

+
+
+ + + + Write in this labeled div: + + + + + + + + Write in this wrapped input: + + + + + + +
+ Back to index... +
+ + diff --git a/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.ts b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.ts new file mode 100644 index 0000000000..fe6d87dc48 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-form-controls.component.ts @@ -0,0 +1,70 @@ +import {Component, OnInit} from "angular2/core"; +import {CORE_DIRECTIVES, FORM_DIRECTIVES} from "angular2/common"; +import {A11yHelper} from "../services/a11y-helper.service"; +import {ROUTER_DIRECTIVES} from "angular2/router"; +import {A11yInputWrapper} from "./a11y-input-wrapper.component"; +import {A11yValueHelper} from "../shared/a11y-value-helper.component"; +import {A11yCustomControl} from "../shared/a11y-custom-control.component"; + +@Component({ + selector: 'a11y-form-controls', + templateUrl: './app/form-controls/a11y-form-controls.component.html', + directives: [ + CORE_DIRECTIVES, + FORM_DIRECTIVES, + ROUTER_DIRECTIVES, + A11yCustomControl, + A11yInputWrapper, + A11yValueHelper + ] +}) +export class A11yFormControls implements OnInit { + + checkBoxes:any; + radioButtons:any; + selectOptions:any; + + inputModel:string; + inputExplicitModel: string; + inputWrappedModel: string; + inputWrappedSaveModel:string = ''; + inputDivModel: string = ''; + textModel:string; + selectModel: string = 'Curiosity'; + searchModel: string; + filterModel: string; + + radioModel:string = 'TypeScript'; + checkboxModel:Array = ["Observables", "Components"]; + + + constructor(private _a11yHelper:A11yHelper) { + } + + generateSkiplink(hash:string){ + return this._a11yHelper.getInternalLink(hash, 'FormControls'); + } + + isChecked(item:string):boolean { + return this._a11yHelper.isStringInArray(this.checkboxModel, item); + } + + toggleCheckbox(item:string):void { + this._a11yHelper.toggleItemInArray(this.checkboxModel, item); + } + + onSave(){ + this.inputWrappedSaveModel = this.inputWrappedModel; + } + + ngOnInit() { + this.checkBoxes = this._a11yHelper.getCheckboxModel(); + this.radioButtons = this._a11yHelper.getRadiobuttonsModel(); + this.selectOptions = this._a11yHelper.getSelectOptions(); + } + + updateSelect(value: string):void{ + this.selectModel = value; + } + +} diff --git a/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.css b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.css new file mode 100644 index 0000000000..14f209046a --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.css @@ -0,0 +1,27 @@ +/* #docregion */ +:host input { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} + +:host input:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); +} +/* #enddocregion */ diff --git a/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.html b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.html new file mode 100644 index 0000000000..544ea235fc --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.html @@ -0,0 +1,19 @@ + +
+
+ +
+
+ diff --git a/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.ts b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.ts new file mode 100644 index 0000000000..5798b37660 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/form-controls/a11y-input-wrapper.component.ts @@ -0,0 +1,18 @@ +import {Component, Output, EventEmitter} from "angular2/core"; + +// #docregion +@Component({ + selector: 'a11y-input-wrapper', + templateUrl: './app/form-controls/a11y-input-wrapper.component.html', + styleUrls: ['./app/form-controls/a11y-input-wrapper.component.css'] +}) +export class A11yInputWrapper{ + + @Output() + onSave: EventEmitter = new EventEmitter(); + + save(){ + this.onSave.emit(null); + } +} +// #enddocregion diff --git a/public/docs/_examples/cb-a11y/ts/app/main.ts b/public/docs/_examples/cb-a11y/ts/app/main.ts new file mode 100644 index 0000000000..4dc919769e --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/main.ts @@ -0,0 +1,5 @@ +import {bootstrap} from 'angular2/platform/browser'; +import {AppComponent} from './app.component'; + + +bootstrap(AppComponent); \ No newline at end of file diff --git a/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.html b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.html new file mode 100644 index 0000000000..52a6e22017 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.html @@ -0,0 +1,18 @@ + + + + diff --git a/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.ts b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.ts new file mode 100644 index 0000000000..4d1a80535f --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-error-demo.component.ts @@ -0,0 +1,19 @@ +import {Component} from "angular2/core"; + +// #docregion +@Component({ + selector: 'a11y-error-demo', + templateUrl: './app/managing-focus/a11y-error-demo.component.html' +}) +export class A11yErrorDemo { + + hideErrorConfirmation:boolean = true; + + setFocusOn(element: any) { + this.hideErrorConfirmation = false; + setTimeout(() => { + element.focus(); + }, 200); + } +} +// #enddocregion diff --git a/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.html b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.html new file mode 100644 index 0000000000..9507810c3f --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.html @@ -0,0 +1,94 @@ + + + + +
+
+

Managing focus

+
+ +
+
+

The focus outline

+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +

Focus flow

+ +
+
+ + +
+
+ +
+
+ +
+
+ + + +
+ +
+
+

Focusing custom controls

+
+
+ +
+
+ + Do something... + +
+
+ +
+
+ +
+
+
+ +
+
+

Internal focus in a component

+
+
+ + +
+ + Back to index... + +
+ diff --git a/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.ts b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.ts new file mode 100644 index 0000000000..ca1d7846a8 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/managing-focus/a11y-managing-focus.component.ts @@ -0,0 +1,44 @@ +import {Component, OnInit} from "angular2/core"; +import {A11yHelper} from "../services/a11y-helper.service"; +import {CORE_DIRECTIVES} from "angular2/common"; +import {A11yErrorDemo} from "./a11y-error-demo.component"; +import {ROUTER_DIRECTIVES} from "angular2/router"; +import {A11yCustomButton} from "../shared/a11y-custom-button.component"; +import {A11yValueHelper} from "../shared/a11y-value-helper.component"; + +@Component({ + selector: 'a11y-managing-focus', + templateUrl: './app/managing-focus/a11y-managing-focus.component.html', + directives: [ + CORE_DIRECTIVES, + ROUTER_DIRECTIVES, + A11yCustomButton, + A11yValueHelper, + A11yErrorDemo + ] +}) +export class A11yManagingFocus implements OnInit{ + + countriesWorkedIn: Array; + buttonClicks: number = 0; + + constructor(private _a11yHelper:A11yHelper) { + } + + generateSkiplink(hash:string) { + return this._a11yHelper.getInternalLink(hash, 'ManagingFocus'); + } + + onClick():void { + this.buttonClicks++; + } + + generateButtonString(): string{ + return `Button has been clicked ${this.buttonClicks} times`; + } + + ngOnInit():void{ + this.countriesWorkedIn = this._a11yHelper.getCountriesWorkedIn() + } + +} diff --git a/public/docs/_examples/cb-a11y/ts/app/services/a11y-helper.service.ts b/public/docs/_examples/cb-a11y/ts/app/services/a11y-helper.service.ts new file mode 100644 index 0000000000..eab283b6c6 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/services/a11y-helper.service.ts @@ -0,0 +1,111 @@ +import {Injectable} from "angular2/core"; +import {Router} from "angular2/router"; + +@Injectable() +export class A11yHelper { + + constructor(private _router: Router){} + + getInternalLink(hash: string, instructionName: string): string{ + let instruction = this._router.generate([instructionName]); + let path = '/' + instruction.toUrlPath() + hash; + return path; + } + + generateUniqueIdString():string { + return (this.randomGuidSnippet() + + this.randomGuidSnippet() + "-" + + this.randomGuidSnippet() + "-4" + + this.randomGuidSnippet().substr(0, 3) + "-" + + this.randomGuidSnippet() + "-" + + this.randomGuidSnippet() + + this.randomGuidSnippet() + + this.randomGuidSnippet()).toLowerCase(); + } + + getCheckboxModel():any { + return [ + { + name: 'Template syntax', + value: 'Template syntax' + }, + { + name: 'Observables', + value: 'Observables' + }, + { + name: 'Components', + value: 'Components' + }, + { + name: 'Forms', + value: 'Forms' + } + ]; + } + + getRadiobuttonsModel():any { + return [ + { + name: 'TypeScript', + value: 'TypeScript' + }, + { + name: 'JavaScript', + value: 'JavaScript' + }, + { + name: 'ES6', + value: 'ES6' + }, + { + name: 'Dart', + value: 'Dart' + } + ]; + } + + getSelectOptions():any { + return [ + { + name: 'Curiosity', + value: 'Curiosity' + }, + { + name: 'Increased userbase', + value: 'Increased userbase' + }, + { + name: 'Legal reasons', + value: 'Legal reasons' + } + ]; + } + + getCountriesWorkedIn():Array{ + return ['The USA', 'The Netherlands', 'South Africa', 'Germany', 'The UK']; + } + + toggleItemInArray(stringArray:Array, item:string): void { + var entryIndex = stringArray.indexOf(item); + if (entryIndex != -1) { + stringArray.splice(entryIndex, 1); + } else { + stringArray.push(item); + } + } + + isStringInArray(stringArray: Array, item: string): boolean { + return stringArray.indexOf(item.toString()) != -1; + } + + removeHtmlStringBreaks(inputValue: string):string{ + return inputValue.replace(new RegExp('
', 'g'), '') + .replace(new RegExp('
', 'g'), '\n') + .replace(new RegExp('
', 'g'), '') + } + + private randomGuidSnippet():string { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + } +} diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.html b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.html new file mode 100644 index 0000000000..ceb79a69d3 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.html @@ -0,0 +1,3 @@ + + + diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.ts b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.ts new file mode 100644 index 0000000000..0c03b7bf9b --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-button.component.ts @@ -0,0 +1,25 @@ +import {Component, EventEmitter, Output} from "angular2/core"; + +// #docregion +@Component({ + selector: 'a11y-custom-button', + templateUrl: './app/shared/a11y-custom-button.component.html', + host: { + 'role': 'button', + 'tabindex': '0', + 'class': 'btn btn-primary', + '(keydown.space)': 'onKeyDown()', + '(keydown.enter)': 'onKeyDown()' + } +}) +export class A11yCustomButton { + + @Output() + click:EventEmitter = new EventEmitter(); + + onKeyDown() { + this.click.emit(null); + } + +} +// #enddocregion diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.css b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.css new file mode 100644 index 0000000000..85aa2a3e40 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.css @@ -0,0 +1,6 @@ +/* #docregion */ +div.edit-box{ + height: auto; + min-height: 50px; +} +/* #enddocregion */ diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.html b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.html new file mode 100644 index 0000000000..21fdf24fbe --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.html @@ -0,0 +1,18 @@ + +
+
+ +
+
+
+ diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.ts b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.ts new file mode 100644 index 0000000000..a62782d9ba --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-custom-control.component.ts @@ -0,0 +1,68 @@ +import {Component, OnInit, Provider, forwardRef,} from "angular2/core"; +import {A11yHelper} from "../services/a11y-helper.service"; +import {NG_VALUE_ACCESSOR, ControlValueAccessor} from "angular2/common"; + +// #docregion +const noop = () => { +}; + +const A11Y_CUSTOM_CONTROL_VALUE_ACCESSOR = new Provider( + NG_VALUE_ACCESSOR, { + useExisting: forwardRef(() => A11yCustomControl), + multi: true + }); + +@Component({ + selector: 'a11y-custom-control', + templateUrl: './app/shared/a11y-custom-control.component.html', + styleUrls: ['./app/shared/a11y-custom-control.component.css'], + providers: [A11Y_CUSTOM_CONTROL_VALUE_ACCESSOR] +}) +export class A11yCustomControl implements OnInit, ControlValueAccessor { + + uniqueId:string; + + private _innerValue:any = ''; + outerValue:string = ''; + + private _onTouchedCallback:() => void = noop; + private _onChangeCallback:(_:any) => void = noop; + + constructor(private _a11yHelper:A11yHelper) { + } + + onChange(event:any, value:string) { + if (event.keyCode == 13) { + event.preventDefault(); + } + else { + this._innerValue = this._a11yHelper.removeHtmlStringBreaks(value); + this._onChangeCallback(this._innerValue); + } + } + + onBlur(){ + this._onTouchedCallback(); + } + + writeValue(value:any) { + if (value != this._innerValue) { + this._innerValue = value; + this.outerValue = value; + } + } + + registerOnChange(fn:any) { + this._onChangeCallback = fn; + } + + registerOnTouched(fn:any) { + this._onTouchedCallback = fn; + } + + ngOnInit():void { + this.uniqueId = this._a11yHelper.generateUniqueIdString(); + } + +} +// #enddocregion diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.html b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.html new file mode 100644 index 0000000000..bde42e27d4 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.html @@ -0,0 +1,5 @@ + + Current value: {{displayValue}} + diff --git a/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.ts b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.ts new file mode 100644 index 0000000000..6443299ec6 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/app/shared/a11y-value-helper.component.ts @@ -0,0 +1,18 @@ +import {Component, Input} from "angular2/core"; + +@Component({ + selector: 'a11y-value-helper', + templateUrl: './app/shared/a11y-value-helper.component.html', + styles: [` + .value-label { + position:relative; + top: -15px; + } +`] +}) +export class A11yValueHelper { + + @Input() + displayValue: any; + +} diff --git a/public/docs/_examples/cb-a11y/ts/example-config.json b/public/docs/_examples/cb-a11y/ts/example-config.json new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/example-config.json @@ -0,0 +1 @@ + diff --git a/public/docs/_examples/cb-a11y/ts/index.html b/public/docs/_examples/cb-a11y/ts/index.html new file mode 100644 index 0000000000..e56cff83f3 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/index.html @@ -0,0 +1,42 @@ + + + + + A11y demonstration + + + + + + + + + + + + + + + + + + + + + + +loading... + + + diff --git a/public/docs/_examples/cb-a11y/ts/plnkr.json b/public/docs/_examples/cb-a11y/ts/plnkr.json new file mode 100644 index 0000000000..8e4e448188 --- /dev/null +++ b/public/docs/_examples/cb-a11y/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "A11y Cookbook samples", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook", "a11y"] +} \ No newline at end of file diff --git a/public/docs/dart/latest/cookbook/_data.json b/public/docs/dart/latest/cookbook/_data.json index 2c28e02169..bd5fd9d990 100644 --- a/public/docs/dart/latest/cookbook/_data.json +++ b/public/docs/dart/latest/cookbook/_data.json @@ -12,6 +12,11 @@ "hide": true }, + "a11y": { + "title": "Accessibility and ARIA Reference", + "navTitle": "Accessibility and ARIA", + "intro": "Learn how to make your Angular 2 sites accessible for everyone" + }, "component-communication": { "title": "Component Interaction", "intro": "Share information between different directives and components" diff --git a/public/docs/dart/latest/cookbook/a11y.jade b/public/docs/dart/latest/cookbook/a11y.jade new file mode 100644 index 0000000000..e44128c6a2 --- /dev/null +++ b/public/docs/dart/latest/cookbook/a11y.jade @@ -0,0 +1,2 @@ +!= partial("../../../_includes/_ts-temp") + diff --git a/public/docs/js/latest/cookbook/_data.json b/public/docs/js/latest/cookbook/_data.json index f4f89de38c..6ce6585204 100644 --- a/public/docs/js/latest/cookbook/_data.json +++ b/public/docs/js/latest/cookbook/_data.json @@ -11,6 +11,12 @@ "intro": "Learn how Angular 1 concepts and techniques map to Angular 2" }, + "a11y": { + "title": "Accessibility and ARIA reference", + "navTitle": "Accessibility and ARIA", + "intro": "Learn how to make your Angular 2 sites accessible for everyone" + }, + "component-communication": { "title": "Component Interaction", "intro": "Share information between different directives and components" @@ -21,7 +27,7 @@ "intro": "Techniques for Dependency Injection" }, - "dynamic-form": { + "dynamic-forms": { "title": "Dynamic Form", "intro": "Render dynamic forms with NgFormModel" }, diff --git a/public/docs/js/latest/cookbook/a11y.jade b/public/docs/js/latest/cookbook/a11y.jade new file mode 100644 index 0000000000..6778b6af28 --- /dev/null +++ b/public/docs/js/latest/cookbook/a11y.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index 211e12bfd4..41b69fc89c 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -11,6 +11,12 @@ "intro": "Learn how Angular 1 concepts and techniques map to Angular 2" }, + "a11y": { + "title": "Accessibility and ARIA Reference", + "navTitle": "Accessibility and ARIA", + "intro": "Learn how to make your Angular 2 sites accessible for everyone" + }, + "component-communication": { "title": "Component Interaction", "intro": "Share information between different directives and components" diff --git a/public/docs/ts/latest/cookbook/a11y.jade b/public/docs/ts/latest/cookbook/a11y.jade new file mode 100644 index 0000000000..00d4a690b5 --- /dev/null +++ b/public/docs/ts/latest/cookbook/a11y.jade @@ -0,0 +1,801 @@ +include ../_util-fns + +:marked + Many people rely on assistive technologies to interact with the web, accessing visual + content via screen readers or braille displays. Some rely exclusively on the keyboard for + input, others adapt to motor impairment via pointing devices other than a mouse. + + This guide will show you how to design Angular components and applications that work well + for all users. + +:marked + **Follow along in this [live example](/resources/live-examples/cb-a11y/ts/plnkr.html)**. + + +.l-main-section + +:marked + ## Table of contents + + [Accessible form control labels](#form-control-labels) + + [Managing focus](#managing-focus) + + [Roles for custom component widgets](#component-roles) + + +.l-main-section + +:marked + ## Accessible form control labels + + Native HTML elements already have accessibility support via the browser. Because Angular extends + HTML and allows you to create your own elements, it's important to ensure that anything you add + also works in an accessible way. + +.l-sub-section + :marked + If there is already an HTML element that provides the function that you need, use that element + instead of writing your own. You'll get all the built-in browser support for focus management, + tabindex, etc. and have more time to think about your code. If you want to accept user text + input, use the `input` element instead of constructing something new. + +:marked + Because assistive technologies can't rely on the visual appearance of a form component, + any form component, or `form control` must be explicitly labeled to ensure that it's clear what + its purpose is. + + We will discuss some ways to do this. + + ### Implicit labeling + + The most direct way to assign an accessible label to a form control is by `implicit + labeling` with a `