diff --git a/src/components/select/demoOptionGroups/index.html b/src/components/select/demoOptionGroups/index.html
index 8f3ac30f8d4..d0258a7aa9a 100644
--- a/src/components/select/demoOptionGroups/index.html
+++ b/src/components/select/demoOptionGroups/index.html
@@ -5,17 +5,26 @@
Pick your pizza below
- {{size}}
+
+ {{size.name}}
+
+
+ {{size.name}}
+
- {{topping.name}}
+
+ {{topping.name}}
- {{topping.name}}
+
+ {{topping.name}}
diff --git a/src/components/select/demoOptionGroups/script.js b/src/components/select/demoOptionGroups/script.js
index a16103c631e..f2cad9b0ff2 100644
--- a/src/components/select/demoOptionGroups/script.js
+++ b/src/components/select/demoOptionGroups/script.js
@@ -2,10 +2,10 @@ angular
.module('selectDemoOptGroups', ['ngMaterial'])
.controller('SelectOptGroupController', function($scope) {
$scope.sizes = [
- "small (12-inch)",
- "medium (14-inch)",
- "large (16-inch)",
- "insane (42-inch)"
+ { surcharge: 'none', name: "small (12-inch)" },
+ { surcharge: 'none', name: "medium (14-inch)" },
+ { surcharge: 'extra', name: "large (16-inch)" },
+ { surcharge: 'extra', name: "insane (42-inch)" }
];
$scope.toppings = [
{ category: 'meat', name: 'Pepperoni' },
diff --git a/src/components/select/select.js b/src/components/select/select.js
index f2889883937..90257bff195 100755
--- a/src/components/select/select.js
+++ b/src/components/select/select.js
@@ -741,6 +741,7 @@ function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) {
return self.options;
}, function() {
self.ngModel.$render();
+ updateOptionSetSizeAndPosition();
});
/**
@@ -1067,9 +1068,32 @@ function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) {
}
};
+ /**
+ * If the options include md-optgroups, then we need to apply aria-setsize and aria-posinset
+ * to help screen readers understand the indexes. When md-optgroups are not used, we save on
+ * perf and extra attributes by not applying these attributes as they are not needed by screen
+ * readers.
+ */
+ function updateOptionSetSizeAndPosition() {
+ var i, options;
+ var hasOptGroup = $element.find('md-optgroup');
+ if (!hasOptGroup.length) {
+ return;
+ }
+
+ options = $element.find('md-option');
+
+ for (i = 0; i < options.length; i++) {
+ options[i].setAttribute('aria-setsize', options.length);
+ options[i].setAttribute('aria-posinset', i + 1);
+ }
+ }
+
function renderMultiple() {
var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue || [];
- if (!angular.isArray(newSelectedValues)) return;
+ if (!angular.isArray(newSelectedValues)) {
+ return;
+ }
var oldSelected = Object.keys(self.selected);
@@ -1371,6 +1395,7 @@ function OptgroupDirective() {
if (!hasSelectHeader()) {
setupLabelElement();
}
+ element.attr('role', 'group');
function hasSelectHeader() {
return element.parent().find('md-select-header').length;
@@ -1387,6 +1412,7 @@ function OptgroupDirective() {
if (attrs.label) {
labelElement.text(attrs.label);
}
+ element.attr('aria-label', labelElement.text());
}
}
}
@@ -1590,7 +1616,7 @@ function SelectProvider($$interimElementProvider) {
/**
* @param {Element|HTMLElement|null=} previousNode
* @param {Element|HTMLElement} node
- * @param {SelectMenuController|Function|Object=} menuController SelectMenuController instance
+ * @param {SelectMenuController|Function|object=} menuController SelectMenuController instance
*/
function focusOptionNode(previousNode, node, menuController) {
var listboxContentNode = opts.contentEl[0];
diff --git a/src/components/select/select.spec.js b/src/components/select/select.spec.js
index 7d4ceebbaec..99c3cf428ba 100755
--- a/src/components/select/select.spec.js
+++ b/src/components/select/select.spec.js
@@ -1446,6 +1446,92 @@ describe('', function() {
clickOption(el, 2);
expect(options.eq(2).attr('aria-selected')).toBe('true');
});
+
+ it('applies label element\'s text to optgroup\'s aria-label', function() {
+ $rootScope.val = [1];
+ var select = $compile(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' One' +
+ ' Two' +
+ ' Three' +
+ ' ' +
+ ' ' +
+ '')($rootScope);
+
+ var optgroups = select.find('md-optgroup');
+ expect(optgroups[0].getAttribute('aria-label')).toBe('stuff');
+ });
+
+ it('applies optgroup\'s label as aria-label', function() {
+ $rootScope.val = [1];
+ var select = $compile(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' One' +
+ ' Two' +
+ ' Three' +
+ ' ' +
+ ' ' +
+ '')($rootScope);
+
+ var optgroups = select.find('md-optgroup');
+ expect(optgroups[0].getAttribute('aria-label')).toBe('stuff');
+ });
+
+ it('applies setsize and posinset when optgroups are used', function() {
+ $rootScope.val = [1];
+ var select = $compile(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' One' +
+ ' Two' +
+ ' Three' +
+ ' ' +
+ ' ' +
+ '')($rootScope);
+ $rootScope.$digest();
+
+ var options = select.find('md-option');
+ expect(options[0].getAttribute('aria-setsize')).toBe('3');
+ expect(options[0].getAttribute('aria-posinset')).toBe('1');
+ });
+
+ it('applies setsize and posinset when optgroups are used with multiple', function() {
+ $rootScope.val = [1];
+ var select = $compile(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' One' +
+ ' Two' +
+ ' Three' +
+ ' ' +
+ ' ' +
+ '')($rootScope);
+ $rootScope.$digest();
+
+ var options = select.find('md-option');
+ expect(options[0].getAttribute('aria-setsize')).toBe('3');
+ expect(options[0].getAttribute('aria-posinset')).toBe('1');
+ });
+
+ it('does not apply setsize and posinset when optgroups are not used', function() {
+ var select = setupSelect('ng-model="$root.model"', [1, 2, 3]);
+ $rootScope.$digest();
+
+ var options = select.find('md-option');
+ expect(options[0].getAttribute('aria-setsize')).toBe(null);
+ expect(options[0].getAttribute('aria-posinset')).toBe(null);
+ });
});
describe('keyboard controls', function() {