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

Commit 35d0cc1

Browse files
wkonkelchrisirhc
authored andcommitted
feat(typeahead): add focus-first option
Add typeahead-focus-first option to prevent first match from being focused. Currently, the first result is automatically focused as you type. Now, set `typeahead-focus-first="false"` and the first result is *not* automatically focused as you type. Closes #908 Closes #2916
1 parent 992b232 commit 35d0cc1

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

src/typeahead/docs/readme.md

+4
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ The typeahead directives provide several attributes:
5151
* `typeahead-wait-ms` <i class="glyphicon glyphicon-eye-open"></i>
5252
_(Defaults: 0)_ :
5353
Minimal wait time after last character typed before typeahead kicks-in
54+
55+
* `typeahead-focus-first`
56+
_(Defaults: true)_ :
57+
Should the first match automatically be focused as you type?

src/typeahead/test/typeahead.spec.js

+95-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ describe('typeahead tests', function () {
8080
this.message = function () {
8181
return 'Expected "' + this.actual + '" to be opened.';
8282
};
83-
return typeaheadEl.length === 1 && typeaheadEl.hasClass('ng-hide') === false && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active');
83+
84+
return (typeaheadEl.length === 1 &&
85+
typeaheadEl.hasClass('ng-hide') === false &&
86+
liEls.length === noOfMatches &&
87+
(activeIdx === -1 ? !$(liEls).hasClass('active') : $(liEls[activeIdx]).hasClass('active'))
88+
);
8489
}
8590
});
8691
});
@@ -409,7 +414,7 @@ describe('typeahead tests', function () {
409414
triggerKeyDown(element, 38);
410415
expect(element).toBeOpenWithActive(2, 1);
411416

412-
// Up arrow key goes back to last element
417+
// Up arrow key goes back to first element
413418
triggerKeyDown(element, 38);
414419
expect(element).toBeOpenWithActive(2, 0);
415420
});
@@ -670,4 +675,92 @@ describe('typeahead tests', function () {
670675
});
671676
});
672677

678+
describe('focus first', function () {
679+
it('should focus the first element by default', function () {
680+
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue"></div>');
681+
changeInputValueTo(element, 'b');
682+
expect(element).toBeOpenWithActive(2, 0);
683+
684+
// Down arrow key
685+
triggerKeyDown(element, 40);
686+
expect(element).toBeOpenWithActive(2, 1);
687+
688+
// Down arrow key goes back to first element
689+
triggerKeyDown(element, 40);
690+
expect(element).toBeOpenWithActive(2, 0);
691+
692+
// Up arrow key goes back to last element
693+
triggerKeyDown(element, 38);
694+
expect(element).toBeOpenWithActive(2, 1);
695+
696+
// Up arrow key goes back to first element
697+
triggerKeyDown(element, 38);
698+
expect(element).toBeOpenWithActive(2, 0);
699+
});
700+
701+
it('should not focus the first element until keys are pressed', function () {
702+
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-focus-first="false"></div>');
703+
changeInputValueTo(element, 'b');
704+
expect(element).toBeOpenWithActive(2, -1);
705+
706+
// Down arrow key goes to first element
707+
triggerKeyDown(element, 40);
708+
expect(element).toBeOpenWithActive(2, 0);
709+
710+
// Down arrow key goes to second element
711+
triggerKeyDown(element, 40);
712+
expect(element).toBeOpenWithActive(2, 1);
713+
714+
// Down arrow key goes back to first element
715+
triggerKeyDown(element, 40);
716+
expect(element).toBeOpenWithActive(2, 0);
717+
718+
// Up arrow key goes back to last element
719+
triggerKeyDown(element, 38);
720+
expect(element).toBeOpenWithActive(2, 1);
721+
722+
// Up arrow key goes back to first element
723+
triggerKeyDown(element, 38);
724+
expect(element).toBeOpenWithActive(2, 0);
725+
726+
// New input goes back to no focus
727+
changeInputValueTo(element, 'a');
728+
changeInputValueTo(element, 'b');
729+
expect(element).toBeOpenWithActive(2, -1);
730+
731+
// Up arrow key goes to last element
732+
triggerKeyDown(element, 38);
733+
expect(element).toBeOpenWithActive(2, 1);
734+
});
735+
});
736+
737+
it('should not capture enter or tab until an item is focused', function () {
738+
$scope.select_count = 0;
739+
$scope.onSelect = function ($item, $model, $label) {
740+
$scope.select_count = $scope.select_count + 1;
741+
};
742+
var element = prepareInputEl('<div><input ng-model="result" ng-keydown="keyDownEvent = $event" typeahead="item for item in source | filter:$viewValue" typeahead-on-select="onSelect($item, $model, $label)" typeahead-focus-first="false"></div>');
743+
changeInputValueTo(element, 'b');
744+
745+
// enter key should not be captured when nothing is focused
746+
triggerKeyDown(element, 13);
747+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
748+
expect($scope.select_count).toEqual(0);
749+
750+
// tab key should not be captured when nothing is focused
751+
triggerKeyDown(element, 9);
752+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
753+
expect($scope.select_count).toEqual(0);
754+
755+
// down key should be captured and focus first element
756+
triggerKeyDown(element, 40);
757+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
758+
expect(element).toBeOpenWithActive(2, 0);
759+
760+
// enter key should be captured now that something is focused
761+
triggerKeyDown(element, 13);
762+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
763+
expect($scope.select_count).toEqual(1);
764+
});
765+
673766
});

src/typeahead/typeahead.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
5959

6060
var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
6161

62+
var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
63+
6264
//INTERNAL VARIABLES
6365

6466
//model setter executed upon match selection
@@ -131,7 +133,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
131133
if (onCurrentRequest && hasFocus) {
132134
if (matches.length > 0) {
133135

134-
scope.activeIdx = 0;
136+
scope.activeIdx = focusFirst ? 0 : -1;
135137
scope.matches.length = 0;
136138

137139
//transform labels
@@ -272,14 +274,19 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
272274
return;
273275
}
274276

277+
// if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
278+
if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) {
279+
return;
280+
}
281+
275282
evt.preventDefault();
276283

277284
if (evt.which === 40) {
278285
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
279286
scope.$digest();
280287

281288
} else if (evt.which === 38) {
282-
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
289+
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
283290
scope.$digest();
284291

285292
} else if (evt.which === 13 || evt.which === 9) {

0 commit comments

Comments
 (0)