Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit a84966f

Browse files
committed
feat(dropdown): Add keynav support (fix for #1228)
feat(dropdown): Fix indentation issues and correct breaks.
1 parent 2b77933 commit a84966f

File tree

2 files changed

+162
-11
lines changed

2 files changed

+162
-11
lines changed

Diff for: src/dropdown/dropdown.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
6060
else if ( openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen ) {
6161
evt.preventDefault();
6262
evt.stopPropagation();
63-
openScope.focusMenuEntry(evt.which);
63+
openScope.focusDropdownEntry(evt.which);
6464
}
6565
};
6666
}])
@@ -71,7 +71,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
7171
templateScope,
7272
openClass = dropdownConfig.openClass,
7373
getIsOpen,
74-
setIsOpen = angular.noop,
74+
setIsOpen = angular.noop,
7575
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
7676
appendToBody = false,
7777
keynavEnabled =false,
@@ -125,8 +125,8 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
125125
return keynavEnabled;
126126
};
127127

128-
scope.focusMenuEntry = function(keyCode) {
129-
var elems = self.dropdownMenu ?
128+
scope.focusDropdownEntry = function(keyCode) {
129+
var elems = self.dropdownMenu ? //If append to body is used.
130130
(angular.element(self.dropdownMenu).find('a')) :
131131
(angular.element(self.$element).find('ul').eq(0).find('a'));
132132

@@ -135,14 +135,22 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
135135
if ( !angular.isNumber(self.selectedOption)) {
136136
self.selectedOption = 0;
137137
} else {
138-
self.selectedOption = (self.selectedOption === elems.length -1 ? self.selectedOption : self.selectedOption+1);
138+
self.selectedOption = (self.selectedOption === elems.length -1 ?
139+
self.selectedOption :
140+
self.selectedOption + 1);
139141
}
142+
break;
140143
}
141-
break;
142144
case (38): {
143-
self.selectedOption = (self.selectedOption === 0 ? 0 : self.selectedOption-1);
145+
if ( !angular.isNumber(self.selectedOption)) {
146+
return;
147+
} else {
148+
self.selectedOption = (self.selectedOption === 0 ?
149+
0 :
150+
self.selectedOption - 1);
151+
}
152+
break;
144153
}
145-
break;
146154
}
147155
elems[self.selectedOption].focus();
148156
};

Diff for: src/dropdown/test/dropdown.spec.js

+146-3
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ describe('dropdownToggle', function() {
238238
});
239239

240240
return $compile('<li dropdown><a href dropdown-toggle></a>' +
241-
'<ul><li><a href="#something">Hello</a></li></ul></li>')($rootScope);
241+
'<ul><li><a href="#something">Hello</a></li></ul></li>')($rootScope);
242242
}
243243

