1616 *
1717 * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
1818 * 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`.
2021 *
2122 * Below is a more detailed breakdown of the attributes handled by ngAria:
2223 *
2324 * | Directive | Supported Attributes |
2425 * |---------------------------------------------|----------------------------------------------------------------------------------------|
26+ * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
2527 * | {@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 |
2631 * | {@link ng.directive:ngShow ngShow} | aria-hidden |
2732 * | {@link ng.directive:ngHide ngHide} | aria-hidden |
2833 * | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
2934 * | {@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 |
3236 *
3337 * Find out more information about each directive by reading the
3438 * {@link guide/accessibility ngAria Developer Guide}.
@@ -90,7 +94,6 @@ function $AriaProvider() {
9094 ariaDisabled : true ,
9195 ariaRequired : true ,
9296 ariaInvalid : true ,
93- ariaMultiline : true ,
9497 ariaValue : true ,
9598 tabindex : true ,
9699 bindKeypress : true ,
@@ -108,11 +111,10 @@ function $AriaProvider() {
108111 * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
109112 * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
110113 * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
111- * - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
112114 * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
113115 * - **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
116118 * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
117119 * using ng-click, making them more accessible to users of assistive technologies
118120 *
@@ -151,28 +153,31 @@ function $AriaProvider() {
151153 *
152154 *```js
153155 * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
154- * return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
156+ * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false );
155157 * }])
156158 *```
157159 * Shown above, the ngAria module creates a directive with the same signature as the
158160 * 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`.
163165 *
164166 * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
165167 * anything to enable this feature. The `aria-disabled` attribute is automatically managed
166168 * simply as a silent side-effect of using `ng-disabled` with the ngAria module.
167169 *
168170 * The full list of directives that interface with ngAria:
169171 * * **ngModel**
172+ * * **ngChecked**
173+ * * **ngRequired**
174+ * * **ngDisabled**
175+ * * **ngValue**
170176 * * **ngShow**
171177 * * **ngHide**
172178 * * **ngClick**
173179 * * **ngDblclick**
174180 * * **ngMessages**
175- * * **ngDisabled**
176181 *
177182 * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
178183 * directive.
@@ -198,13 +203,25 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
198203. directive ( 'ngHide' , [ '$aria' , function ( $aria ) {
199204 return $aria . $$watchExpr ( 'ngHide' , 'aria-hidden' , [ ] , false ) ;
200205} ] )
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 ) {
202216
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 ) ) ;
205219 }
206220
207221 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
208225 return ! elem . attr ( 'role' ) && ( elem . attr ( 'type' ) === role ) && ( elem [ 0 ] . nodeName !== 'INPUT' ) ;
209226 }
210227
@@ -214,8 +231,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
214231
215232 return ( ( type || role ) === 'checkbox' || role === 'menuitemcheckbox' ) ? 'checkbox' :
216233 ( ( 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' : '' ;
219235 }
220236
221237 return {
@@ -227,38 +243,32 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
227243
228244 return {
229245 pre : function ( scope , elem , attr , ngModel ) {
230- if ( shape === 'checkbox' && attr . type !== 'checkbox' ) {
246+ if ( shape === 'checkbox' ) {
231247 //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
232248 ngModel . $isEmpty = function ( value ) {
233249 return value === false ;
234250 } ;
235251 }
236252 } ,
237253 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 ) ;
240256
241257 function ngAriaWatchModelValue ( ) {
242258 return ngModel . $modelValue ;
243259 }
244260
245261 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+ } ;
258266 }
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+ } ;
262272 }
263273
264274 switch ( shape ) {
@@ -267,9 +277,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
267277 if ( shouldAttachRole ( shape , elem ) ) {
268278 elem . attr ( 'role' , shape ) ;
269279 }
270- if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem ) ) {
280+ if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem , false ) ) {
271281 scope . $watch ( ngAriaWatchModelValue , shape === 'radio' ?
272- getRadioReaction ( ) : ngAriaCheckboxReaction ) ;
282+ getRadioReaction ( ) : getCheckboxReaction ( ) ) ;
273283 }
274284 if ( needsTabIndex ) {
275285 elem . attr ( 'tabindex' , 0 ) ;
@@ -306,22 +316,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
306316 elem . attr ( 'tabindex' , 0 ) ;
307317 }
308318 break ;
309- case 'multiline' :
310- if ( shouldAttachAttr ( 'aria-multiline' , 'ariaMultiline' , elem ) ) {
311- elem . attr ( 'aria-multiline' , true ) ;
312- }
313- break ;
314319 }
315320
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' ] ) ;
321326 } ) ;
322327 }
323328
324- if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem ) ) {
329+ if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem , true ) ) {
325330 scope . $watch ( function ngAriaInvalidWatch ( ) {
326331 return ngModel . $invalid ;
327332 } , function ngAriaInvalidReaction ( newVal ) {
@@ -334,7 +339,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
334339 } ;
335340} ] )
336341. directive ( 'ngDisabled' , [ '$aria' , function ( $aria ) {
337- return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , [ ] ) ;
342+ return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , nodeBlackList , false ) ;
338343} ] )
339344. directive ( 'ngMessages' , function ( ) {
340345 return {
0 commit comments