1
1
/**
2
- * @license AngularJS v1.3.13
3
- * (c) 2010-2014 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.4.3
3
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
4
4
* License: MIT
5
5
*/
6
6
( function ( window , angular , undefined ) { 'use strict' ;
19
19
*
20
20
* ## Usage
21
21
*
22
- * For ngAria to do its magic, simply include the module as a dependency. The directives supported
23
- * by ngAria are:
22
+ * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
23
+ * directives are supported :
24
24
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
25
25
*
26
26
* Below is a more detailed breakdown of the attributes handled by ngAria:
27
27
*
28
28
* | Directive | Supported Attributes |
29
29
* |---------------------------------------------|----------------------------------------------------------------------------------------|
30
- * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required |
31
30
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
32
31
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
33
32
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
34
- * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event |
35
33
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
36
34
* | {@link module:ngMessages ngMessages} | aria-live |
35
+ * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
36
+ * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
37
37
*
38
38
* Find out more information about each directive by reading the
39
39
* {@link guide/accessibility ngAria Developer Guide}.
@@ -88,7 +88,8 @@ function $AriaProvider() {
88
88
ariaMultiline : true ,
89
89
ariaValue : true ,
90
90
tabindex : true ,
91
- bindKeypress : true
91
+ bindKeypress : true ,
92
+ bindRoleForClick : true
92
93
} ;
93
94
94
95
/**
@@ -107,6 +108,8 @@ function $AriaProvider() {
107
108
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
108
109
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and
109
110
* `<li>` elements with ng-click
111
+ * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
112
+ * using ng-click, making them more accessible to users of assistive technologies
110
113
*
111
114
* @description
112
115
* Enables/disables various ARIA attributes
@@ -120,9 +123,8 @@ function $AriaProvider() {
120
123
var ariaCamelName = attr . $normalize ( ariaAttr ) ;
121
124
if ( config [ ariaCamelName ] && ! attr [ ariaCamelName ] ) {
122
125
scope . $watch ( attr [ attrName ] , function ( boolVal ) {
123
- if ( negate ) {
124
- boolVal = ! boolVal ;
125
- }
126
+ // ensure boolean value
127
+ boolVal = negate ? ! boolVal : ! ! boolVal ;
126
128
elem . attr ( ariaAttr , boolVal ) ;
127
129
} ) ;
128
130
}
@@ -134,6 +136,7 @@ function $AriaProvider() {
134
136
* @name $aria
135
137
*
136
138
* @description
139
+ * @priority 200
137
140
*
138
141
* The $aria service contains helper methods for applying common
139
142
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
@@ -197,6 +200,10 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
197
200
return $aria . config ( normalizedAttr ) && ! elem . attr ( attr ) ;
198
201
}
199
202
203
+ function shouldAttachRole ( role , elem ) {
204
+ return ! elem . attr ( 'role' ) && ( elem . attr ( 'type' ) === role ) && ( elem [ 0 ] . nodeName !== 'INPUT' ) ;
205
+ }
206
+
200
207
function getShape ( attr , elem ) {
201
208
var type = attr . type ,
202
209
role = attr . role ;
@@ -210,82 +217,112 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
210
217
return {
211
218
restrict : 'A' ,
212
219
require : '?ngModel' ,
213
- link : function ( scope , elem , attr , ngModel ) {
220
+ priority : 200 , //Make sure watches are fired after any other directives that affect the ngModel value
221
+ compile : function ( elem , attr ) {
214
222
var shape = getShape ( attr , elem ) ;
215
- var needsTabIndex = shouldAttachAttr ( 'tabindex' , 'tabindex' , elem ) ;
216
-
217
- function ngAriaWatchModelValue ( ) {
218
- return ngModel . $modelValue ;
219
- }
220
-
221
- function getRadioReaction ( ) {
222
- if ( needsTabIndex ) {
223
- needsTabIndex = false ;
224
- return function ngAriaRadioReaction ( newVal ) {
225
- var boolVal = newVal === attr . value ;
226
- elem . attr ( 'aria-checked' , boolVal ) ;
227
- elem . attr ( 'tabindex' , 0 - ! boolVal ) ;
228
- } ;
229
- } else {
230
- return function ngAriaRadioReaction ( newVal ) {
231
- elem . attr ( 'aria-checked' , newVal === attr . value ) ;
232
- } ;
233
- }
234
- }
235
223
236
- function ngAriaCheckboxReaction ( newVal ) {
237
- elem . attr ( 'aria-checked' , ! ! newVal ) ;
238
- }
224
+ return {
225
+ pre : function ( scope , elem , attr , ngModel ) {
226
+ if ( shape === 'checkbox' && attr . type !== 'checkbox' ) {
227
+ //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
228
+ ngModel . $isEmpty = function ( value ) {
229
+ return value === false ;
230
+ } ;
231
+ }
232
+ } ,
233
+ post : function ( scope , elem , attr , ngModel ) {
234
+ var needsTabIndex = shouldAttachAttr ( 'tabindex' , 'tabindex' , elem ) ;
239
235
240
- switch ( shape ) {
241
- case 'radio' :
242
- case 'checkbox' :
243
- if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem ) ) {
244
- scope . $watch ( ngAriaWatchModelValue , shape === 'radio' ?
245
- getRadioReaction ( ) : ngAriaCheckboxReaction ) ;
236
+ function ngAriaWatchModelValue ( ) {
237
+ return ngModel . $modelValue ;
246
238
}
247
- break ;
248
- case 'range' :
249
- if ( $aria . config ( 'ariaValue' ) ) {
250
- if ( attr . min && ! elem . attr ( 'aria-valuemin' ) ) {
251
- elem . attr ( 'aria-valuemin' , attr . min ) ;
252
- }
253
- if ( attr . max && ! elem . attr ( 'aria-valuemax' ) ) {
254
- elem . attr ( 'aria-valuemax ' , attr . max ) ;
255
- }
256
- if ( ! elem . attr ( 'aria-valuenow' ) ) {
257
- scope . $watch ( ngAriaWatchModelValue , function ngAriaValueNowReaction ( newVal ) {
258
- elem . attr ( 'aria-valuenow ' , newVal ) ;
259
- } ) ;
239
+
240
+ function getRadioReaction ( ) {
241
+ if ( needsTabIndex ) {
242
+ needsTabIndex = false ;
243
+ return function ngAriaRadioReaction ( newVal ) {
244
+ var boolVal = ( attr . value == ngModel . $viewValue ) ;
245
+ elem . attr ( 'aria-checked' , boolVal ) ;
246
+ elem . attr ( 'tabindex ' , 0 - ! boolVal ) ;
247
+ } ;
248
+ } else {
249
+ return function ngAriaRadioReaction ( newVal ) {
250
+ elem . attr ( 'aria-checked ' , ( attr . value == ngModel . $viewValue ) ) ;
251
+ } ;
260
252
}
261
253
}
262
- break ;
263
- case 'multiline' :
264
- if ( shouldAttachAttr ( 'aria-multiline' , 'ariaMultiline' , elem ) ) {
265
- elem . attr ( 'aria-multiline' , true ) ;
254
+
255
+ function ngAriaCheckboxReaction ( ) {
256
+ elem . attr ( 'aria-checked' , ! ngModel . $isEmpty ( ngModel . $viewValue ) ) ;
266
257
}
267
- break ;
268
- }
269
258
270
- if ( needsTabIndex ) {
271
- elem . attr ( 'tabindex' , 0 ) ;
272
- }
259
+ switch ( shape ) {
260
+ case 'radio' :
261
+ case 'checkbox' :
262
+ if ( shouldAttachRole ( shape , elem ) ) {
263
+ elem . attr ( 'role' , shape ) ;
264
+ }
265
+ if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem ) ) {
266
+ scope . $watch ( ngAriaWatchModelValue , shape === 'radio' ?
267
+ getRadioReaction ( ) : ngAriaCheckboxReaction ) ;
268
+ }
269
+ break ;
270
+ case 'range' :
271
+ if ( shouldAttachRole ( shape , elem ) ) {
272
+ elem . attr ( 'role' , 'slider' ) ;
273
+ }
274
+ if ( $aria . config ( 'ariaValue' ) ) {
275
+ var needsAriaValuemin = ! elem . attr ( 'aria-valuemin' ) &&
276
+ ( attr . hasOwnProperty ( 'min' ) || attr . hasOwnProperty ( 'ngMin' ) ) ;
277
+ var needsAriaValuemax = ! elem . attr ( 'aria-valuemax' ) &&
278
+ ( attr . hasOwnProperty ( 'max' ) || attr . hasOwnProperty ( 'ngMax' ) ) ;
279
+ var needsAriaValuenow = ! elem . attr ( 'aria-valuenow' ) ;
273
280
274
- if ( ngModel . $validators . required && shouldAttachAttr ( 'aria-required' , 'ariaRequired' , elem ) ) {
275
- scope . $watch ( function ngAriaRequiredWatch ( ) {
276
- return ngModel . $error . required ;
277
- } , function ngAriaRequiredReaction ( newVal ) {
278
- elem . attr ( 'aria-required' , ! ! newVal ) ;
279
- } ) ;
280
- }
281
+ if ( needsAriaValuemin ) {
282
+ attr . $observe ( 'min' , function ngAriaValueMinReaction ( newVal ) {
283
+ elem . attr ( 'aria-valuemin' , newVal ) ;
284
+ } ) ;
285
+ }
286
+ if ( needsAriaValuemax ) {
287
+ attr . $observe ( 'max' , function ngAriaValueMinReaction ( newVal ) {
288
+ elem . attr ( 'aria-valuemax' , newVal ) ;
289
+ } ) ;
290
+ }
291
+ if ( needsAriaValuenow ) {
292
+ scope . $watch ( ngAriaWatchModelValue , function ngAriaValueNowReaction ( newVal ) {
293
+ elem . attr ( 'aria-valuenow' , newVal ) ;
294
+ } ) ;
295
+ }
296
+ }
297
+ break ;
298
+ case 'multiline' :
299
+ if ( shouldAttachAttr ( 'aria-multiline' , 'ariaMultiline' , elem ) ) {
300
+ elem . attr ( 'aria-multiline' , true ) ;
301
+ }
302
+ break ;
303
+ }
281
304
282
- if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem ) ) {
283
- scope . $watch ( function ngAriaInvalidWatch ( ) {
284
- return ngModel . $invalid ;
285
- } , function ngAriaInvalidReaction ( newVal ) {
286
- elem . attr ( 'aria-invalid' , ! ! newVal ) ;
287
- } ) ;
288
- }
305
+ if ( needsTabIndex ) {
306
+ elem . attr ( 'tabindex' , 0 ) ;
307
+ }
308
+
309
+ if ( ngModel . $validators . required && shouldAttachAttr ( 'aria-required' , 'ariaRequired' , elem ) ) {
310
+ scope . $watch ( function ngAriaRequiredWatch ( ) {
311
+ return ngModel . $error . required ;
312
+ } , function ngAriaRequiredReaction ( newVal ) {
313
+ elem . attr ( 'aria-required' , ! ! newVal ) ;
314
+ } ) ;
315
+ }
316
+
317
+ if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem ) ) {
318
+ scope . $watch ( function ngAriaInvalidWatch ( ) {
319
+ return ngModel . $invalid ;
320
+ } , function ngAriaInvalidReaction ( newVal ) {
321
+ elem . attr ( 'aria-invalid' , ! ! newVal ) ;
322
+ } ) ;
323
+ }
324
+ }
325
+ } ;
289
326
}
290
327
} ;
291
328
} ] )
@@ -310,19 +347,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
310
347
var fn = $parse ( attr . ngClick , /* interceptorFn */ null , /* expensiveChecks */ true ) ;
311
348
return function ( scope , elem , attr ) {
312
349
350
+ var nodeBlackList = [ 'BUTTON' , 'A' , 'INPUT' , 'TEXTAREA' ] ;
351
+
313
352
function isNodeOneOf ( elem , nodeTypeArray ) {
314
353
if ( nodeTypeArray . indexOf ( elem [ 0 ] . nodeName ) !== - 1 ) {
315
354
return true ;
316
355
}
317
356
}
318
357
358
+ if ( $aria . config ( 'bindRoleForClick' )
359
+ && ! elem . attr ( 'role' )
360
+ && ! isNodeOneOf ( elem , nodeBlackList ) ) {
361
+ elem . attr ( 'role' , 'button' ) ;
362
+ }
363
+
319
364
if ( $aria . config ( 'tabindex' ) && ! elem . attr ( 'tabindex' ) ) {
320
365
elem . attr ( 'tabindex' , 0 ) ;
321
366
}
322
367
323
- if ( $aria . config ( 'bindKeypress' ) && ! attr . ngKeypress && isNodeOneOf ( elem , [ 'DIV' , 'LI' ] ) ) {
368
+ if ( $aria . config ( 'bindKeypress' ) && ! attr . ngKeypress && ! isNodeOneOf ( elem , nodeBlackList ) ) {
324
369
elem . on ( 'keypress' , function ( event ) {
325
- if ( event . keyCode === 32 || event . keyCode === 13 ) {
370
+ var keyCode = event . which || event . keyCode ;
371
+ if ( keyCode === 32 || keyCode === 13 ) {
326
372
scope . $apply ( callback ) ;
327
373
}
328
374
0 commit comments