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

Typeahead: move focus to text input with arrow up #2650

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions src/typeahead/test/typeahead.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,15 @@ describe('typeahead tests', function () {
var typeaheadEl = findDropDown(this.actual);
var liEls = findMatches(this.actual);

var hasCorrectClass = (activeIdx === -1) ? true : $(liEls[activeIdx]).hasClass('active');

this.message = function () {
return 'Expected "' + this.actual + '" to be opened.';
};
return typeaheadEl.length === 1 && typeaheadEl.hasClass('ng-hide') === false && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active');
return typeaheadEl.length === 1 &&
typeaheadEl.hasClass('ng-hide') === false &&
liEls.length === noOfMatches &&
hasCorrectClass;
}
});
});
Expand Down Expand Up @@ -137,7 +142,8 @@ describe('typeahead tests', function () {
expect(inputEl.attr('aria-activedescendant')).toBeUndefined();

changeInputValueTo(element, 'ba');
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);
triggerKeyDown(element, 40);
expect(findDropDown(element).attr('id')).toBe(ownsId);
expect(inputEl.attr('aria-expanded')).toBe('true');
var activeOptionId = ownsId + '-option-0';
Expand All @@ -154,7 +160,7 @@ describe('typeahead tests', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source \n' +
'| filter:$viewValue"></div>');
changeInputValueTo(element, 'ba');
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);

changeInputValueTo(element, '');
expect(element).toBeClosed();
Expand All @@ -172,7 +178,9 @@ describe('typeahead tests', function () {
};
var element = prepareInputEl('<div><input ng-model="result" typeahead="updaterFn(item) as item for item in source | filter:$viewValue"></div>');
changeInputValueTo(element, 'f');
triggerKeyDown(element, 40);
triggerKeyDown(element, 13);

expect($scope.result).toEqual('prefixfoo');
});

Expand Down Expand Up @@ -211,6 +219,7 @@ describe('typeahead tests', function () {
expect($scope.form.input.$error.editable).toBeTruthy();

changeInputValueTo(element, 'foo');
triggerKeyDown(element, 40);
triggerKeyDown(element, 13);
expect($scope.result).toEqual('foo');
expect($scope.form.input.$error.editable).toBeFalsy();
Expand Down Expand Up @@ -254,6 +263,7 @@ describe('typeahead tests', function () {
expect(element).toBeClosed();

$timeout.flush();
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(1, 0);
}));

Expand Down Expand Up @@ -319,6 +329,7 @@ describe('typeahead tests', function () {
var inputEl = findInput(element);

changeInputValueTo(element, 'b');
triggerKeyDown(element, 40);
triggerKeyDown(element, 13);

expect($scope.result).toEqual('bar');
Expand All @@ -332,6 +343,7 @@ describe('typeahead tests', function () {
var inputEl = findInput(element);

changeInputValueTo(element, 'b');
triggerKeyDown(element, 40);
triggerKeyDown(element, 9);

expect($scope.result).toEqual('bar');
Expand Down Expand Up @@ -365,6 +377,7 @@ describe('typeahead tests', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead-on-select="onSelect($item, $model, $label)" typeahead="state.code as state.name for state in states | filter:$viewValue"></div>');

changeInputValueTo(element, 'Alas');
triggerKeyDown(element, 40);
triggerKeyDown(element, 13);

expect($scope.result).toEqual('AL');
Expand All @@ -379,6 +392,7 @@ describe('typeahead tests', function () {
var inputEl = findInput(element);

changeInputValueTo(element, 'Alas');
triggerKeyDown(element, 40);
triggerKeyDown(element, 13);

expect($scope.result).toEqual('AL');
Expand All @@ -395,28 +409,30 @@ describe('typeahead tests', function () {

it('should activate prev/next matches on up/down keys', function () {
changeInputValueTo(element, 'b');

expect(element).toBeOpenWithActive(2, -1);

// Down arrow key to first element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 0);

// Down arrow key
// Down arrow key to second element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 1);

// Down arrow key goes back to first element
// Down arrow key goes back to input
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);

// Up arrow key goes back to last element
// Up arrow key goes to last element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 1);

// Up arrow key goes back to last element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 0);
});

it('should close popup on escape key', function () {
changeInputValueTo(element, 'b');
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);

// Escape key
triggerKeyDown(element, 27);
Expand All @@ -425,7 +441,7 @@ describe('typeahead tests', function () {

it('should highlight match on mouseenter', function () {
changeInputValueTo(element, 'b');
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);

findMatches(element).eq(1).trigger('mouseenter');
expect(element).toBeOpenWithActive(2, 1);
Expand All @@ -450,7 +466,7 @@ describe('typeahead tests', function () {

deferred.resolve(['good', 'stuff']);
$scope.$digest();
expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);
});

it('should not display anything when promise is rejected', function () {
Expand All @@ -470,6 +486,7 @@ describe('typeahead tests', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue"></div>');

changeInputValueTo(element, 'b');
triggerKeyDown(element, 40);

$document.find('body').click();
$scope.$digest();
Expand Down Expand Up @@ -498,8 +515,9 @@ describe('typeahead tests', function () {
var inputEl = findInput(element);

changeInputValueTo(element, 'bar');
expect(element).toBeOpenWithActive(1, 0);
expect(element).toBeOpenWithActive(1, -1);

triggerKeyDown(element, 40);
triggerKeyDown(element, 13);

expect($scope.email).toEqual('[email protected]');
Expand Down Expand Up @@ -594,7 +612,7 @@ describe('typeahead tests', function () {
inputEl.click();
$scope.$digest();

expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);
});

it('issue #1238 - allow names like "query" to be used inside "in" expressions ', function () {
Expand All @@ -606,7 +624,7 @@ describe('typeahead tests', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in query($viewValue)"></div>');
changeInputValueTo(element, 'bar');

expect(element).toBeOpenWithActive(2, 0);
expect(element).toBeOpenWithActive(2, -1);
});

it('issue #1773 - should not trigger an error when used with ng-focus', function () {
Expand Down Expand Up @@ -660,6 +678,7 @@ describe('typeahead tests', function () {
it('append typeahead results to body', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
changeInputValueTo(element, 'ba');
triggerKeyDown(element, 40);
expect($document.find('body')).toBeOpenWithActive(2, 0);
});

Expand Down
18 changes: 15 additions & 3 deletions src/typeahead/typeahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
if (onCurrentRequest && hasFocus) {
if (matches.length > 0) {

scope.activeIdx = 0;
scope.activeIdx = -1;
scope.matches.length = 0;

//transform labels
Expand Down Expand Up @@ -170,7 +170,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
//we need to propagate user's query so we can higlight matches
scope.query = undefined;

//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
var timeoutPromise;

var scheduleSearchWithTimeout = function(inputValue) {
Expand Down Expand Up @@ -274,10 +274,22 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap

evt.preventDefault();

if (evt.which === 40) {
if (evt.which === 40 && scope.activeIdx === (scope.matches.length - 1)) {
scope.activeIdx = -1;
scope.$digest();

} else if (evt.which === 40) {
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();

} else if (evt.which === 38 && scope.activeIdx === 0) {
scope.activeIdx = -1;
scope.$digest();

} else if (evt.which === 38 && scope.activeIdx === -1) {
scope.activeIdx = scope.matches.length - 1;
scope.$digest();

} else if (evt.which === 38) {
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
Expand Down