From 197c848dc137ea52dbc40702bb6eb9fefc8c7748 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 23 Nov 2013 12:38:02 +0100 Subject: [PATCH] v0.7.0 --- bower.json | 2 +- ui-bootstrap-tpls.js | 334 ++++++++++++++++++++------------------- ui-bootstrap-tpls.min.js | 4 +- ui-bootstrap.js | 284 ++++++++++++++++++--------------- ui-bootstrap.min.js | 4 +- 5 files changed, 337 insertions(+), 291 deletions(-) diff --git a/bower.json b/bower.json index 12bf8ae..a5279f2 100644 --- a/bower.json +++ b/bower.json @@ -3,7 +3,7 @@ "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" }, "name": "angular-bootstrap", - "version": "0.6.0", + "version": "0.7.0", "main": ["./ui-bootstrap-tpls.js"], "dependencies": { "angular": ">=1" diff --git a/ui-bootstrap-tpls.js b/ui-bootstrap-tpls.js index ed018f8..08c2b78 100644 --- a/ui-bootstrap-tpls.js +++ b/ui-bootstrap-tpls.js @@ -107,23 +107,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) var isCollapsed; var initialAnimSkip = true; - scope.$watch(function (){ return element[0].scrollHeight; }, function (value) { - //The listener is called when scollHeight changes - //It actually does on 2 scenarios: - // 1. Parent is set to display none - // 2. angular bindings inside are resolved - //When we have a change of scrollHeight we are setting again the correct height if the group is opened - if (element[0].scrollHeight !== 0) { - if (!isCollapsed) { - if (initialAnimSkip) { - fixUpHeight(scope, element, element[0].scrollHeight + 'px'); - } else { - fixUpHeight(scope, element, 'auto'); - } - } - } - }); - + scope.$watch(attrs.collapse, function(value) { if (value) { collapse(); @@ -151,6 +135,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) initialAnimSkip = false; if ( !isCollapsed ) { fixUpHeight(scope, element, 'auto'); + element.addClass('in'); } } else { doTransition({ height : element[0].scrollHeight + 'px' }) @@ -159,6 +144,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) // the group while the animation was still running if ( !isCollapsed ) { fixUpHeight(scope, element, 'auto'); + element.addClass('in'); } }); } @@ -167,6 +153,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) var collapse = function() { isCollapsed = true; + element.removeClass('in'); if (initialAnimSkip) { initialAnimSkip = false; fixUpHeight(scope, element, 0); @@ -186,10 +173,13 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) }) .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { - + // This array keeps track of the accordion groups this.groups = []; + // Keep reference to user's scope to properly assign `is-open` + this.scope = $scope; + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to this.closeOthers = function(openGroup) { var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; @@ -259,12 +249,9 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) getIsOpen = $parse(attrs.isOpen); setIsOpen = getIsOpen.assign; - scope.$watch( - function watchIsOpen() { return getIsOpen(scope.$parent); }, - function updateOpen(value) { scope.isOpen = value; } - ); - - scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false; + accordionCtrl.scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); } scope.$watch('isOpen', function(value) { @@ -272,7 +259,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) accordionCtrl.closeOthers(scope); } if ( setIsOpen ) { - setIsOpen(scope.$parent, value); + setIsOpen(accordionCtrl.scope, value); } }); } @@ -331,7 +318,7 @@ angular.module("ui.bootstrap.alert", []).directive('alert', function () { type: '=', close: '&' }, - link: function(scope, iElement, iAttrs, controller) { + link: function(scope, iElement, iAttrs) { scope.closeable = "close" in iAttrs; } }; @@ -349,19 +336,18 @@ angular.module('ui.bootstrap.bindHtml', []) }); angular.module('ui.bootstrap.buttons', []) - .constant('buttonConfig', { - activeClass:'active', - toggleEvent:'click' - }) +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) - .directive('btnRadio', ['buttonConfig', function (buttonConfig) { +.directive('btnRadio', ['buttonConfig', function (buttonConfig) { var activeClass = buttonConfig.activeClass || 'active'; var toggleEvent = buttonConfig.toggleEvent || 'click'; return { - - require:'ngModel', - link:function (scope, element, attrs, ngModelCtrl) { + require: 'ngModel', + link: function (scope, element, attrs, ngModelCtrl) { //model -> UI ngModelCtrl.$render = function () { @@ -381,14 +367,13 @@ angular.module('ui.bootstrap.buttons', []) }; }]) - .directive('btnCheckbox', ['buttonConfig', function (buttonConfig) { - +.directive('btnCheckbox', ['buttonConfig', function (buttonConfig) { var activeClass = buttonConfig.activeClass || 'active'; var toggleEvent = buttonConfig.toggleEvent || 'click'; return { - require:'ngModel', - link:function (scope, element, attrs, ngModelCtrl) { + require: 'ngModel', + link: function (scope, element, attrs, ngModelCtrl) { function getTrueValue() { var trueValue = scope.$eval(attrs.btnCheckboxTrue); @@ -415,6 +400,7 @@ angular.module('ui.bootstrap.buttons', []) } }; }]); + /** * @ngdoc overview * @name ui.bootstrap.carousel @@ -803,9 +789,10 @@ angular.module('ui.bootstrap.position', []) offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; } + var boundingClientRect = element[0].getBoundingClientRect(); return { - width: element.prop('offsetWidth'), - height: element.prop('offsetHeight'), + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), top: elBCR.top - offsetParentBCR.top, left: elBCR.left - offsetParentBCR.left }; @@ -818,8 +805,8 @@ angular.module('ui.bootstrap.position', []) offset: function (element) { var boundingClientRect = element[0].getBoundingClientRect(); return { - width: element.prop('offsetWidth'), - height: element.prop('offsetHeight'), + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) }; @@ -1082,25 +1069,49 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position']) .constant('datepickerPopupConfig', { dateFormat: 'yyyy-MM-dd', - closeOnDateSelection: true + currentText: 'Today', + toggleWeeksText: 'Weeks', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false }) -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', -function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig) { +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig', +function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) { return { restrict: 'EA', require: 'ngModel', link: function(originalScope, element, attrs, ngModel) { + var dateFormat; + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.dateFormat; + ngModel.$render(); + }); - var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; - var dateFormat = attrs.datepickerPopup || datepickerPopupConfig.dateFormat; + var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; + var appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; - // create a child scope for the datepicker directive so we are not polluting original scope + // create a child scope for the datepicker directive so we are not polluting original scope var scope = originalScope.$new(); + originalScope.$on('$destroy', function() { scope.$destroy(); }); + attrs.$observe('currentText', function(text) { + scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText; + }); + attrs.$observe('toggleWeeksText', function(text) { + scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText; + }); + attrs.$observe('clearText', function(text) { + scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText; + }); + attrs.$observe('closeText', function(text) { + scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText; + }); + var getIsOpen, setIsOpen; if ( attrs.isOpen ) { getIsOpen = $parse(attrs.isOpen); @@ -1135,12 +1146,12 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon }; // popup element used to display calendar - var popupEl = angular.element(''); + var popupEl = angular.element('
'); popupEl.attr({ 'ng-model': 'date', 'ng-change': 'dateSelection()' }); - var datepickerEl = popupEl.find('datepicker'); + var datepickerEl = angular.element(popupEl.children()[0]); if (attrs.datepickerOptions) { datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions))); } @@ -1211,7 +1222,7 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon if (attrs.showWeeks) { addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks'); } else { - scope.showWeeks = true; + scope.showWeeks = datepickerConfig.showWeeks; datepickerEl.attr('show-weeks', 'showWeeks'); } if (attrs.dateDisabled) { @@ -1219,7 +1230,7 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon } function updatePosition() { - scope.position = $position.position(element); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); scope.position.top = scope.position.top + element.prop('offsetHeight'); } @@ -1255,14 +1266,19 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon $setModelValue(originalScope, null); }; - element.after($compile(popupEl)(scope)); + var $popup = $compile(popupEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } } }; }]) -.directive('datepickerPopupWrap', [function() { +.directive('datepickerPopupWrap', function() { return { - restrict:'E', + restrict:'EA', replace: true, transclude: true, templateUrl: 'template/datepicker/popup.html', @@ -1273,7 +1289,7 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon }); } }; -}]); +}); /* * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js @@ -1308,7 +1324,7 @@ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', [' closeMenu(); } - if (!elementWasOpen) { + if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { element.parent().addClass('open'); openElement = element; closeMenu = function (event) { @@ -1327,6 +1343,7 @@ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', [' } }; }]); + angular.module('ui.bootstrap.modal', []) /** @@ -1465,7 +1482,7 @@ angular.module('ui.bootstrap.modal', []) modalWindow.modalDomEl.remove(); //remove backdrop if no longer needed - if (backdropIndex() == -1) { + if (backdropDomEl && backdropIndex() == -1) { backdropDomEl.remove(); backdropDomEl = undefined; } @@ -1642,10 +1659,12 @@ angular.module('ui.bootstrap.modal', []) return $modalProvider; }); + angular.module('ui.bootstrap.pagination', []) .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) { - var self = this; + var self = this, + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; this.init = function(defaultItemsPerPage) { if ($attrs.itemsPerPage) { @@ -1670,7 +1689,8 @@ angular.module('ui.bootstrap.pagination', []) }; this.calculateTotalPages = function() { - return this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); }; this.getAttributeValue = function(attribute, defaultValue, interpolate) { @@ -1679,7 +1699,9 @@ angular.module('ui.bootstrap.pagination', []) this.render = function() { this.page = parseInt($scope.page, 10) || 1; - $scope.pages = this.getPages(this.page, $scope.totalPages); + if (this.page > 0 && this.page <= $scope.totalPages) { + $scope.pages = this.getPages(this.page, $scope.totalPages); + } }; $scope.selectPage = function(page) { @@ -1689,14 +1711,16 @@ angular.module('ui.bootstrap.pagination', []) } }; + $scope.$watch('page', function() { + self.render(); + }); + $scope.$watch('totalItems', function() { $scope.totalPages = self.calculateTotalPages(); }); $scope.$watch('totalPages', function(value) { - if ( $attrs.numPages ) { - $scope.numPages = value; // Readonly variable - } + setNumPages($scope.$parent, value); // Readonly variable if ( self.page > value ) { $scope.selectPage(value); @@ -1704,10 +1728,6 @@ angular.module('ui.bootstrap.pagination', []) self.render(); } }); - - $scope.$watch('page', function() { - self.render(); - }); }]) .constant('paginationConfig', { @@ -1727,8 +1747,7 @@ angular.module('ui.bootstrap.pagination', []) scope: { page: '=', totalItems: '=', - onSelectPage:' &', - numPages: '=' + onSelectPage:' &' }, controller: 'PaginationController', templateUrl: 'template/pagination/pagination.html', @@ -1848,8 +1867,7 @@ angular.module('ui.bootstrap.pagination', []) scope: { page: '=', totalItems: '=', - onSelectPage:' &', - numPages: '=' + onSelectPage:' &' }, controller: 'PaginationController', templateUrl: 'template/pagination/pager.html', @@ -1986,7 +2004,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap 'title="'+startSym+'tt_title'+endSym+'" '+ 'content="'+startSym+'tt_content'+endSym+'" '+ 'placement="'+startSym+'tt_placement'+endSym+'" '+ - 'animation="tt_animation()" '+ + 'animation="tt_animation" '+ 'is-open="tt_isOpen"'+ '>'+ ''; @@ -1998,10 +2016,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap var tooltip = $compile( template )( scope ); var transitionTimeout; var popupTimeout; - var $body; + var $body = $document.find( 'body' ); var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; var triggers = getTriggers( undefined ); var hasRegisteredTriggers = false; + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); // By default, the tooltip is not open. // TODO add ability to start tooltip opened @@ -2017,6 +2036,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Show the tooltip with delay if specified, otherwise show it immediately function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } if ( scope.tt_popupDelay ) { popupTimeout = $timeout( show, scope.tt_popupDelay ); } else { @@ -2054,7 +2076,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Now we add it to the DOM because need some info about it. But it's not // visible yet anyway. if ( appendToBody ) { - $body = $body || $document.find( 'body' ); $body.append( tooltip ); } else { element.after( tooltip ); @@ -2117,8 +2138,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // And now we remove it from the DOM. However, if we have animation, we // need to wait for it to expire beforehand. // FIXME: this is a placeholder for a port of the transitions library. - if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { - transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); + if ( scope.tt_animation ) { + transitionTimeout = $timeout(function () { + tooltip.remove(); + }, 500); } else { tooltip.remove(); } @@ -2128,7 +2151,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * Observe the relevant attributes. */ attrs.$observe( type, function ( val ) { - scope.tt_content = val; + if (val) { + scope.tt_content = val; + } else { + if ( scope.tt_isOpen ) { + hide(); + } + } }); attrs.$observe( prefix+'Title', function ( val ) { @@ -2139,8 +2168,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap scope.tt_placement = angular.isDefined( val ) ? val : options.placement; }); - attrs.$observe( prefix+'Animation', function ( val ) { - scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; + attrs.$observe(prefix + 'Animation', function (val) { + scope.tt_animation = angular.isDefined(val) ? !!val : options.animation; }); attrs.$observe( prefix+'PopupDelay', function ( val ) { @@ -2184,11 +2213,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { - if ( scope.tt_isOpen ) { - hide(); - } else { - tooltip.remove(); - } + $timeout.cancel( popupTimeout ); + tooltip.remove(); + tooltip.unbind(); + tooltip = null; + $body = null; }); } }; @@ -2361,29 +2390,20 @@ angular.module('ui.bootstrap.rating', []) this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; - this.createDefaultRange = function(len) { - var defaultStateObject = { + this.createRateObjects = function(states) { + var defaultOptions = { stateOn: this.stateOn, stateOff: this.stateOff }; - var states = new Array(len); - for (var i = 0; i < len; i++) { - states[i] = defaultStateObject; - } - return states; - }; - - this.normalizeRange = function(states) { for (var i = 0, n = states.length; i < n; i++) { - states[i].stateOn = states[i].stateOn || this.stateOn; - states[i].stateOff = states[i].stateOff || this.stateOff; + states[i] = angular.extend({ index: i }, defaultOptions, states[i]); } return states; }; // Get objects used in template - $scope.range = angular.isDefined($attrs.ratingStates) ? this.normalizeRange(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createDefaultRange(this.maxRange); + $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange)); $scope.rate = function(value) { if ( $scope.readonly || $scope.value === value) { @@ -2447,11 +2467,9 @@ angular.module('ui.bootstrap.tabs', []) }; }) -.controller('TabsetController', ['$scope', '$element', -function TabsetCtrl($scope, $element) { - +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { var ctrl = this, - tabs = ctrl.tabs = $scope.tabs = []; + tabs = ctrl.tabs = $scope.tabs = []; ctrl.select = function(tab) { angular.forEach(tabs, function(tab) { @@ -2608,8 +2626,7 @@ function TabsetCtrl($scope, $element) { */ -.directive('tab', ['$parse', '$http', '$templateCache', '$compile', -function($parse, $http, $templateCache, $compile) { +.directive('tab', ['$parse', function($parse) { return { require: '^tabset', restrict: 'EA', @@ -2631,8 +2648,13 @@ function($parse, $http, $templateCache, $compile) { if (attrs.active) { getActive = $parse(attrs.active); setActive = getActive.assign; - scope.$parent.$watch(getActive, function updateActive(value) { - scope.active = !!value; + scope.$parent.$watch(getActive, function updateActive(value, oldVal) { + // Avoid re-initializing scope.active as it is already initialized + // below. (watcher is called async during init with value === + // oldVal) + if (value !== oldVal) { + scope.active = !!value; + } }); scope.active = getActive(scope.$parent); } else { @@ -2640,6 +2662,8 @@ function($parse, $http, $templateCache, $compile) { } scope.$watch('active', function(active) { + // Note this watcher also initializes and assigns scope.active to the + // attrs.active expression. setActive(scope.$parent, active); if (active) { tabsetCtrl.select(scope); @@ -2666,9 +2690,6 @@ function($parse, $http, $templateCache, $compile) { scope.$on('$destroy', function() { tabsetCtrl.removeTab(scope); }); - if (scope.active) { - setActive(scope.$parent, true); - } //We need to transclude later, once the content container is ready. @@ -2694,7 +2715,7 @@ function($parse, $http, $templateCache, $compile) { }; }]) -.directive('tabContentTransclude', ['$compile', '$parse', function($compile, $parse) { +.directive('tabContentTransclude', function() { return { restrict: 'A', require: '^tabset', @@ -2723,9 +2744,9 @@ function($parse, $http, $templateCache, $compile) { node.tagName.toLowerCase() === 'data-tab-heading' ); } -}]) +}) -.directive('tabsetTitles', ['$http', function($http) { +.directive('tabsetTitles', function() { return { restrict: 'A', require: '^tabset', @@ -2742,10 +2763,7 @@ function($parse, $http, $templateCache, $compile) { } } }; -}]) - -; - +}); angular.module('ui.bootstrap.timepicker', []) @@ -3047,9 +3065,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap //expressions used by typeahead var parserResult = typeaheadParser.parse(attrs.typeahead); + var hasFocus; //pop-up element used to display matches - var popUpEl = angular.element(''); + var popUpEl = angular.element('
'); popUpEl.attr({ matches: 'matches', active: 'activeIdx', @@ -3078,11 +3097,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap var locals = {$viewValue: inputValue}; isLoadingSetter(originalScope, true); - $q.when(parserResult.source(scope, locals)).then(function(matches) { + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { //it might happen that several async queries were in progress if a user were typing fast //but we are interested only in responses that correspond to the current view value - if (inputValue === modelCtrl.$viewValue) { + if (inputValue === modelCtrl.$viewValue && hasFocus) { if (matches.length > 0) { scope.activeIdx = 0; @@ -3127,7 +3146,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue modelCtrl.$parsers.unshift(function (inputValue) { - resetMatches(); + hasFocus = true; + if (inputValue && inputValue.length >= minSearch) { if (waitTime > 0) { if (timeoutPromise) { @@ -3139,13 +3159,22 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap } else { getMatchesAsync(inputValue); } + } else { + isLoadingSetter(originalScope, false); + resetMatches(); } if (isEditable) { return inputValue; } else { - modelCtrl.$setValidity('editable', false); - return undefined; + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } } }); @@ -3199,6 +3228,9 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap //typeahead is open and an "interesting" key was pressed if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + if (evt.which === 13) { + evt.preventDefault(); + } return; } @@ -3225,6 +3257,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap } }); + element.bind('blur', function (evt) { + hasFocus = false; + }); + // Keep reference to click handler to unbind it. var dismissClickHandler = function (evt) { if (element[0] !== evt.target) { @@ -3247,7 +3283,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap .directive('typeaheadPopup', function () { return { - restrict:'E', + restrict:'EA', scope:{ matches:'=', query:'=', @@ -3282,7 +3318,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { return { - restrict:'E', + restrict:'EA', scope:{ index:'=', match:'=', @@ -3383,18 +3419,19 @@ angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/popup.html", - "\n" + + ""); }]); angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { @@ -3430,7 +3467,7 @@ angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$tem $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", "
\n" + "
\n" + - "
\n" + + "
\n" + "
\n" + ""); }]); @@ -3474,12 +3511,6 @@ angular.module("template/rating/rating.html", []).run(["$templateCache", functio ""); }]); -angular.module("template/tabs/pane.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/pane.html", - "
\n" + - ""); -}]); - angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/tabs/tab.html", "
  • \n" + @@ -3488,19 +3519,6 @@ angular.module("template/tabs/tab.html", []).run(["$templateCache", function($te ""); }]); -angular.module("template/tabs/tabs.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tabs.html", - "
    \n" + - " \n" + - "
    \n" + - "
    \n" + - ""); -}]); - angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/tabs/tabset-titles.html", "