From ec0baadcb68a4fa8da27d76b7e6a4e0840acd7fa Mon Sep 17 00:00:00 2001 From: mohamed amr Date: Sun, 28 Feb 2016 23:06:44 +0200 Subject: [PATCH] feat(ngAria): add support for aria-readonly based on ngReadonly Closes #14140 Closes #14077 --- docs/content/guide/accessibility.ngdoc | 23 +++++++++- src/ngAria/aria.js | 13 ++++-- test/ngAria/ariaSpec.js | 60 ++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/docs/content/guide/accessibility.ngdoc b/docs/content/guide/accessibility.ngdoc index 3df8e9c69718..cf6f66b59f4f 100644 --- a/docs/content/guide/accessibility.ngdoc +++ b/docs/content/guide/accessibility.ngdoc @@ -28,12 +28,13 @@ added it as a dependency, you can test a few things: * Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support. [Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/) -##Supported directives +## Supported directives Currently, ngAria interfaces with the following directives: * {@link guide/accessibility#ngmodel ngModel} * {@link guide/accessibility#ngdisabled ngDisabled} * {@link guide/accessibility#ngrequired ngRequired} + * {@link guide/accessibility#ngreadonly ngReadonly} * {@link guide/accessibility#ngvaluechecked ngChecked} * {@link guide/accessibility#ngvaluechecked ngValue} * {@link guide/accessibility#ngshow ngShow} @@ -57,6 +58,7 @@ attributes (if they have not been explicitly specified by the developer): * aria-valuenow * aria-invalid * aria-required + * aria-readonly ###Example @@ -203,6 +205,25 @@ Becomes: ``` +

ngReadonly

+ +The boolean `readonly` attribute is only valid for native form controls such as `input` and +`textarea`. To properly indicate custom element directives such as `` or `` +as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add +`aria-readonly`. This tells accessibility APIs when a custom control is read-only. + +###Example + +```html + +``` + +Becomes: + +```html + +``` +

ngShow

>The {@link ng.ngShow ngShow} directive shows or hides the diff --git a/src/ngAria/aria.js b/src/ngAria/aria.js index 400d951e2bc0..a06545a4646d 100644 --- a/src/ngAria/aria.js +++ b/src/ngAria/aria.js @@ -16,7 +16,7 @@ * * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following * directives are supported: - * `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, + * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, * `ngDblClick`, and `ngMessages`. * * Below is a more detailed breakdown of the attributes handled by ngAria: @@ -25,8 +25,9 @@ * |---------------------------------------------|----------------------------------------------------------------------------------------| * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | - * | {@link ng.directive:ngRequired ngRequired} | aria-required | - * | {@link ng.directive:ngChecked ngChecked} | aria-checked | + * | {@link ng.directive:ngRequired ngRequired} | aria-required + * | {@link ng.directive:ngChecked ngChecked} | aria-checked + * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly || * | {@link ng.directive:ngValue ngValue} | aria-checked | * | {@link ng.directive:ngShow ngShow} | aria-hidden | * | {@link ng.directive:ngHide ngHide} | aria-hidden | @@ -91,6 +92,7 @@ function $AriaProvider() { var config = { ariaHidden: true, ariaChecked: true, + ariaReadonly: true, ariaDisabled: true, ariaRequired: true, ariaInvalid: true, @@ -108,6 +110,7 @@ function $AriaProvider() { * * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags + * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags @@ -170,6 +173,7 @@ function $AriaProvider() { * The full list of directives that interface with ngAria: * * **ngModel** * * **ngChecked** + * * **ngReadonly** * * **ngRequired** * * **ngDisabled** * * **ngValue** @@ -209,6 +213,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) { .directive('ngChecked', ['$aria', function($aria) { return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); }]) +.directive('ngReadonly', ['$aria', function($aria) { + return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); +}]) .directive('ngRequired', ['$aria', function($aria) { return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); }]) diff --git a/test/ngAria/ariaSpec.js b/test/ngAria/ariaSpec.js index f287a4d42808..ccdbf11aa1d0 100644 --- a/test/ngAria/ariaSpec.js +++ b/test/ngAria/ariaSpec.js @@ -396,6 +396,66 @@ describe('$aria', function() { }); }); + describe('aria-readonly', function() { + beforeEach(injectScopeAndCompiler); + + they('should not attach itself to native $prop controls', { + input: '', + textarea: '', + select: '', + button: '' + }, function(tmpl) { + var element = $compile(tmpl)(scope); + scope.$apply('val = true'); + + expect(element.attr('readonly')).toBeDefined(); + expect(element.attr('aria-readonly')).toBeUndefined(); + }); + + it('should attach itself to custom controls', function() { + compileElement('
'); + expect(element.attr('aria-readonly')).toBe('false'); + + scope.$apply('val = true'); + expect(element.attr('aria-readonly')).toBe('true'); + + }); + + it('should not attach itself if an aria-readonly attribute is already present', function() { + compileElement('
'); + + expect(element.attr('aria-readonly')).toBe('userSetValue'); + }); + + it('should always set aria-readonly to a boolean value', function() { + compileElement('
'); + + scope.$apply('val = "test angular"'); + expect(element.attr('aria-readonly')).toBe('true'); + + scope.$apply('val = null'); + expect(element.attr('aria-readonly')).toBe('false'); + + scope.$apply('val = {}'); + expect(element.attr('aria-readonly')).toBe('true'); + }); + }); + + describe('aria-readonly when disabled', function() { + beforeEach(configAriaProvider({ + ariaReadonly: false + })); + beforeEach(injectScopeAndCompiler); + + it('should not add the aria-readonly attribute', function() { + compileElement(""); + expect(element.attr('aria-readonly')).toBeUndefined(); + + compileElement("
"); + expect(element.attr('aria-readonly')).toBeUndefined(); + }); + }); + describe('aria-required', function() { beforeEach(injectScopeAndCompiler);