Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit ec0baad

Browse files
m-amrNarretz
authored andcommitted
feat(ngAria): add support for aria-readonly based on ngReadonly
Closes #14140 Closes #14077
1 parent 9147d5c commit ec0baad

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

docs/content/guide/accessibility.ngdoc

+22-1
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ added it as a dependency, you can test a few things:
2828
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
2929
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
3030

31-
##Supported directives
31+
## Supported directives
3232
Currently, ngAria interfaces with the following directives:
3333

3434
* {@link guide/accessibility#ngmodel ngModel}
3535
* {@link guide/accessibility#ngdisabled ngDisabled}
3636
* {@link guide/accessibility#ngrequired ngRequired}
37+
* {@link guide/accessibility#ngreadonly ngReadonly}
3738
* {@link guide/accessibility#ngvaluechecked ngChecked}
3839
* {@link guide/accessibility#ngvaluechecked ngValue}
3940
* {@link guide/accessibility#ngshow ngShow}
@@ -57,6 +58,7 @@ attributes (if they have not been explicitly specified by the developer):
5758
* aria-valuenow
5859
* aria-invalid
5960
* aria-required
61+
* aria-readonly
6062

6163
###Example
6264

@@ -203,6 +205,25 @@ Becomes:
203205
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
204206
```
205207

208+
<h2 id="ngreadonly">ngReadonly</h2>
209+
210+
The boolean `readonly` attribute is only valid for native form controls such as `input` and
211+
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
212+
as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add
213+
`aria-readonly`. This tells accessibility APIs when a custom control is read-only.
214+
215+
###Example
216+
217+
```html
218+
<md-checkbox ng-readonly="val"></md-checkbox>
219+
```
220+
221+
Becomes:
222+
223+
```html
224+
<md-checkbox ng-readonly="val" aria-readonly="true"></md-checkbox>
225+
```
226+
206227
<h2 id="ngshow">ngShow</h2>
207228

208229
>The {@link ng.ngShow ngShow} directive shows or hides the

src/ngAria/aria.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
1818
* directives are supported:
19-
* `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
19+
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
2020
* `ngDblClick`, and `ngMessages`.
2121
*
2222
* Below is a more detailed breakdown of the attributes handled by ngAria:
@@ -25,8 +25,9 @@
2525
* |---------------------------------------------|----------------------------------------------------------------------------------------|
2626
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
2727
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
28-
* | {@link ng.directive:ngRequired ngRequired} | aria-required |
29-
* | {@link ng.directive:ngChecked ngChecked} | aria-checked |
28+
* | {@link ng.directive:ngRequired ngRequired} | aria-required
29+
* | {@link ng.directive:ngChecked ngChecked} | aria-checked
30+
* | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly ||
3031
* | {@link ng.directive:ngValue ngValue} | aria-checked |
3132
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
3233
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
@@ -91,6 +92,7 @@ function $AriaProvider() {
9192
var config = {
9293
ariaHidden: true,
9394
ariaChecked: true,
95+
ariaReadonly: true,
9496
ariaDisabled: true,
9597
ariaRequired: true,
9698
ariaInvalid: true,
@@ -108,6 +110,7 @@ function $AriaProvider() {
108110
*
109111
* - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
110112
* - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
113+
* - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags
111114
* - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
112115
* - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
113116
* - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
@@ -170,6 +173,7 @@ function $AriaProvider() {
170173
* The full list of directives that interface with ngAria:
171174
* * **ngModel**
172175
* * **ngChecked**
176+
* * **ngReadonly**
173177
* * **ngRequired**
174178
* * **ngDisabled**
175179
* * **ngValue**
@@ -209,6 +213,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
209213
.directive('ngChecked', ['$aria', function($aria) {
210214
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
211215
}])
216+
.directive('ngReadonly', ['$aria', function($aria) {
217+
return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false);
218+
}])
212219
.directive('ngRequired', ['$aria', function($aria) {
213220
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
214221
}])

test/ngAria/ariaSpec.js

+60
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,66 @@ describe('$aria', function() {
396396
});
397397
});
398398

399+
describe('aria-readonly', function() {
400+
beforeEach(injectScopeAndCompiler);
401+
402+
they('should not attach itself to native $prop controls', {
403+
input: '<input ng-readonly="val">',
404+
textarea: '<textarea ng-readonly="val"></textarea>',
405+
select: '<select ng-readonly="val"></select>',
406+
button: '<button ng-readonly="val"></button>'
407+
}, function(tmpl) {
408+
var element = $compile(tmpl)(scope);
409+
scope.$apply('val = true');
410+
411+
expect(element.attr('readonly')).toBeDefined();
412+
expect(element.attr('aria-readonly')).toBeUndefined();
413+
});
414+
415+
it('should attach itself to custom controls', function() {
416+
compileElement('<div ng-readonly="val"></div>');
417+
expect(element.attr('aria-readonly')).toBe('false');
418+
419+
scope.$apply('val = true');
420+
expect(element.attr('aria-readonly')).toBe('true');
421+
422+
});
423+
424+
it('should not attach itself if an aria-readonly attribute is already present', function() {
425+
compileElement('<div ng-readonly="val" aria-readonly="userSetValue"></div>');
426+
427+
expect(element.attr('aria-readonly')).toBe('userSetValue');
428+
});
429+
430+
it('should always set aria-readonly to a boolean value', function() {
431+
compileElement('<div ng-readonly="val"></div>');
432+
433+
scope.$apply('val = "test angular"');
434+
expect(element.attr('aria-readonly')).toBe('true');
435+
436+
scope.$apply('val = null');
437+
expect(element.attr('aria-readonly')).toBe('false');
438+
439+
scope.$apply('val = {}');
440+
expect(element.attr('aria-readonly')).toBe('true');
441+
});
442+
});
443+
444+
describe('aria-readonly when disabled', function() {
445+
beforeEach(configAriaProvider({
446+
ariaReadonly: false
447+
}));
448+
beforeEach(injectScopeAndCompiler);
449+
450+
it('should not add the aria-readonly attribute', function() {
451+
compileElement("<input ng-model='val' readonly>");
452+
expect(element.attr('aria-readonly')).toBeUndefined();
453+
454+
compileElement("<div ng-model='val' ng-readonly='true'></div>");
455+
expect(element.attr('aria-readonly')).toBeUndefined();
456+
});
457+
});
458+
399459
describe('aria-required', function() {
400460
beforeEach(injectScopeAndCompiler);
401461

0 commit comments

Comments
 (0)