Skip to content

Commit

Permalink
fix(ngAria): apply to custom inputs only
Browse files Browse the repository at this point in the history
Apply ARIA attrs and tabindex more selectively

Closes angular#11500
  • Loading branch information
marcysutton committed Apr 22, 2015
1 parent 8914f8e commit a777616
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 168 deletions.
94 changes: 53 additions & 41 deletions src/ngAria/aria.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ function $AriaProvider() {
config = angular.extend(config, newConfig);
};

function watchExpr(attrName, ariaAttr, negate) {
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
return function(scope, elem, attr) {
var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !attr[ariaCamelName]) {
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) {
if (negate) {
boolVal = !boolVal;
Expand All @@ -124,6 +124,12 @@ function $AriaProvider() {
};
}

function isNodeOneOf(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
return true;
}
}

/**
* @ngdoc service
* @name $aria
Expand Down Expand Up @@ -175,22 +181,24 @@ function $AriaProvider() {
config: function(key) {
return config[key];
},
$$watchExpr: watchExpr
$$watchExpr: watchExpr,
nodeBlackList: ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT'],
isNodeOneOf: isNodeOneOf
};
};
}


ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
}])
.directive('ngHide', ['$aria', function($aria) {
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
}])
.directive('ngModel', ['$aria', function($aria) {

function shouldAttachAttr(attr, normalizedAttr, elem) {
return $aria.config(normalizedAttr) && !elem.attr(attr);
function shouldAttachAttr(attr, normalizedAttr, elem, nodeBlacklist) {
return !$aria.isNodeOneOf(elem, (nodeBlacklist || [])) && $aria.config(normalizedAttr) && !elem.attr(attr);
}

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

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

return {
pre: function(scope, elem, attr, ngModel) {
if (shape === 'checkbox' && attr.type !== 'checkbox') {
if (shape === 'checkbox') {
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
ngModel.$isEmpty = function(value) {
return value === false;
};
}
},
post: function(scope, elem, attr, ngModel) {
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, $aria.nodeBlackList);

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

switch (shape) {
case 'radiogroup':
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
break;
case 'radio':
case 'checkbox':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', shape);
}
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, ['INPUT'])) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
break;
case 'range':
if (shouldAttachRole(shape, elem)) {
elem.attr('role', 'slider');
}
if ($aria.config('ariaValue')) {
if (!$aria.isNodeOneOf(elem, ['INPUT']) && $aria.config('ariaValue')) {
if (attr.min && !elem.attr('aria-valuemin')) {
elem.attr('aria-valuemin', attr.min);
}
Expand All @@ -277,6 +294,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
});
}
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
Expand All @@ -285,11 +305,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
break;
}

if (needsTabIndex) {
elem.attr('tabindex', 0);
}

if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem, $aria.nodeBlackList)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
}, function ngAriaRequiredReaction(newVal) {
Expand All @@ -310,7 +326,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
};
}])
.directive('ngDisabled', ['$aria', function($aria) {
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', $aria.nodeBlackList);
}])
.directive('ngMessages', function() {
return {
Expand All @@ -329,33 +345,29 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
compile: function(elem, attr) {
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
return function(scope, elem, attr) {

var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];

function isNodeOneOf(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
return true;

if (!$aria.isNodeOneOf(elem, $aria.nodeBlackList)) {

if (!elem.attr('role')) {
elem.attr('role', 'button');
}

if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
}
if (!elem.attr('role') && !isNodeOneOf(elem, nodeBlackList)) {
elem.attr('role', 'button');
}

if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}

if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
elem.on('keypress', function(event) {
var keyCode = event.which || event.keyCode;
if (keyCode === 32 || keyCode === 13) {
scope.$apply(callback);
}
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
elem.on('keypress', function(event) {
var keyCode = event.which || event.keyCode;
if (keyCode === 32 || keyCode === 13) {
scope.$apply(callback);
}

function callback() {
fn(scope, { $event: event });
}
});
function callback() {
fn(scope, { $event: event });
}
});
}
}
};
}
Expand Down
Loading

0 comments on commit a777616

Please sign in to comment.