Skip to content

Commit b7ad1b8

Browse files
wkonkelOron Nadiv
authored and
Oron Nadiv
committed
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 angular-ui#908 Closes angular-ui#2916
1 parent 3b8fc16 commit b7ad1b8

File tree

5 files changed

+127
-4
lines changed

5 files changed

+127
-4
lines changed

dist/ui-bootstrap-0.13.0-SNAPSHOT.min.js

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/ui-bootstrap-tpls-0.13.0-SNAPSHOT.min.js

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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
@@ -64,6 +64,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
6464

6565
var leftOffset = originalScope.$eval(attrs.typeaheadPopupLeftOffset) || 0;
6666

67+
var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
68+
6769
//INTERNAL VARIABLES
6870

6971
//model setter executed upon match selection
@@ -136,7 +138,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
136138
if (onCurrentRequest && hasFocus) {
137139
if (matches.length > 0) {
138140

139-
scope.activeIdx = 0;
141+
scope.activeIdx = focusFirst ? 0 : -1;
140142
scope.matches.length = 0;
141143

142144
//transform labels
@@ -290,6 +292,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
290292
return;
291293
}
292294

295+
// if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
296+
if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) {
297+
return;
298+
}
299+
293300
evt.preventDefault();
294301

295302
var i;
@@ -304,7 +311,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
304311

305312
} else if (evt.which === 38) {
306313
for (i = 0; i < scope.matches.length; i++) {
307-
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
314+
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
308315
if (!scope.matches[scope.activeIdx].model.notSelectable) {
309316
break;
310317
}

0 commit comments

Comments
 (0)