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:ngRequired 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,37 +243,26 @@ 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 ) ;
240255
241256 function ngAriaWatchModelValue ( ) {
242257 return ngModel . $modelValue ;
243258 }
244259
245- 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- }
260+ function getRadioReaction ( newVal ) {
261+ var boolVal = ( attr . value == ngModel . $viewValue ) ;
262+ elem . attr ( 'aria-checked' , boolVal ) ;
258263 }
259264
260- function ngAriaCheckboxReaction ( ) {
265+ function getCheckboxReaction ( ) {
261266 elem . attr ( 'aria-checked' , ! ngModel . $isEmpty ( ngModel . $viewValue ) ) ;
262267 }
263268
@@ -267,9 +272,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
267272 if ( shouldAttachRole ( shape , elem ) ) {
268273 elem . attr ( 'role' , shape ) ;
269274 }
270- if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem ) ) {
275+ if ( shouldAttachAttr ( 'aria-checked' , 'ariaChecked' , elem , false ) ) {
271276 scope . $watch ( ngAriaWatchModelValue , shape === 'radio' ?
272- getRadioReaction ( ) : ngAriaCheckboxReaction ) ;
277+ getRadioReaction : getCheckboxReaction ) ;
273278 }
274279 if ( needsTabIndex ) {
275280 elem . attr ( 'tabindex' , 0 ) ;
@@ -306,22 +311,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
306311 elem . attr ( 'tabindex' , 0 ) ;
307312 }
308313 break ;
309- case 'multiline' :
310- if ( shouldAttachAttr ( 'aria-multiline' , 'ariaMultiline' , elem ) ) {
311- elem . attr ( 'aria-multiline' , true ) ;
312- }
313- break ;
314314 }
315315
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 ) ;
316+ if ( ! attr . hasOwnProperty ( 'ngRequired' ) && ngModel . $validators . required
317+ && shouldAttachAttr ( 'aria-required' , 'ariaRequired' , elem , false ) ) {
318+ // ngModel.$error.required is undefined on custom controls
319+ attr . $observe ( 'required' , function ( ) {
320+ elem . attr ( 'aria-required' , ! ! attr [ 'required' ] ) ;
321321 } ) ;
322322 }
323323
324- if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem ) ) {
324+ if ( shouldAttachAttr ( 'aria-invalid' , 'ariaInvalid' , elem , true ) ) {
325325 scope . $watch ( function ngAriaInvalidWatch ( ) {
326326 return ngModel . $invalid ;
327327 } , function ngAriaInvalidReaction ( newVal ) {
@@ -334,7 +334,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
334334 } ;
335335} ] )
336336. directive ( 'ngDisabled' , [ '$aria' , function ( $aria ) {
337- return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , [ ] ) ;
337+ return $aria . $$watchExpr ( 'ngDisabled' , 'aria-disabled' , nodeBlackList , false ) ;
338338} ] )
339339. directive ( 'ngMessages' , function ( ) {
340340 return {
0 commit comments