diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 1561361ee6..790f7ee535 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -131,15 +131,23 @@ describe('typeahead tests', function () { it('should open and close typeahead based on matches', function () { var element = prepareInputEl('
'); var inputEl = findInput(element); + var ownsId = inputEl.attr('aria-owns'); + expect(inputEl.attr('aria-expanded')).toBe('false'); + expect(inputEl.attr('aria-activedescendant')).toBeUndefined(); changeInputValueTo(element, 'ba'); expect(element).toBeOpenWithActive(2, 0); + expect(findDropDown(element).attr('id')).toBe(ownsId); expect(inputEl.attr('aria-expanded')).toBe('true'); + var activeOptionId = ownsId + '-option-0'; + expect(inputEl.attr('aria-activedescendant')).toBe(activeOptionId); + expect(findDropDown(element).find('li.active').attr('id')).toBe(activeOptionId); changeInputValueTo(element, ''); expect(element).toBeClosed(); expect(inputEl.attr('aria-expanded')).toBe('false'); + expect(inputEl.attr('aria-activedescendant')).toBeUndefined(); }); it('should not open typeahead if input value smaller than a defined threshold', function () { diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 7151b81bb8..8795ef6fe0 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -69,15 +69,25 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap var hasFocus; + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); element.attr({ 'aria-autocomplete': 'list', - 'aria-expanded': false + 'aria-expanded': false, + 'aria-owns': popupId }); //pop-up element used to display matches var popUpEl = angular.element(''); popUpEl.attr({ + id: popupId, matches: 'matches', active: 'activeIdx', select: 'select(activeIdx)', @@ -89,19 +99,26 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); } - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); - var resetMatches = function() { scope.matches = []; scope.activeIdx = -1; element.attr('aria-expanded', false); }; + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + var getMatchesAsync = function(inputValue) { var locals = {$viewValue: inputValue}; @@ -121,6 +138,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap for(var i=0; i