Skip to content

Commit a777616

Browse files
committed
fix(ngAria): apply to custom inputs only
Apply ARIA attrs and tabindex more selectively Closes angular#11500
1 parent 8914f8e commit a777616

File tree

2 files changed

+156
-168
lines changed

2 files changed

+156
-168
lines changed

Diff for: src/ngAria/aria.js

+53-41
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ function $AriaProvider() {
110110
config = angular.extend(config, newConfig);
111111
};
112112

113-
function watchExpr(attrName, ariaAttr, negate) {
113+
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
114114
return function(scope, elem, attr) {
115115
var ariaCamelName = attr.$normalize(ariaAttr);
116-
if (config[ariaCamelName] && !attr[ariaCamelName]) {
116+
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
117117
scope.$watch(attr[attrName], function(boolVal) {
118118
if (negate) {
119119
boolVal = !boolVal;
@@ -124,6 +124,12 @@ function $AriaProvider() {
124124
};
125125
}
126126

127+
function isNodeOneOf(elem, nodeTypeArray) {
128+
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
129+
return true;
130+
}
131+
}
132+
127133
/**
128134
* @ngdoc service
129135
* @name $aria
@@ -175,22 +181,24 @@ function $AriaProvider() {
175181
config: function(key) {
176182
return config[key];
177183
},
178-
$$watchExpr: watchExpr
184+
$$watchExpr: watchExpr,
185+
nodeBlackList: ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT'],
186+
isNodeOneOf: isNodeOneOf
179187
};
180188
};
181189
}
182190

183191

184192
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
185-
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
193+
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
186194
}])
187195
.directive('ngHide', ['$aria', function($aria) {
188-
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
196+
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
189197
}])
190198
.directive('ngModel', ['$aria', function($aria) {
191199

192-
function shouldAttachAttr(attr, normalizedAttr, elem) {
193-
return $aria.config(normalizedAttr) && !elem.attr(attr);
200+
function shouldAttachAttr(attr, normalizedAttr, elem, nodeBlacklist) {
201+
return !$aria.isNodeOneOf(elem, (nodeBlacklist || [])) && $aria.config(normalizedAttr) && !elem.attr(attr);
194202
}
195203

196204
function shouldAttachRole(role, elem) {
@@ -203,6 +211,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
203211

204212
return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' :
205213
((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' :
214+
(type || role) === 'radiogroup' ? 'radiogroup' :
206215
(type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' :
207216
(type || role) === 'textbox' || elem[0].nodeName === 'TEXTAREA' ? 'multiline' : '';
208217
}
@@ -216,15 +225,15 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
216225

217226
return {
218227
pre: function(scope, elem, attr, ngModel) {
219-
if (shape === 'checkbox' && attr.type !== 'checkbox') {
228+
if (shape === 'checkbox') {
220229
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
221230
ngModel.$isEmpty = function(value) {
222231
return value === false;
223232
};
224233
}
225234
},
226235
post: function(scope, elem, attr, ngModel) {
227-
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
236+
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, $aria.nodeBlackList);
228237

229238
function ngAriaWatchModelValue() {
230239
return ngModel.$modelValue;
@@ -250,21 +259,29 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
250259
}
251260

252261
switch (shape) {
262+
case 'radiogroup':
263+
if (needsTabIndex) {
264+
elem.attr('tabindex', 0);
265+
}
266+
break;
253267
case 'radio':
254268
case 'checkbox':
255269
if (shouldAttachRole(shape, elem)) {
256270
elem.attr('role', shape);
257271
}
258-
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
272+
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, ['INPUT'])) {
259273
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
260274
getRadioReaction() : ngAriaCheckboxReaction);
261275
}
276+
if (needsTabIndex) {
277+
elem.attr('tabindex', 0);
278+
}
262279
break;
263280
case 'range':
264281
if (shouldAttachRole(shape, elem)) {
265282
elem.attr('role', 'slider');
266283
}
267-
if ($aria.config('ariaValue')) {
284+
if (!$aria.isNodeOneOf(elem, ['INPUT']) && $aria.config('ariaValue')) {
268285
if (attr.min && !elem.attr('aria-valuemin')) {
269286
elem.attr('aria-valuemin', attr.min);
270287
}
@@ -277,6 +294,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
277294
});
278295
}
279296
}
297+
if (needsTabIndex) {
298+
elem.attr('tabindex', 0);
299+
}
280300
break;
281301
case 'multiline':
282302
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
@@ -285,11 +305,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
285305
break;
286306
}
287307

288-
if (needsTabIndex) {
289-
elem.attr('tabindex', 0);
290-
}
291-
292-
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
308+
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem, $aria.nodeBlackList)) {
293309
scope.$watch(function ngAriaRequiredWatch() {
294310
return ngModel.$error.required;
295311
}, function ngAriaRequiredReaction(newVal) {
@@ -310,7 +326,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
310326
};
311327
}])
312328
.directive('ngDisabled', ['$aria', function($aria) {
313-
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
329+
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', $aria.nodeBlackList);
314330
}])
315331
.directive('ngMessages', function() {
316332
return {
@@ -329,33 +345,29 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
329345
compile: function(elem, attr) {
330346
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
331347
return function(scope, elem, attr) {
332-
333-
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
334-
335-
function isNodeOneOf(elem, nodeTypeArray) {
336-
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
337-
return true;
348+
349+
if (!$aria.isNodeOneOf(elem, $aria.nodeBlackList)) {
350+
351+
if (!elem.attr('role')) {
352+
elem.attr('role', 'button');
353+
}
354+
355+
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
356+
elem.attr('tabindex', 0);
338357
}
339-
}
340-
if (!elem.attr('role') && !isNodeOneOf(elem, nodeBlackList)) {
341-
elem.attr('role', 'button');
342-
}
343-
344-
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
345-
elem.attr('tabindex', 0);
346-
}
347358

348-
if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
349-
elem.on('keypress', function(event) {
350-
var keyCode = event.which || event.keyCode;
351-
if (keyCode === 32 || keyCode === 13) {
352-
scope.$apply(callback);
353-
}
359+
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
360+
elem.on('keypress', function(event) {
361+
var keyCode = event.which || event.keyCode;
362+
if (keyCode === 32 || keyCode === 13) {
363+
scope.$apply(callback);
364+
}
354365

355-
function callback() {
356-
fn(scope, { $event: event });
357-
}
358-
});
366+
function callback() {
367+
fn(scope, { $event: event });
368+
}
369+
});
370+
}
359371
}
360372
};
361373
}

0 commit comments

Comments
 (0)