16
16
*
17
17
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
18
18
* directives are supported:
19
- * `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
19
+ * `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
20
+ * `ngDblClick`, and `ngMessages`.
20
21
*
21
22
* Below is a more detailed breakdown of the attributes handled by ngAria:
22
23
*
23
24
* | Directive | Supported Attributes |
24
25
* |---------------------------------------------|----------------------------------------------------------------------------------------|
26
+ * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
25
27
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
28
+ * | {@link ng.directive:input ngRequired} | aria-required |
29
+ * | {@link ng.directive:ngChecked ngChecked} | aria-checked |
30
+ * | {@link ng.directive:ngValue ngValue} | aria-checked |
26
31
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
27
32
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
28
33
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
29
34
* | {@link module:ngMessages ngMessages} | aria-live |
30
- * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
31
- * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
35
+ * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
32
36
*
33
37
* Find out more information about each directive by reading the
34
38
* {@link guide/accessibility ngAria Developer Guide}.
@@ -90,7 +94,6 @@ function $AriaProvider() {
90
94
ariaDisabled : true ,
91
95
ariaRequired : true ,
92
96
ariaInvalid : true ,
93
- ariaMultiline : true ,
94
97
ariaValue : true ,
95
98
tabindex : true ,
96
99
bindKeypress : true ,
@@ -108,11 +111,10 @@ function $AriaProvider() {
108
111
* - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
109
112
* - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
110
113
* - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
111
- * - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
112
114
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
113
115
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
114
- * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `< div> ` and
115
- * `<li> ` elements with ng-click
116
+ * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `div` and
117
+ * `li ` elements with ng-click
116
118
* - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
117
119
* using ng-click, making them more accessible to users of assistive technologies
118
120
*
@@ -151,28 +153,31 @@ function $AriaProvider() {
151
153
*
152
154
*```js
153
155
* ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
154
- * return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
156
+ * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false );
155
157
* }])
156
158
*```
157
159
* Shown above, the ngAria module creates a directive with the same signature as the
158
160
* traditional `ng-disabled` directive. But this ngAria version is dedicated to
159
- * solely managing accessibility attributes. The internal `$aria` service is used to watch the
160
- * boolean attribute `ngDisabled`. If it has not been explicitly set by the developer,
161
- * `aria-disabled` is injected as an attribute with its value synchronized to the value in
162
- * `ngDisabled`.
161
+ * solely managing accessibility attributes on custom elements . The internal `$aria` service is
162
+ * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the
163
+ * developer, `aria-disabled` is injected as an attribute with its value synchronized to the
164
+ * value in `ngDisabled`.
163
165
*
164
166
* Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
165
167
* anything to enable this feature. The `aria-disabled` attribute is automatically managed
166
168
* simply as a silent side-effect of using `ng-disabled` with the ngAria module.
167
169
*
168
170
* The full list of directives that interface with ngAria:
169
171
* * **ngModel**
172
+ * * **ngChecked**
173
+ * * **ngRequired**
174
+ * * **ngDisabled**
175
+ * * **ngValue**
170
176
* * **ngShow**
171
177
* * **ngHide**
172
178
* * **ngClick**
173
179
* * **ngDblclick**
174
180
* * **ngMessages**
175
- * * **ngDisabled**
176
181
*
177
182
* Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
178
183
* directive.
@@ -198,13 +203,25 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
198
203
. directive ( 'ngHide' , [ '$aria' , function ( $aria ) {
199
204
return $aria . $$watchExpr ( 'ngHide' , 'aria-hidden' , [ ] , false ) ;
200
205
} ] )
201
- . directive ( 'ngModel' , [ '$aria' , function ( $aria ) {
206
+ . directive ( 'ngValue' , [ '$aria' , function ( $aria ) {
207
+ return $aria . $$watchExpr ( 'ngValue' , 'aria-checked' , nodeBlackList , false ) ;
208
+ } ] )
209
+ . directive ( 'ngChecked' , [ '$aria' , function ( $aria ) {
210
+ return $aria . $$watchExpr ( 'ngChecked' , 'aria-checked' , nodeBlackList , false ) ;
211
+ } ] )
212
+ . directive ( 'ngRequired' , [ '$aria' , function ( $aria ) {
213
+ return $aria . $$watchExpr ( 'ngRequired' , 'aria-required' , nodeBlackList , false ) ;
214
+ } ] )
215
+ . directive ( 'ngModel' , [ '$aria' , '$parse' , function ( $aria , $parse ) {
202
216
203
- function shouldAttachAttr ( attr , normalizedAttr , elem ) {
204
- return $aria . config ( normalizedAttr ) && ! elem . attr ( attr ) ;
217
+ function shouldAttachAttr ( attr , normalizedAttr , elem , allowBlacklistEls ) {
218
+ return $aria . config ( normalizedAttr ) && ! elem . attr ( attr ) && ( allowBlacklistEls || ! isNodeOneOf ( elem , nodeBlackList ) ) ;
205
219
}
206
220
207
221
function shouldAttachRole ( role , elem ) {
222
+ // if element does not have role attribute
223
+ // AND element type is equal to role (if custom element has a type equaling shape) <-- remove?
224
+ // AND element is not INPUT
208
225
return ! elem . attr ( 'role' ) && ( elem . attr ( 'type' ) === role ) && ( elem [ 0 ] . nodeName !== 'INPUT' ) ;
209
226
}
210
227
@@ -214,8 +231,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
214
231
215
232
return ( ( type || role ) === 'checkbox' || role === 'menuitemcheckbox' ) ? 'checkbox' :
216
233
( ( type || role ) === 'radio' || role === 'menuitemradio' ) ? 'radio' :
217
- ( type === 'range' || role === 'progressbar' || role === 'slider' ) ? 'range' :
218
- ( type || role ) === 'textbox' || elem [ 0 ] . nodeName === 'TEXTAREA' ? 'multiline' : '' ;
234
+ ( type === 'range' || role === 'progressbar' || role === 'slider' ) ? 'range' : '' ;
219
235
}
220
236
221
237
return {
@@ -227,38 +243,32 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
227
243
228
244
return {
229
245
pre : function ( scope , elem , attr , ngModel ) {
230
- if ( shape === 'checkbox' && attr . type !== 'checkbox' ) {
246
+ if ( shape === 'checkbox' ) {
231
247
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
232
248
ngModel . $isEmpty = function ( value ) {
233
249
return value === false ;
234
250
} ;
235
251
}
236
252
} ,
237
253
post : function ( scope , elem , attr , ngModel ) {
238
- var needsTabIndex = shouldAttachAttr ( 'tabindex' , 'tabindex' , elem )
239
- && ! isNodeOneOf ( elem , nodeBlackList ) ;
254
+ var needsTabIndex = shouldAttachAttr ( 'tabindex' , 'tabindex' , elem , false ) ;
255
+ var ngTrueValue = $parse ( attr . ngTrueValue ) ( scope ) ;
240
256
241
257
function ngAriaWatchModelValue ( ) {
242
258
return ngModel . $modelValue ;
243
259
}
244
260
245
261
function getRadioReaction ( ) {
246
- if ( needsTabIndex ) {
247
- needsTabIndex = false ;
248
- return function ngAriaRadioReaction ( newVal ) {
249
- var boolVal = ( attr . value == ngModel . $viewValue ) ;
250
- elem . attr ( 'aria-checked' , boolVal ) ;
251
- elem . attr ( 'tabindex' , 0 - ! boolVal ) ;
252
- } ;
253
- } else {
254
- return function ngAriaRadioReaction ( newVal ) {
255
- elem . attr ( 'aria-checked' , ( attr . value == ngModel . $viewValue ) ) ;
256
- } ;
257
- }
262
+ return function ngAriaRadioReaction ( newVal ) {
263
+ var boolVal = ( attr . value == ngModel . $viewValue ) ;
264
+ elem . attr ( 'aria-checked' , boolVal ) ;
265
+ } ;
258
266
}
259
-
260
- function ngAriaCheckboxReaction ( ) {
261
- elem . attr ( 'aria-checked' , ! ngModel . $isEmpty ( ngModel . $viewValue ) ) ;
267
+ function getCheckboxReaction ( ) {
268
+ return function ngAriaCheckboxReaction ( newVal ) {
269
+ var modelValue = attr . ngTrueValue ? ngModel . $viewValue == ngTrueValue : newVal ;
270
+ elem . attr ( 'aria-checked' , modelValue ) ;
271
+ } ;
262
272
}
263
273
264
274
switch ( shape ) {
@@ -267,9 +277,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
267
277
if ( shouldAttachRole ( shape , elem ) ) {
268
278
elem . attr ( 'role' , shape ) ;
269
279
}
270
- if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem ) ) {
280
+ if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem , false ) ) {
271
281
scope . $watch ( ngAriaWatchModelValue , shape === 'radio' ?
272
- getRadioReaction ( ) : ngAriaCheckboxReaction ) ;
282
+ getRadioReaction ( ) : getCheckboxReaction ( ) ) ;
273
283
}
274
284
if ( needsTabIndex ) {
275
285
elem . attr ( 'tabindex' , 0 ) ;
@@ -306,22 +316,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
306
316
elem . attr ( 'tabindex' , 0 ) ;
307
317
}
308
318
break ;
309
- case 'multiline' :
310
- if ( shouldAttachAttr ( 'aria-multiline' , 'ariaMultiline' , elem ) ) {
311
- elem . attr ( 'aria-multiline' , true ) ;
312
- }
313
- break ;
314
319
}
315
320
316
- if ( ngModel . $validators . required && shouldAttachAttr ( 'aria-required' , 'ariaRequired' , elem ) ) {
317
- scope . $watch ( function ngAriaRequiredWatch ( ) {
318
- return ngModel . $error . required ;
319
- } , function ngAriaRequiredReaction ( newVal ) {
320
- elem . attr ( 'aria-required' , ! ! newVal ) ;
321
+ if ( ! attr . hasOwnProperty ( 'ngRequired' ) && ngModel . $validators . required
322
+ && shouldAttachAttr ( 'aria-required' , 'ariaRequired' , elem , false ) ) {
323
+ // ngModel.$error.required is undefined on custom controls
324
+ scope . $watch ( attr [ 'required' ] , function ngAriaRequiredWatch ( ) {
325
+ elem . attr ( 'aria-required' , ! ! attr [ 'required' ] ) ;
321
326
} ) ;
322
327
}
323
328
324
- if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem ) ) {
329
+ if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem , true ) ) {
325
330
scope . $watch ( function ngAriaInvalidWatch ( ) {
326
331
return ngModel . $invalid ;
327
332
} , function ngAriaInvalidReaction ( newVal ) {
@@ -334,7 +339,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
334
339
} ;
335
340
} ] )
336
341
. directive ( 'ngDisabled' , [ '$aria' , function ( $aria ) {
337
- return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , [ ] ) ;
342
+ return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , nodeBlackList , false ) ;
338
343
} ] )
339
344
. directive ( 'ngMessages' , function ( ) {
340
345
return {
0 commit comments