From 99675b89e6b99779d3615d75fca93f7bd914dc8a Mon Sep 17 00:00:00 2001 From: bleggett Date: Sun, 10 May 2015 19:13:15 -0400 Subject: [PATCH] feat(dropdown): Add keynav support (fix for #1228) feat(dropdown): Fix indentation issues and correct breaks. --- src/dropdown/dropdown.js | 24 +++-- src/dropdown/test/dropdown.spec.js | 149 ++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 11 deletions(-) diff --git a/src/dropdown/dropdown.js b/src/dropdown/dropdown.js index 249a9ef63b..7e7befa985 100644 --- a/src/dropdown/dropdown.js +++ b/src/dropdown/dropdown.js @@ -60,7 +60,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) else if ( openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen ) { evt.preventDefault(); evt.stopPropagation(); - openScope.focusMenuEntry(evt.which); + openScope.focusDropdownEntry(evt.which); } }; }]) @@ -70,7 +70,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) scope = $scope.$new(), // create a child scope so we are not polluting original one openClass = dropdownConfig.openClass, getIsOpen, - setIsOpen = angular.noop, + setIsOpen = angular.noop, toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, appendToBody = false, keynavEnabled =false, @@ -124,8 +124,8 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) return keynavEnabled; }; - scope.focusMenuEntry = function(keyCode) { - var elems = self.dropdownMenu ? + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. (angular.element(self.dropdownMenu).find('a')) : (angular.element(self.$element).find('ul').eq(0).find('a')); @@ -134,14 +134,22 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) if ( !angular.isNumber(self.selectedOption)) { self.selectedOption = 0; } else { - self.selectedOption = (self.selectedOption === elems.length -1 ? self.selectedOption : self.selectedOption+1); + self.selectedOption = (self.selectedOption === elems.length -1 ? + self.selectedOption : + self.selectedOption + 1); } + break; } - break; case (38): { - self.selectedOption = (self.selectedOption === 0 ? 0 : self.selectedOption-1); + if ( !angular.isNumber(self.selectedOption)) { + return; + } else { + self.selectedOption = (self.selectedOption === 0 ? + 0 : + self.selectedOption - 1); + } + break; } - break; } elems[self.selectedOption].focus(); }; diff --git a/src/dropdown/test/dropdown.spec.js b/src/dropdown/test/dropdown.spec.js index fa741caca4..ee7fcf8097 100644 --- a/src/dropdown/test/dropdown.spec.js +++ b/src/dropdown/test/dropdown.spec.js @@ -238,7 +238,7 @@ describe('dropdownToggle', function() { }); return $compile('
  • ' + - '
  • ')($rootScope); + '')($rootScope); } beforeEach(function() { @@ -375,8 +375,8 @@ describe('dropdownToggle', function() { describe('`auto-close` option', function() { function dropdown(autoClose) { return $compile('
  • ')($rootScope); + (autoClose === void 0 ? '' : 'auto-close="'+autoClose+'"') + + '>')($rootScope); } it('should close on document click if no auto-close is specified', function() { @@ -507,4 +507,147 @@ describe('dropdownToggle', function() { expect(isFocused(focusEl)).toBe(true); }); }); + + describe('`keyboard-nav` option', function() { + function dropdown() { + return $compile('
  • ')($rootScope); + } + beforeEach(function() { + element = dropdown(); + }); + + it('should focus first list element when down arrow pressed', function() { + $document.find('body').append(element); + clickDropdownToggle(); + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a').eq(0); + expect(isFocused(focusEl)).toBe(true); + }); + + it('should not focus first list element when up arrow pressed after dropdown toggled', function() { + $document.find('body').append(element); + clickDropdownToggle(); + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + + triggerKeyDown($document, 38); + var focusEl = element.find('ul').eq(0).find('a').eq(0); + expect(isFocused(focusEl)).toBe(false); + }); + + it('should not focus any list element when down arrow pressed if closed', function() { + $document.find('body').append(element); + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(false); + var focusEl = element.find('ul').eq(0).find('a'); + expect(isFocused(focusEl[0])).toBe(false); + expect(isFocused(focusEl[1])).toBe(false); + }); + + it('should not change focus when other keys are pressed', function() { + $document.find('body').append(element); + clickDropdownToggle(); + triggerKeyDown($document, 37); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a'); + expect(isFocused(focusEl[0])).toBe(false); + expect(isFocused(focusEl[1])).toBe(false); + }); + + it('should focus second list element when down arrow pressed twice', function() { + $document.find('body').append(element); + clickDropdownToggle(); + triggerKeyDown($document, 40); + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a').eq(1); + expect(isFocused(focusEl)).toBe(true); + }); + + it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() { + $document.find('body').append(element); + clickDropdownToggle(); + triggerKeyDown($document, 40); + triggerKeyDown($document, 40); + + triggerKeyDown($document, 38); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a').eq(0); + expect(isFocused(focusEl)).toBe(true); + }); + + it('should stay focused on final list element if down pressed at list end', function() { + $document.find('body').append(element); + clickDropdownToggle(); + triggerKeyDown($document, 40); + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a').eq(1); + expect(isFocused(focusEl)).toBe(true); + + triggerKeyDown($document, 40); + expect(isFocused(focusEl)).toBe(true); + }); + + it('should close if esc is pressed while focused', function() { + element = dropdown('disabled'); + $document.find('body').append(element); + clickDropdownToggle(); + + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = element.find('ul').eq(0).find('a').eq(0); + expect(isFocused(focusEl)).toBe(true); + + triggerKeyDown($document, 27); + expect(element.hasClass(dropdownConfig.openClass)).toBe(false); + }); + }); + + describe('`keyboard-nav` option with `dropdown-append-to-body` option', function() { + function dropdown() { + return $compile('
  • ')($rootScope); + } + + beforeEach(function() { + element = dropdown(); + }); + + it('should focus first list element when down arrow pressed', function() { + clickDropdownToggle(); + + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var focusEl = $document.find('ul').eq(0).find('a'); + expect(isFocused(focusEl)).toBe(true); + }); + + it('should not focus first list element when down arrow pressed if closed', function() { + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(false); + var focusEl = $document.find('ul').eq(0).find('a'); + expect(isFocused(focusEl)).toBe(false); + }); + + it('should focus second list element when down arrow pressed twice', function() { + clickDropdownToggle(); + triggerKeyDown($document, 40); + triggerKeyDown($document, 40); + + expect(element.hasClass(dropdownConfig.openClass)).toBe(true); + var elem1 = $document.find('ul'); + var elem2 = elem1.find('a'); + var focusEl = $document.find('ul').eq(0).find('a').eq(1); + expect(isFocused(focusEl)).toBe(true); + }); + }); });