diff --git a/src/typeahead/docs/demo.html b/src/typeahead/docs/demo.html index ff4dea90db..1e995eefbe 100644 --- a/src/typeahead/docs/demo.html +++ b/src/typeahead/docs/demo.html @@ -66,7 +66,7 @@
Model: {{customSelected | json}}- +
Model: {{customPopupSelected | json}}diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index 6248af76a7..ac7fdc4f3f 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -82,3 +82,7 @@ The typeahead directives provide several attributes: * `typeahead-is-open` _(Defaults: angular.noop)_ : Binding to a variable that indicates if dropdown is open + +* `typeahead-show-hint` + _(Defaults: false)_ : + Should input show hint that matches the first option? \ No newline at end of file diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index b03b13f131..55d34a1568 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -967,6 +967,85 @@ describe('typeahead tests', function() { expect($scope.result).toEqual($scope.states[0]); }); }); + + describe('input hint', function() { + var element; + + beforeEach(function() { + element = prepareInputEl(''); + }); + + it('should show hint when input matches first match', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + }); + + it('should not show hint when input does not match first match', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'las'); + expect(hintEl.val()).toEqual(''); + }); + + it('should reset hint when a match is clicked', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + + var match = findMatches(element).find('a').eq(0); + match.click(); + $scope.$digest(); + expect(hintEl.val()).toEqual(''); + }); + + it('should reset hint when click outside', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + + $document.find('body').click(); + $scope.$digest(); + expect(hintEl.val()).toEqual(''); + }); + + it('should reset hint on enter', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + triggerKeyDown(element, 13); + expect(hintEl.val()).toEqual(''); + }); + + it('should reset hint on tab', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + triggerKeyDown(element, 9); + expect(hintEl.val()).toEqual(''); + }); + + it('should reset hint on escape key', function() { + var hintEl = findInput(element); + + expect(hintEl.val()).toEqual(''); + changeInputValueTo(element, 'Alas'); + expect(hintEl.val()).toEqual('Alaska'); + triggerKeyDown(element, 27); + expect(hintEl.val()).toEqual(''); + }); + }); describe('append to element id', function() { it('append typeahead results to element', function() { diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index bacbe7217b..a2274b0f9d 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -71,6 +71,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) //binding to a variable that indicates if dropdown is open var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop; + var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false; + //INTERNAL VARIABLES //model setter executed upon match selection @@ -111,6 +113,33 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) 'aria-owns': popupId }); + //add read-only input to show hint + if (showHint) { + var inputsContainer = angular.element(''); + inputsContainer.css('position', 'relative'); + element.after(inputsContainer); + var hintInputElem = element.clone(); + hintInputElem.attr('placeholder', ''); + hintInputElem.val(''); + hintInputElem.css({ + 'position': 'absolute', + 'top': '0px', + 'left': '0px', + 'border-color': 'transparent', + 'box-shadow': 'none', + 'opacity': 1, + 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)', + 'color': '#999' + }); + element.css({ + 'position': 'relative', + 'vertical-align': 'top', + 'background-color': 'transparent' + }); + inputsContainer.append(hintInputElem); + hintInputElem.after(element); + } + //pop-up element used to display matches var popUpEl = angular.element(''); popUpEl.attr({ @@ -132,10 +161,17 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); } + var resetHint = function() { + if (showHint) { + hintInputElem.val(''); + } + }; + var resetMatches = function() { scope.matches = []; scope.activeIdx = -1; element.attr('aria-expanded', false); + resetHint(); }; var getMatchId = function(index) { @@ -196,6 +232,16 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { scope.select(0); } + + if (showHint) { + var firstLabel = scope.matches[0].label; + if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { + hintInputElem.val(inputValue + firstLabel.slice(inputValue.length)); + } + else { + hintInputElem.val(''); + } + } } else { resetMatches(); isNoResultsSetter(originalScope, true); @@ -377,6 +423,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) } // Prevent jQuery cache memory leak popUpEl.remove(); + + if (showHint) { + inputsContainer.remove(); + } }); var $popup = $compile(popUpEl)(scope);