244244
beforeEach(function() {
@@ -375,8 +375,8 @@ describe('dropdownToggle', function() {
375375
describe('`auto-close` option', function() {
376376
function dropdown(autoClose) {
377377
return $compile('<li dropdown ' +
378-
(autoClose === void 0 ? '' : 'auto-close="'+autoClose+'"') +
379-
'><a href dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
378+
(autoClose === void 0 ? '' : 'auto-close="'+autoClose+'"') +
379+
'><a href dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
380380
}
381381

382382
it('should close on document click if no auto-close is specified', function() {
@@ -507,4 +507,147 @@ describe('dropdownToggle', function() {
507507
expect(isFocused(focusEl)).toBe(true);
508508
});
509509
});
510+
511+
describe('`keyboard-nav` option', function() {
512+
function dropdown() {
513+
return $compile('<li dropdown keyboard-nav><a href dropdown-toggle></a><ul><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
514+
}
515+
beforeEach(function() {
516+
element = dropdown();
517+
});
518+
519+
it('should focus first list element when down arrow pressed', function() {
520+
$document.find('body').append(element);
521+
clickDropdownToggle();
522+
triggerKeyDown($document, 40);
523+
524+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
525+
var focusEl = element.find('ul').eq(0).find('a').eq(0);
526+
expect(isFocused(focusEl)).toBe(true);
527+
});
528+
529+
it('should not focus first list element when up arrow pressed after dropdown toggled', function() {
530+
$document.find('body').append(element);
531+
clickDropdownToggle();
532+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
533+
534+
triggerKeyDown($document, 38);
535+
var focusEl = element.find('ul').eq(0).find('a').eq(0);
536+
expect(isFocused(focusEl)).toBe(false);
537+
});
538+
539+
it('should not focus any list element when down arrow pressed if closed', function() {
540+
$document.find('body').append(element);
541+
triggerKeyDown($document, 40);
542+
543+
expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
544+
var focusEl = element.find('ul').eq(0).find('a');
545+
expect(isFocused(focusEl[0])).toBe(false);
546+
expect(isFocused(focusEl[1])).toBe(false);
547+
});
548+
549+
it('should not change focus when other keys are pressed', function() {
550+
$document.find('body').append(element);
551+
clickDropdownToggle();
552+
triggerKeyDown($document, 37);
553+
554+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
555+
var focusEl = element.find('ul').eq(0).find('a');
556+
expect(isFocused(focusEl[0])).toBe(false);
557+
expect(isFocused(focusEl[1])).toBe(false);
558+
});
559+
560+
it('should focus second list element when down arrow pressed twice', function() {
561+
$document.find('body').append(element);
562+
clickDropdownToggle();
563+
triggerKeyDown($document, 40);
564+
triggerKeyDown($document, 40);
565+
566+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
567+
var focusEl = element.find('ul').eq(0).find('a').eq(1);
568+
expect(isFocused(focusEl)).toBe(true);
569+
});
570+
571+
it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() {
572+
$document.find('body').append(element);
573+
clickDropdownToggle();
574+
triggerKeyDown($document, 40);
575+
triggerKeyDown($document, 40);
576+
577+
triggerKeyDown($document, 38);
578+
579+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
580+
var focusEl = element.find('ul').eq(0).find('a').eq(0);
581+
expect(isFocused(focusEl)).toBe(true);
582+
});
583+
584+
it('should stay focused on final list element if down pressed at list end', function() {
585+
$document.find('body').append(element);
586+
clickDropdownToggle();
587+
triggerKeyDown($document, 40);
588+
triggerKeyDown($document, 40);
589+
590+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
591+
var focusEl = element.find('ul').eq(0).find('a').eq(1);
592+
expect(isFocused(focusEl)).toBe(true);
593+
594+
triggerKeyDown($document, 40);
595+
expect(isFocused(focusEl)).toBe(true);
596+
});
597+
598+
it('should close if esc is pressed while focused', function() {
599+
element = dropdown('disabled');
600+
$document.find('body').append(element);
601+
clickDropdownToggle();
602+
603+
triggerKeyDown($document, 40);
604+
605+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
606+
var focusEl = element.find('ul').eq(0).find('a').eq(0);
607+
expect(isFocused(focusEl)).toBe(true);
608+
609+
triggerKeyDown($document, 27);
610+
expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
611+
});
612+
});
613+
614+
describe('`keyboard-nav` option with `dropdown-append-to-body` option', function() {
615+
function dropdown() {
616+
return $compile('<li dropdown dropdown-append-to-body keyboard-nav><a href dropdown-toggle></a><ul class="dropdown-menu" id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
617+
}
618+
619+
beforeEach(function() {
620+
element = dropdown();
621+
});
622+
623+
it('should focus first list element when down arrow pressed', function() {
624+
clickDropdownToggle();
625+
626+
triggerKeyDown($document, 40);
627+
628+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
629+
var focusEl = $document.find('ul').eq(0).find('a');
630+
expect(isFocused(focusEl)).toBe(true);
631+
});
632+
633+
it('should not focus first list element when down arrow pressed if closed', function() {
634+
triggerKeyDown($document, 40);
635+
636+
expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
637+
var focusEl = $document.find('ul').eq(0).find('a');
638+
expect(isFocused(focusEl)).toBe(false);
639+
});
640+
641+
it('should focus second list element when down arrow pressed twice', function() {
642+
clickDropdownToggle();
643+
triggerKeyDown($document, 40);
644+
triggerKeyDown($document, 40);
645+
646+
expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
647+
var elem1 = $document.find('ul');
648+
var elem2 = elem1.find('a');
649+
var focusEl = $document.find('ul').eq(0).find('a').eq(1);
650+
expect(isFocused(focusEl)).toBe(true);
651+
});
652+
});
510653
});

0 commit comments

Comments
 (0)